diff --git a/geo/CHANGES.md b/geo/CHANGES.md index e68ac20e9..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 @@ -48,21 +50,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 diff --git a/geo/src/algorithm/affine_ops.rs b/geo/src/algorithm/affine_ops.rs index 24245a430..82ca784be 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. @@ -376,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]) @@ -389,13 +391,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) } @@ -489,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 { @@ -525,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/mod.rs b/geo/src/algorithm/mod.rs index 1697d0385..6e8881ea5 100644 --- a/geo/src/algorithm/mod.rs +++ b/geo/src/algorithm/mod.rs @@ -237,8 +237,12 @@ 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; + +/// Boolean Operations based on constrained triangulation +pub mod spade_boolops; +pub use spade_boolops::SpadeBoolops; /// Transform a geometry using PROJ. #[cfg(feature = "use-proj")] 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 new file mode 100644 index 000000000..fc22b56c1 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/mod.rs @@ -0,0 +1,10 @@ +pub mod error; +pub mod helper; +pub mod trait_def; +pub mod trait_impl; + +pub use error::SpadeBoolopsError; +pub use trait_def::SpadeBoolops; + +#[cfg(test)] +pub mod tests; diff --git a/geo/src/algorithm/spade_boolops/test_data/collinear_outline_parts.wkt b/geo/src/algorithm/spade_boolops/test_data/collinear_outline_parts.wkt new file mode 100644 index 000000000..eb0cf9ddb --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/duplicate_points_case_1.wkt b/geo/src/algorithm/spade_boolops/test_data/duplicate_points_case_1.wkt new file mode 100644 index 000000000..7d384f6d2 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/duplicate_points_case_2.wkt b/geo/src/algorithm/spade_boolops/test_data/duplicate_points_case_2.wkt new file mode 100644 index 000000000..056f090eb --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/duplicate_points_case_3.wkt b/geo/src/algorithm/spade_boolops/test_data/duplicate_points_case_3.wkt new file mode 100644 index 000000000..59d9efa0e --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/hole_after_union.wkt b/geo/src/algorithm/spade_boolops/test_data/hole_after_union.wkt new file mode 100644 index 000000000..a7996bec6 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/holes_are_preserved.wkt b/geo/src/algorithm/spade_boolops/test_data/holes_are_preserved.wkt new file mode 100644 index 000000000..b1243e444 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/intersection_address_001.wkt b/geo/src/algorithm/spade_boolops/test_data/intersection_address_001.wkt new file mode 100644 index 000000000..cc9b01fe6 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/intersection_address_002.wkt b/geo/src/algorithm/spade_boolops/test_data/intersection_address_002.wkt new file mode 100644 index 000000000..e3b22d5b5 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/intersection_fail_after_union_fix_1.wkt b/geo/src/algorithm/spade_boolops/test_data/intersection_fail_after_union_fix_1.wkt new file mode 100644 index 000000000..fff830edf --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/intersection_fail_after_union_fix_2.wkt b/geo/src/algorithm/spade_boolops/test_data/intersection_fail_after_union_fix_2.wkt new file mode 100644 index 000000000..67e885e1f --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/missing_triangle_case_1.wkt b/geo/src/algorithm/spade_boolops/test_data/missing_triangle_case_1.wkt new file mode 100644 index 000000000..50c6ac44a --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/missing_triangle_case_2.wkt b/geo/src/algorithm/spade_boolops/test_data/missing_triangle_case_2.wkt new file mode 100644 index 000000000..126f5335b --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/missing_triangle_case_3.wkt b/geo/src/algorithm/spade_boolops/test_data/missing_triangle_case_3.wkt new file mode 100644 index 000000000..ac45cad36 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/multiple_unions.wkt b/geo/src/algorithm/spade_boolops/test_data/multiple_unions.wkt new file mode 100644 index 000000000..35491a1fa --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/simple_union.wkt b/geo/src/algorithm/spade_boolops/test_data/simple_union.wkt new file mode 100644 index 000000000..1063f4eb0 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/star.wkt b/geo/src/algorithm/spade_boolops/test_data/star.wkt new file mode 100644 index 000000000..e6580850b --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/two_holes_after_union.wkt b/geo/src/algorithm/spade_boolops/test_data/two_holes_after_union.wkt new file mode 100644 index 000000000..7e2947ca7 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_address_001.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_001.wkt new file mode 100644 index 000000000..bb22fec6d --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_address_002.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_002.wkt new file mode 100644 index 000000000..799cb0f73 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_address_003.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_003.wkt new file mode 100644 index 000000000..2783c855d --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_address_004.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_004.wkt new file mode 100644 index 000000000..ffdb7a4c9 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_address_005.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_005.wkt new file mode 100644 index 000000000..eb62a0e87 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_address_006.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_006.wkt new file mode 100644 index 000000000..92a062d20 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_address_007.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_007.wkt new file mode 100644 index 000000000..03bd21ca0 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_address_008.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_008.wkt new file mode 100644 index 000000000..41dab80ab --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_address_009.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_009.wkt new file mode 100644 index 000000000..914fc6b7c --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_address_010.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_010.wkt new file mode 100644 index 000000000..862a9c38b --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_address_011.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_011.wkt new file mode 100644 index 000000000..9b5e05614 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_address_012.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_012.wkt new file mode 100644 index 000000000..8e8fcdea1 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_address_013.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_013.wkt new file mode 100644 index 000000000..d244d81ee --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_address_014.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_014.wkt new file mode 100644 index 000000000..6ab96ff4c --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_address_015.wkt b/geo/src/algorithm/spade_boolops/test_data/union_address_015.wkt new file mode 100644 index 000000000..a65531f9c --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_both_intermediate_points_stay.wkt b/geo/src/algorithm/spade_boolops/test_data/union_both_intermediate_points_stay.wkt new file mode 100644 index 000000000..172d1ca78 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_fails_overlap.wkt b/geo/src/algorithm/spade_boolops/test_data/union_fails_overlap.wkt new file mode 100644 index 000000000..a7cce1eee --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_not_completely_shared_line.wkt b/geo/src/algorithm/spade_boolops/test_data/union_not_completely_shared_line.wkt new file mode 100644 index 000000000..96c10021b --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_one_intermediate_point_stays.wkt b/geo/src/algorithm/spade_boolops/test_data/union_one_intermediate_point_stays.wkt new file mode 100644 index 000000000..c4868ec11 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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/test_data/union_still_fails_overlap.wkt b/geo/src/algorithm/spade_boolops/test_data/union_still_fails_overlap.wkt new file mode 100644 index 000000000..c6785b432 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/test_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 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..5cb503f5b --- /dev/null +++ b/geo/src/algorithm/spade_boolops/tests/legacy_tests.rs @@ -0,0 +1,172 @@ +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.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"); + let expected = MultiPolygon::try_from_wkt_str(wkt_union).unwrap(); + assert_eq!( + output, expected, + "out: {output:?} vs expected: {expected:?}" + ); +} + +#[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] +#[ignore = "takes too long for CI"] +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 new file mode 100644 index 000000000..8a2b3a974 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/tests/mod.rs @@ -0,0 +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..eb3c07454 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/tests/open_issues_tests.rs @@ -0,0 +1,1085 @@ +use crate::SpadeBoolops; +use geo_types::{Coord, LineString, MultiPolygon, Polygon}; +use wkt::TryFromWkt; + +#[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(); + } +} + +#[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(); +} + +#[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(); +} + +#[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(); +} 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..df7fadb70 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/tests/spade_boolops_tests.rs @@ -0,0 +1,658 @@ +use super::test_helper::*; +use super::*; +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); + } + + 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); + } + }; +} + +define_test!( + name = star_shape_slightly_offset_difference_1, + path = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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![8], +); + +define_test!( + name = simple_union, + path = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_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 = "../test_data/union_address_008.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_9_works, + path = "../test_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 = "../test_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 = "../test_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 = "../test_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) +} 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..d473477ee --- /dev/null +++ b/geo/src/algorithm/spade_boolops/trait_def.rs @@ -0,0 +1,58 @@ +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, OpType, 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, + Self: Contains> + Scale + Sized, +{ + 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, 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, 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, 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 new file mode 100644 index 000000000..01cb3ee23 --- /dev/null +++ b/geo/src/algorithm/spade_boolops/trait_impl.rs @@ -0,0 +1,101 @@ +use crate::algorithm::intersects::Intersects; +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}; + +use crate::triangulate_spade::SpadeTriangulationFloat; +use crate::{OpType, TriangulateSpade}; + +impl SpadeBoolops for Polygon +where + T: SpadeTriangulationFloat, +{ + fn boolop) -> bool>( + p1: &Self, + p2: &Self, + _op_type: OpType, + op_pred: F, + ) -> SpadeBoolopsResult { + vec![p1.clone(), p2.clone()] + .constrained_outer_triangulation(Default::default()) + .map_err(SpadeBoolopsError::TriangulationError)? + .into_iter() + .filter(|tri| op_pred(tri)) + .collect::>() + .stitch_triangulation() + .map_err(SpadeBoolopsError::StitchError) + } +} + +impl SpadeBoolops for MultiPolygon +where + T: SpadeTriangulationFloat, +{ + fn boolop) -> bool>( + p1: &Self, + p2: &Self, + op_type: OpType, + op_pred: F, + ) -> SpadeBoolopsResult { + // 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(Default::default()) + .map_err(SpadeBoolopsError::TriangulationError)? + .into_iter() + .filter(|tri| op_pred(tri)) + .map(|tri| tri.to_polygon()) + .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(Default::default()) + .map_err(SpadeBoolopsError::TriangulationError)? + .stitch_triangulation() + .map_err(SpadeBoolopsError::StitchError) + } +} diff --git a/geo/src/algorithm/stitch.rs b/geo/src/algorithm/stitch.rs index f1fa48580..1fa8027f4 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! /// @@ -42,49 +42,47 @@ pub(crate) trait StitchTriangles { /// # 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,13 +123,25 @@ pub(crate) trait StitchTriangles { /// /// 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>; } +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> { 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 {