From 93dce717a63382c5bc673576bd483aaed5ac72cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20H=C3=BCgel?= Date: Tue, 14 Feb 2023 16:40:27 +0000 Subject: [PATCH] Implement RTreeObject for Geometry enum Fixes https://github.com/georust/geo/issues/982 --- geo-types/src/geometry/geometry_collection.rs | 56 +++++++++++++++++++ geo-types/src/geometry/mod.rs | 33 +++++++++++ geo-types/src/geometry/multi_line_string.rs | 39 +++++++++++++ geo-types/src/geometry/multi_point.rs | 36 +++++++++++- geo-types/src/geometry/multi_polygon.rs | 39 +++++++++++++ geo-types/src/geometry/rect.rs | 27 +++++++++ geo-types/src/geometry/triangle.rs | 39 +++++++++++++ geo-types/src/private_utils.rs | 18 ++++++ 8 files changed, 286 insertions(+), 1 deletion(-) diff --git a/geo-types/src/geometry/geometry_collection.rs b/geo-types/src/geometry/geometry_collection.rs index ce5bb2338..75c7e672c 100644 --- a/geo-types/src/geometry/geometry_collection.rs +++ b/geo-types/src/geometry/geometry_collection.rs @@ -1,4 +1,8 @@ +#[cfg(any(feature = "rstar_0_8", feature = "rstar_0_9"))] +use crate::{coord, Point, Rect}; use crate::{CoordNum, Geometry}; +#[cfg(any(feature = "rstar_0_8", feature = "rstar_0_9"))] +use num_traits::Bounded; use alloc::vec; use alloc::vec::Vec; @@ -247,6 +251,58 @@ impl<'a, T: CoordNum> GeometryCollection { } } +// Return a new rectangle that encompasses the provided rectangles +fn bounding_rect_merge(a: Rect, b: Rect) -> Rect { + Rect::new( + coord! { + x: crate::private_utils::partial_min(a.min().x, b.min().x), + y: crate::private_utils::partial_min(a.min().y, b.min().y), + }, + coord! { + x: crate::private_utils::partial_max(a.max().x, b.max().x), + y: crate::private_utils::partial_max(a.max().y, b.max().y), + }, + ) +} + +#[cfg(any(feature = "rstar_0_8", feature = "rstar_0_9"))] +macro_rules! impl_rstar_geometry_collection { + ($rstar:ident) => { + impl $rstar::RTreeObject for GeometryCollection + where + T: ::num_traits::Float + ::$rstar::RTreeNum, + { + type Envelope = ::$rstar::AABB>; + + fn envelope(&self) -> Self::Envelope { + let bounding_rect = self.iter().fold(None, |acc, next| { + let next_bounding_rect = next.envelope(); + let lower = next_bounding_rect.lower(); + let upper = next_bounding_rect.upper(); + let rect = Rect::new(lower, upper); + Some(bounding_rect_merge(acc.unwrap(), rect)) + }); + match bounding_rect { + None => ::$rstar::AABB::from_corners( + Point::new(Bounded::min_value(), Bounded::min_value()), + Point::new(Bounded::max_value(), Bounded::max_value()), + ), + Some(b) => ::$rstar::AABB::from_corners( + Point::new(b.min().x, b.min().y), + Point::new(b.max().x, b.max().y), + ), + } + } + } + }; +} + +#[cfg(feature = "rstar_0_8")] +impl_rstar_geometry_collection!(rstar_0_8); + +#[cfg(feature = "rstar_0_9")] +impl_rstar_geometry_collection!(rstar_0_9); + #[cfg(any(feature = "approx", test))] impl RelativeEq for GeometryCollection where diff --git a/geo-types/src/geometry/mod.rs b/geo-types/src/geometry/mod.rs index 4faf01753..74262105a 100644 --- a/geo-types/src/geometry/mod.rs +++ b/geo-types/src/geometry/mod.rs @@ -269,6 +269,39 @@ where } } +#[cfg(any(feature = "rstar_0_8", feature = "rstar_0_9"))] +macro_rules! impl_rstar_geometry { + ($rstar:ident) => { + impl $rstar::RTreeObject for Geometry + where + T: ::num_traits::Float + ::$rstar::RTreeNum, + { + type Envelope = ::$rstar::AABB>; + + fn envelope(&self) -> Self::Envelope { + match self { + Self::Point(x) => x.envelope(), + Self::Line(x) => x.envelope(), + Self::LineString(x) => x.envelope(), + Self::Polygon(x) => x.envelope(), + Self::Rect(x) => x.envelope(), + Self::Triangle(x) => x.envelope(), + Self::MultiPoint(x) => x.envelope(), + Self::MultiLineString(x) => x.envelope(), + Self::MultiPolygon(x) => x.envelope(), + Self::GeometryCollection(x) => x.envelope(), + } + } + } + }; +} + +#[cfg(feature = "rstar_0_8")] +impl_rstar_geometry!(rstar_0_8); + +#[cfg(feature = "rstar_0_9")] +impl_rstar_geometry!(rstar_0_9); + #[cfg(any(feature = "approx", test))] impl RelativeEq for Geometry where diff --git a/geo-types/src/geometry/multi_line_string.rs b/geo-types/src/geometry/multi_line_string.rs index 5cc84b515..ae28a9e8d 100644 --- a/geo-types/src/geometry/multi_line_string.rs +++ b/geo-types/src/geometry/multi_line_string.rs @@ -1,3 +1,8 @@ +#[cfg(any(feature = "rstar_0_8", feature = "rstar_0_9"))] +use crate::Point; +#[cfg(any(feature = "rstar_0_8", feature = "rstar_0_9"))] +use num_traits::Bounded; + use crate::{CoordNum, LineString}; use alloc::vec; @@ -118,6 +123,40 @@ impl MultiLineString { } } +#[cfg(any(feature = "rstar_0_8", feature = "rstar_0_9"))] +macro_rules! impl_rstar_multi_linestring { + ($rstar:ident) => { + impl $rstar::RTreeObject for MultiLineString + where + T: ::num_traits::Float + ::$rstar::RTreeNum, + { + type Envelope = ::$rstar::AABB>; + + fn envelope(&self) -> Self::Envelope { + let bounding_rect = crate::private_utils::get_bounding_rect( + self.iter().flat_map(|line| line.0.iter().cloned()), + ); + match bounding_rect { + None => ::$rstar::AABB::from_corners( + Point::new(Bounded::min_value(), Bounded::min_value()), + Point::new(Bounded::max_value(), Bounded::max_value()), + ), + Some(b) => ::$rstar::AABB::from_corners( + Point::new(b.min().x, b.min().y), + Point::new(b.max().x, b.max().y), + ), + } + } + } + }; +} + +#[cfg(feature = "rstar_0_8")] +impl_rstar_multi_linestring!(rstar_0_8); + +#[cfg(feature = "rstar_0_9")] +impl_rstar_multi_linestring!(rstar_0_9); + #[cfg(any(feature = "approx", test))] impl RelativeEq for MultiLineString where diff --git a/geo-types/src/geometry/multi_point.rs b/geo-types/src/geometry/multi_point.rs index c59622013..2c22b9cbe 100644 --- a/geo-types/src/geometry/multi_point.rs +++ b/geo-types/src/geometry/multi_point.rs @@ -1,7 +1,8 @@ use crate::{CoordNum, Point}; - #[cfg(any(feature = "approx", test))] use approx::{AbsDiffEq, RelativeEq}; +#[cfg(any(feature = "rstar_0_8", feature = "rstar_0_9"))] +use num_traits::Bounded; use alloc::vec; use alloc::vec::Vec; @@ -99,6 +100,39 @@ impl MultiPoint { } } +#[cfg(any(feature = "rstar_0_8", feature = "rstar_0_9"))] +macro_rules! impl_rstar_multi_point { + ($rstar:ident) => { + impl $rstar::RTreeObject for MultiPoint + where + T: ::num_traits::Float + ::$rstar::RTreeNum, + { + type Envelope = ::$rstar::AABB>; + + fn envelope(&self) -> Self::Envelope { + let bounding_rect = + crate::private_utils::get_bounding_rect(self.0.iter().map(|p| p.0)); + match bounding_rect { + None => ::$rstar::AABB::from_corners( + Point::new(Bounded::min_value(), Bounded::min_value()), + Point::new(Bounded::max_value(), Bounded::max_value()), + ), + Some(b) => ::$rstar::AABB::from_corners( + Point::new(b.min().x, b.min().y), + Point::new(b.max().x, b.max().y), + ), + } + } + } + }; +} + +#[cfg(feature = "rstar_0_8")] +impl_rstar_multi_point!(rstar_0_8); + +#[cfg(feature = "rstar_0_9")] +impl_rstar_multi_point!(rstar_0_9); + #[cfg(any(feature = "approx", test))] impl RelativeEq for MultiPoint where diff --git a/geo-types/src/geometry/multi_polygon.rs b/geo-types/src/geometry/multi_polygon.rs index 15116353e..dce637804 100644 --- a/geo-types/src/geometry/multi_polygon.rs +++ b/geo-types/src/geometry/multi_polygon.rs @@ -1,4 +1,8 @@ +#[cfg(any(feature = "rstar_0_8", feature = "rstar_0_9"))] +use crate::Point; use crate::{CoordNum, Polygon}; +#[cfg(any(feature = "rstar_0_8", feature = "rstar_0_9"))] +use num_traits::Bounded; use alloc::vec; use alloc::vec::Vec; @@ -91,6 +95,41 @@ impl MultiPolygon { } } +#[cfg(any(feature = "rstar_0_8", feature = "rstar_0_9"))] +macro_rules! impl_rstar_multi_polygon { + ($rstar:ident) => { + impl $rstar::RTreeObject for MultiPolygon + where + T: ::num_traits::Float + ::$rstar::RTreeNum, + { + type Envelope = ::$rstar::AABB>; + + fn envelope(&self) -> Self::Envelope { + let bounding_rect = crate::private_utils::get_bounding_rect( + self.iter() + .flat_map(|poly| poly.exterior().0.iter().cloned()), + ); + match bounding_rect { + None => ::$rstar::AABB::from_corners( + Point::new(Bounded::min_value(), Bounded::min_value()), + Point::new(Bounded::max_value(), Bounded::max_value()), + ), + Some(b) => ::$rstar::AABB::from_corners( + Point::new(b.min().x, b.min().y), + Point::new(b.max().x, b.max().y), + ), + } + } + } + }; +} + +#[cfg(feature = "rstar_0_8")] +impl_rstar_multi_polygon!(rstar_0_8); + +#[cfg(feature = "rstar_0_9")] +impl_rstar_multi_polygon!(rstar_0_9); + #[cfg(any(feature = "approx", test))] impl RelativeEq for MultiPolygon where diff --git a/geo-types/src/geometry/rect.rs b/geo-types/src/geometry/rect.rs index 9d8a7b77d..76e77a55c 100644 --- a/geo-types/src/geometry/rect.rs +++ b/geo-types/src/geometry/rect.rs @@ -1,5 +1,7 @@ use crate::{coord, polygon, Coord, CoordFloat, CoordNum, Line, Polygon}; +#[cfg(any(feature = "rstar_0_8", feature = "rstar_0_9"))] +use crate::Point; #[cfg(any(feature = "approx", test))] use approx::{AbsDiffEq, RelativeEq}; @@ -378,6 +380,31 @@ impl Rect { static RECT_INVALID_BOUNDS_ERROR: &str = "Failed to create Rect: 'min' coordinate's x/y value must be smaller or equal to the 'max' x/y value"; +#[cfg(any(feature = "rstar_0_8", feature = "rstar_0_9"))] +macro_rules! impl_rstar_rect { + ($rstar:ident) => { + impl $rstar::RTreeObject for Rect + where + T: ::num_traits::Float + ::$rstar::RTreeNum, + { + type Envelope = ::$rstar::AABB>; + + fn envelope(&self) -> Self::Envelope { + ::$rstar::AABB::from_corners( + Point::new(self.min().x, self.min().y), + Point::new(self.max().x, self.max().y), + ) + } + } + }; +} + +#[cfg(feature = "rstar_0_8")] +impl_rstar_rect!(rstar_0_8); + +#[cfg(feature = "rstar_0_9")] +impl_rstar_rect!(rstar_0_9); + #[cfg(any(feature = "approx", test))] impl RelativeEq for Rect where diff --git a/geo-types/src/geometry/triangle.rs b/geo-types/src/geometry/triangle.rs index 831e57cb1..eb5f86076 100644 --- a/geo-types/src/geometry/triangle.rs +++ b/geo-types/src/geometry/triangle.rs @@ -1,4 +1,11 @@ use crate::{polygon, Coord, CoordNum, Line, Polygon}; +#[cfg(any(feature = "rstar_0_8", feature = "rstar_0_9"))] +use num_traits::Bounded; + +#[cfg(any(feature = "rstar_0_8", feature = "rstar_0_9"))] +use crate::private_utils::get_bounding_rect; +#[cfg(any(feature = "rstar_0_8", feature = "rstar_0_9"))] +use crate::Point; #[cfg(any(feature = "approx", test))] use approx::{AbsDiffEq, RelativeEq}; @@ -63,6 +70,38 @@ impl> + Copy, T: CoordNum> From<[IC; 3]> for Triangle { } } +#[cfg(any(feature = "rstar_0_8", feature = "rstar_0_9"))] +macro_rules! impl_rstar_triangle { + ($rstar:ident) => { + impl $rstar::RTreeObject for Triangle + where + T: ::num_traits::Float + ::$rstar::RTreeNum, + { + type Envelope = ::$rstar::AABB>; + + fn envelope(&self) -> Self::Envelope { + let bounding_rect = get_bounding_rect(self.to_array()); + match bounding_rect { + None => ::$rstar::AABB::from_corners( + Point::new(Bounded::min_value(), Bounded::min_value()), + Point::new(Bounded::max_value(), Bounded::max_value()), + ), + Some(b) => ::$rstar::AABB::from_corners( + Point::new(b.min().x, b.min().y), + Point::new(b.max().x, b.max().y), + ), + } + } + } + }; +} + +#[cfg(feature = "rstar_0_8")] +impl_rstar_triangle!(rstar_0_8); + +#[cfg(feature = "rstar_0_9")] +impl_rstar_triangle!(rstar_0_9); + #[cfg(any(feature = "approx", test))] impl RelativeEq for Triangle where diff --git a/geo-types/src/private_utils.rs b/geo-types/src/private_utils.rs index 1311a7b4f..e251e3e1d 100644 --- a/geo-types/src/private_utils.rs +++ b/geo-types/src/private_utils.rs @@ -5,6 +5,24 @@ use crate::{Coord, CoordFloat, CoordNum, Line, LineString, Point, Rect}; +// The Rust standard library has `max` for `Ord`, but not for `PartialOrd` +pub fn partial_max(a: T, b: T) -> T { + if a > b { + a + } else { + b + } +} + +// The Rust standard library has `min` for `Ord`, but not for `PartialOrd` +pub fn partial_min(a: T, b: T) -> T { + if a < b { + a + } else { + b + } +} + pub fn line_string_bounding_rect(line_string: &LineString) -> Option> where T: CoordNum,