From e30504c5fb87b5d50a47d12483b60497aa8c69da Mon Sep 17 00:00:00 2001 From: aviac Date: Tue, 13 Aug 2024 15:25:25 +0200 Subject: [PATCH 01/33] expose stitch in geo --- geo/src/algorithm/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/geo/src/algorithm/mod.rs b/geo/src/algorithm/mod.rs index 1697d0385..8eadc51e4 100644 --- a/geo/src/algorithm/mod.rs +++ b/geo/src/algorithm/mod.rs @@ -239,6 +239,7 @@ pub use simplify_vw::{SimplifyVw, SimplifyVwIdx, SimplifyVwPreserve}; /// Stitch together triangles with adjacent sides. Alternative to unioning triangles via BooleanOps. #[allow(dead_code)] pub(crate) mod stitch; +pub use stitch::StitchTriangles; /// Transform a geometry using PROJ. #[cfg(feature = "use-proj")] From 5525afe68db49f09d9a294cb9f9a2305c2b8746a Mon Sep 17 00:00:00 2001 From: aviac Date: Tue, 13 Aug 2024 15:37:52 +0200 Subject: [PATCH 02/33] seal StitchTriangles properly --- geo/src/algorithm/stitch.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/geo/src/algorithm/stitch.rs b/geo/src/algorithm/stitch.rs index f1fa48580..4cc0ec20c 100644 --- a/geo/src/algorithm/stitch.rs +++ b/geo/src/algorithm/stitch.rs @@ -8,7 +8,7 @@ use crate::{Contains, GeoFloat}; // ========= Error Type ============ #[derive(Debug)] -pub(crate) enum LineStitchingError { +pub enum LineStitchingError { IncompleteRing(&'static str), } @@ -25,7 +25,7 @@ pub(crate) type TriangleStitchingResult = Result; // ========= Main Algo ============ /// Trait to stitch together split up triangles. -pub(crate) trait StitchTriangles { +pub trait StitchTriangles: private::Stitchable { /// This stitching only happens along identical edges which are located in two separate /// geometries. Please read about the required pre conditions of the inputs! /// @@ -129,9 +129,21 @@ pub(crate) trait StitchTriangles { fn stitch_triangulation(&self) -> TriangleStitchingResult>; } +mod private { + use super::*; + + pub trait Stitchable: AsRef<[Triangle]> {} + impl Stitchable for S + where + S: AsRef<[Triangle]>, + T: GeoFloat, + { + } +} + impl StitchTriangles for S where - S: AsRef<[Triangle]>, + S: private::Stitchable, T: GeoFloat, { fn stitch_triangulation(&self) -> TriangleStitchingResult> { From 2bd7cc7a91ba172d57ff1566858ffd025d5e0a81 Mon Sep 17 00:00:00 2001 From: aviac Date: Tue, 13 Aug 2024 16:07:36 +0200 Subject: [PATCH 03/33] fixup docs --- geo/src/algorithm/stitch.rs | 60 ++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/geo/src/algorithm/stitch.rs b/geo/src/algorithm/stitch.rs index 4cc0ec20c..1fa8027f4 100644 --- a/geo/src/algorithm/stitch.rs +++ b/geo/src/algorithm/stitch.rs @@ -42,49 +42,47 @@ pub trait StitchTriangles: private::Stitchable { /// # Pre Conditions /// /// - The triangles in the input must not overlap! This also forbids identical triangles in the - /// input set. If you want to do a union on overlapping triangles then c.f. [`SpadeBoolops`]. + /// input set. If you want to do a union on overlapping triangles then c.f. `SpadeBoolops`. /// - Input triangles should be valid polygons. For a definition of validity /// c.f. /// /// # Examples /// - /// ```no_run - /// // commented this out since the trait is private and the doc test will fail - /// - /// //use geo::StitchTriangles; - /// //use geo::{Coord, Triangle, polygon}; + /// ```text + /// use geo::StitchTriangles; + /// use geo::{Coord, Triangle, polygon}; /// - /// //let tri1 = Triangle::from([ - /// // Coord { x: 0.0, y: 0.0 }, - /// // Coord { x: 1.0, y: 0.0 }, - /// // Coord { x: 0.0, y: 1.0 }, - /// //]); - /// //let tri2 = Triangle::from([ - /// // Coord { x: 1.0, y: 1.0 }, - /// // Coord { x: 1.0, y: 0.0 }, - /// // Coord { x: 0.0, y: 1.0 }, - /// //]); + /// let tri1 = Triangle::from([ + /// Coord { x: 0.0, y: 0.0 }, + /// Coord { x: 1.0, y: 0.0 }, + /// Coord { x: 0.0, y: 1.0 }, + /// ]); + /// let tri2 = Triangle::from([ + /// Coord { x: 1.0, y: 1.0 }, + /// Coord { x: 1.0, y: 0.0 }, + /// Coord { x: 0.0, y: 1.0 }, + /// ]); /// - /// //let result = vec![tri1, tri2].stitch_triangulation(); + /// let result = vec![tri1, tri2].stitch_triangulation(); /// - /// //assert!(result.is_ok()); + /// assert!(result.is_ok()); /// - /// //let mp = result.unwrap(); + /// let mp = result.unwrap(); /// - /// //assert_eq!(mp.0.len(), 1); + /// assert_eq!(mp.0.len(), 1); /// - /// //let poly = mp.0[0].clone(); - /// //// 4 coords + 1 duplicate for closed-ness - /// //assert_eq!(poly.exterior().0.len(), 4 + 1); + /// let poly = mp.0[0].clone(); + /// // 4 coords + 1 duplicate for closed-ness + /// assert_eq!(poly.exterior().0.len(), 4 + 1); /// - /// //let expected = polygon![ - /// // Coord { x: 1.0, y: 1.0 }, - /// // Coord { x: 0.0, y: 1.0 }, - /// // Coord { x: 0.0, y: 0.0 }, - /// // Coord { x: 1.0, y: 0.0 }, - /// //]; + /// let expected = polygon![ + /// Coord { x: 1.0, y: 1.0 }, + /// Coord { x: 0.0, y: 1.0 }, + /// Coord { x: 0.0, y: 0.0 }, + /// Coord { x: 1.0, y: 0.0 }, + /// ]; /// - /// //assert_eq!(poly, expected); + /// assert_eq!(poly, expected); /// ``` /// /// # Additional Notes @@ -125,7 +123,7 @@ pub trait StitchTriangles: private::Stitchable { /// /// If you want to do something more general like a /// [`Boolean Operation Union`](https://en.wikipedia.org/wiki/Boolean_operations_on_polygons) - /// you should use the trait [`BooleanOps`] or [`SpadeBoolops`]. + /// you should use the trait `BooleanOps` or `SpadeBoolops`. fn stitch_triangulation(&self) -> TriangleStitchingResult>; } From 379023bf10321e5124cfaed62d6fc0f47bc250af Mon Sep 17 00:00:00 2001 From: aviac Date: Fri, 19 Jan 2024 09:33:06 +0100 Subject: [PATCH 04/33] chore(cleanup): use pr suggestions Authored-by: RobWalt --- geo/src/algorithm/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/geo/src/algorithm/mod.rs b/geo/src/algorithm/mod.rs index 8eadc51e4..6eb7db34c 100644 --- a/geo/src/algorithm/mod.rs +++ b/geo/src/algorithm/mod.rs @@ -237,8 +237,7 @@ pub mod simplify_vw; pub use simplify_vw::{SimplifyVw, SimplifyVwIdx, SimplifyVwPreserve}; /// Stitch together triangles with adjacent sides. Alternative to unioning triangles via BooleanOps. -#[allow(dead_code)] -pub(crate) mod stitch; +pub mod stitch; pub use stitch::StitchTriangles; /// Transform a geometry using PROJ. From 60081423f184355578cfb4bda234b384c5f54fe9 Mon Sep 17 00:00:00 2001 From: aviac Date: Tue, 12 Dec 2023 08:29:17 +0100 Subject: [PATCH 05/33] chore: move change from changelog to unreleased --- geo/CHANGES.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/geo/CHANGES.md b/geo/CHANGES.md index e68ac20e9..d10039f0e 100644 --- a/geo/CHANGES.md +++ b/geo/CHANGES.md @@ -48,21 +48,6 @@ * * PERF: small improvements to TriangulateSpade trait * -* POSSIBLY BREAKING: Minimum supported version of Rust (MSRV) is now 1.70 - * -* Add topological equality comparison method: - * -* Add docs to Relate trait - * -* Add remaining Relate predicates - * -* Update rstar to v0.12.0 -* Implement `CoordsIter` for arrays and slices. This is useful when you'd like to use traits - implemented for `CoordsIter` without re-allocating (e.g., creating a `MultiPoint`). -* Add `compose_many` method to `AffineOps` - * -* Point in `Triangle` and `Rect` performance improvemnets - * ## 0.27.0 From 58a4d3ef9b68aacf8a7d6c07e6086224bdd4a8f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20H=C3=BCgel?= Date: Mon, 16 Oct 2023 16:05:46 +0100 Subject: [PATCH 06/33] Add inverse method to AffineTransform --- geo/src/algorithm/affine_ops.rs | 47 ++++++++++++++++++++++++++++----- geo/src/algorithm/translate.rs | 2 +- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/geo/src/algorithm/affine_ops.rs b/geo/src/algorithm/affine_ops.rs index 24245a430..671d0098f 100644 --- a/geo/src/algorithm/affine_ops.rs +++ b/geo/src/algorithm/affine_ops.rs @@ -36,7 +36,7 @@ use std::{fmt, ops::Mul, ops::Neg}; /// line_string![(x: 2.0, y: 2.0),(x: 4.0, y: 4.0)] /// ); /// ``` -pub trait AffineOps { +pub trait AffineOps { /// Apply `transform` immutably, outputting a new geometry. #[must_use] fn affine_transform(&self, transform: &AffineTransform) -> Self; @@ -45,7 +45,9 @@ pub trait AffineOps { fn affine_transform_mut(&mut self, transform: &AffineTransform); } -impl + MapCoords> AffineOps for M { +impl + MapCoords> AffineOps + for M +{ fn affine_transform(&self, transform: &AffineTransform) -> Self { self.map_coords(|c| transform.apply(c)) } @@ -124,16 +126,16 @@ impl + MapCoords> Affin /// ``` #[derive(Copy, Clone, PartialEq, Eq)] -pub struct AffineTransform([[T; 3]; 3]); +pub struct AffineTransform([[T; 3]; 3]); -impl Default for AffineTransform { +impl Default for AffineTransform { fn default() -> Self { // identity matrix Self::identity() } } -impl AffineTransform { +impl AffineTransform { /// Create a new affine transformation by composing two `AffineTransform`s. /// /// This is a **cumulative** operation; the new transform is *added* to the existing transform. @@ -222,6 +224,37 @@ impl AffineTransform { ) } + /// Return the inverse of a given transform. Composing a transform with its inverse yields + /// the [identity matrix](Self::identity) + pub fn inverse(&self) -> Option + where + ::Output: Mul, + <::Output as Mul>::Output: ToPrimitive, + { + let a = self.0[0][0]; + let b = self.0[0][1]; + let xoff = self.0[0][2]; + let d = self.0[1][0]; + let e = self.0[1][1]; + let yoff = self.0[1][2]; + + let determinant = a * e - b * d; + + if determinant == T::zero() { + return None; // The matrix is not invertible + } + + let inv_det = T::one() / determinant; + Some(Self::new( + e * inv_det, + T::from(-b * inv_det).unwrap(), + (b * yoff - e * xoff) * inv_det, + T::from(-d * inv_det).unwrap(), + a * inv_det, + (d * xoff - a * yoff) * inv_det, + )) + } + /// Whether the transformation is equivalent to the [identity matrix](Self::identity), /// that is, whether it's application will be a a no-op. /// @@ -389,13 +422,13 @@ impl fmt::Debug for AffineTransform { } } -impl From<[T; 6]> for AffineTransform { +impl From<[T; 6]> for AffineTransform { fn from(arr: [T; 6]) -> Self { Self::new(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]) } } -impl From<(T, T, T, T, T, T)> for AffineTransform { +impl From<(T, T, T, T, T, T)> for AffineTransform { fn from(tup: (T, T, T, T, T, T)) -> Self { Self::new(tup.0, tup.1, tup.2, tup.3, tup.4, tup.5) } diff --git a/geo/src/algorithm/translate.rs b/geo/src/algorithm/translate.rs index 51a7208d8..e3fbef51c 100644 --- a/geo/src/algorithm/translate.rs +++ b/geo/src/algorithm/translate.rs @@ -39,7 +39,7 @@ pub trait Translate { impl Translate for G where - T: CoordNum, + T: CoordNum + std::ops::Neg, G: AffineOps, { fn translate(&self, x_offset: T, y_offset: T) -> Self { From c8ab39d26b559bd50437245849abe93015563562 Mon Sep 17 00:00:00 2001 From: aviac Date: Wed, 11 Oct 2023 11:09:01 +0200 Subject: [PATCH 07/33] feat: implement boolops based on spade --- geo/src/algorithm/mod.rs | 4 + geo/src/algorithm/spade_boolops.rs | 128 +++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 geo/src/algorithm/spade_boolops.rs diff --git a/geo/src/algorithm/mod.rs b/geo/src/algorithm/mod.rs index 6eb7db34c..6e8881ea5 100644 --- a/geo/src/algorithm/mod.rs +++ b/geo/src/algorithm/mod.rs @@ -240,6 +240,10 @@ pub use simplify_vw::{SimplifyVw, SimplifyVwIdx, SimplifyVwPreserve}; pub mod stitch; pub use stitch::StitchTriangles; +/// Boolean Operations based on constrained triangulation +pub mod spade_boolops; +pub use spade_boolops::SpadeBoolops; + /// Transform a geometry using PROJ. #[cfg(feature = "use-proj")] pub mod transform; diff --git a/geo/src/algorithm/spade_boolops.rs b/geo/src/algorithm/spade_boolops.rs new file mode 100644 index 000000000..8c8b35508 --- /dev/null +++ b/geo/src/algorithm/spade_boolops.rs @@ -0,0 +1,128 @@ +use geo_types::{MultiPolygon, Point, Polygon, Triangle}; + +use crate::stitch::LineStitchingError; +use crate::triangulate_spade::{SpadeTriangulationFloat, TriangulationError}; +use crate::{Centroid, Contains, Scale, Stitch, TriangulateSpade}; + +// ====== Error ======== + +#[derive(Debug)] +pub enum SpadeBoolopsError { + TriangulationError(TriangulationError), + StitchError(LineStitchingError), +} + +impl std::fmt::Display for SpadeBoolopsError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for SpadeBoolopsError {} + +pub type SpadeBoolopsResult = Result, SpadeBoolopsError>; + +// ======== Spade Boolops trait ============ + +pub trait SpadeBoolops +where + T: SpadeTriangulationFloat, + Self: Contains> + Scale + Sized, +{ + fn boolop) -> bool>( + p1: &Self, + p2: &Self, + op_pred: F, + ) -> SpadeBoolopsResult; + + fn difference(p1: &Self, p2: &Self) -> SpadeBoolopsResult { + Self::boolop(p1, p2, |tri| { + contains_triangle(p1, tri) && !contains_triangle(p2, tri) + }) + } + + fn intersection(p1: &Self, p2: &Self) -> SpadeBoolopsResult { + Self::boolop(p1, p2, |tri| { + contains_triangle(p1, tri) && contains_triangle(p2, tri) + }) + } + + fn union(p1: &Self, p2: &Self) -> SpadeBoolopsResult { + Self::boolop(p1, p2, |tri| { + contains_triangle(p1, tri) || contains_triangle(p2, tri) + }) + } +} + +// ========== trait impls ========== + +impl SpadeBoolops for Polygon +where + T: SpadeTriangulationFloat, +{ + fn boolop) -> bool>( + p1: &Self, + p2: &Self, + op_pred: F, + ) -> SpadeBoolopsResult { + vec![p1.clone(), p2.clone()] + .unconstrained_triangulation() + .map_err(SpadeBoolopsError::TriangulationError)? + .into_iter() + .filter(|tri| op_pred(tri)) + .map(|tri| tri.to_polygon()) + .collect::>() + .stitch_together() + .map_err(SpadeBoolopsError::StitchError) + } +} + +impl SpadeBoolops for MultiPolygon +where + T: SpadeTriangulationFloat, +{ + fn boolop) -> bool>( + p1: &Self, + p2: &Self, + op_pred: F, + ) -> SpadeBoolopsResult { + vec![p1.clone(), p2.clone()] + .unconstrained_triangulation() + .map_err(SpadeBoolopsError::TriangulationError)? + .into_iter() + .filter(|tri| op_pred(tri)) + .map(|tri| tri.to_polygon()) + .collect::>() + .stitch_together() + .map_err(SpadeBoolopsError::StitchError) + } +} + +// ========== Helper ========= + +fn contains_triangle(p: &P, tri: &Triangle) -> bool +where + P: Contains> + Scale, + T: SpadeTriangulationFloat, +{ + // this scaling is to prevent tiny triangles that can potentially occur on the outer boundary + // of the poly to be included into the result + p.scale(>::from(0.9999)) + .contains(&tri.centroid()) +} + +#[cfg(test)] +mod spade_boolops_tests { + use super::*; + use geo_types::*; + + #[test] + fn basic_intersection_compiles() { + let zero = Coord::zero(); + let one = Coord { x: 1.0, y: 1.0 }; + let rect1 = Rect::new(zero, one * 2.0); + let rect2 = Rect::new(one, one * 3.0); + + SpadeBoolops::intersection(&rect1.to_polygon(), &rect2.to_polygon()).unwrap(); + } +} From f70e7c234e8f8fbc17b6668ea905432e55ddaac6 Mon Sep 17 00:00:00 2001 From: RobWalt Date: Sat, 28 Oct 2023 21:37:19 +0200 Subject: [PATCH 08/33] refactor: create directory for spade boolops --- geo/src/algorithm/{spade_boolops.rs => spade_boolops/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename geo/src/algorithm/{spade_boolops.rs => spade_boolops/mod.rs} (100%) diff --git a/geo/src/algorithm/spade_boolops.rs b/geo/src/algorithm/spade_boolops/mod.rs similarity index 100% rename from geo/src/algorithm/spade_boolops.rs rename to geo/src/algorithm/spade_boolops/mod.rs From 1aa34332e3eb3566169cbf3ee2b37d4bd90abd23 Mon Sep 17 00:00:00 2001 From: RobWalt Date: Sat, 28 Oct 2023 21:45:22 +0200 Subject: [PATCH 09/33] refactor: split up spade boolops module --- geo/src/algorithm/spade_boolops/error.rs | 19 +++ geo/src/algorithm/spade_boolops/helper.rs | 15 ++ geo/src/algorithm/spade_boolops/mod.rs | 132 +----------------- .../spade_boolops/spade_boolops_tests.rs | 12 ++ geo/src/algorithm/spade_boolops/trait_def.rs | 36 +++++ geo/src/algorithm/spade_boolops/trait_impl.rs | 49 +++++++ 6 files changed, 138 insertions(+), 125 deletions(-) create mode 100644 geo/src/algorithm/spade_boolops/error.rs create mode 100644 geo/src/algorithm/spade_boolops/helper.rs create mode 100644 geo/src/algorithm/spade_boolops/spade_boolops_tests.rs create mode 100644 geo/src/algorithm/spade_boolops/trait_def.rs create mode 100644 geo/src/algorithm/spade_boolops/trait_impl.rs diff --git a/geo/src/algorithm/spade_boolops/error.rs b/geo/src/algorithm/spade_boolops/error.rs new file mode 100644 index 000000000..04779dfc3 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/error.rs @@ -0,0 +1,19 @@ +use crate::stitch::LineStitchingError; +use crate::triangulate_spade::TriangulationError; +use geo_types::MultiPolygon; + +#[derive(Debug)] +pub enum SpadeBoolopsError { + TriangulationError(TriangulationError), + StitchError(LineStitchingError), +} + +impl std::fmt::Display for SpadeBoolopsError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for SpadeBoolopsError {} + +pub type SpadeBoolopsResult = Result, SpadeBoolopsError>; diff --git a/geo/src/algorithm/spade_boolops/helper.rs b/geo/src/algorithm/spade_boolops/helper.rs new file mode 100644 index 000000000..e3ee53c65 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/helper.rs @@ -0,0 +1,15 @@ +use geo_types::{Point, Triangle}; + +use crate::triangulate_spade::SpadeTriangulationFloat; +use crate::{Centroid, Contains, Scale}; + +pub fn contains_triangle(p: &P, tri: &Triangle) -> bool +where + P: Contains> + Scale, + T: SpadeTriangulationFloat, +{ + // this scaling is to prevent tiny triangles that can potentially occur on the outer boundary + // of the poly to be included into the result + p.scale(>::from(0.9999)) + .contains(&tri.centroid()) +} diff --git a/geo/src/algorithm/spade_boolops/mod.rs b/geo/src/algorithm/spade_boolops/mod.rs index 8c8b35508..c2886fdf5 100644 --- a/geo/src/algorithm/spade_boolops/mod.rs +++ b/geo/src/algorithm/spade_boolops/mod.rs @@ -1,128 +1,10 @@ -use geo_types::{MultiPolygon, Point, Polygon, Triangle}; +pub mod error; +pub mod helper; +pub mod trait_def; +pub mod trait_impl; -use crate::stitch::LineStitchingError; -use crate::triangulate_spade::{SpadeTriangulationFloat, TriangulationError}; -use crate::{Centroid, Contains, Scale, Stitch, TriangulateSpade}; - -// ====== Error ======== - -#[derive(Debug)] -pub enum SpadeBoolopsError { - TriangulationError(TriangulationError), - StitchError(LineStitchingError), -} - -impl std::fmt::Display for SpadeBoolopsError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - -impl std::error::Error for SpadeBoolopsError {} - -pub type SpadeBoolopsResult = Result, SpadeBoolopsError>; - -// ======== Spade Boolops trait ============ - -pub trait SpadeBoolops -where - T: SpadeTriangulationFloat, - Self: Contains> + Scale + Sized, -{ - fn boolop) -> bool>( - p1: &Self, - p2: &Self, - op_pred: F, - ) -> SpadeBoolopsResult; - - fn difference(p1: &Self, p2: &Self) -> SpadeBoolopsResult { - Self::boolop(p1, p2, |tri| { - contains_triangle(p1, tri) && !contains_triangle(p2, tri) - }) - } - - fn intersection(p1: &Self, p2: &Self) -> SpadeBoolopsResult { - Self::boolop(p1, p2, |tri| { - contains_triangle(p1, tri) && contains_triangle(p2, tri) - }) - } - - fn union(p1: &Self, p2: &Self) -> SpadeBoolopsResult { - Self::boolop(p1, p2, |tri| { - contains_triangle(p1, tri) || contains_triangle(p2, tri) - }) - } -} - -// ========== trait impls ========== - -impl SpadeBoolops for Polygon -where - T: SpadeTriangulationFloat, -{ - fn boolop) -> bool>( - p1: &Self, - p2: &Self, - op_pred: F, - ) -> SpadeBoolopsResult { - vec![p1.clone(), p2.clone()] - .unconstrained_triangulation() - .map_err(SpadeBoolopsError::TriangulationError)? - .into_iter() - .filter(|tri| op_pred(tri)) - .map(|tri| tri.to_polygon()) - .collect::>() - .stitch_together() - .map_err(SpadeBoolopsError::StitchError) - } -} - -impl SpadeBoolops for MultiPolygon -where - T: SpadeTriangulationFloat, -{ - fn boolop) -> bool>( - p1: &Self, - p2: &Self, - op_pred: F, - ) -> SpadeBoolopsResult { - vec![p1.clone(), p2.clone()] - .unconstrained_triangulation() - .map_err(SpadeBoolopsError::TriangulationError)? - .into_iter() - .filter(|tri| op_pred(tri)) - .map(|tri| tri.to_polygon()) - .collect::>() - .stitch_together() - .map_err(SpadeBoolopsError::StitchError) - } -} - -// ========== Helper ========= - -fn contains_triangle(p: &P, tri: &Triangle) -> bool -where - P: Contains> + Scale, - T: SpadeTriangulationFloat, -{ - // this scaling is to prevent tiny triangles that can potentially occur on the outer boundary - // of the poly to be included into the result - p.scale(>::from(0.9999)) - .contains(&tri.centroid()) -} +pub use error::SpadeBoolopsError; +pub use trait_def::SpadeBoolops; #[cfg(test)] -mod spade_boolops_tests { - use super::*; - use geo_types::*; - - #[test] - fn basic_intersection_compiles() { - let zero = Coord::zero(); - let one = Coord { x: 1.0, y: 1.0 }; - let rect1 = Rect::new(zero, one * 2.0); - let rect2 = Rect::new(one, one * 3.0); - - SpadeBoolops::intersection(&rect1.to_polygon(), &rect2.to_polygon()).unwrap(); - } -} +pub mod spade_boolops_tests; diff --git a/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs b/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs new file mode 100644 index 000000000..6efdc70d2 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs @@ -0,0 +1,12 @@ +use super::*; +use geo_types::*; + +#[test] +fn basic_intersection_compiles() { + let zero = Coord::zero(); + let one = Coord { x: 1.0, y: 1.0 }; + let rect1 = Rect::new(zero, one * 2.0); + let rect2 = Rect::new(one, one * 3.0); + + SpadeBoolops::intersection(&rect1.to_polygon(), &rect2.to_polygon()).unwrap(); +} diff --git a/geo/src/algorithm/spade_boolops/trait_def.rs b/geo/src/algorithm/spade_boolops/trait_def.rs new file mode 100644 index 000000000..78d2dab1d --- /dev/null +++ b/geo/src/algorithm/spade_boolops/trait_def.rs @@ -0,0 +1,36 @@ +pub use crate::spade_boolops::error::{SpadeBoolopsError, SpadeBoolopsResult}; +pub use crate::spade_boolops::helper::contains_triangle; +use geo_types::{Point, Triangle}; + +use crate::triangulate_spade::SpadeTriangulationFloat; +use crate::{Contains, Scale}; + +pub trait SpadeBoolops +where + T: SpadeTriangulationFloat, + Self: Contains> + Scale + Sized, +{ + fn boolop) -> bool>( + p1: &Self, + p2: &Self, + op_pred: F, + ) -> SpadeBoolopsResult; + + fn difference(p1: &Self, p2: &Self) -> SpadeBoolopsResult { + Self::boolop(p1, p2, |tri| { + contains_triangle(p1, tri) && !contains_triangle(p2, tri) + }) + } + + fn intersection(p1: &Self, p2: &Self) -> SpadeBoolopsResult { + Self::boolop(p1, p2, |tri| { + contains_triangle(p1, tri) && contains_triangle(p2, tri) + }) + } + + fn union(p1: &Self, p2: &Self) -> SpadeBoolopsResult { + Self::boolop(p1, p2, |tri| { + contains_triangle(p1, tri) || contains_triangle(p2, tri) + }) + } +} diff --git a/geo/src/algorithm/spade_boolops/trait_impl.rs b/geo/src/algorithm/spade_boolops/trait_impl.rs new file mode 100644 index 000000000..a26778b8d --- /dev/null +++ b/geo/src/algorithm/spade_boolops/trait_impl.rs @@ -0,0 +1,49 @@ +use crate::algorithm::stitch::Stitch; +pub use crate::spade_boolops::error::{SpadeBoolopsError, SpadeBoolopsResult}; +use crate::spade_boolops::trait_def::SpadeBoolops; +use geo_types::{MultiPolygon, Polygon, Triangle}; + +use crate::triangulate_spade::SpadeTriangulationFloat; +use crate::TriangulateSpade; + +impl SpadeBoolops for Polygon +where + T: SpadeTriangulationFloat, +{ + fn boolop) -> bool>( + p1: &Self, + p2: &Self, + op_pred: F, + ) -> SpadeBoolopsResult { + vec![p1.clone(), p2.clone()] + .unconstrained_triangulation() + .map_err(SpadeBoolopsError::TriangulationError)? + .into_iter() + .filter(|tri| op_pred(tri)) + .map(|tri| tri.to_polygon()) + .collect::>() + .stitch_together() + .map_err(SpadeBoolopsError::StitchError) + } +} + +impl SpadeBoolops for MultiPolygon +where + T: SpadeTriangulationFloat, +{ + fn boolop) -> bool>( + p1: &Self, + p2: &Self, + op_pred: F, + ) -> SpadeBoolopsResult { + vec![p1.clone(), p2.clone()] + .unconstrained_triangulation() + .map_err(SpadeBoolopsError::TriangulationError)? + .into_iter() + .filter(|tri| op_pred(tri)) + .map(|tri| tri.to_polygon()) + .collect::>() + .stitch_together() + .map_err(SpadeBoolopsError::StitchError) + } +} From 722081fbb6f0100b556149cb2d7b3a2ccac07edc Mon Sep 17 00:00:00 2001 From: RobWalt Date: Sat, 28 Oct 2023 22:44:17 +0200 Subject: [PATCH 10/33] chore: add spade boolops unit test data in wkt form --- geo/src/algorithm/spade_boolops/data/collinear_outline_parts.wkt | 1 + geo/src/algorithm/spade_boolops/data/duplicate_points_case_1.wkt | 1 + geo/src/algorithm/spade_boolops/data/duplicate_points_case_2.wkt | 1 + geo/src/algorithm/spade_boolops/data/duplicate_points_case_3.wkt | 1 + geo/src/algorithm/spade_boolops/data/hole_after_union.wkt | 1 + geo/src/algorithm/spade_boolops/data/holes_are_preserved.wkt | 1 + .../algorithm/spade_boolops/data/intersection_address_001.wkt | 1 + .../algorithm/spade_boolops/data/intersection_address_002.wkt | 1 + .../spade_boolops/data/intersection_fail_after_union_fix_1.wkt | 1 + .../spade_boolops/data/intersection_fail_after_union_fix_2.wkt | 1 + geo/src/algorithm/spade_boolops/data/missing_triangle_case_1.wkt | 1 + geo/src/algorithm/spade_boolops/data/missing_triangle_case_2.wkt | 1 + geo/src/algorithm/spade_boolops/data/missing_triangle_case_3.wkt | 1 + geo/src/algorithm/spade_boolops/data/multiple_unions.wkt | 1 + geo/src/algorithm/spade_boolops/data/simple_union.wkt | 1 + geo/src/algorithm/spade_boolops/data/star.wkt | 1 + geo/src/algorithm/spade_boolops/data/two_holes_after_union.wkt | 1 + geo/src/algorithm/spade_boolops/data/union_address_001.wkt | 1 + geo/src/algorithm/spade_boolops/data/union_address_002.wkt | 1 + geo/src/algorithm/spade_boolops/data/union_address_003.wkt | 1 + geo/src/algorithm/spade_boolops/data/union_address_004.wkt | 1 + geo/src/algorithm/spade_boolops/data/union_address_005.wkt | 1 + geo/src/algorithm/spade_boolops/data/union_address_006.wkt | 1 + geo/src/algorithm/spade_boolops/data/union_address_007.wkt | 1 + geo/src/algorithm/spade_boolops/data/union_address_008.wkt | 1 + geo/src/algorithm/spade_boolops/data/union_address_009.wkt | 1 + geo/src/algorithm/spade_boolops/data/union_address_010.wkt | 1 + geo/src/algorithm/spade_boolops/data/union_address_011.wkt | 1 + geo/src/algorithm/spade_boolops/data/union_address_012.wkt | 1 + geo/src/algorithm/spade_boolops/data/union_address_013.wkt | 1 + geo/src/algorithm/spade_boolops/data/union_address_014.wkt | 1 + geo/src/algorithm/spade_boolops/data/union_address_015.wkt | 1 + .../spade_boolops/data/union_both_intermediate_points_stay.wkt | 1 + geo/src/algorithm/spade_boolops/data/union_fails_overlap.wkt | 1 + .../spade_boolops/data/union_not_completely_shared_line.wkt | 1 + .../spade_boolops/data/union_one_intermediate_point_stays.wkt | 1 + .../algorithm/spade_boolops/data/union_still_fails_overlap.wkt | 1 + 37 files changed, 37 insertions(+) create mode 100644 geo/src/algorithm/spade_boolops/data/collinear_outline_parts.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/duplicate_points_case_1.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/duplicate_points_case_2.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/duplicate_points_case_3.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/hole_after_union.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/holes_are_preserved.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/intersection_address_001.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/intersection_address_002.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/intersection_fail_after_union_fix_1.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/intersection_fail_after_union_fix_2.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/missing_triangle_case_1.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/missing_triangle_case_2.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/missing_triangle_case_3.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/multiple_unions.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/simple_union.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/star.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/two_holes_after_union.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_address_001.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_address_002.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_address_003.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_address_004.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_address_005.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_address_006.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_address_007.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_address_008.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_address_009.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_address_010.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_address_011.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_address_012.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_address_013.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_address_014.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_address_015.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_both_intermediate_points_stay.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_fails_overlap.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_not_completely_shared_line.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_one_intermediate_point_stays.wkt create mode 100644 geo/src/algorithm/spade_boolops/data/union_still_fails_overlap.wkt diff --git a/geo/src/algorithm/spade_boolops/data/collinear_outline_parts.wkt b/geo/src/algorithm/spade_boolops/data/collinear_outline_parts.wkt new file mode 100644 index 000000000..eb0cf9ddb --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/collinear_outline_parts.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((-18.5 1.1148996,-18.5 -4.554501,2.8999994 -4.242997,2.8999991 0.99029636,-18.5 1.1148996)),POLYGON((-18.5 6.85,2.899999 7.5,2.8999996 -9,-18.5 -9,-18.5 6.85))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/duplicate_points_case_1.wkt b/geo/src/algorithm/spade_boolops/data/duplicate_points_case_1.wkt new file mode 100644 index 000000000..7d384f6d2 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/duplicate_points_case_1.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((-3.466464 0.9141397,-3.4187205 2.2369194,-10.025221 2.4116945,-10.090524 -0.048262052,-3.466464 0.9141397)),POLYGON((-10.090524 -0.048262052,-3.466464 0.9141397,-3.2192326 7.763933,-9.885997 7.6562467,-10.090524 -0.048262052))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/duplicate_points_case_2.wkt b/geo/src/algorithm/spade_boolops/data/duplicate_points_case_2.wkt new file mode 100644 index 000000000..056f090eb --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/duplicate_points_case_2.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((-7.0219817 4.148702,-9.554118 0.5560738,-12.323914 1.7359772,-12.50497 4.1031623,-7.0219817 4.148702)),POLYGON((-7.392356 9.639786,-7.0219817 4.148702,-9.554118 0.5560738,-12.230486 0.5144694,-12.91143 9.417376,-7.392356 9.639786))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/duplicate_points_case_3.wkt b/geo/src/algorithm/spade_boolops/data/duplicate_points_case_3.wkt new file mode 100644 index 000000000..59d9efa0e --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/duplicate_points_case_3.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((-0.70531404 1.6649652,-13.588498 1.7720098,-12.138729 -0.48307407,-8.598329 -1.9073501,-0.70531404 1.6649652)),POLYGON((-10.151698 -0.48403928,-8.598329 -1.9073501,-8.529049 -0.4848275,-10.151698 -0.48403928))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/hole_after_union.wkt b/geo/src/algorithm/spade_boolops/data/hole_after_union.wkt new file mode 100644 index 000000000..a7996bec6 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/hole_after_union.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((0 0,5 0,5 2.5,2.5 2.5,2.5 7.5,5 7.5,5 10,0 10,0 0)),POLYGON((10 10,5 10,5 7.5,7.5 7.5,7.5 2.5,5 2.5,5 0,10 0,10 10))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/holes_are_preserved.wkt b/geo/src/algorithm/spade_boolops/data/holes_are_preserved.wkt new file mode 100644 index 000000000..b1243e444 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/holes_are_preserved.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((0 0,10 0,10 10,0 10,0 0),(2.5 2.5,5 2.5,5 5,2.5 5,2.5 2.5))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/intersection_address_001.wkt b/geo/src/algorithm/spade_boolops/data/intersection_address_001.wkt new file mode 100644 index 000000000..cc9b01fe6 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/intersection_address_001.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((-3.383462 0.16653681,-10.665726 -0.6342745,-12.017065 -6.967066,-2.395382 -6.1546187,-3.383462 0.16653681)),POLYGON((-14 2.7,0.39999905 3.949998,0.39999962 -8.95,-13.999999 -8.95,-14 2.7))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/intersection_address_002.wkt b/geo/src/algorithm/spade_boolops/data/intersection_address_002.wkt new file mode 100644 index 000000000..e3b22d5b5 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/intersection_address_002.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((10 10,9 10,9 11,1 11,1 10,0 10,0 0,10 0,10 10)),POLYGON((7.5 7.5,6 7.5,6 8,4 8,4 7.5,2.5 7.5,2.5 2.5,7.5 2.5,7.5 7.5))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/intersection_fail_after_union_fix_1.wkt b/geo/src/algorithm/spade_boolops/data/intersection_fail_after_union_fix_1.wkt new file mode 100644 index 000000000..fff830edf --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/intersection_fail_after_union_fix_1.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((-12.503868 -2.113399,-6.3538685 -1.113399,-7.1038685 1.4866009,-11.703869 1.4866009,-12.503868 -2.113399)),POLYGON((-16.1 5.2000003,-1.5 7.3,-1.5500001 -5.15,-15.85 -5.35,-16.1 5.2000003))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/intersection_fail_after_union_fix_2.wkt b/geo/src/algorithm/spade_boolops/data/intersection_fail_after_union_fix_2.wkt new file mode 100644 index 000000000..67e885e1f --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/intersection_fail_after_union_fix_2.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((-8.340716 -0.29907477,-8.790716 0.4509253,-11.703869 1.4866009,-12.503868 -2.113399,-8.340716 -0.29907477)),POLYGON((-12.503868 -2.113399,-6.3538685 -1.113399,-7.1038685 1.4866009,-11.703869 1.4866009,-12.503868 -2.113399))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/missing_triangle_case_1.wkt b/geo/src/algorithm/spade_boolops/data/missing_triangle_case_1.wkt new file mode 100644 index 000000000..50c6ac44a --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/missing_triangle_case_1.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((-10.579803 -13.75,-10.579803 -4.753352,-20.800001 -5.1445236,-20.800001 -25.355036,-16.0562 -25.355036,-16.056202 -13.75,-10.579803 -13.75)),POLYGON((-14.379889 -0.2594614,-8.145693 -0.5267252,-7.7950563 -8.07181,-14.332443 -7.882949,-14.379889 -0.2594614))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/missing_triangle_case_2.wkt b/geo/src/algorithm/spade_boolops/data/missing_triangle_case_2.wkt new file mode 100644 index 000000000..126f5335b --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/missing_triangle_case_2.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((-20.800001 -5.1445236,-20.800001 -25.355036,-16.0562 -25.355036,-16.056202 -13.75,-10.579803 -13.75,-10.579803 -7.99136,-14.332443 -7.882949,-14.351022 -4.897693,-20.800001 -5.1445236)),POLYGON((-8.145693 -0.5267252,-7.7950563 -8.07181,-10.579803 -7.99136,-10.579803 -4.753352,-14.351022 -4.897693,-14.379889 -0.2594614,-8.145693 -0.5267252))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/missing_triangle_case_3.wkt b/geo/src/algorithm/spade_boolops/data/missing_triangle_case_3.wkt new file mode 100644 index 000000000..ac45cad36 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/missing_triangle_case_3.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((-20.800001 -5.1445236,-20.800001 -25.355036,-16.0562 -25.355036,-16.056202 -13.75,-10.579803 -13.75,-10.579803 -7.99136,-14.332443 -7.882949,-14.351022 -4.897693,-20.800001 -5.1445236)),POLYGON((-20.800001 7.25,-20.800001 -13.75,-14.295929 -13.75,-14.332443 -7.882949,-14.379889 -0.2594614,-14.423152 6.6920257,-20.800001 7.25))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/multiple_unions.wkt b/geo/src/algorithm/spade_boolops/data/multiple_unions.wkt new file mode 100644 index 000000000..35491a1fa --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/multiple_unions.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((9.334806 -60.840466,12.238895 -61.407673,12.238895 -70.09725,7.701256 -70.09725,9.334806 -60.840466)),POLYGON((14.961479 -61.770683,13.509435 -70.09725,12.261583 -70.09725,12.261583 -61.407673,14.961479 -61.770683)),POLYGON((7.338245 -71.75349,7.701256 -70.09725,12.238895 -70.09725,12.238895 -72.456825,7.338245 -71.75349)),POLYGON((13.146423 -72.66102,12.261583 -72.456825,12.261583 -70.09725,13.509435 -70.09725,13.146423 -72.66102))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/simple_union.wkt b/geo/src/algorithm/spade_boolops/data/simple_union.wkt new file mode 100644 index 000000000..1063f4eb0 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/simple_union.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((9.356785 -60.8609,12.261583 -61.42824,12.261583 -70.11994,7.722836 -70.11994,9.356785 -60.8609)),POLYGON((14.962138 -61.791344,13.509739 -70.11994,12.261583 -70.11994,12.261583 -61.42824,14.962138 -61.791344))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/star.wkt b/geo/src/algorithm/spade_boolops/data/star.wkt new file mode 100644 index 000000000..e6580850b --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/star.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((-2.875 -0.25,-5.225 1.9,-4.375 -1.75,-7.375 -1.3000001,-3.575 -3.6000001,-7.0750003 -4.85,-3.825 -5.5,-6.425 -8.3,-2.575 -6.55,-2.575 -9.6,-0.725 -7.4500003,1.225 -9.7,1.025 -7.25,3.825 -6.9500003,2.075 -5,4.725 -2.25,1.875 -2.25,3.425 1,0.22500001 -0.45000002,0.125 2.45,-1.6750001 0.05,-2.575 3,-2.875 -0.25))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/two_holes_after_union.wkt b/geo/src/algorithm/spade_boolops/data/two_holes_after_union.wkt new file mode 100644 index 000000000..7e2947ca7 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/two_holes_after_union.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((0 0,5 0,5 2.5,2.5 2.5,2.5 7.5,5 7.5,5 12.5,2.5 12.5,2.5 17.5,5 17.5,5 20,0 20,0 0)),POLYGON((10 20,5 20,5 17.5,7.5 17.5,7.5 12.5,5 12.5,5 7.5,7.5 7.5,7.5 2.5,5 2.5,5 0,10 0,10 20))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_address_001.wkt b/geo/src/algorithm/spade_boolops/data/union_address_001.wkt new file mode 100644 index 000000000..bb22fec6d --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_address_001.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((12.937555 -26.36888,9.630956 -28.502926,9.842015 -47.076164,22.036566 -47.076164,21.989664 -44.613804,22.76355 -44.05098,29.611258 -47.076164,51.16278 -47.076164,51.16278 -61.0999,52.40569 -61.662724,54.21142 -60.91229,60.42595 -63.82022,60.590107 -63.445004,67.179855 -66.51709,67.78958 -68.557335,69.90018 -69.44848,71.58865 -68.58079,81.93057 -73.3648,81.10979 -75.053276,85.35442 -76.74175,86.03451 -75.264336,91.96763 -77.96121,97.61933 -64.73481,12.937555 -26.36888)),POLYGON((49.966778 -60.583977,49.56811 -58.8486,47.926537 -58.098164,44.66684 -56.620747,44.971703 -56.011017,35.68508 -51.696022,35.450573 -49.679234,30.408594 -47.427933,29.611258 -47.076164,51.16278 -47.076164,51.16278 -61.0999,49.966778 -60.583977)),POLYGON((44.127464 -116.11608,25.577677 -116.04573,13.406576 -116.16299,10.686254 -113.34886,10.451743 -89.59293,10.240684 -73.64622,9.93582 -56.409687,9.842015 -47.076164,22.036566 -47.076164,22.153822 -57.11322,22.177273 -59.927345,22.97461 -59.903896,22.99806 -64.64101,23.795395 -64.59411,23.912651 -69.143616,25.132107 -69.09671,25.179008 -71.98119,23.959553 -71.98119,24.12371 -77.28113,25.343166 -77.32803,25.413519 -80.44702,24.170612 -80.44702,24.194063 -81.43197,24.264418 -84.12884,25.390068 -84.05849,25.413519 -87.88101,24.33477 -87.90446,24.428574 -91.51592,25.624578 -91.49247,25.601128 -95.12739,24.522379 -95.15084,24.663084 -101.50607,41.524395 -101.365364,41.524395 -100.63838,42.579693 -99.676895,45.511078 -99.65344,46.566376 -100.63838,46.613277 -101.31847,51.16278 -101.29501,51.16278 -116.04573,44.127464 -116.11608))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_address_002.wkt b/geo/src/algorithm/spade_boolops/data/union_address_002.wkt new file mode 100644 index 000000000..799cb0f73 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_address_002.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((44.127464 -116.11608,25.577677 -116.04573,13.406576 -116.16299,10.686254 -113.34886,10.451743 -89.59293,10.240684 -73.64622,9.93582 -56.409687,9.842015 -47.076164,22.036566 -47.076164,22.153822 -57.11322,22.177273 -59.927345,22.97461 -59.903896,22.99806 -64.64101,23.795395 -64.59411,23.912651 -69.143616,25.132107 -69.09671,25.179008 -71.98119,23.959553 -71.98119,24.12371 -77.28113,25.343166 -77.32803,25.413519 -80.44702,24.170612 -80.44702,24.194063 -81.43197,24.264418 -84.12884,25.390068 -84.05849,25.413519 -87.88101,24.33477 -87.90446,24.428574 -91.51592,25.624578 -91.49247,25.601128 -95.12739,24.522379 -95.15084,24.663084 -101.50607,41.524395 -101.365364,41.524395 -100.63838,42.579693 -99.676895,45.511078 -99.65344,46.566376 -100.63838,46.613277 -101.31847,51.16278 -101.29501,51.16278 -116.04573,44.127464 -116.11608)),POLYGON((44.971703 -56.011017,44.66684 -56.620747,47.926537 -58.098164,49.56811 -58.8486,49.966778 -60.583977,52.40569 -61.662724,54.21142 -60.91229,60.42595 -63.82022,60.590107 -63.445004,65.608635 -65.79011,67.179855 -66.51709,67.78958 -68.557335,69.90018 -69.44848,71.58865 -68.58079,76.63063 -70.925896,81.93057 -73.3648,81.10979 -75.053276,85.35442 -76.74175,86.03451 -75.264336,91.96763 -77.96121,97.61933 -64.73481,26.609524 -32.935177,12.937555 -26.36888,9.630956 -28.502926,9.842015 -47.076164,22.036566 -47.076164,21.989664 -44.613804,22.76355 -44.05098,30.408594 -47.427933,35.450573 -49.679234,35.68508 -51.696022,44.971703 -56.011017))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_address_003.wkt b/geo/src/algorithm/spade_boolops/data/union_address_003.wkt new file mode 100644 index 000000000..2783c855d --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_address_003.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((51.16278 -61.0999,52.40569 -61.662724,54.21142 -60.91229,60.42595 -63.82022,60.590107 -63.445004,67.179855 -66.51709,67.78958 -68.557335,69.90018 -69.44848,71.58865 -68.58079,81.93057 -73.3648,81.10979 -75.053276,85.35442 -76.74175,86.03451 -75.264336,91.96763 -77.96121,97.61933 -64.73481,88.89554 -60.88884,51.16278 -43.86337,51.16278 -61.0999)),POLYGON((89.90393 -115.506355,89.90393 -101.48262,82.14163 -101.43572,82.11818 -100.56803,81.156685 -99.84105,77.99079 -99.8176,77.28726 -100.52113,77.21691 -101.43572,71.91697 -101.34192,71.94042 -102.30341,67.27366 -102.373764,67.320564 -99.958305,65.186516 -100.028656,63.28698 -100.028656,63.310432 -101.31847,61.97372 -101.365364,51.16278 -101.29501,51.16278 -116.04573,89.90393 -115.506355))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_address_004.wkt b/geo/src/algorithm/spade_boolops/data/union_address_004.wkt new file mode 100644 index 000000000..ffdb7a4c9 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_address_004.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((147.21832 -41.893482,147.21832 -47.076164,139.80779 -47.076164,147.21832 -41.893482)),POLYGON((138.35382 -46.98236,139.29187 -47.076164,138.33037 -47.076164,138.35382 -46.98236)),POLYGON((153.66736 3.6484768,153.19835 -4.559394,152.16649 -17.410574,151.11119 -29.112654,150.57182 -35.796204,151.22845 -36.78115,152.23685 -38.235115,156.8567 -34.905064,165.25218 -28.831242,172.00609 -23.976871,178.52548 -33.521454,159.17836 -47.076164,147.21832 -47.076164,147.21832 -41.893482,149.02405 -40.603672,148.03911 -39.19661,147.21832 -37.9537,147.21832 4.117498,153.66736 3.6484768)),POLYGON((146.86656 -37.46123,146.18648 -36.476288,139.33876 -36.030716,143.04404 4.422362,147.21832 4.117498,147.21832 -37.9537,146.86656 -37.46123))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_address_005.wkt b/geo/src/algorithm/spade_boolops/data/union_address_005.wkt new file mode 100644 index 000000000..eb62a0e87 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_address_005.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((89.90393 -115.506355,89.90393 -101.48262,82.14163 -101.43572,82.11818 -100.56803,81.156685 -99.84105,77.99079 -99.8176,77.28726 -100.52113,77.21691 -101.43572,71.91697 -101.34192,71.94042 -102.30341,67.27366 -102.373764,67.320564 -99.958305,65.186516 -100.028656,63.28698 -100.028656,63.310432 -101.31847,61.97372 -101.365364,51.32694 -101.27156,61.856464 -115.95193,89.90393 -115.506355)),POLYGON((91.96763 -77.96121,97.61933 -64.73481,88.89554 -60.88884,26.609524 -32.935177,12.937555 -26.36888,9.630956 -28.502926,9.842015 -47.076164,22.036566 -47.076164,21.989664 -44.613804,22.76355 -44.05098,29.611258 -47.076164,51.16278 -47.076164,51.16278 -61.0999,52.40569 -61.662724,54.21142 -60.91229,60.42595 -63.82022,60.590107 -63.445004,67.179855 -66.51709,67.78958 -68.557335,69.90018 -69.44848,71.58865 -68.58079,81.93057 -73.3648,81.10979 -75.053276,85.35442 -76.74175,86.03451 -75.264336,91.96763 -77.96121)),POLYGON((44.127464 -116.11608,25.577677 -116.04573,13.406576 -116.16299,10.686254 -113.34886,10.451743 -89.59293,10.240684 -73.64622,9.93582 -56.409687,9.842015 -47.076164,22.036566 -47.076164,22.153822 -57.11322,22.177273 -59.927345,22.97461 -59.903896,22.99806 -64.64101,23.795395 -64.59411,23.912651 -69.143616,25.132107 -69.09671,25.179008 -71.98119,23.959553 -71.98119,24.12371 -77.28113,25.343166 -77.32803,25.413519 -80.44702,24.170612 -80.44702,24.194063 -81.43197,24.264418 -84.12884,25.390068 -84.05849,25.413519 -87.88101,24.33477 -87.90446,24.428574 -91.51592,25.624578 -91.49247,25.601128 -95.12739,24.522379 -95.15084,24.663084 -101.50607,41.524395 -101.365364,41.524395 -100.63838,42.579693 -99.676895,45.511078 -99.65344,46.566376 -100.63838,46.613277 -101.31847,51.16278 -101.29501,51.16278 -116.04573,44.127464 -116.11608)),POLYGON((49.966778 -60.583977,49.56811 -58.8486,47.926537 -58.098164,44.66684 -56.620747,44.971703 -56.011017,35.68508 -51.696022,35.450573 -49.679234,30.408594 -47.427933,29.611258 -47.076164,51.16278 -47.076164,51.16278 -61.0999,49.966778 -60.583977))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_address_006.wkt b/geo/src/algorithm/spade_boolops/data/union_address_006.wkt new file mode 100644 index 000000000..92a062d20 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_address_006.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((12.937555 -26.36888,9.630956 -28.502926,9.842015 -47.076164,22.036566 -47.076164,21.989664 -44.613804,22.76355 -44.05098,29.611258 -47.076164,58.315357 -47.076164,47.152653 -42.034187,26.609524 -32.935177,12.937555 -26.36888)),POLYGON((77.240364 -55.61235,88.89554 -60.88884,97.61933 -64.73481,91.96763 -77.96121,86.03451 -75.264336,85.35442 -76.74175,81.10979 -75.053276,81.93057 -73.3648,76.63063 -70.925896,71.58865 -68.58079,69.90018 -69.44848,67.78958 -68.557335,67.179855 -66.51709,65.608635 -65.79011,60.590107 -63.445004,60.42595 -63.82022,54.21142 -60.91229,52.40569 -61.662724,51.16278 -61.0999,51.16278 -47.076164,58.315357 -47.076164,77.240364 -55.61235)),POLYGON((51.32694 -101.27156,61.97372 -101.365364,63.310432 -101.31847,63.28698 -100.028656,65.186516 -100.028656,67.320564 -99.958305,67.27366 -102.373764,71.94042 -102.30341,71.91697 -101.34192,77.21691 -101.43572,77.28726 -100.52113,77.99079 -99.8176,81.156685 -99.84105,82.11818 -100.56803,82.14163 -101.43572,89.90393 -101.48262,89.90393 -115.506355,80.26555 -115.67052,61.856464 -115.95193,51.16278 -116.04573,51.16278 -101.29501,51.32694 -101.27156))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_address_007.wkt b/geo/src/algorithm/spade_boolops/data/union_address_007.wkt new file mode 100644 index 000000000..03bd21ca0 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_address_007.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((9.630956 -28.502926,12.937555 -26.36888,26.609524 -32.935177,47.152653 -42.034187,50.62341 -43.62886,51.16278 -43.86337,51.16278 -47.076164,29.611258 -47.076164,22.76355 -44.05098,21.989664 -44.613804,22.036566 -47.076164,9.842015 -47.076164,9.630956 -28.502926)),POLYGON((58.315357 -47.076164,51.16278 -47.076164,51.16278 -43.86337,58.315357 -47.076164))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_address_008.wkt b/geo/src/algorithm/spade_boolops/data/union_address_008.wkt new file mode 100644 index 000000000..41dab80ab --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_address_008.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((51.16278 21.65889,73.7696 14.6001215,75.20012 29.139778,75.55189 34.369366,68.61037 39.458244,51.16278 48.979374,51.16278 21.65889)),POLYGON((37.092148 48.979374,51.105915 54.115463,37.045246 65.81724,36.904537 54.959396,37.092148 48.979374)),POLYGON((37.56117 25.903532,37.39701 38.801617,37.092148 48.979374,51.16278 48.979374,51.16278 21.65889,37.56117 25.903532))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_address_009.wkt b/geo/src/algorithm/spade_boolops/data/union_address_009.wkt new file mode 100644 index 000000000..914fc6b7c --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_address_009.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((147.21832 -56.175175,147.21832 -47.076164,141.16795 -53.290695,142.95023 -55.729607,142.69226 -57.230473,146.56169 -58.21542,146.7962 -56.50349,147.21832 -56.175175)),POLYGON((138.35382 -46.98236,138.00206 -50.71108,139.40912 -50.804882,141.16795 -53.290695,139.73743 -47.14652,138.35382 -46.98236)),POLYGON((146.18648 -36.476288,149.02405 -40.603672,139.80779 -47.076164,159.17836 -47.076164,178.52548 -33.521454,172.00609 -23.976871,152.23685 -38.235115,150.57182 -35.796204,153.19835 -4.559394,153.66736 3.6484768,143.04404 4.422362,139.33876 -36.030716,146.18648 -36.476288)),POLYGON((158.52173 -47.545185,159.43633 -48.858444,160.51508 -50.406216,166.65926 -51.81328,178.6193 -54.533604,183.73163 -55.682705,184.29445 -53.337597,191.82224 -55.35439,191.37666 -57.418083,193.20586 -57.86365,192.66647 -60.372917,191.96294 -63.515358,194.16734 -64.17199,194.94122 -66.39984,194.35495 -68.698044,192.73683 -69.80024,157.51334 -60.959194,159.06111 -54.93227,158.16997 -53.61901,156.43459 -51.10975,155.5669 -49.81994,147.21832 -56.175175,147.21832 -47.076164,159.17836 -47.076164,158.52173 -47.545185))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_address_010.wkt b/geo/src/algorithm/spade_boolops/data/union_address_010.wkt new file mode 100644 index 000000000..862a9c38b --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_address_010.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((138.33037 -47.076164,139.29187 -47.076164,138.35382 -46.98236,138.33037 -47.076164)),POLYGON((150.57182 -35.796204,151.11119 -29.112654,152.16649 -17.410574,153.19835 -4.559394,153.66736 3.6484768,143.04404 4.422362,139.33876 -36.030716,146.18648 -36.476288,149.02405 -40.603672,139.80779 -47.076164,159.17836 -47.076164,178.52548 -33.521454,172.00609 -23.976871,152.23685 -38.235115,150.57182 -35.796204)),POLYGON((146.7962 -56.50349,146.56169 -58.21542,142.69226 -57.230473,142.95023 -55.729607,141.16795 -53.290695,139.40912 -50.804882,138.00206 -50.71108,138.33037 -47.076164,139.29187 -47.076164,139.73743 -47.14652,139.80779 -47.076164,147.21832 -47.076164,147.21832 -56.175175,146.7962 -56.50349))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_address_011.wkt b/geo/src/algorithm/spade_boolops/data/union_address_011.wkt new file mode 100644 index 000000000..9b5e05614 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_address_011.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((-55.933933 10.458998,-56.601376 10.063477,-63.99268 26.972008,-61.12515 26.304567,-55.983376 33.44867,-29.359852 14.167009,-6.3949018 46.253666,-5.455539 45.907585,5.717935 38.14548,6.187617 38.738766,6.187617 6.1329846,-10.498434 6.1329846,-10.547874 6.231865,-10.745635 6.1329846,-53.98105 6.1329846,-55.933933 10.458998)),POLYGON((9.351787 42.89174,23.93663 32.385704,22.107346 29.987858,20.0803 27.29337,6.558418 8.135311,7.3989005 6.1329846,6.187617 6.1329846,6.187617 38.738766,9.351787 42.89174))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_address_012.wkt b/geo/src/algorithm/spade_boolops/data/union_address_012.wkt new file mode 100644 index 000000000..8e8fcdea1 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_address_012.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((-34.27683 -9.499561,-34.944267 -9.895081,-42.335545 7.013391,-39.468025 6.345951,-34.326267 13.490027,-7.7028413 -5.791563,15.262028 26.294981,16.201387 25.948902,27.374823 18.186825,27.844501 18.780106,27.844501 -13.825559,11.158509 -13.825559,11.10907 -13.726679,10.91131 -13.825559,-32.323948 -13.825559,-34.27683 -9.499561)),POLYGON((31.00866 22.933064,45.593452 12.427069,43.764175 10.029229,41.737137 7.3347507,28.215302 -11.82324,29.05578 -13.825559,27.844501 -13.825559,27.844501 18.780106,31.00866 22.933064))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_address_013.wkt b/geo/src/algorithm/spade_boolops/data/union_address_013.wkt new file mode 100644 index 000000000..d244d81ee --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_address_013.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((-4.9185357 -6.1884294,-4.9490414 -6.2680273,-6.713882 -5.5572667,-9.273427 -12.408308,2.5063581 -16.938995,5.2466393 -9.762046,-4.9185357 -6.1884294)),POLYGON((12.783405 -6.3217034,11.311835 -10.268187,14.695763 -11.540936,16.130924 -7.4845257,12.783405 -6.3217034)),POLYGON((-4.9490414 -6.2680273,-4.9185357 -6.1884294,-6.713882 -5.5572667,-4.9490414 -6.2680273)),POLYGON((21.452772 -9.333168,16.130924 -7.4845257,14.695763 -11.540936,16.907276 -12.372723,15.786002 -15.194951,18.849855 -16.376352,21.452772 -9.333168)),POLYGON((15.01364 -17.237974,15.786002 -15.194951,16.907276 -12.372723,11.311835 -10.268187,10.775763 -11.705835,5.2466393 -9.762046,3.5326302 -14.280994,9.258297 -16.640015,9.806425 -15.244781,15.01364 -17.237974)),POLYGON((15.247331 -26.124361,18.849855 -16.376352,15.786002 -15.194951,15.01364 -17.237974,9.806425 -15.244781,9.258297 -16.640015,3.5326302 -14.280994,2.5063581 -16.938995,-9.273427 -12.408308,-10.852026 -16.633686,15.247331 -26.124361))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_address_014.wkt b/geo/src/algorithm/spade_boolops/data/union_address_014.wkt new file mode 100644 index 000000000..6ab96ff4c --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_address_014.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((4.085312 8.793465,-1.1130188 10.735999,-2.6249332 6.7605267,2.3803997 4.707058,4.085312 8.793465)),POLYGON((6.288779 -18.000025,8.894644 -11.044519,-6.713882 -5.5572667,-9.257319 -12.365192,6.288779 -18.000025)),POLYGON((4.085312 8.793465,2.3803997 4.707058,-2.6249332 6.7605267,-1.1130188 10.735999,-9.56151 13.893067,-12.320872 4.9090977,-8.021401 3.3689885,-13.540125 -10.812849,-9.257319 -12.365192,-6.713882 -5.5572667,8.894644 -11.044519,14.823549 4.780755,4.085312 8.793465))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_address_015.wkt b/geo/src/algorithm/spade_boolops/data/union_address_015.wkt new file mode 100644 index 000000000..a65531f9c --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_address_015.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((-19.114342 38.992756,-22.977892 46.447132,-24.091503 48.6289,-17.250748 48.6289,-15.773508 45.788055,-15.773508 40.719986,-19.114342 38.992756)),POLYGON((-15.114432 44.49263,-9.841824 47.24257,-8.432764 44.538082,-10.432719 43.492653,-15.773508 40.719986,-15.773508 45.788055,-15.114432 44.49263)),POLYGON((-31.159525 62.333138,-23.136978 66.51486,-15.773508 70.35568,-15.773508 56.42418,-20.682487 53.87878,-19.909779 52.401543,-19.227976 52.401543,-17.250748 48.6289,-24.091503 48.6289,-31.159525 62.333138)),POLYGON((-15.2053385 70.65113,-9.523649 59.696835,-15.773508 56.42418,-15.773508 70.35568,-15.2053385 70.65113))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_both_intermediate_points_stay.wkt b/geo/src/algorithm/spade_boolops/data/union_both_intermediate_points_stay.wkt new file mode 100644 index 000000000..172d1ca78 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_both_intermediate_points_stay.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((10 -60,11 -59,11 -58,10 -57,10 -60)),POLYGON((11 -59,11 -58,12 -57,12 -60,11 -59))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_fails_overlap.wkt b/geo/src/algorithm/spade_boolops/data/union_fails_overlap.wkt new file mode 100644 index 000000000..a7cce1eee --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_fails_overlap.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((-31.159525 62.333138,-15.773508 70.35568,-15.773508 56.42418,-31.159525 62.333138)),POLYGON((-15.773508 70.35568,-15.2053385 70.65113,-9.523649 59.696835,-15.773508 56.42418,-15.773508 70.35568))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_not_completely_shared_line.wkt b/geo/src/algorithm/spade_boolops/data/union_not_completely_shared_line.wkt new file mode 100644 index 000000000..96c10021b --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_not_completely_shared_line.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((0 0,0 50,50 50,50 0,0 0)),POLYGON((0 50,0 100,50 100,50 50,0 50)),POLYGON((50 50,50 100,100 100,100 50,50 50))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_one_intermediate_point_stays.wkt b/geo/src/algorithm/spade_boolops/data/union_one_intermediate_point_stays.wkt new file mode 100644 index 000000000..c4868ec11 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_one_intermediate_point_stays.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((10 -60,11 -59,11 -57,10 -57,10 -60)),POLYGON((11 -59,11 -57,12 -57,12 -60,11 -59))) \ No newline at end of file diff --git a/geo/src/algorithm/spade_boolops/data/union_still_fails_overlap.wkt b/geo/src/algorithm/spade_boolops/data/union_still_fails_overlap.wkt new file mode 100644 index 000000000..c6785b432 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/data/union_still_fails_overlap.wkt @@ -0,0 +1 @@ +GEOMETRYCOLLECTION(POLYGON((56.70213 36.49281,52.4295 33.742874,52.815857 33.12925,53.088577 33.10652,53.33857 32.62926,52.90676 32.311085,52.543133 32.379265,50.04319 30.765667,49.83865 30.356585,49.36139 30.083864,49.020485 30.652033,49.111393 30.924753,48.793217 31.424744,45.225117 29.106613,40.49795 26.083954,40.86158 25.49306,40.99794 25.56124,41.247932 24.993069,40.907032 24.743074,40.58886 24.743074,37.952553 23.129475,37.86165 22.856754,37.384384 22.584032,37.043484 23.174929,37.20257 23.402197,36.92985 23.788551,32.56631 20.99316,25.339201 32.35654,22.839258 36.992798,16.612125 48.6289,24.384678 48.6289,24.975574 47.469837,25.907372 47.969826,27.95278 44.06082,27.24825 43.674465,29.725466 38.924572,29.248205 38.560947,30.066368 36.515537,43.79333 45.174435,42.566086 47.17439,41.90701 46.788033,40.838852 48.6289,48.884125 48.6289,56.70213 36.49281)),POLYGON((38.54345 52.515175,37.611652 51.969734,35.498062 55.696922,35.88442 55.92419,33.157207 60.969532,38.975258 64.014915,48.884125 48.6289,40.838852 48.6289,38.54345 52.515175)),POLYGON((15.04398 51.515198,21.20293 54.742397,24.384678 48.6289,16.612125 48.6289,15.04398 51.515198))) \ No newline at end of file From d1605494eac222201d558f38a6a308b4dbeac3fa Mon Sep 17 00:00:00 2001 From: RobWalt Date: Sat, 28 Oct 2023 22:44:42 +0200 Subject: [PATCH 11/33] chore: implement simple wkt loader fn for tests --- .../spade_boolops/spade_boolops_tests.rs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs b/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs index 6efdc70d2..3c2caf904 100644 --- a/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs +++ b/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs @@ -1,5 +1,18 @@ use super::*; use geo_types::*; +use wkt::TryFromWkt; + +// helper + +fn load_wkt(data_str: &str) -> Result>, String> { + let GeometryCollection(data) = + GeometryCollection::::try_from_wkt_str(data_str).map_err(|e| format!("{e:?}"))?; + let data = data + .into_iter() + .filter_map(|g| g.try_into().ok()) + .collect::>(); + Ok(data) +} #[test] fn basic_intersection_compiles() { @@ -10,3 +23,11 @@ fn basic_intersection_compiles() { SpadeBoolops::intersection(&rect1.to_polygon(), &rect2.to_polygon()).unwrap(); } + +#[test] +fn load_star_works() { + _ = pretty_env_logger::try_init(); + let data = include_str!("./data/star.wkt"); + let data = load_wkt(data).unwrap(); + info!("{data:?}"); +} From a76599f03d71e1015dcf43a8ad9182d6e57b3f7b Mon Sep 17 00:00:00 2001 From: RobWalt Date: Sat, 28 Oct 2023 22:55:50 +0200 Subject: [PATCH 12/33] chore: implement test helpers and write first proper test --- .../spade_boolops/spade_boolops_tests.rs | 68 ++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs b/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs index 3c2caf904..2e3521db5 100644 --- a/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs +++ b/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs @@ -1,8 +1,66 @@ +use crate::triangulate_spade::SpadeTriangulationFloat; +use crate::HasDimensions; + use super::*; use geo_types::*; use wkt::TryFromWkt; // helper +// +pub fn multipolygon_from(v: Vec>) -> MultiPolygon { + MultiPolygon::new(v) +} + +pub fn is_multipolygon_nonempty(multipolygon: &MultiPolygon) { + let condition_true = + !multipolygon.is_empty() && !multipolygon.iter().any(|poly| poly.is_empty()); + assert!( + condition_true, + "polygon was empty even though non-empty was expected" + ); +} + +pub fn is_multipolygon_empty(multipolygon: &MultiPolygon) { + let condition_true = multipolygon.is_empty() && multipolygon.iter().all(|poly| poly.is_empty()); + assert!( + condition_true, + "polygon was non-empty even though empty was expected" + ); +} + +pub fn has_num_holes( + multipolygon: &MultiPolygon, + expected_num_holes: usize, +) { + let false_num_holes = multipolygon + .iter() + .map(|poly| poly.interiors().len()) + .find(|&num_holes| num_holes != expected_num_holes); + assert!(false_num_holes.is_none(), "A polygon had not the expected number of holes ({expected_num_holes}), but {} holes instead\n\n{multipolygon:?}", false_num_holes.unwrap()); +} + +pub fn has_num_vertices( + multipolygon: &MultiPolygon, + expected_num_vertices: usize, +) { + let false_num_vertices = multipolygon + .iter() + .map(|poly| poly.exterior().coords().count()) + .find(|&num_vertices| num_vertices != expected_num_vertices); + assert!(false_num_vertices.is_none(), "A polygon had not the expected number of vertices ({expected_num_vertices}), but {} vertices instead", false_num_vertices.unwrap()); +} + +pub fn has_num_polygons( + multipolygon: &MultiPolygon, + expected_num_polys: usize, +) { + assert_eq!( + multipolygon.0.len(), + expected_num_polys, + "A multipolygon had not the expected number of polygons ({expected_num_polys}), but {} polygons instead", + multipolygon.0.len() + ); +} fn load_wkt(data_str: &str) -> Result>, String> { let GeometryCollection(data) = @@ -25,9 +83,15 @@ fn basic_intersection_compiles() { } #[test] -fn load_star_works() { +fn star_intersects_self_properly() { _ = pretty_env_logger::try_init(); let data = include_str!("./data/star.wkt"); let data = load_wkt(data).unwrap(); - info!("{data:?}"); + let poly1 = &data[0]; + + let intersection = Polygon::intersection(poly1, poly1).unwrap(); + + is_multipolygon_nonempty(&intersection); + has_num_polygons(&intersection, 1); + has_num_holes(&intersection, 0); } From 84393eddb96c0689712a5c29cbf3b57b9608cac5 Mon Sep 17 00:00:00 2001 From: RobWalt Date: Sat, 28 Oct 2023 22:58:05 +0200 Subject: [PATCH 13/33] chore: add second test which fails for some reason --- .../spade_boolops/spade_boolops_tests.rs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs b/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs index 2e3521db5..a47fe4308 100644 --- a/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs +++ b/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs @@ -95,3 +95,24 @@ fn star_intersects_self_properly() { has_num_polygons(&intersection, 1); has_num_holes(&intersection, 0); } + +#[test] +fn star_shape_slightly_offset_difference() { + _ = pretty_env_logger::try_init(); + let data = include_str!("./data/star.wkt"); + let data = load_wkt(data).unwrap(); + let poly1 = &data[0]; + + let mut poly2 = poly1.clone(); + poly2.exterior_mut(|ext| { + ext.coords_mut().skip(1).take(1).for_each(|coord| { + *coord = *coord + Coord { x: 0.1, y: 0.1 }; + }); + }); + + let difference = Polygon::difference(poly1, &poly2).unwrap(); + + is_multipolygon_nonempty(&difference); + has_num_polygons(&difference, 1); + has_num_holes(&difference, 0); +} From 9e627316bbd3ee52fdf356483d1784d885a53b92 Mon Sep 17 00:00:00 2001 From: RobWalt Date: Sat, 28 Oct 2023 23:03:01 +0200 Subject: [PATCH 14/33] fix: big oopsie: called the wrong triangulation method --- geo/src/algorithm/spade_boolops/trait_impl.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/geo/src/algorithm/spade_boolops/trait_impl.rs b/geo/src/algorithm/spade_boolops/trait_impl.rs index a26778b8d..f4e4f170f 100644 --- a/geo/src/algorithm/spade_boolops/trait_impl.rs +++ b/geo/src/algorithm/spade_boolops/trait_impl.rs @@ -16,7 +16,7 @@ where op_pred: F, ) -> SpadeBoolopsResult { vec![p1.clone(), p2.clone()] - .unconstrained_triangulation() + .constrained_outer_triangulation() .map_err(SpadeBoolopsError::TriangulationError)? .into_iter() .filter(|tri| op_pred(tri)) @@ -37,7 +37,7 @@ where op_pred: F, ) -> SpadeBoolopsResult { vec![p1.clone(), p2.clone()] - .unconstrained_triangulation() + .constrained_outer_triangulation() .map_err(SpadeBoolopsError::TriangulationError)? .into_iter() .filter(|tri| op_pred(tri)) From 1060fab440217119e80619667f77128a13a4b895 Mon Sep 17 00:00:00 2001 From: RobWalt Date: Sat, 28 Oct 2023 23:05:03 +0200 Subject: [PATCH 15/33] chore: added missing num vertex assertions --- geo/src/algorithm/spade_boolops/spade_boolops_tests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs b/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs index a47fe4308..3e17505a9 100644 --- a/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs +++ b/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs @@ -94,6 +94,7 @@ fn star_intersects_self_properly() { is_multipolygon_nonempty(&intersection); has_num_polygons(&intersection, 1); has_num_holes(&intersection, 0); + has_num_vertices(&intersection, 23); } #[test] @@ -115,4 +116,5 @@ fn star_shape_slightly_offset_difference() { is_multipolygon_nonempty(&difference); has_num_polygons(&difference, 1); has_num_holes(&difference, 0); + has_num_vertices(&difference, 4); } From c7efaca0a26831cb06706268162758d94114b884 Mon Sep 17 00:00:00 2001 From: RobWalt Date: Sat, 28 Oct 2023 23:21:37 +0200 Subject: [PATCH 16/33] chore: added test definition macro and further tests --- .../spade_boolops/spade_boolops_tests.rs | 109 ++++++++++++------ 1 file changed, 76 insertions(+), 33 deletions(-) diff --git a/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs b/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs index 3e17505a9..3c8aabe45 100644 --- a/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs +++ b/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs @@ -82,39 +82,82 @@ fn basic_intersection_compiles() { SpadeBoolops::intersection(&rect1.to_polygon(), &rect2.to_polygon()).unwrap(); } -#[test] -fn star_intersects_self_properly() { - _ = pretty_env_logger::try_init(); - let data = include_str!("./data/star.wkt"); - let data = load_wkt(data).unwrap(); - let poly1 = &data[0]; - - let intersection = Polygon::intersection(poly1, poly1).unwrap(); - - is_multipolygon_nonempty(&intersection); - has_num_polygons(&intersection, 1); - has_num_holes(&intersection, 0); - has_num_vertices(&intersection, 23); +macro_rules! define_test { + ( + name = $test_name:ident, + path = $path:expr, + operation = $op:expr, + results: + empty = $empty:expr, + num_polys = $num_polys:expr, + num_holes = $num_holes:expr, + num_verts = $num_verts:expr, + ) => { + #[test] + fn $test_name() { + _ = pretty_env_logger::try_init(); + let data = include_str!($path); + let data = load_wkt(data).unwrap(); + + let f = $op; + let res = f(data); + + if $empty { + is_multipolygon_empty(&res); + } else { + is_multipolygon_nonempty(&res); + } + has_num_polygons(&res, $num_polys); + has_num_holes(&res, $num_holes); + has_num_vertices(&res, $num_verts); + } + }; } -#[test] -fn star_shape_slightly_offset_difference() { - _ = pretty_env_logger::try_init(); - let data = include_str!("./data/star.wkt"); - let data = load_wkt(data).unwrap(); - let poly1 = &data[0]; - - let mut poly2 = poly1.clone(); - poly2.exterior_mut(|ext| { - ext.coords_mut().skip(1).take(1).for_each(|coord| { - *coord = *coord + Coord { x: 0.1, y: 0.1 }; +define_test!( + name = star_shape_slightly_offset_difference, + path = "./data/star.wkt", + operation = |data: Vec>| { + let poly1 = &data[0]; + let mut poly2 = poly1.clone(); + poly2.exterior_mut(|ext| { + ext.coords_mut().skip(1).take(1).for_each(|coord| { + *coord = *coord + Coord { x: 0.1, y: 0.1 }; + }); }); - }); - - let difference = Polygon::difference(poly1, &poly2).unwrap(); - - is_multipolygon_nonempty(&difference); - has_num_polygons(&difference, 1); - has_num_holes(&difference, 0); - has_num_vertices(&difference, 4); -} + Polygon::difference(poly1, &poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 0, + num_verts = 4, +); + +define_test!( + name = star_intersects_self_properly, + path = "./data/star.wkt", + operation = |data: Vec>| { + let poly1 = &data[0]; + Polygon::intersection(poly1, poly1).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 0, + num_verts = 23, +); + +define_test!( + name = duplicate_points_intersect_works_1, + path = "./data/duplicate_points_case_1.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 0, + num_verts = 5, +); From 89d7936c7d92c8574b4d335fb68755e5592c3b62 Mon Sep 17 00:00:00 2001 From: RobWalt Date: Sat, 28 Oct 2023 23:43:47 +0200 Subject: [PATCH 17/33] chore: add some more tests --- .../spade_boolops/spade_boolops_tests.rs | 284 +++++++++++++++++- 1 file changed, 280 insertions(+), 4 deletions(-) diff --git a/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs b/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs index 3c8aabe45..32d33b649 100644 --- a/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs +++ b/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs @@ -6,11 +6,15 @@ use geo_types::*; use wkt::TryFromWkt; // helper -// + pub fn multipolygon_from(v: Vec>) -> MultiPolygon { MultiPolygon::new(v) } +pub fn empty_poly() -> Polygon { + Polygon::new(LineString::new(vec![]), vec![]) +} + pub fn is_multipolygon_nonempty(multipolygon: &MultiPolygon) { let condition_true = !multipolygon.is_empty() && !multipolygon.iter().any(|poly| poly.is_empty()); @@ -45,7 +49,7 @@ pub fn has_num_vertices( ) { let false_num_vertices = multipolygon .iter() - .map(|poly| poly.exterior().coords().count()) + .map(|poly| poly.exterior().coords().count() + poly.interiors().iter().map(|i| i.coords().count()).sum::()) .find(|&num_vertices| num_vertices != expected_num_vertices); assert!(false_num_vertices.is_none(), "A polygon had not the expected number of vertices ({expected_num_vertices}), but {} vertices instead", false_num_vertices.unwrap()); } @@ -115,7 +119,7 @@ macro_rules! define_test { } define_test!( - name = star_shape_slightly_offset_difference, + name = star_shape_slightly_offset_difference_1, path = "./data/star.wkt", operation = |data: Vec>| { let poly1 = &data[0]; @@ -134,6 +138,26 @@ define_test!( num_verts = 4, ); +define_test!( + name = star_shape_slightly_offset_difference_2, + path = "./data/star.wkt", + operation = |data: Vec>| { + let poly1 = &data[0]; + let mut poly2 = poly1.clone(); + poly2.exterior_mut(|ext| { + ext.coords_mut().skip(1).take(1).for_each(|coord| { + *coord = *coord + Coord { x: 0.1, y: 0.1 }; + }); + }); + Polygon::difference(&poly2, poly1).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 0, + num_verts = 4, +); + define_test!( name = star_intersects_self_properly, path = "./data/star.wkt", @@ -149,7 +173,7 @@ define_test!( ); define_test!( - name = duplicate_points_intersect_works_1, + name = duplicate_points_intersection_works_1, path = "./data/duplicate_points_case_1.wkt", operation = |data: Vec>| { let [poly1, poly2] = [&data[0], &data[1]]; @@ -161,3 +185,255 @@ define_test!( num_holes = 0, num_verts = 5, ); + +define_test!( + name = duplicate_points_intersection_works_2, + path = "./data/duplicate_points_case_2.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 0, + num_verts = 6, +); + +define_test!( + name = duplicate_points_difference_works_1, + path = "./data/duplicate_points_case_3.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::difference(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 1, + num_verts = 9, +); + +define_test!( + name = collinear_outline_parts_intersection_works, + path = "./data/collinear_outline_parts.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 0, + num_verts = 5, +); + +define_test!( + name = missing_triangle_intersection_works_1, + path = "./data/missing_triangle_case_1.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 0, + num_verts = 5, +); + +define_test!( + name = missing_triangle_intersection_empty, + path = "./data/missing_triangle_case_2.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = true, + num_polys = 0, + num_holes = 0, + num_verts = 0, +); + +define_test!( + name = missing_triangle_intersection_works_2, + path = "./data/missing_triangle_case_3.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 0, + num_verts = 7, +); + +define_test!( + name = intersection_at_address_works_1, + path = "./data/intersection_address_001.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 0, + num_verts = 5, +); + +define_test!( + name = difference_at_address_works_1, + path = "./data/intersection_address_001.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::difference(poly2, poly1).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 1, + num_verts = 10, +); + +define_test!( + name = intersection_at_address_works_2, + path = "./data/intersection_address_002.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 0, + num_verts = 9, +); + +define_test!( + name = difference_at_address_works_2, + path = "./data/intersection_address_002.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::difference(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 1, + num_verts = 18, +); + +define_test!( + name = intersection_doesnt_fail_after_union_fix_1, + path = "./data/intersection_fail_after_union_fix_1.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 0, + num_verts = 5, +); + +define_test!( + name = difference_doesnt_fail_after_union_fix_1, + path = "./data/intersection_fail_after_union_fix_1.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::difference(poly2, poly1).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 1, + num_verts = 10, +); + +define_test!( + name = intersection_doesnt_fail_after_union_fix_2, + path = "./data/intersection_fail_after_union_fix_2.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 0, + num_verts = 5, +); + +define_test!( + name = holes_are_preserved_by_union, + path = "./data/holes_are_preserved.wkt", + operation = |data: Vec>| { + let poly1 = &data[0]; + Polygon::union(poly1, poly1).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 1, + num_verts = 10, +); + +define_test!( + name = holes_are_preserved_by_intersection, + path = "./data/holes_are_preserved.wkt", + operation = |data: Vec>| { + let poly1 = &data[0]; + Polygon::intersection(poly1, poly1).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 1, + num_verts = 10, +); + +define_test!( + name = holes_are_preserved_by_difference, + path = "./data/holes_are_preserved.wkt", + operation = |data: Vec>| { + let poly1 = &data[0]; + Polygon::difference(poly1, &empty_poly()).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 1, + num_verts = 10, +); + +define_test!( + name = one_hole_after_union, + path = "./data/hole_after_union.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::union(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 1, + num_verts = 14, +); + +define_test!( + name = two_holes_after_union, + path = "./data/two_holes_after_union.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::union(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = 2, + num_verts = 21, +); From 70f383f3a783078e6a537590dc9593e81b37a4dc Mon Sep 17 00:00:00 2001 From: RobWalt Date: Sun, 29 Oct 2023 00:35:41 +0200 Subject: [PATCH 18/33] chore: add all tests and restructure test layout --- geo/src/algorithm/spade_boolops/mod.rs | 2 +- .../spade_boolops/spade_boolops_tests.rs | 439 ------------ geo/src/algorithm/spade_boolops/tests/mod.rs | 4 + .../tests/spade_boolops_tests.rs | 654 ++++++++++++++++++ .../spade_boolops/tests/test_helper.rs | 108 +++ 5 files changed, 767 insertions(+), 440 deletions(-) delete mode 100644 geo/src/algorithm/spade_boolops/spade_boolops_tests.rs create mode 100644 geo/src/algorithm/spade_boolops/tests/mod.rs create mode 100644 geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs create mode 100644 geo/src/algorithm/spade_boolops/tests/test_helper.rs diff --git a/geo/src/algorithm/spade_boolops/mod.rs b/geo/src/algorithm/spade_boolops/mod.rs index c2886fdf5..fc22b56c1 100644 --- a/geo/src/algorithm/spade_boolops/mod.rs +++ b/geo/src/algorithm/spade_boolops/mod.rs @@ -7,4 +7,4 @@ pub use error::SpadeBoolopsError; pub use trait_def::SpadeBoolops; #[cfg(test)] -pub mod spade_boolops_tests; +pub mod tests; diff --git a/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs b/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs deleted file mode 100644 index 32d33b649..000000000 --- a/geo/src/algorithm/spade_boolops/spade_boolops_tests.rs +++ /dev/null @@ -1,439 +0,0 @@ -use crate::triangulate_spade::SpadeTriangulationFloat; -use crate::HasDimensions; - -use super::*; -use geo_types::*; -use wkt::TryFromWkt; - -// helper - -pub fn multipolygon_from(v: Vec>) -> MultiPolygon { - MultiPolygon::new(v) -} - -pub fn empty_poly() -> Polygon { - Polygon::new(LineString::new(vec![]), vec![]) -} - -pub fn is_multipolygon_nonempty(multipolygon: &MultiPolygon) { - let condition_true = - !multipolygon.is_empty() && !multipolygon.iter().any(|poly| poly.is_empty()); - assert!( - condition_true, - "polygon was empty even though non-empty was expected" - ); -} - -pub fn is_multipolygon_empty(multipolygon: &MultiPolygon) { - let condition_true = multipolygon.is_empty() && multipolygon.iter().all(|poly| poly.is_empty()); - assert!( - condition_true, - "polygon was non-empty even though empty was expected" - ); -} - -pub fn has_num_holes( - multipolygon: &MultiPolygon, - expected_num_holes: usize, -) { - let false_num_holes = multipolygon - .iter() - .map(|poly| poly.interiors().len()) - .find(|&num_holes| num_holes != expected_num_holes); - assert!(false_num_holes.is_none(), "A polygon had not the expected number of holes ({expected_num_holes}), but {} holes instead\n\n{multipolygon:?}", false_num_holes.unwrap()); -} - -pub fn has_num_vertices( - multipolygon: &MultiPolygon, - expected_num_vertices: usize, -) { - let false_num_vertices = multipolygon - .iter() - .map(|poly| poly.exterior().coords().count() + poly.interiors().iter().map(|i| i.coords().count()).sum::()) - .find(|&num_vertices| num_vertices != expected_num_vertices); - assert!(false_num_vertices.is_none(), "A polygon had not the expected number of vertices ({expected_num_vertices}), but {} vertices instead", false_num_vertices.unwrap()); -} - -pub fn has_num_polygons( - multipolygon: &MultiPolygon, - expected_num_polys: usize, -) { - assert_eq!( - multipolygon.0.len(), - expected_num_polys, - "A multipolygon had not the expected number of polygons ({expected_num_polys}), but {} polygons instead", - multipolygon.0.len() - ); -} - -fn load_wkt(data_str: &str) -> Result>, String> { - let GeometryCollection(data) = - GeometryCollection::::try_from_wkt_str(data_str).map_err(|e| format!("{e:?}"))?; - let data = data - .into_iter() - .filter_map(|g| g.try_into().ok()) - .collect::>(); - Ok(data) -} - -#[test] -fn basic_intersection_compiles() { - let zero = Coord::zero(); - let one = Coord { x: 1.0, y: 1.0 }; - let rect1 = Rect::new(zero, one * 2.0); - let rect2 = Rect::new(one, one * 3.0); - - SpadeBoolops::intersection(&rect1.to_polygon(), &rect2.to_polygon()).unwrap(); -} - -macro_rules! define_test { - ( - name = $test_name:ident, - path = $path:expr, - operation = $op:expr, - results: - empty = $empty:expr, - num_polys = $num_polys:expr, - num_holes = $num_holes:expr, - num_verts = $num_verts:expr, - ) => { - #[test] - fn $test_name() { - _ = pretty_env_logger::try_init(); - let data = include_str!($path); - let data = load_wkt(data).unwrap(); - - let f = $op; - let res = f(data); - - if $empty { - is_multipolygon_empty(&res); - } else { - is_multipolygon_nonempty(&res); - } - has_num_polygons(&res, $num_polys); - has_num_holes(&res, $num_holes); - has_num_vertices(&res, $num_verts); - } - }; -} - -define_test!( - name = star_shape_slightly_offset_difference_1, - path = "./data/star.wkt", - operation = |data: Vec>| { - let poly1 = &data[0]; - let mut poly2 = poly1.clone(); - poly2.exterior_mut(|ext| { - ext.coords_mut().skip(1).take(1).for_each(|coord| { - *coord = *coord + Coord { x: 0.1, y: 0.1 }; - }); - }); - Polygon::difference(poly1, &poly2).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 0, - num_verts = 4, -); - -define_test!( - name = star_shape_slightly_offset_difference_2, - path = "./data/star.wkt", - operation = |data: Vec>| { - let poly1 = &data[0]; - let mut poly2 = poly1.clone(); - poly2.exterior_mut(|ext| { - ext.coords_mut().skip(1).take(1).for_each(|coord| { - *coord = *coord + Coord { x: 0.1, y: 0.1 }; - }); - }); - Polygon::difference(&poly2, poly1).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 0, - num_verts = 4, -); - -define_test!( - name = star_intersects_self_properly, - path = "./data/star.wkt", - operation = |data: Vec>| { - let poly1 = &data[0]; - Polygon::intersection(poly1, poly1).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 0, - num_verts = 23, -); - -define_test!( - name = duplicate_points_intersection_works_1, - path = "./data/duplicate_points_case_1.wkt", - operation = |data: Vec>| { - let [poly1, poly2] = [&data[0], &data[1]]; - Polygon::intersection(poly1, poly2).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 0, - num_verts = 5, -); - -define_test!( - name = duplicate_points_intersection_works_2, - path = "./data/duplicate_points_case_2.wkt", - operation = |data: Vec>| { - let [poly1, poly2] = [&data[0], &data[1]]; - Polygon::intersection(poly1, poly2).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 0, - num_verts = 6, -); - -define_test!( - name = duplicate_points_difference_works_1, - path = "./data/duplicate_points_case_3.wkt", - operation = |data: Vec>| { - let [poly1, poly2] = [&data[0], &data[1]]; - Polygon::difference(poly1, poly2).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 1, - num_verts = 9, -); - -define_test!( - name = collinear_outline_parts_intersection_works, - path = "./data/collinear_outline_parts.wkt", - operation = |data: Vec>| { - let [poly1, poly2] = [&data[0], &data[1]]; - Polygon::intersection(poly1, poly2).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 0, - num_verts = 5, -); - -define_test!( - name = missing_triangle_intersection_works_1, - path = "./data/missing_triangle_case_1.wkt", - operation = |data: Vec>| { - let [poly1, poly2] = [&data[0], &data[1]]; - Polygon::intersection(poly1, poly2).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 0, - num_verts = 5, -); - -define_test!( - name = missing_triangle_intersection_empty, - path = "./data/missing_triangle_case_2.wkt", - operation = |data: Vec>| { - let [poly1, poly2] = [&data[0], &data[1]]; - Polygon::intersection(poly1, poly2).unwrap() - }, - results: - empty = true, - num_polys = 0, - num_holes = 0, - num_verts = 0, -); - -define_test!( - name = missing_triangle_intersection_works_2, - path = "./data/missing_triangle_case_3.wkt", - operation = |data: Vec>| { - let [poly1, poly2] = [&data[0], &data[1]]; - Polygon::intersection(poly1, poly2).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 0, - num_verts = 7, -); - -define_test!( - name = intersection_at_address_works_1, - path = "./data/intersection_address_001.wkt", - operation = |data: Vec>| { - let [poly1, poly2] = [&data[0], &data[1]]; - Polygon::intersection(poly1, poly2).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 0, - num_verts = 5, -); - -define_test!( - name = difference_at_address_works_1, - path = "./data/intersection_address_001.wkt", - operation = |data: Vec>| { - let [poly1, poly2] = [&data[0], &data[1]]; - Polygon::difference(poly2, poly1).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 1, - num_verts = 10, -); - -define_test!( - name = intersection_at_address_works_2, - path = "./data/intersection_address_002.wkt", - operation = |data: Vec>| { - let [poly1, poly2] = [&data[0], &data[1]]; - Polygon::intersection(poly1, poly2).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 0, - num_verts = 9, -); - -define_test!( - name = difference_at_address_works_2, - path = "./data/intersection_address_002.wkt", - operation = |data: Vec>| { - let [poly1, poly2] = [&data[0], &data[1]]; - Polygon::difference(poly1, poly2).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 1, - num_verts = 18, -); - -define_test!( - name = intersection_doesnt_fail_after_union_fix_1, - path = "./data/intersection_fail_after_union_fix_1.wkt", - operation = |data: Vec>| { - let [poly1, poly2] = [&data[0], &data[1]]; - Polygon::intersection(poly1, poly2).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 0, - num_verts = 5, -); - -define_test!( - name = difference_doesnt_fail_after_union_fix_1, - path = "./data/intersection_fail_after_union_fix_1.wkt", - operation = |data: Vec>| { - let [poly1, poly2] = [&data[0], &data[1]]; - Polygon::difference(poly2, poly1).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 1, - num_verts = 10, -); - -define_test!( - name = intersection_doesnt_fail_after_union_fix_2, - path = "./data/intersection_fail_after_union_fix_2.wkt", - operation = |data: Vec>| { - let [poly1, poly2] = [&data[0], &data[1]]; - Polygon::intersection(poly1, poly2).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 0, - num_verts = 5, -); - -define_test!( - name = holes_are_preserved_by_union, - path = "./data/holes_are_preserved.wkt", - operation = |data: Vec>| { - let poly1 = &data[0]; - Polygon::union(poly1, poly1).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 1, - num_verts = 10, -); - -define_test!( - name = holes_are_preserved_by_intersection, - path = "./data/holes_are_preserved.wkt", - operation = |data: Vec>| { - let poly1 = &data[0]; - Polygon::intersection(poly1, poly1).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 1, - num_verts = 10, -); - -define_test!( - name = holes_are_preserved_by_difference, - path = "./data/holes_are_preserved.wkt", - operation = |data: Vec>| { - let poly1 = &data[0]; - Polygon::difference(poly1, &empty_poly()).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 1, - num_verts = 10, -); - -define_test!( - name = one_hole_after_union, - path = "./data/hole_after_union.wkt", - operation = |data: Vec>| { - let [poly1, poly2] = [&data[0], &data[1]]; - Polygon::union(poly1, poly2).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 1, - num_verts = 14, -); - -define_test!( - name = two_holes_after_union, - path = "./data/two_holes_after_union.wkt", - operation = |data: Vec>| { - let [poly1, poly2] = [&data[0], &data[1]]; - Polygon::union(poly1, poly2).unwrap() - }, - results: - empty = false, - num_polys = 1, - num_holes = 2, - num_verts = 21, -); diff --git a/geo/src/algorithm/spade_boolops/tests/mod.rs b/geo/src/algorithm/spade_boolops/tests/mod.rs new file mode 100644 index 000000000..8b3739e05 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/tests/mod.rs @@ -0,0 +1,4 @@ +pub use super::*; + +mod spade_boolops_tests; +mod test_helper; diff --git a/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs b/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs new file mode 100644 index 000000000..b98711773 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs @@ -0,0 +1,654 @@ +use super::*; +use super::test_helper::*; +use geo_types::*; + +// helper + +#[test] +fn basic_intersection_compiles() { + let zero = Coord::zero(); + let one = Coord { x: 1.0, y: 1.0 }; + let rect1 = Rect::new(zero, one * 2.0); + let rect2 = Rect::new(one, one * 3.0); + + SpadeBoolops::intersection(&rect1.to_polygon(), &rect2.to_polygon()).unwrap(); +} + +macro_rules! define_test { + ( + name = $test_name:ident, + path = $path:expr, + operation = $op:expr, + results: + empty = $empty:expr, + num_polys = $num_polys:expr, + num_holes = $num_holes:expr, + num_verts = $num_verts:expr, + ) => { + #[test] + fn $test_name() { + _ = pretty_env_logger::try_init(); + let data = include_str!($path); + let data = load_wkt(data).unwrap(); + + let f = $op; + let res = f(data); + + if $empty { + is_multipolygon_empty(&res); + } else { + is_multipolygon_nonempty(&res); + } + has_num_polygons(&res, $num_polys); + has_num_holes(&res, $num_holes); + has_num_vertices(&res, $num_verts); + } + }; +} + +define_test!( + name = star_shape_slightly_offset_difference_1, + path = "../data/star.wkt", + operation = |data: Vec>| { + let poly1 = &data[0]; + let mut poly2 = poly1.clone(); + poly2.exterior_mut(|ext| { + ext.coords_mut().skip(1).take(1).for_each(|coord| { + *coord = *coord + Coord { x: 0.1, y: 0.1 }; + }); + }); + Polygon::difference(poly1, &poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![4], +); + +define_test!( + name = star_shape_slightly_offset_difference_2, + path = "../data/star.wkt", + operation = |data: Vec>| { + let poly1 = &data[0]; + let mut poly2 = poly1.clone(); + poly2.exterior_mut(|ext| { + ext.coords_mut().skip(1).take(1).for_each(|coord| { + *coord = *coord + Coord { x: 0.1, y: 0.1 }; + }); + }); + Polygon::difference(&poly2, poly1).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![4], +); + +define_test!( + name = star_intersects_self_properly, + path = "../data/star.wkt", + operation = |data: Vec>| { + let poly1 = &data[0]; + Polygon::intersection(poly1, poly1).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![23], +); + +define_test!( + name = duplicate_points_intersection_works_1, + path = "../data/duplicate_points_case_1.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![5], +); + +define_test!( + name = duplicate_points_intersection_works_2, + path = "../data/duplicate_points_case_2.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![6], +); + +define_test!( + name = duplicate_points_difference_works_1, + path = "../data/duplicate_points_case_3.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::difference(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![1], + num_verts = vec![9], +); + +define_test!( + name = collinear_outline_parts_intersection_works, + path = "../data/collinear_outline_parts.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![5], +); + +define_test!( + name = missing_triangle_intersection_works_1, + path = "../data/missing_triangle_case_1.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![5], +); + +define_test!( + name = missing_triangle_intersection_empty, + path = "../data/missing_triangle_case_2.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = true, + num_polys = 0, + num_holes = vec![], + num_verts = vec![], +); + +define_test!( + name = missing_triangle_intersection_works_2, + path = "../data/missing_triangle_case_3.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![7], +); + +define_test!( + name = intersection_at_address_works_1, + path = "../data/intersection_address_001.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![5], +); + +define_test!( + name = difference_at_address_works_1, + path = "../data/intersection_address_001.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::difference(poly2, poly1).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![1], + num_verts = vec![10], +); + +define_test!( + name = intersection_at_address_works_2, + path = "../data/intersection_address_002.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![9], +); + +define_test!( + name = difference_at_address_works_2, + path = "../data/intersection_address_002.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::difference(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![1], + num_verts = vec![18], +); + +define_test!( + name = intersection_doesnt_fail_after_union_fix_1, + path = "../data/intersection_fail_after_union_fix_1.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![5], +); + +define_test!( + name = difference_doesnt_fail_after_union_fix_1, + path = "../data/intersection_fail_after_union_fix_1.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::difference(poly2, poly1).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![1], + num_verts = vec![10], +); + +define_test!( + name = intersection_doesnt_fail_after_union_fix_2, + path = "../data/intersection_fail_after_union_fix_2.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::intersection(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![5], +); + +define_test!( + name = holes_are_preserved_by_union, + path = "../data/holes_are_preserved.wkt", + operation = |data: Vec>| { + let poly1 = &data[0]; + Polygon::union(poly1, poly1).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![1], + num_verts = vec![10], +); + +define_test!( + name = holes_are_preserved_by_intersection, + path = "../data/holes_are_preserved.wkt", + operation = |data: Vec>| { + let poly1 = &data[0]; + Polygon::intersection(poly1, poly1).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![1], + num_verts = vec![10], +); + +define_test!( + name = holes_are_preserved_by_difference, + path = "../data/holes_are_preserved.wkt", + operation = |data: Vec>| { + let poly1 = &data[0]; + Polygon::difference(poly1, &empty_poly()).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![1], + num_verts = vec![10], +); + +define_test!( + name = one_hole_after_union, + path = "../data/hole_after_union.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::union(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![1], + num_verts = vec![14], +); + +define_test!( + name = two_holes_after_union, + path = "../data/two_holes_after_union.wkt", + operation = |data: Vec>| { + let [poly1, poly2] = [&data[0], &data[1]]; + Polygon::union(poly1, poly2).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![2], + num_verts = vec![21], +); + +define_test!( + name = union_at_address_13_works, + path = "../data/union_address_013.wkt", + operation = |data: Vec>| { + // imprecise inputs lead to hole + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![1], + num_verts = vec![17], +); + +define_test!( + name = union_at_address_14_works, + path = "../data/union_address_014.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![11], +); + +define_test!( + name = simple_union, + path = "../data/simple_union.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![7], +); + +define_test!( + name = multiple_unions, + path = "../data/multiple_unions.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 2, + num_holes = vec![0, 0], + num_verts = vec![7, 7], +); + +define_test!( + name = union_preserved_intermediate_points_1, + path = "../data/union_both_intermediate_points_stay.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![7], +); + +define_test!( + name = union_preserved_intermediate_points_2, + path = "../data/union_one_intermediate_point_stays.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![7], +); + +define_test!( + name = union_not_completely_shared_line, + path = "../data/union_not_completely_shared_line.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![9], +); + +define_test!( + name = union_at_address_015_works, + path = "../data/union_address_015.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![20], +); + +define_test!( + name = union_works_on_overlap_1, + path = "../data/union_fails_overlap.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![6], +); + +define_test!( + name = union_works_on_overlap_2, + path = "../data/union_still_fails_overlap.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![52], +); + +define_test!( + name = union_at_address_1_works, + path = "../data/union_address_001.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![69], +); + +define_test!( + name = union_at_address_2_works, + path = "../data/union_address_002.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![70], +); + +define_test!( + name = union_at_address_3_works, + path = "../data/union_address_003.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 2, + num_holes = vec![0, 0], + num_verts = vec![19, 18], +); + +define_test!( + name = union_at_address_4_works, + path = "../data/union_address_004.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 2, + num_holes = vec![0, 0], + num_verts = vec![4, 24], +); + +define_test!( + name = union_at_address_5_works, + path = "../data/union_address_005.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 2, + num_holes = vec![0, 0], + num_verts = vec![71, 19], +); + +define_test!( + name = union_at_address_6_works, + path = "../data/union_address_006.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 2, + num_holes = vec![0, 0], + num_verts = vec![22, 31], +); + +define_test!( + name = union_at_address_7_works, + path = "../data/union_address_007.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![14], +); + +define_test!( + name = union_at_address_8_works, + path = "../data/union_address_008.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 2, + num_holes = vec![0, 0], + num_verts = vec![10, 5], +); + +define_test!( + name = union_at_address_9_works, + path = "../data/union_address_009.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 2, + num_holes = vec![0, 0], + num_verts = vec![41, 6], +); + +define_test!( + name = union_at_address_10_works, + path = "../data/union_address_010.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![28], +); + +define_test!( + name = union_at_address_11_works, + path = "../data/union_address_011.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![22], +); + +define_test!( + name = union_at_address_12_works, + path = "../data/union_address_012.wkt", + operation = |data: Vec>| { + MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() + }, + results: + empty = false, + num_polys = 1, + num_holes = vec![0], + num_verts = vec![22], +); diff --git a/geo/src/algorithm/spade_boolops/tests/test_helper.rs b/geo/src/algorithm/spade_boolops/tests/test_helper.rs new file mode 100644 index 000000000..024610d75 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/tests/test_helper.rs @@ -0,0 +1,108 @@ +use crate::triangulate_spade::SpadeTriangulationFloat; +use crate::HasDimensions; + +use geo_types::*; +use wkt::TryFromWkt; + +pub fn multipolygon_from(v: Vec>) -> MultiPolygon { + MultiPolygon::new(v) +} + +pub fn empty_multipoly() -> MultiPolygon { + MultiPolygon::new(vec![]) +} + +pub fn empty_poly() -> Polygon { + Polygon::new(LineString::new(vec![]), vec![]) +} + +pub fn is_multipolygon_nonempty(multipolygon: &MultiPolygon) { + let condition_true = + !multipolygon.is_empty() && !multipolygon.iter().any(|poly| poly.is_empty()); + assert!( + condition_true, + "polygon was empty even though non-empty was expected" + ); +} + +pub fn is_multipolygon_empty(multipolygon: &MultiPolygon) { + let condition_true = multipolygon.is_empty() && multipolygon.iter().all(|poly| poly.is_empty()); + assert!( + condition_true, + "polygon was non-empty even though empty was expected" + ); +} + +pub fn has_num_holes( + multipolygon: &MultiPolygon, + mut expected_num_holes: Vec, +) { + let calc_num_hole = |poly: &Polygon| poly.interiors().len(); + multipolygon.iter().for_each(|poly| { + let num_holes = calc_num_hole(poly); + if let Some(pos) = expected_num_holes.iter().position(|&n| n == num_holes) { + expected_num_holes.remove(pos); + } + }); + let has_right_num_holes = expected_num_holes.is_empty(); + let num_holes = multipolygon + .iter() + .map(|p| calc_num_hole(p)) + .collect::>(); + assert!(has_right_num_holes, "A polygon had not the expected number of holes ({expected_num_holes:?}), but {num_holes:?} holes instead"); +} + +/// This exists soley for getting notified (or warned if you will) about non-trivial changes to the +/// algorithm and not to assert a desired amount of vertices. +/// +/// If, for example, a change to the algorithm would simplify the resulting geometry, then these +/// assertions would notify the developer. They could then check if the changes make sense. +/// +/// In the end this is just a way to prevent errors slipping through the hands of the devs +pub fn has_num_vertices( + multipolygon: &MultiPolygon, + mut expected_num_vertices: Vec, +) { + let calc_num_vertices = |poly: &Polygon| { + poly.exterior().coords().count() + + poly + .interiors() + .iter() + .map(|i| i.coords().count()) + .sum::() + }; + multipolygon.iter().for_each(|poly| { + let num_verts = calc_num_vertices(poly); + if let Some(pos) = expected_num_vertices.iter().position(|&n| n == num_verts) { + expected_num_vertices.remove(pos); + } + }); + let has_right_num_vertices = expected_num_vertices.is_empty(); + let num_vertices = multipolygon + .iter() + .map(|p| calc_num_vertices(p)) + .collect::>(); + assert!(has_right_num_vertices, "A polygon had not the expected number of vertices ({expected_num_vertices:?}), but {num_vertices:?} vertices instead"); +} + +pub fn has_num_polygons( + multipolygon: &MultiPolygon, + expected_num_polys: usize, +) { + assert_eq!( + multipolygon.0.len(), + expected_num_polys, + "A multipolygon had not the expected number of polygons ({expected_num_polys}), but {} polygons instead", + multipolygon.0.len() + ); +} + +pub fn load_wkt(data_str: &str) -> Result>, String> { + let GeometryCollection(data) = + GeometryCollection::::try_from_wkt_str(data_str).map_err(|e| format!("{e:?}"))?; + let data = data + .into_iter() + .filter_map(|g| g.try_into().ok()) + .collect::>(); + Ok(data) +} From 1104e8d2322983365e646769bc49b28a0ab48f3d Mon Sep 17 00:00:00 2001 From: RobWalt Date: Sun, 29 Oct 2023 00:36:33 +0200 Subject: [PATCH 19/33] chore: rename data dir to test_data --- .../collinear_outline_parts.wkt | 0 .../duplicate_points_case_1.wkt | 0 .../duplicate_points_case_2.wkt | 0 .../duplicate_points_case_3.wkt | 0 .../{data => test_data}/hole_after_union.wkt | 0 .../holes_are_preserved.wkt | 0 .../intersection_address_001.wkt | 0 .../intersection_address_002.wkt | 0 .../intersection_fail_after_union_fix_1.wkt | 0 .../intersection_fail_after_union_fix_2.wkt | 0 .../missing_triangle_case_1.wkt | 0 .../missing_triangle_case_2.wkt | 0 .../missing_triangle_case_3.wkt | 0 .../{data => test_data}/multiple_unions.wkt | 0 .../{data => test_data}/simple_union.wkt | 0 .../{data => test_data}/star.wkt | 0 .../two_holes_after_union.wkt | 0 .../{data => test_data}/union_address_001.wkt | 0 .../{data => test_data}/union_address_002.wkt | 0 .../{data => test_data}/union_address_003.wkt | 0 .../{data => test_data}/union_address_004.wkt | 0 .../{data => test_data}/union_address_005.wkt | 0 .../{data => test_data}/union_address_006.wkt | 0 .../{data => test_data}/union_address_007.wkt | 0 .../{data => test_data}/union_address_008.wkt | 0 .../{data => test_data}/union_address_009.wkt | 0 .../{data => test_data}/union_address_010.wkt | 0 .../{data => test_data}/union_address_011.wkt | 0 .../{data => test_data}/union_address_012.wkt | 0 .../{data => test_data}/union_address_013.wkt | 0 .../{data => test_data}/union_address_014.wkt | 0 .../{data => test_data}/union_address_015.wkt | 0 .../union_both_intermediate_points_stay.wkt | 0 .../union_fails_overlap.wkt | 0 .../union_not_completely_shared_line.wkt | 0 .../union_one_intermediate_point_stays.wkt | 0 .../union_still_fails_overlap.wkt | 0 .../tests/spade_boolops_tests.rs | 88 +++++++++---------- 38 files changed, 44 insertions(+), 44 deletions(-) rename geo/src/algorithm/spade_boolops/{data => test_data}/collinear_outline_parts.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/duplicate_points_case_1.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/duplicate_points_case_2.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/duplicate_points_case_3.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/hole_after_union.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/holes_are_preserved.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/intersection_address_001.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/intersection_address_002.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/intersection_fail_after_union_fix_1.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/intersection_fail_after_union_fix_2.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/missing_triangle_case_1.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/missing_triangle_case_2.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/missing_triangle_case_3.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/multiple_unions.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/simple_union.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/star.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/two_holes_after_union.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_address_001.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_address_002.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_address_003.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_address_004.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_address_005.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_address_006.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_address_007.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_address_008.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_address_009.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_address_010.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_address_011.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_address_012.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_address_013.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_address_014.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_address_015.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_both_intermediate_points_stay.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_fails_overlap.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_not_completely_shared_line.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_one_intermediate_point_stays.wkt (100%) rename geo/src/algorithm/spade_boolops/{data => test_data}/union_still_fails_overlap.wkt (100%) diff --git a/geo/src/algorithm/spade_boolops/data/collinear_outline_parts.wkt b/geo/src/algorithm/spade_boolops/test_data/collinear_outline_parts.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/collinear_outline_parts.wkt rename to geo/src/algorithm/spade_boolops/test_data/collinear_outline_parts.wkt diff --git a/geo/src/algorithm/spade_boolops/data/duplicate_points_case_1.wkt b/geo/src/algorithm/spade_boolops/test_data/duplicate_points_case_1.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/duplicate_points_case_1.wkt rename to geo/src/algorithm/spade_boolops/test_data/duplicate_points_case_1.wkt diff --git a/geo/src/algorithm/spade_boolops/data/duplicate_points_case_2.wkt b/geo/src/algorithm/spade_boolops/test_data/duplicate_points_case_2.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/duplicate_points_case_2.wkt rename to geo/src/algorithm/spade_boolops/test_data/duplicate_points_case_2.wkt diff --git a/geo/src/algorithm/spade_boolops/data/duplicate_points_case_3.wkt b/geo/src/algorithm/spade_boolops/test_data/duplicate_points_case_3.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/duplicate_points_case_3.wkt rename to geo/src/algorithm/spade_boolops/test_data/duplicate_points_case_3.wkt diff --git a/geo/src/algorithm/spade_boolops/data/hole_after_union.wkt b/geo/src/algorithm/spade_boolops/test_data/hole_after_union.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/hole_after_union.wkt rename to geo/src/algorithm/spade_boolops/test_data/hole_after_union.wkt diff --git a/geo/src/algorithm/spade_boolops/data/holes_are_preserved.wkt b/geo/src/algorithm/spade_boolops/test_data/holes_are_preserved.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/holes_are_preserved.wkt rename to geo/src/algorithm/spade_boolops/test_data/holes_are_preserved.wkt diff --git a/geo/src/algorithm/spade_boolops/data/intersection_address_001.wkt b/geo/src/algorithm/spade_boolops/test_data/intersection_address_001.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/intersection_address_001.wkt rename to geo/src/algorithm/spade_boolops/test_data/intersection_address_001.wkt diff --git a/geo/src/algorithm/spade_boolops/data/intersection_address_002.wkt b/geo/src/algorithm/spade_boolops/test_data/intersection_address_002.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/intersection_address_002.wkt rename to geo/src/algorithm/spade_boolops/test_data/intersection_address_002.wkt diff --git a/geo/src/algorithm/spade_boolops/data/intersection_fail_after_union_fix_1.wkt b/geo/src/algorithm/spade_boolops/test_data/intersection_fail_after_union_fix_1.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/intersection_fail_after_union_fix_1.wkt rename to geo/src/algorithm/spade_boolops/test_data/intersection_fail_after_union_fix_1.wkt diff --git a/geo/src/algorithm/spade_boolops/data/intersection_fail_after_union_fix_2.wkt b/geo/src/algorithm/spade_boolops/test_data/intersection_fail_after_union_fix_2.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/intersection_fail_after_union_fix_2.wkt rename to geo/src/algorithm/spade_boolops/test_data/intersection_fail_after_union_fix_2.wkt diff --git a/geo/src/algorithm/spade_boolops/data/missing_triangle_case_1.wkt b/geo/src/algorithm/spade_boolops/test_data/missing_triangle_case_1.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/missing_triangle_case_1.wkt rename to geo/src/algorithm/spade_boolops/test_data/missing_triangle_case_1.wkt diff --git a/geo/src/algorithm/spade_boolops/data/missing_triangle_case_2.wkt b/geo/src/algorithm/spade_boolops/test_data/missing_triangle_case_2.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/missing_triangle_case_2.wkt rename to geo/src/algorithm/spade_boolops/test_data/missing_triangle_case_2.wkt diff --git a/geo/src/algorithm/spade_boolops/data/missing_triangle_case_3.wkt b/geo/src/algorithm/spade_boolops/test_data/missing_triangle_case_3.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/missing_triangle_case_3.wkt rename to geo/src/algorithm/spade_boolops/test_data/missing_triangle_case_3.wkt diff --git a/geo/src/algorithm/spade_boolops/data/multiple_unions.wkt b/geo/src/algorithm/spade_boolops/test_data/multiple_unions.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/multiple_unions.wkt rename to geo/src/algorithm/spade_boolops/test_data/multiple_unions.wkt diff --git a/geo/src/algorithm/spade_boolops/data/simple_union.wkt b/geo/src/algorithm/spade_boolops/test_data/simple_union.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/simple_union.wkt rename to geo/src/algorithm/spade_boolops/test_data/simple_union.wkt diff --git a/geo/src/algorithm/spade_boolops/data/star.wkt b/geo/src/algorithm/spade_boolops/test_data/star.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/star.wkt rename to geo/src/algorithm/spade_boolops/test_data/star.wkt diff --git a/geo/src/algorithm/spade_boolops/data/two_holes_after_union.wkt b/geo/src/algorithm/spade_boolops/test_data/two_holes_after_union.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/two_holes_after_union.wkt rename to geo/src/algorithm/spade_boolops/test_data/two_holes_after_union.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_address_001.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_001.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_address_001.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_address_001.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_address_002.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_002.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_address_002.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_address_002.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_address_003.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_003.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_address_003.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_address_003.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_address_004.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_004.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_address_004.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_address_004.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_address_005.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_005.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_address_005.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_address_005.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_address_006.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_006.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_address_006.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_address_006.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_address_007.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_007.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_address_007.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_address_007.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_address_008.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_008.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_address_008.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_address_008.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_address_009.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_009.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_address_009.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_address_009.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_address_010.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_010.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_address_010.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_address_010.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_address_011.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_011.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_address_011.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_address_011.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_address_012.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_012.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_address_012.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_address_012.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_address_013.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_013.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_address_013.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_address_013.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_address_014.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_014.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_address_014.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_address_014.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_address_015.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_015.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_address_015.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_address_015.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_both_intermediate_points_stay.wkt b/geo/src/algorithm/spade_boolops/test_data/union_both_intermediate_points_stay.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_both_intermediate_points_stay.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_both_intermediate_points_stay.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_fails_overlap.wkt b/geo/src/algorithm/spade_boolops/test_data/union_fails_overlap.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_fails_overlap.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_fails_overlap.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_not_completely_shared_line.wkt b/geo/src/algorithm/spade_boolops/test_data/union_not_completely_shared_line.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_not_completely_shared_line.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_not_completely_shared_line.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_one_intermediate_point_stays.wkt b/geo/src/algorithm/spade_boolops/test_data/union_one_intermediate_point_stays.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_one_intermediate_point_stays.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_one_intermediate_point_stays.wkt diff --git a/geo/src/algorithm/spade_boolops/data/union_still_fails_overlap.wkt b/geo/src/algorithm/spade_boolops/test_data/union_still_fails_overlap.wkt similarity index 100% rename from geo/src/algorithm/spade_boolops/data/union_still_fails_overlap.wkt rename to geo/src/algorithm/spade_boolops/test_data/union_still_fails_overlap.wkt diff --git a/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs b/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs index b98711773..9f4da65eb 100644 --- a/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs +++ b/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs @@ -48,7 +48,7 @@ macro_rules! define_test { define_test!( name = star_shape_slightly_offset_difference_1, - path = "../data/star.wkt", + path = "../test_data/star.wkt", operation = |data: Vec>| { let poly1 = &data[0]; let mut poly2 = poly1.clone(); @@ -68,7 +68,7 @@ define_test!( define_test!( name = star_shape_slightly_offset_difference_2, - path = "../data/star.wkt", + path = "../test_data/star.wkt", operation = |data: Vec>| { let poly1 = &data[0]; let mut poly2 = poly1.clone(); @@ -88,7 +88,7 @@ define_test!( define_test!( name = star_intersects_self_properly, - path = "../data/star.wkt", + path = "../test_data/star.wkt", operation = |data: Vec>| { let poly1 = &data[0]; Polygon::intersection(poly1, poly1).unwrap() @@ -102,7 +102,7 @@ define_test!( define_test!( name = duplicate_points_intersection_works_1, - path = "../data/duplicate_points_case_1.wkt", + path = "../test_data/duplicate_points_case_1.wkt", operation = |data: Vec>| { let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() @@ -116,7 +116,7 @@ define_test!( define_test!( name = duplicate_points_intersection_works_2, - path = "../data/duplicate_points_case_2.wkt", + path = "../test_data/duplicate_points_case_2.wkt", operation = |data: Vec>| { let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() @@ -130,7 +130,7 @@ define_test!( define_test!( name = duplicate_points_difference_works_1, - path = "../data/duplicate_points_case_3.wkt", + path = "../test_data/duplicate_points_case_3.wkt", operation = |data: Vec>| { let [poly1, poly2] = [&data[0], &data[1]]; Polygon::difference(poly1, poly2).unwrap() @@ -144,7 +144,7 @@ define_test!( define_test!( name = collinear_outline_parts_intersection_works, - path = "../data/collinear_outline_parts.wkt", + path = "../test_data/collinear_outline_parts.wkt", operation = |data: Vec>| { let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() @@ -158,7 +158,7 @@ define_test!( define_test!( name = missing_triangle_intersection_works_1, - path = "../data/missing_triangle_case_1.wkt", + path = "../test_data/missing_triangle_case_1.wkt", operation = |data: Vec>| { let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() @@ -172,7 +172,7 @@ define_test!( define_test!( name = missing_triangle_intersection_empty, - path = "../data/missing_triangle_case_2.wkt", + path = "../test_data/missing_triangle_case_2.wkt", operation = |data: Vec>| { let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() @@ -186,7 +186,7 @@ define_test!( define_test!( name = missing_triangle_intersection_works_2, - path = "../data/missing_triangle_case_3.wkt", + path = "../test_data/missing_triangle_case_3.wkt", operation = |data: Vec>| { let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() @@ -200,7 +200,7 @@ define_test!( define_test!( name = intersection_at_address_works_1, - path = "../data/intersection_address_001.wkt", + path = "../test_data/intersection_address_001.wkt", operation = |data: Vec>| { let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() @@ -214,7 +214,7 @@ define_test!( define_test!( name = difference_at_address_works_1, - path = "../data/intersection_address_001.wkt", + path = "../test_data/intersection_address_001.wkt", operation = |data: Vec>| { let [poly1, poly2] = [&data[0], &data[1]]; Polygon::difference(poly2, poly1).unwrap() @@ -228,7 +228,7 @@ define_test!( define_test!( name = intersection_at_address_works_2, - path = "../data/intersection_address_002.wkt", + path = "../test_data/intersection_address_002.wkt", operation = |data: Vec>| { let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() @@ -242,7 +242,7 @@ define_test!( define_test!( name = difference_at_address_works_2, - path = "../data/intersection_address_002.wkt", + path = "../test_data/intersection_address_002.wkt", operation = |data: Vec>| { let [poly1, poly2] = [&data[0], &data[1]]; Polygon::difference(poly1, poly2).unwrap() @@ -256,7 +256,7 @@ define_test!( define_test!( name = intersection_doesnt_fail_after_union_fix_1, - path = "../data/intersection_fail_after_union_fix_1.wkt", + path = "../test_data/intersection_fail_after_union_fix_1.wkt", operation = |data: Vec>| { let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() @@ -270,7 +270,7 @@ define_test!( define_test!( name = difference_doesnt_fail_after_union_fix_1, - path = "../data/intersection_fail_after_union_fix_1.wkt", + path = "../test_data/intersection_fail_after_union_fix_1.wkt", operation = |data: Vec>| { let [poly1, poly2] = [&data[0], &data[1]]; Polygon::difference(poly2, poly1).unwrap() @@ -284,7 +284,7 @@ define_test!( define_test!( name = intersection_doesnt_fail_after_union_fix_2, - path = "../data/intersection_fail_after_union_fix_2.wkt", + path = "../test_data/intersection_fail_after_union_fix_2.wkt", operation = |data: Vec>| { let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() @@ -298,7 +298,7 @@ define_test!( define_test!( name = holes_are_preserved_by_union, - path = "../data/holes_are_preserved.wkt", + path = "../test_data/holes_are_preserved.wkt", operation = |data: Vec>| { let poly1 = &data[0]; Polygon::union(poly1, poly1).unwrap() @@ -312,7 +312,7 @@ define_test!( define_test!( name = holes_are_preserved_by_intersection, - path = "../data/holes_are_preserved.wkt", + path = "../test_data/holes_are_preserved.wkt", operation = |data: Vec>| { let poly1 = &data[0]; Polygon::intersection(poly1, poly1).unwrap() @@ -326,7 +326,7 @@ define_test!( define_test!( name = holes_are_preserved_by_difference, - path = "../data/holes_are_preserved.wkt", + path = "../test_data/holes_are_preserved.wkt", operation = |data: Vec>| { let poly1 = &data[0]; Polygon::difference(poly1, &empty_poly()).unwrap() @@ -340,7 +340,7 @@ define_test!( define_test!( name = one_hole_after_union, - path = "../data/hole_after_union.wkt", + path = "../test_data/hole_after_union.wkt", operation = |data: Vec>| { let [poly1, poly2] = [&data[0], &data[1]]; Polygon::union(poly1, poly2).unwrap() @@ -354,7 +354,7 @@ define_test!( define_test!( name = two_holes_after_union, - path = "../data/two_holes_after_union.wkt", + path = "../test_data/two_holes_after_union.wkt", operation = |data: Vec>| { let [poly1, poly2] = [&data[0], &data[1]]; Polygon::union(poly1, poly2).unwrap() @@ -368,7 +368,7 @@ define_test!( define_test!( name = union_at_address_13_works, - path = "../data/union_address_013.wkt", + path = "../test_data/union_address_013.wkt", operation = |data: Vec>| { // imprecise inputs lead to hole MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() @@ -382,7 +382,7 @@ define_test!( define_test!( name = union_at_address_14_works, - path = "../data/union_address_014.wkt", + path = "../test_data/union_address_014.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -395,7 +395,7 @@ define_test!( define_test!( name = simple_union, - path = "../data/simple_union.wkt", + path = "../test_data/simple_union.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -408,7 +408,7 @@ define_test!( define_test!( name = multiple_unions, - path = "../data/multiple_unions.wkt", + path = "../test_data/multiple_unions.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -421,7 +421,7 @@ define_test!( define_test!( name = union_preserved_intermediate_points_1, - path = "../data/union_both_intermediate_points_stay.wkt", + path = "../test_data/union_both_intermediate_points_stay.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -434,7 +434,7 @@ define_test!( define_test!( name = union_preserved_intermediate_points_2, - path = "../data/union_one_intermediate_point_stays.wkt", + path = "../test_data/union_one_intermediate_point_stays.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -447,7 +447,7 @@ define_test!( define_test!( name = union_not_completely_shared_line, - path = "../data/union_not_completely_shared_line.wkt", + path = "../test_data/union_not_completely_shared_line.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -460,7 +460,7 @@ define_test!( define_test!( name = union_at_address_015_works, - path = "../data/union_address_015.wkt", + path = "../test_data/union_address_015.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -473,7 +473,7 @@ define_test!( define_test!( name = union_works_on_overlap_1, - path = "../data/union_fails_overlap.wkt", + path = "../test_data/union_fails_overlap.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -486,7 +486,7 @@ define_test!( define_test!( name = union_works_on_overlap_2, - path = "../data/union_still_fails_overlap.wkt", + path = "../test_data/union_still_fails_overlap.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -499,7 +499,7 @@ define_test!( define_test!( name = union_at_address_1_works, - path = "../data/union_address_001.wkt", + path = "../test_data/union_address_001.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -512,7 +512,7 @@ define_test!( define_test!( name = union_at_address_2_works, - path = "../data/union_address_002.wkt", + path = "../test_data/union_address_002.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -525,7 +525,7 @@ define_test!( define_test!( name = union_at_address_3_works, - path = "../data/union_address_003.wkt", + path = "../test_data/union_address_003.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -538,7 +538,7 @@ define_test!( define_test!( name = union_at_address_4_works, - path = "../data/union_address_004.wkt", + path = "../test_data/union_address_004.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -551,7 +551,7 @@ define_test!( define_test!( name = union_at_address_5_works, - path = "../data/union_address_005.wkt", + path = "../test_data/union_address_005.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -564,7 +564,7 @@ define_test!( define_test!( name = union_at_address_6_works, - path = "../data/union_address_006.wkt", + path = "../test_data/union_address_006.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -577,7 +577,7 @@ define_test!( define_test!( name = union_at_address_7_works, - path = "../data/union_address_007.wkt", + path = "../test_data/union_address_007.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -590,7 +590,7 @@ define_test!( define_test!( name = union_at_address_8_works, - path = "../data/union_address_008.wkt", + path = "../test_data/union_address_008.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -603,7 +603,7 @@ define_test!( define_test!( name = union_at_address_9_works, - path = "../data/union_address_009.wkt", + path = "../test_data/union_address_009.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -616,7 +616,7 @@ define_test!( define_test!( name = union_at_address_10_works, - path = "../data/union_address_010.wkt", + path = "../test_data/union_address_010.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -629,7 +629,7 @@ define_test!( define_test!( name = union_at_address_11_works, - path = "../data/union_address_011.wkt", + path = "../test_data/union_address_011.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, @@ -642,7 +642,7 @@ define_test!( define_test!( name = union_at_address_12_works, - path = "../data/union_address_012.wkt", + path = "../test_data/union_address_012.wkt", operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, From bcc809aed69f484cf6139f93064e1d94d3f5c0f8 Mon Sep 17 00:00:00 2001 From: RobWalt Date: Tue, 31 Oct 2023 20:57:15 +0100 Subject: [PATCH 20/33] chore: add legacy tests --- .../spade_boolops/tests/legacy_tests.rs | 167 ++++++++++++++++++ geo/src/algorithm/spade_boolops/tests/mod.rs | 1 + 2 files changed, 168 insertions(+) create mode 100644 geo/src/algorithm/spade_boolops/tests/legacy_tests.rs diff --git a/geo/src/algorithm/spade_boolops/tests/legacy_tests.rs b/geo/src/algorithm/spade_boolops/tests/legacy_tests.rs new file mode 100644 index 000000000..f528c5d7e --- /dev/null +++ b/geo/src/algorithm/spade_boolops/tests/legacy_tests.rs @@ -0,0 +1,167 @@ +use crate::algorithm::spade_boolops::trait_def::SpadeBoolops; +use std::fmt::Display; +use std::str::FromStr; + +use geo_types::*; +use wkt::TryFromWkt; + +use crate::GeoFloat; + +fn check_op( + wkt1: &str, + wkt2: &str, +) -> [MultiPolygon; 2] { + _ = pretty_env_logger::try_init(); + let poly1 = MultiPolygon::::try_from_wkt_str(wkt1) + .or_else(|_| Polygon::::try_from_wkt_str(wkt1).map(MultiPolygon::from)) + .unwrap(); + let poly2 = MultiPolygon::try_from_wkt_str(wkt2) + .or_else(|_| Polygon::::try_from_wkt_str(wkt2).map(MultiPolygon::from)) + .unwrap(); + + [poly1, poly2] +} + +#[test] +fn test_rect_overlapping() { + // Two rects that overlap + let wkt1 = "POLYGON((0 0,1 0,1 1,0 1,0 0))"; + let wkt2 = "POLYGON((0.5 1,2 1,2 2,0.5 2,0.5 1))"; + + let wkt_union = "MULTIPOLYGON(((0 1,0 0,1 0,1 1,2 1,2 2,0.5 2,0.5 1,0 1)))"; + let [p1, p2] = check_op::(wkt1, wkt2); + let output = MultiPolygon::union(&p1, &p2).expect("boolop works"); + assert_eq!(output, MultiPolygon::try_from_wkt_str(wkt_union).unwrap()); +} + +#[test] +fn test_ext_in_hole() { + // A union which outputs a ring inside a hole inside a ext. + let wkt1 = "POLYGON((0 0, 40 0, 40 40, 0 40, 0 0), (10 10, 30 10, 30 30, 10 30, 10 10))"; + let wkt2 = "POLYGON((11 11, 29 11, 29 29, 11 29, 11 11), (15 15, 25 15, 25 25, 15 25, 15 15))"; + let [p1, p2] = check_op::(wkt1, wkt2); + let _valid_output = MultiPolygon::union(&p1, &p2).expect("boolop works"); +} + +#[test] +fn test_invalid_simple() { + // Polygon with holes and invalid + let wkt1 = "POLYGON((0 0, 2 2, 2 0, 0 0), (1 1, 2 1, 1 0))"; + let wkt2 = "POLYGON EMPTY"; + let [p1, p2] = check_op::(wkt1, wkt2); + let _valid_output = MultiPolygon::union(&p1, &p2).expect("boolop works"); +} + +#[test] +fn test_invalid_loops() { + let wkt1 = "POLYGON((0 0, 2 2, 0 4, -2 2, 0 0, 1 2, 0 3, -1 2, 0 0))"; + let wkt2 = "POLYGON EMPTY"; + let [p1, p2] = check_op::(wkt1, wkt2); + let _valid_output = MultiPolygon::union(&p1, &p2).expect("boolop works"); +} + +#[test] +fn test_complex_rects() { + let wkt1 = "MULTIPOLYGON(((-1 -2,-1.0000000000000002 2,-0.8823529411764707 2,-0.8823529411764706 -2,-1 -2)),((-0.7647058823529411 -2,-0.7647058823529412 2,-0.6470588235294118 2,-0.6470588235294118 -2,-0.7647058823529411 -2)),((-0.5294117647058824 -2,-0.5294117647058825 2,-0.41176470588235287 2,-0.4117647058823529 -2,-0.5294117647058824 -2)),((-0.2941176470588236 -2,-0.2941176470588236 2,-0.17647058823529418 2,-0.17647058823529416 -2,-0.2941176470588236 -2)),((-0.05882352941176472 -2,-0.05882352941176472 2,0.05882352941176472 2,0.05882352941176472 -2,-0.05882352941176472 -2)),((0.17647058823529416 -2,0.17647058823529416 2,0.29411764705882365 2,0.2941176470588236 -2,0.17647058823529416 -2)),((0.4117647058823528 -2,0.41176470588235287 2,0.5294117647058821 2,0.5294117647058822 -2,0.4117647058823528 -2)),((0.6470588235294117 -2,0.6470588235294118 2,0.7647058823529411 2,0.7647058823529411 -2,0.6470588235294117 -2)),((0.8823529411764706 -2,0.8823529411764707 2,1.0000000000000002 2,1 -2,0.8823529411764706 -2)))"; + let wkt2 = "MULTIPOLYGON(((-2 -1,2 -1.0000000000000002,2 -0.8823529411764707,-2 -0.8823529411764706,-2 -1)),((-2 -0.7647058823529411,2 -0.7647058823529412,2 -0.6470588235294118,-2 -0.6470588235294118,-2 -0.7647058823529411)),((-2 -0.5294117647058824,2 -0.5294117647058825,2 -0.41176470588235287,-2 -0.4117647058823529,-2 -0.5294117647058824)),((-2 -0.2941176470588236,2 -0.2941176470588236,2 -0.17647058823529418,-2 -0.17647058823529416,-2 -0.2941176470588236)),((-2 -0.05882352941176472,2 -0.05882352941176472,2 0.05882352941176472,-2 0.05882352941176472,-2 -0.05882352941176472)),((-2 0.17647058823529416,2 0.17647058823529416,2 0.29411764705882365,-2 0.2941176470588236,-2 0.17647058823529416)),((-2 0.4117647058823528,2 0.41176470588235287,2 0.5294117647058821,-2 0.5294117647058822,-2 0.4117647058823528)),((-2 0.6470588235294117,2 0.6470588235294118,2 0.7647058823529411,-2 0.7647058823529411,-2 0.6470588235294117)),((-2 0.8823529411764706,2 0.8823529411764707,2 1.0000000000000002,-2 1,-2 0.8823529411764706)))"; + + let mp1 = MultiPolygon::::try_from_wkt_str(wkt1).unwrap(); + let mp2 = MultiPolygon::::try_from_wkt_str(wkt2).unwrap(); + + for p1 in mp1.0.iter() { + for p2 in mp2.0.iter() { + let _valid = Polygon::union(p1, p2); + } + } +} + +#[test] +fn test_complex_rects1() { + let wkt1 = "MULTIPOLYGON(((-1 -2,-1.0000000000000002 2,-0.8823529411764707 2,-0.8823529411764706 -2,-1 -2)))"; + let wkt2 = "MULTIPOLYGON(((-2 -1,2 -1.0000000000000002,2 -0.8823529411764707,-2 -0.8823529411764706,-2 -1)))"; + let [p1, p2] = check_op::(wkt1, wkt2); + let _valid_output = MultiPolygon::union(&p1, &p2).expect("boolop works"); +} + +#[test] +fn test_overlap_issue_867() { + let wkt1 = "POLYGON ((17.724912058920285 -16.37118892052372, 18.06452454246989 -17.693907532504, 19.09389292605319 -17.924001641855178, 17.724912058920285 -16.37118892052372))"; + let wkt2 = "POLYGON ((17.576085274796423 -15.791540153598898, 17.19432983818328 -17.499393422066746, 18.06452454246989 -17.693907532504, 17.576085274796423 -15.791540153598898))"; + let [p1, p2] = check_op::(wkt1, wkt2); + let _valid_output = MultiPolygon::intersection(&p1, &p2).expect("boolop works"); +} + +#[test] +fn test_issue_865() { + // Simplified example + let wkt1 = "POLYGON((0 0, 1 1, 10 0, 0 0))"; + let wkt2 = "POLYGON((1 1, 8 2, 4 1, 1 1))"; + let wkt2d = "POLYGON((1 1, 4 2, 8 1, 1 1))"; + + let [p1, p2] = check_op::(wkt1, wkt2); + let _valid_output = MultiPolygon::union(&p1, &p2).expect("boolop works"); + + let [p1, p2] = check_op::(wkt1, wkt2d); + let _valid_output = MultiPolygon::union(&p1, &p2).expect("boolop works"); + + // Original Example + let wkt1 = "POLYGON((-640 -360,640 -360,640 360,-640 360,-640 -360))"; + let wkt2 = "POLYGON((313.276 359.999,213.319 359.999,50 60,-50 60,-50 110,-8.817 360,-93.151 360,-85.597 225.618,-114.48 359.999,-117.017 360,-85 215,-85 155,-115 155,-154.161 360,-640 360,-640 -360,640 -360,640 360,313.277 360,313.276 359.999))"; + + let [p1, p2] = check_op::(wkt1, wkt2); + let _valid_output = MultiPolygon::difference(&p1, &p2).expect("boolop works"); +} + +#[test] +fn test_issue_885_small() { + let wkt1 = "POLYGON((8055.658 7977.5537,8010.734 7999.9697,8032.9717 8044.537,8077.896 8022.121,8055.658 7977.5537))"; + let wkt2 = "POLYGON((8055.805 7977.847,8010.871 8000.2676,8033.105 8044.8286,8078.039 8022.408,8055.805 7977.847))"; + + let [p1, p2] = check_op::(wkt1, wkt2); + let _valid_output = MultiPolygon::union(&p1, &p2).expect("boolop works"); +} + +#[test] +fn test_issue_885_big_raw() { + let wkt1 = "MULTIPOLYGON(((256 -256,-256 -256,-256 256,256 256,256 -256),(21.427018453144548 100.2247496417564,22.170654296875 97.449462890625,21.255590787413905 95.86452640008609,22.170654296875 92.449462890625,21.255635468249327 90.86460378956318,22.170654296875 87.44970703125,21.255590787413905 85.86477054071109,22.170654296875 82.44970703125,21.255590787413905 80.86477054071109,22.170654296875 77.44970703125,21.255635468249327 75.86484793018818,22.170654296875 72.449951171875,21.255590787413905 70.86501468133609,22.170654296875 67.449951171875,21.255590787413905 65.86501468133609,22.170654296875 62.449951171875,21.255635468249327 60.865092070813176,22.170654296875 57.4501953125,20.964468977514716 55.3610210560243,21.7110595703125 52.57470703125,6.7110595703125036 26.593944917716843,4.94307055353958 26.120213688445443,3.1754150390625036 23.058544527091843,1.4077371841677144 22.584896673394404,-0.36010742187499645 19.522899995841843,-2.127952027917708 19.04920746130898,-3.8956298828124964 15.987499605216843,-5.663474488855209 15.513807070683983,-7.4311523437499964 12.452099214591843,-9.199141360522919 11.978367985320444,-10.966796874999996 8.916698823966843,-12.734474729894785 8.443050970269406,-14.502319335937496 5.381054292716843,-29.502319335937507 1.36181640625,-44.502319335937514 5.381054292716847,-55.48308144947066 16.361816406249996,-59.5023193359375 31.361816406250004,-55.48308144947066 46.36181640625,-51.94760366936858 49.89729418635208,-51.94755898853316 49.8974609375,-40.96679687499999 60.87822305103316,-40.96646337270415 60.878312412704005,-37.43115234374999 64.41362344165816,-37.43098559260209 64.41366812249358,-37.25638533366225 64.58826838143341,-37.15947272204719 64.949951171875,-37.829345703125 67.449951171875,-37.15947272204719 69.949951171875,-37.829345703125 72.449951171875,-37.159505430688846 74.9498291015625,-37.829345703125 77.44970703125,-37.15947272204719 79.94970703125,-37.829345703125 82.44970703125,-37.15947272204719 84.94970703125,-37.829345703125 87.44970703125,-37.6218793727004 88.22398191725448,-43.845336914062514 89.89155233959184,-54.82609902759566 100.872314453125,-58.8453369140625 115.872314453125,-54.82609902759566 130.872314453125,-43.84533691406249 141.85307656665816,-28.845336914062496 145.872314453125,-26.345275878906246 145.20242511772636,-23.845214843749996 145.872314453125,-21.345153808593746 145.20242511772636,-18.845092773437496 145.872314453125,-16.345092773437496 145.20244147204718,-13.845092773437498 145.872314453125,-11.345092773437498 145.20244147204718,-8.845092773437498 145.872314453125,-7.241160801189184 145.4425421764466,-4.753417968749998 146.109130859375,-3.857349940998309 145.86903015497558,-3.8450927734374982 145.872314453125,-2.5414695567288628 145.52300966497347,-0.04333496093749816 146.1923828125,14.956665039062504 142.17314492603316,14.96700942550945 142.16280053958621,15.549804687500004 142.00664101978316,26.53056680103316 131.02587890625,30.5498046875 116.02587890625,21.427018453144548 100.2247496417564),(15.9573974609375 218.888916015625,11.93815957447066 233.888916015625,0.9573974609375036 244.86967812915816,-14.042602539062498 248.888916015625,-29.042602539062493 244.86967812915816,-32.57795824885207 241.3343224193686,-32.57812499999999 241.33427773853316,-36.11348070978957 237.7989220287436,-36.11364746093749 237.79887734790816,-39.64895848989165 234.26356631895402,-39.64929199218749 234.26347695728316,-50.63005410572066 223.28271484375,-50.63009878655608 223.28254809260207,-54.16557656665816 219.7470703125,-58.184814453125 204.7470703125,-54.16557656665816 189.7470703125,-43.184814453125014 178.76630819896684,-28.184814453125007 174.7470703125,-13.184814453124996 178.76630819896684,-11.416969847082285 181.8283048765194,-9.649291992187496 182.30195273021684,-7.88163647771042 185.36362189157043,-6.1136474609374964 185.83735312084184,-4.345969606042708 188.89906097693398,-2.5781249999999964 189.37275351146684,-0.8104471451052081 192.43446136755898,0.9573974609375036 192.90815390209184,15.9573974609375 218.888916015625)))"; + let wkt2 = "POLYGON((19.492919921875 222.424560546875,15.47368203540816 237.424560546875,4.4929199218750036 248.40532266040816,-10.507080078124998 252.424560546875,-25.507080078124993 248.40532266040816,-36.48784219165816 237.424560546875,-40.507080078125 222.424560546875,-36.48784219165816 207.424560546875,-25.507080078125014 196.44379843334184,-10.507080078125005 192.424560546875,4.4929199218750036 196.44379843334184,19.492919921875 222.424560546875))"; + + let [p1, p2] = check_op::(wkt1, wkt2); + let _valid_output = MultiPolygon::difference(&p1, &p2).expect("boolop works"); +} + +#[test] +fn test_issue_885_big_simplified() { + let wkt1 = r#"MULTIPOLYGON(((256.0 -256.0,-256.0 -256.0,-256.0 256.0,256.0 256.0,256.0 -256.0), (15.9573974609375 218.888916015625,-29.042602539062493 244.86967812915816, -32.57795824885207 241.3343224193686, -32.57812499999999 241.33427773853316, -36.11348070978957 237.7989220287436,-36.11364746093749 237.79887734790816, -39.64895848989165 234.26356631895402,0.9573974609375036 192.90815390209184,15.9573974609375 218.888916015625)))"#; + let wkt2 = r#"POLYGON((19.492919921875 222.424560546875,-25.507080078124993 248.40532266040816,-36.48784219165816 237.424560546875, 4.4929199218750036 196.44379843334184,19.492919921875 222.424560546875))"#; + + let [p1, p2] = check_op::(wkt1, wkt2); + let _valid_output = MultiPolygon::difference(&p1, &p2).expect("boolop works"); +} + +// care: This test highlights that the SpadeBoolops algo is awfully slow! :S +#[test] +fn test_issue_894() { + _ = pretty_env_logger::try_init(); + use geo_test_fixtures::multi_polygon; + let a: MultiPolygon = multi_polygon("issue-894/inpa.wkt"); + let b = multi_polygon("issue-894/inpb.wkt"); + let c = multi_polygon("issue-894/inpc.wkt"); + + let aib = MultiPolygon::intersection(&a, &b).unwrap(); + log::info!("first"); + MultiPolygon::intersection(&b, &c).unwrap(); + log::info!("second"); + MultiPolygon::intersection(&aib, &c).unwrap(); + log::info!("third"); +} + +#[test] +fn test_issue_buffer_box() { + // TODO: This test-case breaks the ordering assumption, and does not pass. + let wkt1 = "MULTIPOLYGON(((-164.93595896333647 152.85701803397086,-164.93595896333647 149.53568721641966,-51.873865625542294 153.2197777241044,-51.873865625542294 255.14903655104297,-153.80312445248086 255.14903655104297,-266.86521779027504 251.46494604335822,-266.86521779027504 149.53568721641966,-164.93595896333647 152.85701803397086)))"; + let wkt2 = "MULTIPOLYGON(((-164.93595896333647 149.53568721641966,-51.873865625542294 153.2197777241044,-153.80312445248086 153.2197777241044,-266.86521779027504 149.53568721641966,-164.93595896333647 149.53568721641966)))"; + + let [p1, p2] = check_op::(wkt1, wkt2); + let _valid_output = MultiPolygon::union(&p1, &p2).expect("boolop works"); +} diff --git a/geo/src/algorithm/spade_boolops/tests/mod.rs b/geo/src/algorithm/spade_boolops/tests/mod.rs index 8b3739e05..339e54c50 100644 --- a/geo/src/algorithm/spade_boolops/tests/mod.rs +++ b/geo/src/algorithm/spade_boolops/tests/mod.rs @@ -1,4 +1,5 @@ pub use super::*; +mod legacy_tests; mod spade_boolops_tests; mod test_helper; From c160f82978122663a25a8128713e44306e0f433d Mon Sep 17 00:00:00 2001 From: RobWalt Date: Tue, 31 Oct 2023 21:44:59 +0100 Subject: [PATCH 21/33] chore: add test for issue 1103 --- geo/src/algorithm/spade_boolops/tests/mod.rs | 1 + .../spade_boolops/tests/open_issues_tests.rs | 224 ++++++++++++++++++ 2 files changed, 225 insertions(+) create mode 100644 geo/src/algorithm/spade_boolops/tests/open_issues_tests.rs diff --git a/geo/src/algorithm/spade_boolops/tests/mod.rs b/geo/src/algorithm/spade_boolops/tests/mod.rs index 339e54c50..8a2b3a974 100644 --- a/geo/src/algorithm/spade_boolops/tests/mod.rs +++ b/geo/src/algorithm/spade_boolops/tests/mod.rs @@ -1,5 +1,6 @@ pub use super::*; mod legacy_tests; +mod open_issues_tests; mod spade_boolops_tests; mod test_helper; diff --git a/geo/src/algorithm/spade_boolops/tests/open_issues_tests.rs b/geo/src/algorithm/spade_boolops/tests/open_issues_tests.rs new file mode 100644 index 000000000..be589231a --- /dev/null +++ b/geo/src/algorithm/spade_boolops/tests/open_issues_tests.rs @@ -0,0 +1,224 @@ +use crate::SpadeBoolops; +use geo_types::{Coord, LineString, MultiPolygon, Polygon}; + +#[test] +fn no_1103_union_for_f32_polys() { + let polygons = [ + Polygon::::new( + LineString::from(vec![ + Coord { + x: 3.349_365_2, + y: -55.801_27, + }, + Coord { + x: 171.224_43, + y: -300.0, + }, + Coord { + x: 291.841_64, + y: -300.0, + }, + Coord { + x: 46.650_635, + y: -30.801_27, + }, + Coord { + x: 3.349_365_2, + y: -55.801_27, + }, + ]), + Vec::new(), + ), + Polygon::::new( + LineString::from(vec![ + Coord { + x: 46.650_635, + y: -30.801_27, + }, + Coord { + x: 291.841_64, + y: -300.0, + }, + Coord { + x: 300.0, + y: -228.340_03, + }, + Coord { + x: -3.349_365_2, + y: 55.801_27, + }, + Coord { + x: 46.650_635, + y: -30.801_27, + }, + ]), + Vec::new(), + ), + Polygon::::new( + LineString::from(vec![ + Coord { + x: -46.650_635, + y: 30.801_27, + }, + Coord { + x: 195.837_28, + y: -300.0, + }, + Coord { + x: 300.0, + y: -228.340_03, + }, + Coord { + x: -3.349_365_2, + y: 55.801_27, + }, + Coord { + x: -46.650_635, + y: 30.801_27, + }, + ]), + Vec::new(), + ), + Polygon::::new( + LineString::from(vec![ + Coord { + x: 3.349_365_2, + y: -55.801_27, + }, + Coord { + x: 171.224_43, + y: -300.0, + }, + Coord { + x: 195.837_28, + y: -300.0, + }, + Coord { + x: -46.650_635, + y: 30.801_27, + }, + Coord { + x: 3.349_365_2, + y: -55.801_27, + }, + ]), + Vec::new(), + ), + ]; + + let mut multi = MultiPolygon::new(Vec::new()); + for poly in polygons { + multi = MultiPolygon::union(&multi, &MultiPolygon::new(vec![poly])).unwrap(); + } +} + +#[test] +fn no_1103_union_for_f64_polys() { + let polygons = [ + Polygon::::new( + LineString::from(vec![ + Coord { + x: 3.349365234375, + y: -55.80126953125, + }, + Coord { + x: 171.224_426_269_531_25, + y: -300.0, + }, + Coord { + x: 291.841_644_287_109_4, + y: -300.0, + }, + Coord { + x: 46.650_634_765_625, + y: -30.801_269_531_25, + }, + Coord { + x: 3.349_365_234_375, + y: -55.801_269_531_25, + }, + ]), + Vec::new(), + ), + Polygon::::new( + LineString::from(vec![ + Coord { + x: 46.650_634_765_625, + y: -30.801_269_531_25, + }, + Coord { + x: 291.841_644_287_109_4, + y: -300.0, + }, + Coord { + x: 300.0, + y: -228.340_026_855_468_75, + }, + Coord { + x: -3.349_365_234_375, + y: 55.801_269_531_25, + }, + Coord { + x: 46.650_634_765_625, + y: -30.801_269_531_25, + }, + ]), + Vec::new(), + ), + Polygon::::new( + LineString::from(vec![ + Coord { + x: -46.650_634_765_625, + y: 30.801_269_531_25, + }, + Coord { + x: 195.837_280_273_437_5, + y: -300.0, + }, + Coord { + x: 300.0, + y: -228.340_026_855_468_75, + }, + Coord { + x: -3.349_365_234_375, + y: 55.801_269_531_25, + }, + Coord { + x: -46.650_634_765_625, + y: 30.801_269_531_25, + }, + ]), + Vec::new(), + ), + Polygon::::new( + LineString::from(vec![ + Coord { + x: 3.349_365_234_375, + y: -55.801_269_531_25, + }, + Coord { + x: 171.224_426_269_531_25, + y: -300.0, + }, + Coord { + x: 195.837_280_273_437_5, + y: -300.0, + }, + Coord { + x: -46.650_634_765_625, + y: 30.801_269_531_25, + }, + Coord { + x: 3.349_365_234_375, + y: -55.801_269_531_25, + }, + ]), + Vec::new(), + ), + ]; + + let mut multi = MultiPolygon::new(Vec::new()); + for poly in polygons { + multi = MultiPolygon::union(&multi, &MultiPolygon::new(vec![poly])).unwrap(); + } +} From 047bf3b0236e66f06e597805761e35d005204883 Mon Sep 17 00:00:00 2001 From: RobWalt Date: Tue, 31 Oct 2023 21:50:53 +0100 Subject: [PATCH 22/33] chore: add test for issue 1053 --- .../spade_boolops/tests/open_issues_tests.rs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/geo/src/algorithm/spade_boolops/tests/open_issues_tests.rs b/geo/src/algorithm/spade_boolops/tests/open_issues_tests.rs index be589231a..a77b5cfbf 100644 --- a/geo/src/algorithm/spade_boolops/tests/open_issues_tests.rs +++ b/geo/src/algorithm/spade_boolops/tests/open_issues_tests.rs @@ -222,3 +222,44 @@ fn no_1103_union_for_f64_polys() { multi = MultiPolygon::union(&multi, &MultiPolygon::new(vec![poly])).unwrap(); } } + +#[test] +fn no_1053_intersection_for_f32_polys() { + // Reproduction occurs when intersecting Polygon types, but Polygon does not reproduce. + let geo1 = Polygon::::new( + LineString(vec![ + Coord { x: 0.0, y: 0.0 }, + Coord { x: 0.0, y: 200.0 }, + Coord { x: 200.0, y: 200.0 }, + Coord { x: 200.0, y: 0.0 }, + Coord { x: 0.0, y: 0.0 }, + ]), + vec![], + ); + let geo2 = Polygon::::new( + LineString(vec![ + Coord { + x: -0.17588139, + y: 0.0015348792, + }, + Coord { + x: 1.5845897, + y: 201.73154, + }, + Coord { + x: 200.1759, + y: 199.99846, + }, + Coord { + x: 198.41544, + y: -1.7315454, + }, + Coord { + x: -0.17588139, + y: 0.0015348792, + }, + ]), + vec![], + ); + let _valid_result = Polygon::intersection(&geo1, &geo2).unwrap(); +} From c171ffc8ba3eea31caf3fe88c94fded435864439 Mon Sep 17 00:00:00 2001 From: RobWalt Date: Tue, 31 Oct 2023 21:53:40 +0100 Subject: [PATCH 23/33] chore: add must_use attributes to boolean operations --- geo/src/algorithm/spade_boolops/trait_def.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/geo/src/algorithm/spade_boolops/trait_def.rs b/geo/src/algorithm/spade_boolops/trait_def.rs index 78d2dab1d..c51ee28a7 100644 --- a/geo/src/algorithm/spade_boolops/trait_def.rs +++ b/geo/src/algorithm/spade_boolops/trait_def.rs @@ -16,18 +16,21 @@ where op_pred: F, ) -> SpadeBoolopsResult; + #[must_use = "Use the difference of these two geometries by binding the result to a variable! (`let result = ...`)"] fn difference(p1: &Self, p2: &Self) -> SpadeBoolopsResult { Self::boolop(p1, p2, |tri| { contains_triangle(p1, tri) && !contains_triangle(p2, tri) }) } + #[must_use = "Use the intersection of these two geometries by binding the result to a variable! (`let result = ...`)"] fn intersection(p1: &Self, p2: &Self) -> SpadeBoolopsResult { Self::boolop(p1, p2, |tri| { contains_triangle(p1, tri) && contains_triangle(p2, tri) }) } + #[must_use = "Use the union of these two geometries by binding the result to a variable! (`let result = ...`)"] fn union(p1: &Self, p2: &Self) -> SpadeBoolopsResult { Self::boolop(p1, p2, |tri| { contains_triangle(p1, tri) || contains_triangle(p2, tri) From 71a946e0c4d3dfe14c4c8820cf6801b462125a01 Mon Sep 17 00:00:00 2001 From: RobWalt Date: Tue, 31 Oct 2023 21:57:45 +0100 Subject: [PATCH 24/33] chore: add test for issue 1064 --- .../algorithm/spade_boolops/tests/open_issues_tests.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/geo/src/algorithm/spade_boolops/tests/open_issues_tests.rs b/geo/src/algorithm/spade_boolops/tests/open_issues_tests.rs index a77b5cfbf..873cbf52d 100644 --- a/geo/src/algorithm/spade_boolops/tests/open_issues_tests.rs +++ b/geo/src/algorithm/spade_boolops/tests/open_issues_tests.rs @@ -1,5 +1,6 @@ use crate::SpadeBoolops; use geo_types::{Coord, LineString, MultiPolygon, Polygon}; +use wkt::TryFromWkt; #[test] fn no_1103_union_for_f32_polys() { @@ -263,3 +264,11 @@ fn no_1053_intersection_for_f32_polys() { ); let _valid_result = Polygon::intersection(&geo1, &geo2).unwrap(); } + +#[test] +fn no_1064_intersection_for_f64() { + let a: Polygon = Polygon::try_from_wkt_str("POLYGON ((709799.9999999999 4535330.115932672, 709800.0000000001 4535889.8772568945, 709800.0057476197 4535889.994252375, 709800.1227431173 4535890.000000001, 710109.8788852151 4535889.999999996, 710109.994510683 4535889.99439513, 710110.0025226776 4535889.878911494, 710119.9974094903 4535410.124344491, 710119.9939843 4535410.005891683, 710119.8756285358 4535410.000000003, 710050.1240139506 4535410.000000003, 710050.0040320279 4535410.005955433, 710049.9539423736 4535410.115144073, 710038.1683017325 4535439.579245671, 710037.9601922985 4535440.0325333765, 710037.7079566419 4535440.462831489, 710037.4141048047 4535440.865858022, 710037.0815609565 4535441.237602393, 710036.7136342974 4535441.57436531, 710036.3139861295 4535441.872795589, 710035.8865934211 4535442.129923492, 710035.4357092284 4535442.343190307, 710034.9658203793 4535442.510473766, 710034.4816028172 4535442.6301092245, 710033.9878750739 4535442.7009061435, 710033.489550319 4535442.722160025, 710032.9915874689 4535442.6936593605, 710032.4989418354 4535442.615687774, 710032.016515821 4535442.48902117, 710031.54911013 4535442.314920021, 710031.1013759954 4535442.095116852, 710030.6777688961 4535441.831798956, 710030.2825042206 4535441.527586658, 710029.9195153153 4535441.185507218, 710029.5924143443 4535440.808964732, 710029.3044563448 4535440.401706239, 710029.0585068383 4535439.967784446, 710028.8570133082 4535439.511517367, 710028.701980853 4535439.037445406, 710028.5949522265 4535438.550286128, 710028.536992489 4535438.05488734, 710020.008429941 4535310.126449097, 710019.9938185212 4535310.0024022255, 710019.9208325277 4535309.901040657, 709980.0772727348 4535260.096590921, 709979.9987806884 4535260.007853539, 709979.8970781134 4535260.068614591, 709920.1022372885 4535299.931841814, 709920.0058878824 4535300.003151096, 709920.0000000001 4535300.122873942, 709920.0000000002 4535324.451138855, 709919.9762868701 4535324.937522437, 709919.9053724057 4535325.419292543, 709919.7879292492 4535325.891879465, 709919.6250713767 4535326.350800604, 709919.4183435373 4535326.7917029755, 709919.1697065973 4535327.210404501, 709918.8815189389 4535327.602933702, 709918.5565140966 4535327.965567328, 709918.1977748218 4535328.294865718, 709917.8087038476 4535328.5877053905, 709917.3929916087 4535328.841308688, 709916.9545812428 4535329.053270123, 709916.497631181 4535329.221579175, 709916.0264757108 4535329.344639407, 709915.5455838591 4535329.421283546, 709915.059517008 4535329.450784619, 709914.5728856224 4535329.43286279, 709914.0903055237 4535329.367688051, 709913.6163541072 4535329.255878603, 709913.1555269207 4535329.098494996, 709912.7121950255 4535328.89703004, 709912.2905635366 4535328.653394689, 709911.8946317348 4535328.369899881, 709911.5281551343 4535328.049234638, 709911.1946098561 4535327.694440548, 709910.8971596619 4535327.308882924, 709910.638625942 4535326.896218885, 709910.4214609532 4535326.460362643, 709910.2477245614 4535326.005448406, 709910.119064699 4535325.53579115, 709900.0266965557 4535280.120134492, 709899.9954816136 4535280.006411615, 709899.8778852832 4535280.015264336, 709820.1219250998 4535289.984759362, 709820.0038570677 4535290.005451573, 709819.9450491015 4535290.109901799, 709800.0518466672 4535329.896306664, 709800.0053207672 4535330.001256061, 709799.9999999999 4535330.115932672))").unwrap(); + let b: Polygon = Polygon::try_from_wkt_str("POLYGON ((709800 4600020, 809759.9999999993 4600020, 809759.9999999987 4500000, 709800.0000000003 4500000, 709800 4590240, 709800 4600020))").unwrap(); + + Polygon::intersection(&a, &b).unwrap(); +} From e6162fe56244da56a6a6e4896029a6eb062d1499 Mon Sep 17 00:00:00 2001 From: RobWalt Date: Tue, 31 Oct 2023 22:06:46 +0100 Subject: [PATCH 25/33] chore: add tests for issue 913 --- .../spade_boolops/tests/open_issues_tests.rs | 811 ++++++++++++++++++ 1 file changed, 811 insertions(+) diff --git a/geo/src/algorithm/spade_boolops/tests/open_issues_tests.rs b/geo/src/algorithm/spade_boolops/tests/open_issues_tests.rs index 873cbf52d..eb3c07454 100644 --- a/geo/src/algorithm/spade_boolops/tests/open_issues_tests.rs +++ b/geo/src/algorithm/spade_boolops/tests/open_issues_tests.rs @@ -272,3 +272,814 @@ fn no_1064_intersection_for_f64() { Polygon::intersection(&a, &b).unwrap(); } + +#[test] +fn no_913_intersection_and_difference_for_f32() { + let p1: MultiPolygon = MultiPolygon::new(vec![Polygon::new( + LineString::from(vec![ + Coord { + x: 140.50, + y: 62.24, + }, + Coord { + x: 140.07, + y: 62.34, + }, + Coord { + x: 136.94, + y: 63.05, + }, + Coord { + x: 140.50, + y: 62.24, + }, + ]), + vec![], + )]); + let p2: MultiPolygon = MultiPolygon::new(vec![Polygon::new( + LineString::from(vec![ + Coord { + x: 142.50, + y: 61.44, + }, + Coord { + x: 142.06, + y: 61.55, + }, + Coord { + x: 140.07, + y: 62.34, + }, + Coord { + x: 142.50, + y: 61.44, + }, + ]), + vec![], + )]); + + let intersection = MultiPolygon::intersection(&p1, &p2).unwrap(); + let _valid_result = MultiPolygon::difference(&p1, &intersection).unwrap(); +} + +#[test] +fn no_913_union_for_f64() { + let pg1 = Polygon::new( + LineString(vec![ + Coord { + x: 367.679, + y: 302.785, + }, + Coord { + x: 146.985, + y: 90.13, + }, + Coord { + x: 235.63, + y: 197.771, + }, + Coord { + x: 222.764, + y: 233.747, + }, + Coord { + x: 200.38180602615253, + y: 288.3378373783007, + }, + Coord { + x: 207.575, + y: 288.205, + }, + Coord { + x: 367.679, + y: 302.785, + }, + ]), + vec![], + ); + + let pg2 = Polygon::new( + LineString(vec![ + Coord { + x: 367.679, + y: 302.785, + }, + Coord { + x: 203.351, + y: 279.952, + }, + Coord { + x: 200.373, + y: 288.338, + }, + Coord { + x: 206.73187706275607, + y: 288.2205700292493, + }, + Coord { + x: 208.489, + y: 286.233, + }, + Coord { + x: 212.24, + y: 285.478, + }, + Coord { + x: 367.679, + y: 302.785, + }, + ]), + vec![], + ); + + let _valid_result = Polygon::union(&pg1, &pg2).unwrap(); +} + +#[test] +fn no_913_big_union_for_f64() { + let pg1 = Polygon::new( + LineString(vec![ + Coord { + x: 0.0, + y: 243.4003107156999, + }, + Coord { + x: 66.27444465680952, + y: 250.87823764635957, + }, + Coord { + x: 96.33638429757961, + y: 248.20750628598208, + }, + Coord { + x: 96.52875443322587, + y: 236.0960826183176, + }, + Coord { + x: 109.01797296466019, + y: 237.22982767630356, + }, + Coord { + x: 110.67808356405288, + y: 222.36101161840142, + }, + Coord { + x: 177.32511967843928, + y: 245.4303896678587, + }, + Coord { + x: 220.03681983758034, + y: 231.22020737406902, + }, + Coord { + x: 220.26051686053162, + y: 230.70916392688252, + }, + Coord { + x: 222.76475236003864, + y: 233.7471699986308, + }, + Coord { + x: 235.63037504441513, + y: 197.77142864845837, + }, + Coord { + x: 46.120740248112526, + y: 194.2466054578009, + }, + Coord { + x: 0.0, + y: 197.3285470235943, + }, + Coord { + x: 1.6250409484162764e-15, + y: 195.62006213211825, + }, + Coord { + x: 0.0, + y: 195.62006213211825, + }, + Coord { + x: 0.0, + y: 166.11923271453853, + }, + Coord { + x: 84.46350148899029, + y: 172.13043318345098, + }, + Coord { + x: 85.18497315136065, + y: 135.51645414474072, + }, + Coord { + x: 113.74634762902987, + y: 143.6756896905261, + }, + Coord { + x: 120.613520578221, + y: 116.4384347293961, + }, + Coord { + x: 120.61352057822103, + y: 116.43843472939612, + }, + Coord { + x: 120.95625623486023, + y: 115.07904294864129, + }, + Coord { + x: 133.11594113032265, + y: 121.5727268145846, + }, + Coord { + x: 136.76528203129863, + y: 99.7945926414018, + }, + Coord { + x: 136.76528203129865, + y: 99.79459264140182, + }, + Coord { + x: 136.95169923472406, + y: 98.68211261321306, + }, + Coord { + x: 142.86571296028092, + y: 103.2131667249273, + }, + Coord { + x: 146.98556814305613, + y: 90.1306295921712, + }, + Coord { + x: 149.81840786246337, + y: 87.34720718892197, + }, + Coord { + x: 149.8184078624634, + y: 87.34720718892198, + }, + Coord { + x: 157.36666554726634, + y: 79.93062466875924, + }, + Coord { + x: 184.82983774975878, + y: 111.82526107380869, + }, + Coord { + x: 211.8877775129764, + y: 95.80693442569219, + }, + Coord { + x: 227.5571963507852, + y: 98.78793110381741, + }, + Coord { + x: 218.607932670586, + y: 55.083930748810644, + }, + Coord { + x: 221.83929458508348, + y: 52.149586224826386, + }, + Coord { + x: 221.83929458508348, + y: 52.14958622482635, + }, + Coord { + x: 232.98401357993802, + y: 42.029257391594996, + }, + Coord { + x: 241.8190745801098, + y: 13.993506274058689, + }, + Coord { + x: 241.1380006507843, + y: 0.0, + }, + Coord { + x: 241.60061958968532, + y: 2.044378041651483e-14, + }, + Coord { + x: 241.60061958968532, + y: -2.842170943040401e-14, + }, + Coord { + x: 249.98742446013085, + y: -2.842170943040401e-14, + }, + Coord { + x: 249.98742446013085, + y: 1.85917124373295e-14, + }, + Coord { + x: 254.4202314064203, + y: 2.842170943040401e-14, + }, + Coord { + x: 247.73005601682144, + y: 120.74050514867251, + }, + Coord { + x: 259.2004570591687, + y: 144.14650678216447, + }, + Coord { + x: 266.8397653781389, + y: 154.40474480753, + }, + Coord { + x: 296.8607072090515, + y: 129.49256607782735, + }, + Coord { + x: 352.116978342238, + y: 128.0069901264593, + }, + Coord { + x: 375.0780451860593, + y: 94.15446213853835, + }, + Coord { + x: 374.98659859747573, + y: 100.58888400844128, + }, + Coord { + x: 375.530089594595, + y: 124.2204839556052, + }, + Coord { + x: 400.0, + y: 94.35082668552377, + }, + Coord { + x: 400.0, + y: 101.6071057208606, + }, + Coord { + x: 400.0, + y: 105.80164716197932, + }, + Coord { + x: 400.0, + y: 113.6261499931943, + }, + Coord { + x: 400.0, + y: 311.5380102022039, + }, + Coord { + x: 400.0, + y: 318.2032485086692, + }, + Coord { + x: 400.0, + y: 334.71250969254965, + }, + Coord { + x: 400.0, + y: 352.42377480851513, + }, + Coord { + x: 367.67936762793977, + y: 302.785219017324, + }, + Coord { + x: 243.8528456455854, + y: 299.9323675588228, + }, + Coord { + x: 221.67982343202226, + y: 315.355350903515, + }, + Coord { + x: 221.61838827018173, + y: 313.1570930513446, + }, + Coord { + x: 217.6731029946546, + y: 302.77563506887896, + }, + Coord { + x: 213.10441329274047, + y: 314.6831957928891, + }, + Coord { + x: 212.0282774321647, + y: 314.64026940589486, + }, + Coord { + x: 212.02827743216469, + y: 314.64026940589486, + }, + Coord { + x: 195.75881007955047, + y: 313.99129052009226, + }, + Coord { + x: 195.7588100795505, + y: 313.99129052009226, + }, + Coord { + x: 178.69748010894065, + y: 313.3107247292978, + }, + Coord { + x: 91.47746829048103, + y: 360.79965086361875, + }, + Coord { + x: 91.50558129979011, + y: 359.2500343818318, + }, + Coord { + x: 94.33238284142949, + y: 356.13256626509633, + }, + Coord { + x: 91.61943785201197, + y: 355.85870231057015, + }, + Coord { + x: 162.45878409633931, + y: 319.40542293651487, + }, + Coord { + x: 175.5596215520651, + y: 305.00719248486604, + }, + Coord { + x: 213.481559734292, + y: 289.176602821379, + }, + Coord { + x: 212.24082541367974, + y: 285.47846008552216, + }, + Coord { + x: 208.48923723966695, + y: 286.23369014455, + }, + Coord { + x: 207.57515300304166, + y: 288.20504863551156, + }, + Coord { + x: 206.815011642407, + y: 288.2191843160252, + }, + Coord { + x: 206.79761990399714, + y: 288.2195077348923, + }, + Coord { + x: 206.73263552048343, + y: 288.22071619233367, + }, + Coord { + x: 206.69670020700084, + y: 288.2213844497616, + }, + Coord { + x: 200.38308697914755, + y: 288.338793163584, + }, + Coord { + x: 200.37329511724553, + y: 288.3389752542301, + }, + Coord { + x: 195.94507795197933, + y: 287.70516880563525, + }, + Coord { + x: 195.94507795197936, + y: 287.70516880563525, + }, + Coord { + x: 177.75137408933563, + y: 285.1011215329728, + }, + Coord { + x: 172.47639728970307, + y: 284.71995173556473, + }, + Coord { + x: 172.37763068066027, + y: 284.7175565773759, + }, + Coord { + x: 203.35149933375834, + y: 279.95282911519445, + }, + Coord { + x: 87.0909970255105, + y: 274.6019377637631, + }, + Coord { + x: 0.0, + y: 291.1811710503077, + }, + Coord { + x: 1.1448520206537763e-14, + y: 275.9029490745503, + }, + Coord { + x: -2.842170943040401e-14, + y: 275.9029490745503, + }, + Coord { + x: 0.0, + y: 243.4003107156999, + }, + ]), + vec![], + ); + let pg2 = Polygon::new( + LineString(vec![ + Coord { + x: 367.67936762793977, + y: 302.785219017324, + }, + Coord { + x: 400.0, + y: 330.3364189888756, + }, + Coord { + x: 400.0, + y: 85.7094729663055, + }, + Coord { + x: 375.530089594595, + y: 124.2204839556052, + }, + Coord { + x: 374.98659859747573, + y: 100.58888400844128, + }, + Coord { + x: 375.3219047536835, + y: 76.99586322948593, + }, + Coord { + x: 352.116978342238, + y: 128.0069901264593, + }, + Coord { + x: 296.8607072090515, + y: 129.49256607782735, + }, + Coord { + x: 266.8397653781389, + y: 154.40474480753, + }, + Coord { + x: 259.2004570591687, + y: 144.14650678216447, + }, + Coord { + x: 247.73005601682144, + y: 120.74050514867251, + }, + Coord { + x: 219.9183226476331, + y: 53.89398800789357, + }, + Coord { + x: 214.79766598486256, + y: 58.54396880088095, + }, + Coord { + x: 205.3180727584858, + y: 37.66458141235276, + }, + Coord { + x: 204.9384743502853, + y: 42.52909575499811, + }, + Coord { + x: 198.598592783954, + y: 39.913773753454365, + }, + Coord { + x: 227.5571963507852, + y: 98.78793110381741, + }, + Coord { + x: 211.8877775129764, + y: 95.80693442569219, + }, + Coord { + x: 184.82983774975878, + y: 111.82526107380869, + }, + Coord { + x: 157.3407287714804, + y: 79.95610899189123, + }, + Coord { + x: 146.98556814305613, + y: 90.1306295921712, + }, + Coord { + x: 142.86571296028092, + y: 103.2131667249273, + }, + Coord { + x: 137.06964785547663, + y: 97.97823184186473, + }, + Coord { + x: 133.11594113032265, + y: 121.5727268145846, + }, + Coord { + x: 121.48741956113471, + y: 112.97229086146046, + }, + Coord { + x: 121.26181604684658, + y: 113.86710164957617, + }, + Coord { + x: 235.63037504441513, + y: 197.77142864845837, + }, + Coord { + x: 222.76475236003864, + y: 233.7471699986308, + }, + Coord { + x: 215.48785333121916, + y: 232.73364849354883, + }, + Coord { + x: 177.32511967843928, + y: 245.4303896678587, + }, + Coord { + x: 96.36950989237457, + y: 246.12195328963338, + }, + Coord { + x: 96.33638429757961, + y: 248.20750628598208, + }, + Coord { + x: 66.27444465680952, + y: 250.87823764635957, + }, + Coord { + x: 5.684341886080802e-14, + y: 252.7246858385403, + }, + Coord { + x: 0.0, + y: 287.00851453579764, + }, + Coord { + x: 87.0909970255105, + y: 274.6019377637631, + }, + Coord { + x: 203.35149933375834, + y: 279.95282911519445, + }, + Coord { + x: 186.21553206604779, + y: 286.312588294279, + }, + Coord { + x: 200.37329511724553, + y: 288.3389752542301, + }, + Coord { + x: 204.07584923592933, + y: 288.2701221108328, + }, + Coord { + x: 208.48923723966695, + y: 286.23369014455, + }, + Coord { + x: 212.24082541367974, + y: 285.47846008552216, + }, + Coord { + x: 213.481559734292, + y: 289.176602821379, + }, + Coord { + x: 168.6386654992429, + y: 312.61353982953256, + }, + Coord { + x: 162.45878409633931, + y: 319.40542293651487, + }, + Coord { + x: 93.1525434103433, + y: 357.43372298059234, + }, + Coord { + x: 91.50558129979011, + y: 359.2500343818318, + }, + Coord { + x: 91.43334419528333, + y: 363.2318140095328, + }, + Coord { + x: 178.69748010894065, + y: 313.3107247292978, + }, + Coord { + x: 201.68701198375604, + y: 314.2277627894801, + }, + Coord { + x: 217.6731029946546, + y: 302.77563506887896, + }, + Coord { + x: 221.61838827018173, + y: 313.1570930513446, + }, + Coord { + x: 221.67982343202226, + y: 315.355350903515, + }, + Coord { + x: 243.8528456455854, + y: 299.9323675588228, + }, + Coord { + x: 367.67936762793977, + y: 302.785219017324, + }, + ]), + vec![], + ); + + let _valid_result = Polygon::union(&pg1, &pg2).unwrap(); +} + +#[test] +fn no_913_intersection_and_difference_for_f32_v2() { + let p1: MultiPolygon = MultiPolygon::new(vec![Polygon::new( + LineString::from(vec![ + Coord { + x: 142.501_43, + y: 61.443_245, + }, + Coord { + x: 142.056_63, + y: 61.549_75, + }, + Coord { + x: 140.072_56, + y: 62.337_814, + }, + Coord { + x: 142.501_43, + y: 61.443_245, + }, + ]), + vec![], + )]); + let p2: MultiPolygon = MultiPolygon::new(vec![Polygon::new( + LineString::from(vec![ + Coord { + x: 140.503_59, + y: 62.237_686, + }, + Coord { + x: 140.072_56, + y: 62.337_814, + }, + Coord { + x: 136.939_97, + y: 63.053_394, + }, + Coord { + x: 140.503_59, + y: 62.237_686, + }, + ]), + vec![], + )]); + + let intersection = MultiPolygon::intersection(&p1, &p2).unwrap(); + let _valid_result = MultiPolygon::difference(&p1, &intersection).unwrap(); +} + +#[test] +fn no_913_union_for_f64_simple_triangles() { + let poly1: Polygon = Polygon::try_from_wkt_str("POLYGON((204.0 287.0,206.69670020700084 288.2213844497616,200.38308697914755 288.338793163584,204.0 287.0))").unwrap(); + let poly2: Polygon = Polygon::try_from_wkt_str("POLYGON((210.0 290.0,204.07584923592933 288.2701221108328,212.24082541367974 285.47846008552216,210.0 290.0))").unwrap(); + + let _valid_result = Polygon::union(&poly1, &poly2).unwrap(); +} From 2aad752394b15e603ddbbf8f257a241fb9925298 Mon Sep 17 00:00:00 2001 From: RobWalt Date: Tue, 31 Oct 2023 22:32:42 +0100 Subject: [PATCH 26/33] chore: add first iteration of docs --- geo/src/algorithm/spade_boolops/trait_def.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/geo/src/algorithm/spade_boolops/trait_def.rs b/geo/src/algorithm/spade_boolops/trait_def.rs index c51ee28a7..552af881d 100644 --- a/geo/src/algorithm/spade_boolops/trait_def.rs +++ b/geo/src/algorithm/spade_boolops/trait_def.rs @@ -5,6 +5,24 @@ use geo_types::{Point, Triangle}; use crate::triangulate_spade::SpadeTriangulationFloat; use crate::{Contains, Scale}; +/// 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 and set-difference on pairs of 2-D geometries. +/// +/// These operations are implemented on any existing geo type that +/// implements [`crate::LinesIter`] and [`crate::CoordsIter`] as well +/// as [`crate::Contains`] for a [`crate::Point`]. Further, if the +/// operations exist for a type T, they also exist for Vec and &[T] +/// +/// # Performance +/// +/// Note that the algorithm is based on a somewhat non-trivial Delaunay +/// Triangulation which can create performance hits if you use the +/// operations on huge geometries (~ 1000 vertices per geometry). +/// On a smaller scale, the algorithm works reasonably fast and can even +/// be used for some real-time applications pub trait SpadeBoolops where T: SpadeTriangulationFloat, From 70926a062e0cc8ba94e3e33f22dfae5bdb586545 Mon Sep 17 00:00:00 2001 From: aviac Date: Thu, 2 Nov 2023 12:14:38 +0100 Subject: [PATCH 27/33] feat: implement cheap intersection checks for MultiPolygon performance --- .../spade_boolops/tests/legacy_tests.rs | 2 +- .../tests/spade_boolops_tests.rs | 2 +- geo/src/algorithm/spade_boolops/trait_def.rs | 9 +-- geo/src/algorithm/spade_boolops/trait_impl.rs | 59 ++++++++++++++++++- 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/geo/src/algorithm/spade_boolops/tests/legacy_tests.rs b/geo/src/algorithm/spade_boolops/tests/legacy_tests.rs index f528c5d7e..9edd8cb36 100644 --- a/geo/src/algorithm/spade_boolops/tests/legacy_tests.rs +++ b/geo/src/algorithm/spade_boolops/tests/legacy_tests.rs @@ -28,7 +28,7 @@ fn test_rect_overlapping() { let wkt1 = "POLYGON((0 0,1 0,1 1,0 1,0 0))"; let wkt2 = "POLYGON((0.5 1,2 1,2 2,0.5 2,0.5 1))"; - let wkt_union = "MULTIPOLYGON(((0 1,0 0,1 0,1 1,2 1,2 2,0.5 2,0.5 1,0 1)))"; + let wkt_union = "MULTIPOLYGON(((0.5 2,0.5 1,0 1,0 0,1 0,1 1,2 1,2 2,0.5 2)))"; let [p1, p2] = check_op::(wkt1, wkt2); let output = MultiPolygon::union(&p1, &p2).expect("boolop works"); assert_eq!(output, MultiPolygon::try_from_wkt_str(wkt_union).unwrap()); diff --git a/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs b/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs index 9f4da65eb..451d0d6fb 100644 --- a/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs +++ b/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs @@ -390,7 +390,7 @@ define_test!( empty = false, num_polys = 1, num_holes = vec![0], - num_verts = vec![11], + num_verts = vec![8], ); define_test!( diff --git a/geo/src/algorithm/spade_boolops/trait_def.rs b/geo/src/algorithm/spade_boolops/trait_def.rs index 552af881d..d473477ee 100644 --- a/geo/src/algorithm/spade_boolops/trait_def.rs +++ b/geo/src/algorithm/spade_boolops/trait_def.rs @@ -3,7 +3,7 @@ pub use crate::spade_boolops::helper::contains_triangle; use geo_types::{Point, Triangle}; use crate::triangulate_spade::SpadeTriangulationFloat; -use crate::{Contains, Scale}; +use crate::{Contains, OpType, Scale}; /// Boolean Operations on geometry. /// @@ -31,26 +31,27 @@ where fn boolop) -> bool>( p1: &Self, p2: &Self, + op_type: OpType, op_pred: F, ) -> SpadeBoolopsResult; #[must_use = "Use the difference of these two geometries by binding the result to a variable! (`let result = ...`)"] fn difference(p1: &Self, p2: &Self) -> SpadeBoolopsResult { - Self::boolop(p1, p2, |tri| { + Self::boolop(p1, p2, OpType::Difference, |tri| { contains_triangle(p1, tri) && !contains_triangle(p2, tri) }) } #[must_use = "Use the intersection of these two geometries by binding the result to a variable! (`let result = ...`)"] fn intersection(p1: &Self, p2: &Self) -> SpadeBoolopsResult { - Self::boolop(p1, p2, |tri| { + Self::boolop(p1, p2, OpType::Intersection, |tri| { contains_triangle(p1, tri) && contains_triangle(p2, tri) }) } #[must_use = "Use the union of these two geometries by binding the result to a variable! (`let result = ...`)"] fn union(p1: &Self, p2: &Self) -> SpadeBoolopsResult { - Self::boolop(p1, p2, |tri| { + Self::boolop(p1, p2, OpType::Union, |tri| { contains_triangle(p1, tri) || contains_triangle(p2, tri) }) } diff --git a/geo/src/algorithm/spade_boolops/trait_impl.rs b/geo/src/algorithm/spade_boolops/trait_impl.rs index f4e4f170f..c2d904c6e 100644 --- a/geo/src/algorithm/spade_boolops/trait_impl.rs +++ b/geo/src/algorithm/spade_boolops/trait_impl.rs @@ -1,10 +1,11 @@ +use crate::algorithm::intersects::Intersects; use crate::algorithm::stitch::Stitch; pub use crate::spade_boolops::error::{SpadeBoolopsError, SpadeBoolopsResult}; use crate::spade_boolops::trait_def::SpadeBoolops; use geo_types::{MultiPolygon, Polygon, Triangle}; use crate::triangulate_spade::SpadeTriangulationFloat; -use crate::TriangulateSpade; +use crate::{OpType, TriangulateSpade}; impl SpadeBoolops for Polygon where @@ -13,6 +14,7 @@ where fn boolop) -> bool>( p1: &Self, p2: &Self, + _op_type: OpType, op_pred: F, ) -> SpadeBoolopsResult { vec![p1.clone(), p2.clone()] @@ -34,15 +36,66 @@ where fn boolop) -> bool>( p1: &Self, p2: &Self, + op_type: OpType, op_pred: F, ) -> SpadeBoolopsResult { - vec![p1.clone(), p2.clone()] + // helper function which helps to select only some polys of a multipolygon which pass a + // certain predicate filter + fn polys_with( + mp1: &MultiPolygon, + filter: G, + ) -> Vec> + where + G: Fn(&&Polygon) -> bool, + { + // this clone here is needed since we can't construct the Multipolygons below otherwise + // maybe if constrained_outer_triangulation would accept references, we could get rid + // of this + mp1.iter().filter(filter).cloned().collect::>() + } + + // gets only those polygons from mp1 which intersect any of the polygons in mp2 + let intersecting_polys = |mp1: &MultiPolygon, mp2: &Vec>| { + polys_with(mp1, move |p| mp2.iter().any(|o| p.intersects(o))) + }; + + let p1_inter = intersecting_polys(p1, &p2.0); + // we know p2 can only intersect polys in p1_inter + let p2_inter = intersecting_polys(p2, &p1_inter); + + // do the real boolean operation only on the intersecting parts + let boolop_result = vec![MultiPolygon::new(p1_inter), MultiPolygon::new(p2_inter)] .constrained_outer_triangulation() .map_err(SpadeBoolopsError::TriangulationError)? .into_iter() .filter(|tri| op_pred(tri)) .map(|tri| tri.to_polygon()) - .collect::>() + .collect::>(); + + let non_intersecting_polys = |p1: &MultiPolygon, p2: &MultiPolygon| { + polys_with(p1, move |p| !p2.iter().any(|o| p.intersects(o))) + }; + + // - if we union or difference, then we want to include non intersecting polys of the first + // argument multi polygon in the result + // - if we union , then we want to include non intersecting polys of the second argument + // multi polygon in the result + let p1_non_inter = matches!(op_type, OpType::Union | OpType::Difference) + .then(|| non_intersecting_polys(p1, p2)) + .unwrap_or_default(); + let p2_non_inter = matches!(op_type, OpType::Union) + .then(|| non_intersecting_polys(p2, p1)) + .unwrap_or_default(); + + // do a constrained triangulation and then stitch the triangles together + // + // the triangulation is needed since the non intersecting polygons are not triangles with + // the same characteristics as the boolop_result triangles and the stitch_together function + // gets confused otherwise in some edge case tests + [boolop_result, p1_non_inter, p2_non_inter] + .concat() + .constrained_triangulation() + .map_err(SpadeBoolopsError::TriangulationError)? .stitch_together() .map_err(SpadeBoolopsError::StitchError) } From 5df5957af0d53ba2f2ea7ca46318c06e90422281 Mon Sep 17 00:00:00 2001 From: aviac Date: Mon, 6 Nov 2023 19:35:34 +0100 Subject: [PATCH 28/33] chore: fixes after rebase --- geo/src/algorithm/spade_boolops/trait_impl.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/geo/src/algorithm/spade_boolops/trait_impl.rs b/geo/src/algorithm/spade_boolops/trait_impl.rs index c2d904c6e..31dc632f5 100644 --- a/geo/src/algorithm/spade_boolops/trait_impl.rs +++ b/geo/src/algorithm/spade_boolops/trait_impl.rs @@ -18,7 +18,7 @@ where op_pred: F, ) -> SpadeBoolopsResult { vec![p1.clone(), p2.clone()] - .constrained_outer_triangulation() + .constrained_outer_triangulation(Default::default()) .map_err(SpadeBoolopsError::TriangulationError)? .into_iter() .filter(|tri| op_pred(tri)) @@ -65,7 +65,7 @@ where // do the real boolean operation only on the intersecting parts let boolop_result = vec![MultiPolygon::new(p1_inter), MultiPolygon::new(p2_inter)] - .constrained_outer_triangulation() + .constrained_outer_triangulation(Default::default()) .map_err(SpadeBoolopsError::TriangulationError)? .into_iter() .filter(|tri| op_pred(tri)) @@ -94,7 +94,7 @@ where // gets confused otherwise in some edge case tests [boolop_result, p1_non_inter, p2_non_inter] .concat() - .constrained_triangulation() + .constrained_triangulation(Default::default()) .map_err(SpadeBoolopsError::TriangulationError)? .stitch_together() .map_err(SpadeBoolopsError::StitchError) From c7c54c46a523d78d37fb2821bb32854cf80d1886 Mon Sep 17 00:00:00 2001 From: aviac Date: Tue, 12 Dec 2023 08:32:26 +0100 Subject: [PATCH 29/33] chore: add changelog entry --- geo/CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/geo/CHANGES.md b/geo/CHANGES.md index d10039f0e..3cf6ec488 100644 --- a/geo/CHANGES.md +++ b/geo/CHANGES.md @@ -18,6 +18,8 @@ * * Add `StitchTriangles` trait which implements a new kind of combining algorithm for `Triangle`s * +* Add `SpadeBoolops` trait which implements panic-less boolean operations based on triangulation + * ## 0.28.0 From 9fc916c0fc16a60ad9e18ae4ecb94f05131a60b8 Mon Sep 17 00:00:00 2001 From: aviac Date: Tue, 12 Dec 2023 08:47:59 +0100 Subject: [PATCH 30/33] chore: fix things from merge && adjust tests --- geo/src/algorithm/affine_ops.rs | 37 ++----------------- .../spade_boolops/tests/legacy_tests.rs | 9 ++++- .../tests/spade_boolops_tests.rs | 10 +++-- 3 files changed, 17 insertions(+), 39 deletions(-) diff --git a/geo/src/algorithm/affine_ops.rs b/geo/src/algorithm/affine_ops.rs index 671d0098f..82ca784be 100644 --- a/geo/src/algorithm/affine_ops.rs +++ b/geo/src/algorithm/affine_ops.rs @@ -224,37 +224,6 @@ impl AffineTransform { ) } - /// Return the inverse of a given transform. Composing a transform with its inverse yields - /// the [identity matrix](Self::identity) - pub fn inverse(&self) -> Option - where - ::Output: Mul, - <::Output as Mul>::Output: ToPrimitive, - { - let a = self.0[0][0]; - let b = self.0[0][1]; - let xoff = self.0[0][2]; - let d = self.0[1][0]; - let e = self.0[1][1]; - let yoff = self.0[1][2]; - - let determinant = a * e - b * d; - - if determinant == T::zero() { - return None; // The matrix is not invertible - } - - let inv_det = T::one() / determinant; - Some(Self::new( - e * inv_det, - T::from(-b * inv_det).unwrap(), - (b * yoff - e * xoff) * inv_det, - T::from(-d * inv_det).unwrap(), - a * inv_det, - (d * xoff - a * yoff) * inv_det, - )) - } - /// Whether the transformation is equivalent to the [identity matrix](Self::identity), /// that is, whether it's application will be a a no-op. /// @@ -409,7 +378,7 @@ impl AffineTransform { } } -impl fmt::Debug for AffineTransform { +impl fmt::Debug for AffineTransform { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("AffineTransform") .field("a", &self.0[0][0]) @@ -522,7 +491,7 @@ impl AffineTransform { #[cfg(any(feature = "approx", test))] impl RelativeEq for AffineTransform where - T: AbsDiffEq + CoordNum + RelativeEq, + T: AbsDiffEq + CoordNum + RelativeEq + Neg, { #[inline] fn default_max_relative() -> Self::Epsilon { @@ -558,7 +527,7 @@ where #[cfg(any(feature = "approx", test))] impl AbsDiffEq for AffineTransform where - T: AbsDiffEq + CoordNum, + T: AbsDiffEq + CoordNum + Neg, T::Epsilon: Copy, { type Epsilon = T; diff --git a/geo/src/algorithm/spade_boolops/tests/legacy_tests.rs b/geo/src/algorithm/spade_boolops/tests/legacy_tests.rs index 9edd8cb36..5cb503f5b 100644 --- a/geo/src/algorithm/spade_boolops/tests/legacy_tests.rs +++ b/geo/src/algorithm/spade_boolops/tests/legacy_tests.rs @@ -28,10 +28,14 @@ fn test_rect_overlapping() { let wkt1 = "POLYGON((0 0,1 0,1 1,0 1,0 0))"; let wkt2 = "POLYGON((0.5 1,2 1,2 2,0.5 2,0.5 1))"; - let wkt_union = "MULTIPOLYGON(((0.5 2,0.5 1,0 1,0 0,1 0,1 1,2 1,2 2,0.5 2)))"; + let wkt_union = "MULTIPOLYGON(((0.5 1,0 1,0 0,1 0,1 1,2 1,2 2,0.5 2, 0.5 1)))"; let [p1, p2] = check_op::(wkt1, wkt2); let output = MultiPolygon::union(&p1, &p2).expect("boolop works"); - assert_eq!(output, MultiPolygon::try_from_wkt_str(wkt_union).unwrap()); + let expected = MultiPolygon::try_from_wkt_str(wkt_union).unwrap(); + assert_eq!( + output, expected, + "out: {output:?} vs expected: {expected:?}" + ); } #[test] @@ -141,6 +145,7 @@ fn test_issue_885_big_simplified() { // care: This test highlights that the SpadeBoolops algo is awfully slow! :S #[test] +#[ignore = "takes too long for CI"] fn test_issue_894() { _ = pretty_env_logger::try_init(); use geo_test_fixtures::multi_polygon; diff --git a/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs b/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs index 451d0d6fb..d505abee1 100644 --- a/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs +++ b/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs @@ -39,6 +39,10 @@ macro_rules! define_test { } else { is_multipolygon_nonempty(&res); } + + use wkt::ToWkt; + println!("{}", res.to_wkt()); + has_num_polygons(&res, $num_polys); has_num_holes(&res, $num_holes); has_num_vertices(&res, $num_verts); @@ -596,9 +600,9 @@ define_test!( }, results: empty = false, - num_polys = 2, - num_holes = vec![0, 0], - num_verts = vec![10, 5], + num_polys = 1, + num_holes = vec![0], + num_verts = vec![14], ); define_test!( From fd272b7857619fb82f36e6d2db19e0e90b9dd45f Mon Sep 17 00:00:00 2001 From: aviac Date: Fri, 19 Jan 2024 09:34:00 +0100 Subject: [PATCH 31/33] chore(rebase): cleanup after rebase Authored-by: RobWalt --- .../tests/spade_boolops_tests.rs | 92 +++++++++---------- geo/src/algorithm/spade_boolops/trait_impl.rs | 7 +- 2 files changed, 49 insertions(+), 50 deletions(-) diff --git a/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs b/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs index d505abee1..df7fadb70 100644 --- a/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs +++ b/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs @@ -1,5 +1,5 @@ -use super::*; use super::test_helper::*; +use super::*; use geo_types::*; // helper @@ -19,7 +19,7 @@ macro_rules! define_test { name = $test_name:ident, path = $path:expr, operation = $op:expr, - results: + results: empty = $empty:expr, num_polys = $num_polys:expr, num_holes = $num_holes:expr, @@ -63,7 +63,7 @@ define_test!( }); Polygon::difference(poly1, &poly2).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -83,7 +83,7 @@ define_test!( }); Polygon::difference(&poly2, poly1).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -97,7 +97,7 @@ define_test!( let poly1 = &data[0]; Polygon::intersection(poly1, poly1).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -111,7 +111,7 @@ define_test!( let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -125,7 +125,7 @@ define_test!( let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -139,7 +139,7 @@ define_test!( let [poly1, poly2] = [&data[0], &data[1]]; Polygon::difference(poly1, poly2).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![1], @@ -153,7 +153,7 @@ define_test!( let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -167,7 +167,7 @@ define_test!( let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -181,7 +181,7 @@ define_test!( let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() }, - results: + results: empty = true, num_polys = 0, num_holes = vec![], @@ -195,7 +195,7 @@ define_test!( let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -209,7 +209,7 @@ define_test!( let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -223,7 +223,7 @@ define_test!( let [poly1, poly2] = [&data[0], &data[1]]; Polygon::difference(poly2, poly1).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![1], @@ -237,7 +237,7 @@ define_test!( let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -251,7 +251,7 @@ define_test!( let [poly1, poly2] = [&data[0], &data[1]]; Polygon::difference(poly1, poly2).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![1], @@ -265,7 +265,7 @@ define_test!( let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -279,7 +279,7 @@ define_test!( let [poly1, poly2] = [&data[0], &data[1]]; Polygon::difference(poly2, poly1).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![1], @@ -293,7 +293,7 @@ define_test!( let [poly1, poly2] = [&data[0], &data[1]]; Polygon::intersection(poly1, poly2).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -307,7 +307,7 @@ define_test!( let poly1 = &data[0]; Polygon::union(poly1, poly1).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![1], @@ -321,7 +321,7 @@ define_test!( let poly1 = &data[0]; Polygon::intersection(poly1, poly1).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![1], @@ -335,7 +335,7 @@ define_test!( let poly1 = &data[0]; Polygon::difference(poly1, &empty_poly()).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![1], @@ -349,7 +349,7 @@ define_test!( let [poly1, poly2] = [&data[0], &data[1]]; Polygon::union(poly1, poly2).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![1], @@ -363,7 +363,7 @@ define_test!( let [poly1, poly2] = [&data[0], &data[1]]; Polygon::union(poly1, poly2).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![2], @@ -377,7 +377,7 @@ define_test!( // imprecise inputs lead to hole MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![1], @@ -390,7 +390,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -403,7 +403,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -416,7 +416,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 2, num_holes = vec![0, 0], @@ -429,7 +429,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -442,7 +442,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -455,7 +455,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -468,7 +468,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -481,7 +481,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -494,7 +494,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -507,7 +507,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -520,7 +520,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -533,7 +533,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 2, num_holes = vec![0, 0], @@ -546,7 +546,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 2, num_holes = vec![0, 0], @@ -559,7 +559,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 2, num_holes = vec![0, 0], @@ -572,7 +572,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 2, num_holes = vec![0, 0], @@ -585,7 +585,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -598,7 +598,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -611,7 +611,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 2, num_holes = vec![0, 0], @@ -624,7 +624,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -637,7 +637,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], @@ -650,7 +650,7 @@ define_test!( operation = |data: Vec>| { MultiPolygon::union(&multipolygon_from(data), &empty_multipoly()).unwrap() }, - results: + results: empty = false, num_polys = 1, num_holes = vec![0], diff --git a/geo/src/algorithm/spade_boolops/trait_impl.rs b/geo/src/algorithm/spade_boolops/trait_impl.rs index 31dc632f5..01cb3ee23 100644 --- a/geo/src/algorithm/spade_boolops/trait_impl.rs +++ b/geo/src/algorithm/spade_boolops/trait_impl.rs @@ -1,5 +1,5 @@ use crate::algorithm::intersects::Intersects; -use crate::algorithm::stitch::Stitch; +use crate::algorithm::stitch::StitchTriangles; pub use crate::spade_boolops::error::{SpadeBoolopsError, SpadeBoolopsResult}; use crate::spade_boolops::trait_def::SpadeBoolops; use geo_types::{MultiPolygon, Polygon, Triangle}; @@ -22,9 +22,8 @@ where .map_err(SpadeBoolopsError::TriangulationError)? .into_iter() .filter(|tri| op_pred(tri)) - .map(|tri| tri.to_polygon()) .collect::>() - .stitch_together() + .stitch_triangulation() .map_err(SpadeBoolopsError::StitchError) } } @@ -96,7 +95,7 @@ where .concat() .constrained_triangulation(Default::default()) .map_err(SpadeBoolopsError::TriangulationError)? - .stitch_together() + .stitch_triangulation() .map_err(SpadeBoolopsError::StitchError) } } From 4f643a8a154905129ba7bcdfea1b71cc8cb953ba Mon Sep 17 00:00:00 2001 From: aviac Date: Fri, 26 Jul 2024 15:59:47 +0200 Subject: [PATCH 32/33] fix errors after rebase --- geo/src/algorithm/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/geo/src/algorithm/mod.rs b/geo/src/algorithm/mod.rs index 6e8881ea5..1d98bc67c 100644 --- a/geo/src/algorithm/mod.rs +++ b/geo/src/algorithm/mod.rs @@ -237,8 +237,7 @@ pub mod simplify_vw; pub use simplify_vw::{SimplifyVw, SimplifyVwIdx, SimplifyVwPreserve}; /// Stitch together triangles with adjacent sides. Alternative to unioning triangles via BooleanOps. -pub mod stitch; -pub use stitch::StitchTriangles; +pub(crate) mod stitch; /// Boolean Operations based on constrained triangulation pub mod spade_boolops; From a50681e1784f13f028d35cc93321512805af53a4 Mon Sep 17 00:00:00 2001 From: aviac Date: Wed, 14 Aug 2024 08:20:54 +0200 Subject: [PATCH 33/33] fix export --- geo/src/algorithm/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/geo/src/algorithm/mod.rs b/geo/src/algorithm/mod.rs index 1d98bc67c..6e8881ea5 100644 --- a/geo/src/algorithm/mod.rs +++ b/geo/src/algorithm/mod.rs @@ -237,7 +237,8 @@ pub mod simplify_vw; pub use simplify_vw::{SimplifyVw, SimplifyVwIdx, SimplifyVwPreserve}; /// Stitch together triangles with adjacent sides. Alternative to unioning triangles via BooleanOps. -pub(crate) mod stitch; +pub mod stitch; +pub use stitch::StitchTriangles; /// Boolean Operations based on constrained triangulation pub mod spade_boolops;