diff --git a/geo-benches/Cargo.toml b/geo-benches/Cargo.toml index 52836609c..0dccda51f 100644 --- a/geo-benches/Cargo.toml +++ b/geo-benches/Cargo.toml @@ -25,7 +25,7 @@ num-traits = "0.2" pretty_env_logger = "0.4" rand = "0.10.0" rand_distr = "0.6.0" -rstar = "0.12.0" +rstar = "0.13.0" serde = "1.0.137" serde_derive = "1.0.137" serde_json = "1.0.81" diff --git a/geo-types/CHANGES.md b/geo-types/CHANGES.md index 55bfdd88a..4ada893a9 100644 --- a/geo-types/CHANGES.md +++ b/geo-types/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## Unreleased + +- Add support for `rstar` 0.13 via the `rstar_0_13` feature, alongside the existing `rstar` versions. + ## 0.7.19 - 2026-04-15 - POSSIBLY BREAKING: `Triangle::from([a, b, c])` now enforces CCW winding (same as `Triangle::new`). diff --git a/geo-types/Cargo.toml b/geo-types/Cargo.toml index b6809e0b6..9e9ea8c75 100644 --- a/geo-types/Cargo.toml +++ b/geo-types/Cargo.toml @@ -14,7 +14,7 @@ edition = "2021" default = ["std"] std = ["approx?/std", "num-traits/std", "serde?/std"] multithreading = ["rayon"] -serde = [ "dep:serde", "rstar_0_8?/serde", "rstar_0_9?/serde", "rstar_0_10?/serde", "rstar_0_11?/serde", "rstar_0_12?/serde"] +serde = [ "dep:serde", "rstar_0_8?/serde", "rstar_0_9?/serde", "rstar_0_10?/serde", "rstar_0_11?/serde", "rstar_0_12?/serde", "rstar_0_13?/serde"] rstar = ["rstar_0_8"] rstar_0_8 = ["dep:rstar_0_8", "approx"] @@ -22,6 +22,7 @@ rstar_0_9 = ["dep:rstar_0_9", "approx"] rstar_0_10 = ["dep:rstar_0_10", "approx"] rstar_0_11 = ["dep:rstar_0_11", "approx"] rstar_0_12 = ["dep:rstar_0_12", "approx"] +rstar_0_13 = ["dep:rstar_0_13", "approx"] # Deprecated feature flags with "use-" prefix from the days before `dep:` use-rstar = ["rstar"] @@ -43,6 +44,7 @@ rstar_0_9 = { package = "rstar", version = "0.9", optional = true } rstar_0_10 = { package = "rstar", version = "0.10", optional = true } rstar_0_11 = { package = "rstar", version = "0.11", optional = true } rstar_0_12 = { package = "rstar", version = "0.12", optional = true } +rstar_0_13 = { package = "rstar", version = "0.13", optional = true } serde = { version = "1", optional = true, default-features = false, features = ["alloc", "derive"] } spade_2 = { package = "spade", version = "2", optional = true } diff --git a/geo-types/src/geometry/coord.rs b/geo-types/src/geometry/coord.rs index 2709387ee..4196781df 100644 --- a/geo-types/src/geometry/coord.rs +++ b/geo-types/src/geometry/coord.rs @@ -503,6 +503,42 @@ where } } +#[cfg(feature = "rstar_0_13")] +impl ::rstar_0_13::Point for Coord +where + T: ::num_traits::Float + ::rstar_0_13::RTreeNum, +{ + type Scalar = T; + + const DIMENSIONS: usize = 2; + + #[inline] + fn generate(mut generator: impl FnMut(usize) -> Self::Scalar) -> Self { + coord! { + x: generator(0), + y: generator(1), + } + } + + #[inline] + fn nth(&self, index: usize) -> Self::Scalar { + match index { + 0 => self.x, + 1 => self.y, + _ => unreachable!(), + } + } + + #[inline] + fn nth_mut(&mut self, index: usize) -> &mut Self::Scalar { + match index { + 0 => &mut self.x, + 1 => &mut self.y, + _ => unreachable!(), + } + } +} + #[cfg(feature = "spade_2")] impl ::spade_2::HasPosition for Coord where diff --git a/geo-types/src/geometry/line.rs b/geo-types/src/geometry/line.rs index f74d5aeb5..1668fbc27 100644 --- a/geo-types/src/geometry/line.rs +++ b/geo-types/src/geometry/line.rs @@ -247,7 +247,8 @@ mod approx_integration { feature = "rstar_0_9", feature = "rstar_0_10", feature = "rstar_0_11", - feature = "rstar_0_12" + feature = "rstar_0_12", + feature = "rstar_0_13" ))] macro_rules! impl_rstar_line { ($rstar:ident) => { @@ -289,6 +290,9 @@ impl_rstar_line!(rstar_0_11); #[cfg(feature = "rstar_0_12")] impl_rstar_line!(rstar_0_12); +#[cfg(feature = "rstar_0_13")] +impl_rstar_line!(rstar_0_13); + #[cfg(test)] mod test { use super::*; diff --git a/geo-types/src/geometry/line_string.rs b/geo-types/src/geometry/line_string.rs index ed4fdd18b..938698d28 100644 --- a/geo-types/src/geometry/line_string.rs +++ b/geo-types/src/geometry/line_string.rs @@ -542,7 +542,8 @@ mod approx_integration { feature = "rstar_0_9", feature = "rstar_0_10", feature = "rstar_0_11", - feature = "rstar_0_12" + feature = "rstar_0_12", + feature = "rstar_0_13" ))] macro_rules! impl_rstar_line_string { ($rstar:ident) => { @@ -599,6 +600,9 @@ impl_rstar_line_string!(rstar_0_11); #[cfg(feature = "rstar_0_12")] impl_rstar_line_string!(rstar_0_12); +#[cfg(feature = "rstar_0_13")] +impl_rstar_line_string!(rstar_0_13); + #[cfg(test)] mod test { use super::*; diff --git a/geo-types/src/geometry/multi_polygon.rs b/geo-types/src/geometry/multi_polygon.rs index e9fe5eaf1..cd2a95f42 100644 --- a/geo-types/src/geometry/multi_polygon.rs +++ b/geo-types/src/geometry/multi_polygon.rs @@ -247,7 +247,8 @@ mod approx_integration { feature = "rstar_0_9", feature = "rstar_0_10", feature = "rstar_0_11", - feature = "rstar_0_12" + feature = "rstar_0_12", + feature = "rstar_0_13" ))] macro_rules! impl_rstar_multi_polygon { ($rstar:ident) => { @@ -275,6 +276,8 @@ impl_rstar_multi_polygon!(rstar_0_10); impl_rstar_multi_polygon!(rstar_0_11); #[cfg(feature = "rstar_0_12")] impl_rstar_multi_polygon!(rstar_0_12); +#[cfg(feature = "rstar_0_13")] +impl_rstar_multi_polygon!(rstar_0_13); #[cfg(test)] mod test { diff --git a/geo-types/src/geometry/point.rs b/geo-types/src/geometry/point.rs index b5dbada10..b3389f94c 100644 --- a/geo-types/src/geometry/point.rs +++ b/geo-types/src/geometry/point.rs @@ -784,6 +784,35 @@ where } } +#[cfg(feature = "rstar_0_13")] +impl ::rstar_0_13::Point for Point +where + T: ::num_traits::Float + ::rstar_0_13::RTreeNum, +{ + type Scalar = T; + + const DIMENSIONS: usize = 2; + + fn generate(mut generator: impl FnMut(usize) -> Self::Scalar) -> Self { + Point::new(generator(0), generator(1)) + } + + fn nth(&self, index: usize) -> Self::Scalar { + match index { + 0 => self.0.x, + 1 => self.0.y, + _ => unreachable!(), + } + } + fn nth_mut(&mut self, index: usize) -> &mut Self::Scalar { + match index { + 0 => &mut self.0.x, + 1 => &mut self.0.y, + _ => unreachable!(), + } + } +} + impl AsRef> for Point { fn as_ref(&self) -> &Coord { &self.0 diff --git a/geo-types/src/geometry/polygon.rs b/geo-types/src/geometry/polygon.rs index 4de38d31f..88136ef45 100644 --- a/geo-types/src/geometry/polygon.rs +++ b/geo-types/src/geometry/polygon.rs @@ -651,7 +651,8 @@ mod approx_integration { feature = "rstar_0_9", feature = "rstar_0_10", feature = "rstar_0_11", - feature = "rstar_0_12" + feature = "rstar_0_12", + feature = "rstar_0_13" ))] macro_rules! impl_rstar_polygon { ($rstar:ident) => { @@ -683,6 +684,9 @@ impl_rstar_polygon!(rstar_0_11); #[cfg(feature = "rstar_0_12")] impl_rstar_polygon!(rstar_0_12); +#[cfg(feature = "rstar_0_13")] +impl_rstar_polygon!(rstar_0_13); + #[cfg(test)] mod tests { use super::*; diff --git a/geo-types/src/geometry/triangle.rs b/geo-types/src/geometry/triangle.rs index c2bc6e7df..29d4e2de2 100644 --- a/geo-types/src/geometry/triangle.rs +++ b/geo-types/src/geometry/triangle.rs @@ -235,7 +235,8 @@ mod approx_integration { feature = "rstar_0_9", feature = "rstar_0_10", feature = "rstar_0_11", - feature = "rstar_0_12" + feature = "rstar_0_12", + feature = "rstar_0_13" ))] macro_rules! impl_rstar_triangle { ($rstar:ident) => { @@ -268,3 +269,6 @@ impl_rstar_triangle!(rstar_0_11); #[cfg(feature = "rstar_0_12")] impl_rstar_triangle!(rstar_0_12); + +#[cfg(feature = "rstar_0_13")] +impl_rstar_triangle!(rstar_0_13); diff --git a/geo-types/src/lib.rs b/geo-types/src/lib.rs index ea6905c3a..31a0c8bb2 100644 --- a/geo-types/src/lib.rs +++ b/geo-types/src/lib.rs @@ -72,6 +72,7 @@ //! - `use-rstar_0_10`: Allows geometry types to be inserted into [rstar] R*-trees (`rstar v0.10`) //! - `use-rstar_0_11`: Allows geometry types to be inserted into [rstar] R*-trees (`rstar v0.11`) //! - `use-rstar_0_12`: Allows geometry types to be inserted into [rstar] R*-trees (`rstar v0.12`) +//! - `rstar_0_13`: Allows geometry types to be inserted into [rstar] R*-trees (`rstar v0.13`) //! //! This library can be used in `#![no_std]` environments if the default `std` feature is disabled. At //! the moment, the `arbitrary` and `use-rstar_0_8` features require `std`. This may change in a @@ -144,7 +145,8 @@ mod arbitrary; feature = "rstar_0_9", feature = "rstar_0_10", feature = "rstar_0_11", - feature = "rstar_0_12" + feature = "rstar_0_12", + feature = "rstar_0_13" ))] #[doc(hidden)] pub mod private_utils; @@ -388,6 +390,21 @@ mod tests { assert_relative_eq!(25.999999999999996, l.distance_2(&Point::new(4.0, 10.0))); } + #[cfg(feature = "rstar_0_13")] + #[test] + /// ensure Line's SpatialObject impl is correct + fn line_test_0_13() { + use rstar_0_13::primitives::Line as RStarLine; + use rstar_0_13::{PointDistance, RTreeObject}; + + let rl = RStarLine::new(Point::new(0.0, 0.0), Point::new(5.0, 5.0)); + let l = Line::new(coord! { x: 0.0, y: 0.0 }, coord! { x: 5., y: 5. }); + assert_eq!(rl.envelope(), l.envelope()); + // difference in 15th decimal place + assert_relative_eq!(26.0, rl.distance_2(&Point::new(4.0, 10.0))); + assert_relative_eq!(25.999999999999996, l.distance_2(&Point::new(4.0, 10.0))); + } + #[test] fn test_rects() { let r = Rect::new(coord! { x: -1., y: -1. }, coord! { x: 1., y: 1. }); diff --git a/geo/CHANGES.md b/geo/CHANGES.md index 47aa6118b..d1284975a 100644 --- a/geo/CHANGES.md +++ b/geo/CHANGES.md @@ -2,6 +2,7 @@ ## Unreleased +- Update the `rstar` dependency to 0.13. - Unpin the `earcut` dependency now that the upstream semver violation has been reverted. - - Added `Earcutter` and `TriangulateEarcut::earcut_triangulation_ref` to avoid per-call memory allocations across multiple triangulations. diff --git a/geo/Cargo.toml b/geo/Cargo.toml index ae4f5e90a..1325bd164 100644 --- a/geo/Cargo.toml +++ b/geo/Cargo.toml @@ -31,13 +31,13 @@ __allow_deprecated_features = [] earcut = { version = "0.4.9", optional = true } spade = { version = "2.10.0", optional = true } float_next_after = "2.0.0" -geo-types = { version = "0.7.19", features = ["approx", "rstar_0_12"] } +geo-types = { version = "0.7.19", features = ["approx", "rstar_0_13"] } geographiclib-rs = { version = "0.2.3", default-features = false } log = "0.4.11" num-traits = "0.2" proj = { version = "0.31.0", optional = true } robust = "1.1.0" -rstar = "0.12.0" +rstar = "0.13.0" serde = { version = "1.0", optional = true, features = ["derive"] } i_overlay = { version = "4.5.1, < 4.6.0", default-features = false } sif-itree = "0.4.0" diff --git a/geo/src/algorithm/concave_hull.rs b/geo/src/algorithm/concave_hull.rs index 2a14e17bf..c83c08adc 100644 --- a/geo/src/algorithm/concave_hull.rs +++ b/geo/src/algorithm/concave_hull.rs @@ -366,7 +366,7 @@ where let bbox = AABB::from_corners(point!([min_x, min_y]), point!([max_x, max_y])); // Iterate over all lines in the hull which intersect with the bounding box - let hull_lines = current_hull_tree.locate_in_envelope_intersecting(&bbox); + let hull_lines = current_hull_tree.locate_in_envelope_intersecting(bbox); for hull_line in hull_lines { // Skip lines which share an endpoint if hull_line.start == line.start @@ -633,7 +633,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{coord, line_string, polygon}; + use crate::{Relate, coord, line_string, polygon}; #[test] fn test_concavity() { @@ -723,6 +723,10 @@ mod tests { ] .into(); let hull = mp.concave_hull(); + // The bottom edge runs straight from (0, 0) to (4, 0). The intermediate + // points (2, 0) and (3, 0) are collinear, so whether they are retained as + // explicit vertices depends on R-tree iteration order, which changed in + // rstar 0.13. Compare topologically rather than by exact vertex list. let correct_hull = polygon![ (x: 4.0, y: 0.0), (x: 4.0, y: 5.0), @@ -734,7 +738,7 @@ mod tests { (x: 3.0, y: 0.0), (x: 4.0, y: 0.0), ]; - assert_eq!(hull, correct_hull); + assert!(hull.relate(&correct_hull).is_equal_topo()); } #[test] diff --git a/geo/src/algorithm/euclidean_distance.rs b/geo/src/algorithm/euclidean_distance.rs index 80217a1ba..d1087dae7 100644 --- a/geo/src/algorithm/euclidean_distance.rs +++ b/geo/src/algorithm/euclidean_distance.rs @@ -500,12 +500,12 @@ where geom2 .points() .fold(::max_value(), |acc, point| { - let nearest = tree_a.nearest_neighbor(&point).unwrap(); + let nearest = tree_a.nearest_neighbor(point).unwrap(); #[allow(deprecated)] acc.min(nearest.euclidean_distance(&point)) }) .min(geom1.points().fold(Bounded::max_value(), |acc, point| { - let nearest = tree_b.nearest_neighbor(&point).unwrap(); + let nearest = tree_b.nearest_neighbor(point).unwrap(); #[allow(deprecated)] acc.min(nearest.euclidean_distance(&point)) })) diff --git a/geo/src/algorithm/k_nearest_concave_hull.rs b/geo/src/algorithm/k_nearest_concave_hull.rs index c96f2ecda..339f46148 100644 --- a/geo/src/algorithm/k_nearest_concave_hull.rs +++ b/geo/src/algorithm/k_nearest_concave_hull.rs @@ -105,7 +105,7 @@ where { let mut dataset: rstar::RTree> = rstar::RTree::new(); for coord in coords { - let closest = dataset.nearest_neighbor(coord); + let closest = dataset.nearest_neighbor(*coord); if let Some(closest) = closest && coords_are_equal(coord, closest) { @@ -257,7 +257,7 @@ where T: GeoFloat + RTreeNum, { dataset - .nearest_neighbor_iter(base_coord) + .nearest_neighbor_iter(*base_coord) .take(candidate_no as usize) } diff --git a/geo/src/algorithm/line_measures/metric_spaces/euclidean/distance.rs b/geo/src/algorithm/line_measures/metric_spaces/euclidean/distance.rs index dc502664f..70f5d032e 100644 --- a/geo/src/algorithm/line_measures/metric_spaces/euclidean/distance.rs +++ b/geo/src/algorithm/line_measures/metric_spaces/euclidean/distance.rs @@ -448,11 +448,11 @@ fn nearest_neighbour_distance(geom1: &LineString, geom2: &LineSt geom2 .points() .fold(Bounded::max_value(), |acc: F, point| { - let nearest = tree_a.nearest_neighbor(&point).unwrap(); + let nearest = tree_a.nearest_neighbor(point).unwrap(); acc.min(Euclidean.distance(nearest as &Line, &point)) }) .min(geom1.points().fold(Bounded::max_value(), |acc, point| { - let nearest = tree_b.nearest_neighbor(&point).unwrap(); + let nearest = tree_b.nearest_neighbor(point).unwrap(); acc.min(Euclidean.distance(nearest as &Line, &point)) })) } diff --git a/geo/src/algorithm/outlier_detection.rs b/geo/src/algorithm/outlier_detection.rs index 861efd92e..320744417 100644 --- a/geo/src/algorithm/outlier_detection.rs +++ b/geo/src/algorithm/outlier_detection.rs @@ -212,7 +212,7 @@ where .iter() .map(|point| { let neighbours = tree - .nearest_neighbor_iter_with_distance_2(point) + .nearest_neighbor_iter_with_distance_2(*point) .take(k_neighbours) .collect::>(); let kth_dist = neighbours.last().map(|(_, d)| *d).unwrap(); diff --git a/geo/src/algorithm/simplify_vw.rs b/geo/src/algorithm/simplify_vw.rs index 1cb145d50..05dde15b2 100644 --- a/geo/src/algorithm/simplify_vw.rs +++ b/geo/src/algorithm/simplify_vw.rs @@ -415,7 +415,7 @@ where orig[triangle.right], ) .bounding_rect(); - tree.locate_in_envelope_intersecting(&rstar::AABB::from_corners( + tree.locate_in_envelope_intersecting(rstar::AABB::from_corners( bounding_rect.min().into(), bounding_rect.max().into(), )) diff --git a/geo/src/algorithm/triangulate_delaunay.rs b/geo/src/algorithm/triangulate_delaunay.rs index 17c849434..ebae1479b 100644 --- a/geo/src/algorithm/triangulate_delaunay.rs +++ b/geo/src/algorithm/triangulate_delaunay.rs @@ -629,7 +629,7 @@ pub(crate) fn snap_or_register_point( snap_radius: T, ) -> Coord { known_points - .nearest_neighbor(&point) + .nearest_neighbor(point) // only snap if closest is within snap radius .filter(|nearest_point| Euclidean.distance(**nearest_point, point) < snap_radius) .cloned()