Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions geo/src/algorithm/contains_properly/polygon.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::{ContainsProperly, impl_contains_properly_from_relate};
use crate::coordinate_position::{CoordPos, CoordinatePosition, coord_pos_relative_to_ring};
use crate::geometry::*;
use crate::monotone::chain::MonotoneChainIter;
use crate::{BoundingRect, CoordsIter, HasDimensions, Intersects, LinesIter};
use crate::{GeoFloat, GeoNum};

Expand Down Expand Up @@ -200,14 +201,35 @@ impl_contains_properly_from_relate!(MultiPolygon<T>, [Point<T>,MultiPoint<T>,Lin

/// Return true if the boundary of lhs intersects any of the boundaries of rhs
/// where lhs and rhs are both polygons/multipolygons
/// This is a short circuit version of boundary_intersects which doesn't use the monotone algorithm
fn boundary_intersects_monotone<'a, T, G1, G2>(lhs: &'a G1, rhs: &'a G2) -> bool
where
T: GeoNum + 'a,
G1: MonotoneChainIter<'a, T>,
G2: MonotoneChainIter<'a, T>,
{
// use monotone when larger
let rhs_arr = rhs.chains().collect::<Vec<_>>();

lhs.chains()
.any(|lhs| rhs_arr.iter().any(|rhs| lhs.intersects(rhs)))
}

fn boundary_intersects<'a, T, G1, G2>(lhs: &'a G1, rhs: &'a G2) -> bool
where
T: GeoNum,
G1: LinesIter<'a, Scalar = T>,
G2: LinesIter<'a, Scalar = T>,
T: GeoNum + 'a,
G1: MonotoneChainIter<'a, T> + LinesIter<'a, Scalar = T> + CoordsIter,
G2: MonotoneChainIter<'a, T> + LinesIter<'a, Scalar = T> + CoordsIter,
Line<T>: Intersects<Line<T>>,
Rect<T>: Intersects<Rect<T>>,
{
// arbitrary threshold to use monotone
const THRESHOLD: usize = 20;

if lhs.coords_count() > THRESHOLD || rhs.coords_count() > THRESHOLD {
return boundary_intersects_monotone(lhs, rhs);
}

let rhs_bbox_cache = rhs
.lines_iter()
.map(|l| (l, l.bounding_rect()))
Expand Down
2 changes: 1 addition & 1 deletion geo/src/algorithm/intersects/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ where
}

// A cheap bbox check to see if we can skip the more expensive intersection computation
fn has_disjoint_bboxes<T, A, B>(a: &A, b: &B) -> bool
pub(crate) fn has_disjoint_bboxes<T, A, B>(a: &A, b: &B) -> bool
where
T: CoordNum,
A: BoundingRect<T>,
Expand Down
9 changes: 5 additions & 4 deletions geo/src/algorithm/intersects/polygon.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::{Intersects, has_disjoint_bboxes};
use crate::coordinate_position::CoordPos;
use crate::{BoundingRect, CoordinatePosition, CoordsIter, LinesIter};
use crate::monotone::MonotoneChainIter;
use crate::{BoundingRect, CoordinatePosition, CoordsIter};
use crate::{
Coord, CoordNum, GeoNum, Line, LineString, MultiLineString, MultiPoint, MultiPolygon, Point,
Polygon, Rect, Triangle,
Expand Down Expand Up @@ -50,10 +51,10 @@ where
self.exterior().coords_iter().take(1).any(|p|polygon.intersects(&p))
|| polygon.exterior().coords_iter().take(1).any(|p|self.intersects(&p))
// exterior exterior
|| self.exterior().lines_iter().any(|self_line| polygon.exterior().lines_iter().any(|poly_line| self_line.intersects(&poly_line)))
|| self.exterior_chains().any(|self_chain| polygon.exterior_chains().any( |poly_chain| self_chain.intersects(&poly_chain)))
// exterior interior
||self.interiors().iter().any(|inner_line_string| polygon.exterior().intersects(inner_line_string))
||polygon.interiors().iter().any(|inner_line_string| self.exterior().intersects(inner_line_string))
|| self.exterior_chains().any(|self_chain| polygon.interior_chains().any(|poly_chain| self_chain.intersects(&poly_chain)))
|| self.interior_chains().any(|self_chain| polygon.exterior_chains().any(|poly_chain| self_chain.intersects(&poly_chain)))

// interior interior (not needed)
/*
Expand Down
3 changes: 3 additions & 0 deletions geo/src/algorithm/lines_iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ impl<'a, T: CoordNum> LineStringIter<'a, T> {
fn new(line_string: &'a LineString<T>) -> Self {
Self(line_string.0.windows(2))
}
pub(crate) fn new_from_coords(line_string: &'a [Coord<T>]) -> Self {
Self(line_string.windows(2))
}
}

impl<T: CoordNum> Iterator for LineStringIter<'_, T> {
Expand Down
60 changes: 60 additions & 0 deletions geo/src/algorithm/monotone/chain/chain_iter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use super::{MonotoneChain, MonotoneChainSegment};
use crate::geometry::*;
use crate::lines_iter::{LineStringIter, LinesIter};
use crate::{CoordNum, GeoNum};
use std::iter;

pub trait MonotoneChainIter<'a, T: GeoNum + 'a> {
fn chains(&'a self) -> impl Iterator<Item = MonotoneChain<'a, T>> {
self.exterior_chains().chain(self.interior_chains())
}
fn exterior_chains(&'a self) -> impl Iterator<Item = MonotoneChain<'a, T>>;
fn interior_chains(&'a self) -> impl Iterator<Item = MonotoneChain<'a, T>>;
}

impl<'a, T: GeoNum + 'a> MonotoneChainIter<'a, T> for LineString<T> {
fn exterior_chains(&'a self) -> impl Iterator<Item = MonotoneChain<'a, T>> {
iter::once(self).map(Into::<MonotoneChain<'a, T>>::into)
}
fn interior_chains(&'a self) -> impl Iterator<Item = MonotoneChain<'a, T>> {
iter::empty()
}
}

impl<'a, T: GeoNum + 'a> MonotoneChainIter<'a, T> for MultiLineString<T> {
fn exterior_chains(&'a self) -> impl Iterator<Item = MonotoneChain<'a, T>> {
self.iter().flat_map(MonotoneChainIter::exterior_chains)
}
fn interior_chains(&'a self) -> impl Iterator<Item = MonotoneChain<'a, T>> {
self.iter().flat_map(MonotoneChainIter::interior_chains)
}
}

impl<'a, T: GeoNum + 'a> MonotoneChainIter<'a, T> for Polygon<T> {
fn exterior_chains(&'a self) -> impl Iterator<Item = MonotoneChain<'a, T>> {
self.exterior().chains()
}
fn interior_chains(&'a self) -> impl Iterator<Item = MonotoneChain<'a, T>> {
self.interiors().iter().flat_map(MonotoneChainIter::chains)
}
}

impl<'a, T: GeoNum + 'a> MonotoneChainIter<'a, T> for MultiPolygon<T> {
fn exterior_chains(&'a self) -> impl Iterator<Item = MonotoneChain<'a, T>> {
self.iter().flat_map(MonotoneChainIter::exterior_chains)
}
fn interior_chains(&'a self) -> impl Iterator<Item = MonotoneChain<'a, T>> {
self.iter().flat_map(MonotoneChainIter::interior_chains)
}
}

// ============================================================================

impl<'a, T: CoordNum + 'a> LinesIter<'a> for MonotoneChainSegment<'a, T> {
type Scalar = T;
type Iter = LineStringIter<'a, Self::Scalar>;

fn lines_iter(&'a self) -> Self::Iter {
LineStringIter::new_from_coords(self.ls)
}
}
185 changes: 185 additions & 0 deletions geo/src/algorithm/monotone/chain/intersects.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
use super::{MonotoneChain, MonotoneChainSegment};
use crate::GeoNum;
use crate::geometry::*;
use crate::intersects::has_disjoint_bboxes;
use crate::{BoundingRect, Intersects};

// `Geom` intersects `MonotoneChain` and `MonotoneChainSegment` requires adding a lifetime to the macro
// duplicated from `intersects/mod.rs` but with additional lifetime

macro_rules! symmetric_intersects_impl {
($t:ty, $k:ty) => {
impl<'a, T> $crate::Intersects<$k> for $t
where
$k: $crate::Intersects<$t>,
T: GeoNum,
{
fn intersects(&self, rhs: &$k) -> bool {
rhs.intersects(self)
}
}
};
}

// -----------------------------------------------------------------------------
// Chain Implementation
// -----------------------------------------------------------------------------

impl<'a, G, T: GeoNum> Intersects<G> for MonotoneChain<'a, T>
where
G: BoundingRect<T>,
// G: Intersects<MonotoneChainSegment<'a, T>>,
MonotoneChainSegment<'a, T>: Intersects<G>,
{
fn intersects(&self, rhs: &G) -> bool {
if has_disjoint_bboxes(self, rhs) {
return false;
}

self.segment_iter().any(|seg| seg.intersects(rhs))
}
}

// commented out if they are implemented by blanket impl in main `Intersects` trait
symmetric_intersects_impl!(Coord<T>, MonotoneChain<'a, T>);
// symmetric_intersects_impl!(Point<T>,MonotoneChain<'a, T>);
// symmetric_intersects_impl!(MultiPoint<T>,MonotoneChain<'a, T>);

symmetric_intersects_impl!(Line<T>, MonotoneChain<'a, T>);
symmetric_intersects_impl!(LineString<T>, MonotoneChain<'a, T>);
// symmetric_intersects_impl!(MultiLineString<T>,MonotoneChain<'a, T>);

symmetric_intersects_impl!(Polygon<T>, MonotoneChain<'a, T>);
// symmetric_intersects_impl!(MultiPolygon<T>,MonotoneChain<'a, T>);
symmetric_intersects_impl!(Rect<T>, MonotoneChain<'a, T>);
symmetric_intersects_impl!(Triangle<T>, MonotoneChain<'a, T>);

// symmetric_intersects_impl!(Geometry<T>,MonotoneChain<'a, T>);
// symmetric_intersects_impl!(GeometryCollection<T>,MonotoneChain<'a, T>);

// -----------------------------------------------------------------------------
// Chain Segment Implementation
// -----------------------------------------------------------------------------

impl<'a, G, T: GeoNum> Intersects<G> for MonotoneChainSegment<'a, T>
where
G: BoundingRect<T>,
LineString<T>: Intersects<G>,
{
fn intersects(&self, rhs: &G) -> bool {
if has_disjoint_bboxes(self, rhs) {
return false;
}
// TODO: tune the binary search
// arbitrary but hard lower bound of 2 for algorithm correctness
const SEARCH_THRESHOLD: usize = 2;

if self.ls.len() > SEARCH_THRESHOLD {
if let (l, Some(r)) = self.divide() {
return l.intersects(rhs) || r.intersects(rhs);
}
}
LineString::from_iter(self.ls.iter().cloned()).intersects(rhs)
}
}

// commented out if they are implemented by blanket impl in main `Intersects` trait
symmetric_intersects_impl!(Coord<T>, MonotoneChainSegment<'a, T>);
// symmetric_intersects_impl!(Point<T>,MonotoneChainSegment<'a, T>);
// symmetric_intersects_impl!(MultiPoint<T>,MonotoneChainSegment<'a, T>);

symmetric_intersects_impl!(Line<T>, MonotoneChainSegment<'a, T>);
symmetric_intersects_impl!(LineString<T>, MonotoneChainSegment<'a, T>);
// symmetric_intersects_impl!(MultiLineString<T>,MonotoneChainSegment<'a, T>);

symmetric_intersects_impl!(Polygon<T>, MonotoneChainSegment<'a, T>);
// symmetric_intersects_impl!(MultiPolygon<T>,MonotoneChainSegment<'a, T>);
symmetric_intersects_impl!(Rect<T>, MonotoneChainSegment<'a, T>);
symmetric_intersects_impl!(Triangle<T>, MonotoneChainSegment<'a, T>);

// symmetric_intersects_impl!(Geometry<T>,MonotoneChainSegment<'a, T>);
// symmetric_intersects_impl!(GeometryCollection<T>,MonotoneChainSegment<'a, T>);

#[cfg(test)]
mod tests {
use super::*;
use crate::Convert;
use crate::wkt;

#[test]
fn test_intersects_edgecase() {
let pt: Point<f64> = wkt! {POINT(0 0)}.convert();

let ls0: LineString<f64> = LineString::empty();
let ls1: LineString<f64> = wkt! {LINESTRING(0 0)}.convert();
let ls2: LineString<f64> = wkt! {LINESTRING(0 0,1 1)}.convert();

let mcs0: MonotoneChain<f64> = (&ls0).into();
let mcs1: MonotoneChain<f64> = (&ls1).into();
let mcs2: MonotoneChain<f64> = (&ls2).into();

assert_eq!(ls0.intersects(&pt), mcs0.intersects(&pt));
assert_eq!(ls1.intersects(&pt), mcs1.intersects(&pt));
assert_eq!(ls2.intersects(&pt), mcs2.intersects(&pt));
}

#[test]
fn test_exhaustive_compile_test() {
use geo_types::*;

// data
let c = Coord { x: 0., y: 0. };
let pt: Point = Point::new(0., 0.);
let ls = line_string![(0., 0.).into(), (1., 1.).into()];
let multi_ls = MultiLineString::new(vec![ls.clone()]);
let ln: Line = Line::new((0., 0.), (1., 1.));

let poly = Polygon::new(LineString::from(vec![(0., 0.), (1., 1.), (1., 0.)]), vec![]);
let rect = Rect::new(coord! { x: 10., y: 20. }, coord! { x: 30., y: 10. });
let tri = Triangle::new(
coord! { x: 0., y: 0. },
coord! { x: 10., y: 20. },
coord! { x: 20., y: -10. },
);
let geom = Geometry::Point(pt);
let gc = GeometryCollection::new_from(vec![geom.clone()]);
let multi_point = MultiPoint::new(vec![pt]);
let multi_poly = MultiPolygon::new(vec![poly.clone()]);

let mc: MonotoneChain<f64> = (&ls).into();

// forwards
let _ = mc.intersects(&c);
let _ = mc.intersects(&pt);
let _ = mc.intersects(&multi_point);

let _ = mc.intersects(&ln);
let _ = mc.intersects(&ls);
let _ = mc.intersects(&multi_ls);

let _ = mc.intersects(&poly);
let _ = mc.intersects(&multi_poly);
let _ = mc.intersects(&rect);
let _ = mc.intersects(&tri);

let _ = mc.intersects(&geom);
let _ = mc.intersects(&gc);

// backwards
let _ = c.intersects(&mc);
let _ = pt.intersects(&mc);
let _ = multi_point.intersects(&mc);

let _ = ln.intersects(&mc);
let _ = ls.intersects(&mc);
let _ = multi_ls.intersects(&mc);

let _ = poly.intersects(&mc);
let _ = multi_poly.intersects(&mc);
let _ = rect.intersects(&mc);
let _ = tri.intersects(&mc);

let _ = geom.intersects(&mc);
let _ = gc.intersects(&mc);
}
}
13 changes: 13 additions & 0 deletions geo/src/algorithm/monotone/chain/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
mod monotone_chain;
pub use monotone_chain::MonotoneChain;

mod monotone_segment;
pub(crate) use monotone_segment::MonotoneChainSegment;
use monotone_segment::MonotoneChainSegmentFactory;

mod chain_iter;
pub use chain_iter::MonotoneChainIter;

mod intersects;

mod util;
Loading
Loading