From 7f4b8eaebc0676648d789f21be013c991377dd42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Mon, 4 Dec 2023 20:47:23 +0200 Subject: [PATCH] Add Geometry::difference --- CHANGES.md | 6 +++- src/vector/ops/set.rs | 72 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index bfb4f8e2..7d2758ee 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,9 +2,13 @@ ## Unreleased +- Added `Geometry::difference`. + + - + - Added support for digital elevation model raster processing: `aspect`, `color_relief`, `hillshade`, `roughness`, `slope`, `terrain_ruggedness_index`, `topographic_position_index`. - - + - - Added pre-built bindings for GDAL 3.8 diff --git a/src/vector/ops/set.rs b/src/vector/ops/set.rs index b920772b..9a81ab6a 100644 --- a/src/vector/ops/set.rs +++ b/src/vector/ops/set.rs @@ -74,6 +74,41 @@ impl Geometry { Some(Geometry::with_c_geometry(ogr_geom, true)) } } + + /// Computes the [geometric difference][difference] of `self` and `other`. + /// + /// Generates a new geometry which is the difference of the two geometries operated on. + /// + /// # Notes + /// * Geometry validity is not checked, and invalid geometry will generate unpredictable results. + /// Use [`Geometry::is_valid`] if validity might be in question. + /// * If GEOS is *not* enabled, this function will always return `None`. + /// You may check for GEOS support with [`VersionInfo::has_geos`][has_geos]. + /// + /// # Returns + /// * `Some(geometry)`: a new `Geometry` representing the computed difference + /// * `None`: when the union could not be computed + /// + /// See: [`OGR_G_Difference`][OGR_G_Difference] + /// + /// [OGR_G_Difference]: https://gdal.org/api/vector_c_api.html#_CPPv416OGR_G_Difference12OGRGeometryH12OGRGeometryH + /// [difference]: https://en.wikipedia.org/wiki/Constructive_solid_geometry#Workings + /// [has_geos]: crate::version::VersionInfo::has_geos + pub fn difference(&self, other: &Self) -> Option { + if !self.has_gdal_ptr() { + return None; + } + if !other.has_gdal_ptr() { + return None; + } + unsafe { + let ogr_geom = gdal_sys::OGR_G_Difference(self.c_geometry(), other.c_geometry()); + if ogr_geom.is_null() { + return None; + } + Some(Geometry::with_c_geometry(ogr_geom, true)) + } + } } #[cfg(test)] @@ -172,4 +207,41 @@ mod tests { assert_eq!(res.unwrap().area(), 50.0); } + + #[test] + #[allow(clippy::float_cmp)] + fn test_difference_success() { + let geom = + Geometry::from_wkt("POLYGON ((0.0 10.0, 0.0 0.0, 10.0 0.0, 10.0 10.0, 0.0 10.0))") + .unwrap(); + let other = Geometry::from_wkt("POLYGON ((1 -5, 1 1, -5 1, -5 -5, 1 -5))").unwrap(); + + let res = geom.difference(&other).unwrap(); + assert_eq!(res.area(), 99.0); + } + + #[test] + fn test_difference_no_gdal_ptr() { + let geom = + Geometry::from_wkt("POLYGON ((0.0 10.0, 0.0 0.0, 10.0 0.0, 10.0 10.0, 0.0 10.0))") + .unwrap(); + let other = unsafe { Geometry::lazy_feature_geometry() }; + + let res = geom.difference(&other); + assert!(res.is_none()); + } + + #[test] + #[allow(clippy::float_cmp)] + fn test_difference_no_intersects() { + let geom = + Geometry::from_wkt("POLYGON ((0.0 5.0, 0.0 0.0, 5.0 0.0, 5.0 5.0, 0.0 5.0))").unwrap(); + + let other = + Geometry::from_wkt("POLYGON ((15.0 15.0, 15.0 20.0, 20.0 20.0, 20.0 15.0, 15.0 15.0))") + .unwrap(); + + let res = geom.difference(&other); + assert_eq!(res.unwrap().area(), 25.0); + } }