Skip to content

Commit

Permalink
Merge #886
Browse files Browse the repository at this point in the history
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 <[email protected]>
Co-authored-by: rmanoka <[email protected]>
  • Loading branch information
bors[bot] and rmanoka authored Aug 14, 2022
2 parents 34fa01b + a1bdc10 commit dcb0c87
Show file tree
Hide file tree
Showing 10 changed files with 450 additions and 209 deletions.
9 changes: 6 additions & 3 deletions geo-bool-ops-benches/benches/boolean_ops.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use std::f64::consts::PI;

use criterion::{measurement::Measurement, *};
use geo::algorithm::{BooleanOps, Intersects, Rotate};
use geo::{
algorithm::{BooleanOps, Rotate},
Relate,
};

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

group.bench_with_input(BenchmarkId::new("geo::intersects", steps), &(), |b, _| {
group.bench_with_input(BenchmarkId::new("geo::relate", steps), &(), |b, _| {
b.iter_batched(
polys.sampler(),
|&(ref poly, ref poly2, _, _)| poly.intersects(poly2),
|&(ref poly, ref poly2, _, _)| poly.relate(poly2).is_intersects(),
BatchSize::SmallInput,
);
});
Expand Down
2 changes: 2 additions & 0 deletions geo/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

* Add `clip` boolean op. to clip a 1-D geometry with a 2-D geometry.
* <https://github.com/georust/geo/pull/886>
* Add `Within` trait to determine if Geometry A is completely within Geometry B
* <https://github.com/georust/geo/pull/884>
* Add `Contains` impl for all remaining geometry types.
Expand Down
62 changes: 57 additions & 5 deletions geo/src/algorithm/bool_ops/assembly.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{
cell::Cell,
collections::{BTreeMap, HashMap},
collections::{BTreeMap, HashMap, VecDeque},
};

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

impl<T: GeoFloat> Default for Assembly<T> {
impl<T: GeoFloat> Default for RegionAssembly<T> {
fn default() -> Self {
Self {
segments: Default::default(),
}
}
}

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

while let Some(pt) = iter.next() {
Expand Down Expand Up @@ -175,6 +175,58 @@ impl<T: GeoFloat> Assembly<T> {
}
}

#[derive(Debug)]
pub struct LineAssembly<T: GeoFloat> {
segments: Vec<VecDeque<SweepPoint<T>>>,
end_points: BTreeMap<(usize, SweepPoint<T>), (usize, bool)>,
}

impl<T: GeoFloat> LineAssembly<T> {
pub fn add_edge(&mut self, geom: LineOrPoint<T>, geom_idx: usize) {
// Try to find a line-string with either end-point
if let Some((seg_idx, at_front)) = self.end_points.remove(&(geom_idx, geom.left())) {
if at_front {
self.segments[seg_idx].push_front(geom.right());
} else {
self.segments[seg_idx].push_back(geom.right());
}
self.end_points
.insert((geom_idx, geom.right()), (seg_idx, at_front));
} else if let Some((seg_idx, at_front)) = self.end_points.remove(&(geom_idx, geom.right()))
{
if at_front {
self.segments[seg_idx].push_front(geom.left());
} else {
self.segments[seg_idx].push_back(geom.left());
}
self.end_points
.insert((geom_idx, geom.left()), (seg_idx, at_front));
} else {
let idx = self.segments.len();
self.segments
.push(VecDeque::from_iter([geom.left(), geom.right()]));
self.end_points.insert((geom_idx, geom.left()), (idx, true));
self.end_points
.insert((geom_idx, geom.right()), (idx, false));
}
}
pub fn finish(self) -> Vec<LineString<T>> {
self.segments
.into_iter()
.map(|pts| LineString::from_iter(pts.into_iter().map(|pt| *pt)))
.collect()
}
}

impl<T: GeoFloat> Default for LineAssembly<T> {
fn default() -> Self {
Self {
segments: Default::default(),
end_points: Default::default(),
}
}
}

#[derive(Debug, Clone)]
struct Ring<T: GeoFloat> {
ls: LineString<T>,
Expand Down
60 changes: 52 additions & 8 deletions geo/src/algorithm/bool_ops/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use geo_types::MultiPolygon;
use geo_types::{MultiLineString, MultiPolygon};

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

/// Boolean Operations on geometry.
///
/// Boolean operations are set operations on geometries considered as a subset
/// of the 2-D plane. The operations supported are: intersection, union, xor or
/// symmetric difference, and set-difference.
/// symmetric difference, and set-difference on pairs of 2-D geometries and
/// clipping a 1-D geometry with self.
///
/// These operations are implemented on [`Polygon`] and the [`MultiPolygon`]
/// geometries.
Expand Down Expand Up @@ -37,6 +38,16 @@ pub trait BooleanOps: Sized {
fn difference(&self, other: &Self) -> MultiPolygon<Self::Scalar> {
self.boolean_op(other, OpType::Difference)
}

/// Clip a 1-D geometry with self.
///
/// Returns the set-theoeretic intersection of `self` and `ls` if `invert`
/// is false, and the difference (`ls - self`) otherwise.
fn clip(
&self,
ls: &MultiLineString<Self::Scalar>,
invert: bool,
) -> MultiLineString<Self::Scalar>;
}

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

fn boolean_op(&self, other: &Self, op: OpType) -> MultiPolygon<Self::Scalar> {
let mut bop = Op::new(op, self.coords_count() + other.coords_count());
bop.add_polygon(self, true);
bop.add_polygon(other, false);
let spec = BoolOp::from(op);
let mut bop = Proc::new(spec, self.coords_count() + other.coords_count());
bop.add_polygon(self, 0);
bop.add_polygon(other, 1);
bop.sweep()
}

fn clip(
&self,
ls: &MultiLineString<Self::Scalar>,
invert: bool,
) -> MultiLineString<Self::Scalar> {
let spec = ClipOp::new(invert);
let mut bop = Proc::new(spec, self.coords_count() + ls.coords_count());
bop.add_polygon(self, 0);
ls.0.iter().enumerate().for_each(|(idx, l)| {
bop.add_line_string(l, idx + 1);
});
bop.sweep()
}
}
impl<T: GeoFloat> BooleanOps for MultiPolygon<T> {
type Scalar = T;

fn boolean_op(&self, other: &Self, op: OpType) -> MultiPolygon<Self::Scalar> {
let mut bop = Op::new(op, self.coords_count() + other.coords_count());
bop.add_multi_polygon(self, true);
bop.add_multi_polygon(other, false);
let spec = BoolOp::from(op);
let mut bop = Proc::new(spec, self.coords_count() + other.coords_count());
bop.add_multi_polygon(self, 0);
bop.add_multi_polygon(other, 1);
bop.sweep()
}

fn clip(
&self,
ls: &MultiLineString<Self::Scalar>,
invert: bool,
) -> MultiLineString<Self::Scalar> {
let spec = ClipOp::new(invert);
let mut bop = Proc::new(spec, self.coords_count() + ls.coords_count());
bop.add_multi_polygon(self, 0);
ls.0.iter().enumerate().for_each(|(idx, l)| {
bop.add_line_string(l, idx + 1);
});
bop.sweep()
}
}

mod op;
use op::*;
mod assembly;
use assembly::*;
mod spec;
use spec::*;

#[cfg(test)]
mod tests;
Loading

0 comments on commit dcb0c87

Please sign in to comment.