Skip to content

Commit dcb0c87

Browse files
bors[bot]rmanoka
andauthored
Merge #886
886: Clipping ops r=rmanoka a=rmanoka - [x] I agree to follow the project's [code of conduct](https://github.com/georust/geo/blob/main/CODE_OF_CONDUCT.md). - [x] I added an entry to `CHANGES.md` if knowledge of this change could be valuable to users. --- I had a use-case for clipping a 1-d geom. by a 2-d goem. It's conceptually a bool ops, but needed some refactoring of the region construction implementation. Co-authored-by: Rajsekar Manokaran <rajsekar@gmail.com> Co-authored-by: rmanoka <rajsekar@gmail.com>
2 parents 34fa01b + a1bdc10 commit dcb0c87

File tree

10 files changed

+450
-209
lines changed

10 files changed

+450
-209
lines changed

geo-bool-ops-benches/benches/boolean_ops.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use std::f64::consts::PI;
22

33
use criterion::{measurement::Measurement, *};
4-
use geo::algorithm::{BooleanOps, Intersects, Rotate};
4+
use geo::{
5+
algorithm::{BooleanOps, Rotate},
6+
Relate,
7+
};
58

69
use geo_booleanop::boolean::BooleanOp as OtherBooleanOp;
710
use rand::{thread_rng, Rng};
@@ -77,10 +80,10 @@ fn run_complex<T: Measurement>(c: &mut Criterion<T>) {
7780
},
7881
);
7982

80-
group.bench_with_input(BenchmarkId::new("geo::intersects", steps), &(), |b, _| {
83+
group.bench_with_input(BenchmarkId::new("geo::relate", steps), &(), |b, _| {
8184
b.iter_batched(
8285
polys.sampler(),
83-
|&(ref poly, ref poly2, _, _)| poly.intersects(poly2),
86+
|&(ref poly, ref poly2, _, _)| poly.relate(poly2).is_intersects(),
8487
BatchSize::SmallInput,
8588
);
8689
});

geo/CHANGES.md

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
* Add `clip` boolean op. to clip a 1-D geometry with a 2-D geometry.
6+
* <https://github.com/georust/geo/pull/886>
57
* Add `Within` trait to determine if Geometry A is completely within Geometry B
68
* <https://github.com/georust/geo/pull/884>
79
* Add `Contains` impl for all remaining geometry types.

geo/src/algorithm/bool_ops/assembly.rs

+57-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{
22
cell::Cell,
3-
collections::{BTreeMap, HashMap},
3+
collections::{BTreeMap, HashMap, VecDeque},
44
};
55

66
use crate::{
@@ -20,25 +20,25 @@ use super::op::compare_crossings;
2020
/// describe a bounded region, do not intersect in their interior, and are not
2121
/// degenerate (not a point).
2222
#[derive(Debug)]
23-
pub struct Assembly<T: GeoFloat> {
23+
pub struct RegionAssembly<T: GeoFloat> {
2424
segments: Vec<Segment<T>>,
2525
}
2626

27-
impl<T: GeoFloat> Default for Assembly<T> {
27+
impl<T: GeoFloat> Default for RegionAssembly<T> {
2828
fn default() -> Self {
2929
Self {
3030
segments: Default::default(),
3131
}
3232
}
3333
}
3434

35-
impl<T: GeoFloat> Assembly<T> {
35+
impl<T: GeoFloat> RegionAssembly<T> {
3636
pub fn add_edge(&mut self, edge: LineOrPoint<T>) {
3737
debug_assert!(edge.is_line());
3838
self.segments.push(edge.into());
3939
}
4040
pub fn finish(self) -> MultiPolygon<T> {
41-
let mut iter = CrossingsIter::from_iter(self.segments.iter());
41+
let mut iter = CrossingsIter::new_simple(self.segments.iter());
4242
let mut snakes = vec![];
4343

4444
while let Some(pt) = iter.next() {
@@ -175,6 +175,58 @@ impl<T: GeoFloat> Assembly<T> {
175175
}
176176
}
177177

178+
#[derive(Debug)]
179+
pub struct LineAssembly<T: GeoFloat> {
180+
segments: Vec<VecDeque<SweepPoint<T>>>,
181+
end_points: BTreeMap<(usize, SweepPoint<T>), (usize, bool)>,
182+
}
183+
184+
impl<T: GeoFloat> LineAssembly<T> {
185+
pub fn add_edge(&mut self, geom: LineOrPoint<T>, geom_idx: usize) {
186+
// Try to find a line-string with either end-point
187+
if let Some((seg_idx, at_front)) = self.end_points.remove(&(geom_idx, geom.left())) {
188+
if at_front {
189+
self.segments[seg_idx].push_front(geom.right());
190+
} else {
191+
self.segments[seg_idx].push_back(geom.right());
192+
}
193+
self.end_points
194+
.insert((geom_idx, geom.right()), (seg_idx, at_front));
195+
} else if let Some((seg_idx, at_front)) = self.end_points.remove(&(geom_idx, geom.right()))
196+
{
197+
if at_front {
198+
self.segments[seg_idx].push_front(geom.left());
199+
} else {
200+
self.segments[seg_idx].push_back(geom.left());
201+
}
202+
self.end_points
203+
.insert((geom_idx, geom.left()), (seg_idx, at_front));
204+
} else {
205+
let idx = self.segments.len();
206+
self.segments
207+
.push(VecDeque::from_iter([geom.left(), geom.right()]));
208+
self.end_points.insert((geom_idx, geom.left()), (idx, true));
209+
self.end_points
210+
.insert((geom_idx, geom.right()), (idx, false));
211+
}
212+
}
213+
pub fn finish(self) -> Vec<LineString<T>> {
214+
self.segments
215+
.into_iter()
216+
.map(|pts| LineString::from_iter(pts.into_iter().map(|pt| *pt)))
217+
.collect()
218+
}
219+
}
220+
221+
impl<T: GeoFloat> Default for LineAssembly<T> {
222+
fn default() -> Self {
223+
Self {
224+
segments: Default::default(),
225+
end_points: Default::default(),
226+
}
227+
}
228+
}
229+
178230
#[derive(Debug, Clone)]
179231
struct Ring<T: GeoFloat> {
180232
ls: LineString<T>,

geo/src/algorithm/bool_ops/mod.rs

+52-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
use geo_types::MultiPolygon;
1+
use geo_types::{MultiLineString, MultiPolygon};
22

33
use crate::{CoordsIter, GeoFloat, GeoNum, Polygon};
44

55
/// Boolean Operations on geometry.
66
///
77
/// Boolean operations are set operations on geometries considered as a subset
88
/// of the 2-D plane. The operations supported are: intersection, union, xor or
9-
/// symmetric difference, and set-difference.
9+
/// symmetric difference, and set-difference on pairs of 2-D geometries and
10+
/// clipping a 1-D geometry with self.
1011
///
1112
/// These operations are implemented on [`Polygon`] and the [`MultiPolygon`]
1213
/// geometries.
@@ -37,6 +38,16 @@ pub trait BooleanOps: Sized {
3738
fn difference(&self, other: &Self) -> MultiPolygon<Self::Scalar> {
3839
self.boolean_op(other, OpType::Difference)
3940
}
41+
42+
/// Clip a 1-D geometry with self.
43+
///
44+
/// Returns the set-theoeretic intersection of `self` and `ls` if `invert`
45+
/// is false, and the difference (`ls - self`) otherwise.
46+
fn clip(
47+
&self,
48+
ls: &MultiLineString<Self::Scalar>,
49+
invert: bool,
50+
) -> MultiLineString<Self::Scalar>;
4051
}
4152

4253
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
@@ -51,26 +62,59 @@ impl<T: GeoFloat> BooleanOps for Polygon<T> {
5162
type Scalar = T;
5263

5364
fn boolean_op(&self, other: &Self, op: OpType) -> MultiPolygon<Self::Scalar> {
54-
let mut bop = Op::new(op, self.coords_count() + other.coords_count());
55-
bop.add_polygon(self, true);
56-
bop.add_polygon(other, false);
65+
let spec = BoolOp::from(op);
66+
let mut bop = Proc::new(spec, self.coords_count() + other.coords_count());
67+
bop.add_polygon(self, 0);
68+
bop.add_polygon(other, 1);
69+
bop.sweep()
70+
}
71+
72+
fn clip(
73+
&self,
74+
ls: &MultiLineString<Self::Scalar>,
75+
invert: bool,
76+
) -> MultiLineString<Self::Scalar> {
77+
let spec = ClipOp::new(invert);
78+
let mut bop = Proc::new(spec, self.coords_count() + ls.coords_count());
79+
bop.add_polygon(self, 0);
80+
ls.0.iter().enumerate().for_each(|(idx, l)| {
81+
bop.add_line_string(l, idx + 1);
82+
});
5783
bop.sweep()
5884
}
5985
}
6086
impl<T: GeoFloat> BooleanOps for MultiPolygon<T> {
6187
type Scalar = T;
6288

6389
fn boolean_op(&self, other: &Self, op: OpType) -> MultiPolygon<Self::Scalar> {
64-
let mut bop = Op::new(op, self.coords_count() + other.coords_count());
65-
bop.add_multi_polygon(self, true);
66-
bop.add_multi_polygon(other, false);
90+
let spec = BoolOp::from(op);
91+
let mut bop = Proc::new(spec, self.coords_count() + other.coords_count());
92+
bop.add_multi_polygon(self, 0);
93+
bop.add_multi_polygon(other, 1);
94+
bop.sweep()
95+
}
96+
97+
fn clip(
98+
&self,
99+
ls: &MultiLineString<Self::Scalar>,
100+
invert: bool,
101+
) -> MultiLineString<Self::Scalar> {
102+
let spec = ClipOp::new(invert);
103+
let mut bop = Proc::new(spec, self.coords_count() + ls.coords_count());
104+
bop.add_multi_polygon(self, 0);
105+
ls.0.iter().enumerate().for_each(|(idx, l)| {
106+
bop.add_line_string(l, idx + 1);
107+
});
67108
bop.sweep()
68109
}
69110
}
70111

71112
mod op;
72113
use op::*;
73114
mod assembly;
115+
use assembly::*;
116+
mod spec;
117+
use spec::*;
74118

75119
#[cfg(test)]
76120
mod tests;

0 commit comments

Comments
 (0)