From c3fdeef32b2a0a658fd788a99184a29792acc508 Mon Sep 17 00:00:00 2001 From: zhangfengcdt Date: Wed, 8 Oct 2025 08:52:11 -0700 Subject: [PATCH 1/5] feat: Add distance metric support for RTree neighbor queries Add pluggable distance metrics for RTree neighbor searches, enabling Euclidean, Haversine, and Spheroid distance calculations. This allows users to choose appropriate distance calculations for different coordinate systems (planar vs geographic). --- .gitignore | 2 + Cargo.lock | 13 +- Cargo.toml | 9 +- benches/distance.rs | 249 ++++++++++++++++++ src/rtree/distance.rs | 465 +++++++++++++++++++++++++++++++++ src/rtree/mod.rs | 2 + src/rtree/trait.rs | 593 +++++++++++++++++++++++++++++++++++++++++- src/type.rs | 27 +- 8 files changed, 1348 insertions(+), 12 deletions(-) create mode 100644 benches/distance.rs create mode 100644 src/rtree/distance.rs diff --git a/.gitignore b/.gitignore index 4fe000d..76b13c0 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ target/ # Added by cargo /target + +.idea/ diff --git a/Cargo.lock b/Cargo.lock index cdbd6b7..fa90674 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -463,8 +463,10 @@ dependencies = [ "float_next_after", "geo", "geo-traits", + "geo-types", "geozero", "num-traits", + "rand", "rayon", "rstar", "thiserror 1.0.49", @@ -524,6 +526,7 @@ dependencies = [ "geo-types", "geojson", "log", + "scroll", "serde_json", "thiserror 1.0.49", "wkt", @@ -788,9 +791,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oorandom" @@ -1005,6 +1008,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scroll" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" + [[package]] name = "serde" version = "1.0.189" diff --git a/Cargo.toml b/Cargo.toml index a2beb64..822d209 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ categories = ["data-structures", "algorithms", "science::geo"] bytemuck = "1" float_next_after = "1" geo-traits = "0.3" +geo-types = "0.7.17" +geo = "0.31.0" num-traits = "0.2" rayon = { version = "1.8.0", optional = true } thiserror = "1" @@ -24,10 +26,15 @@ tinyvec = { version = "1", features = ["alloc", "rustc_1_40"] } [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } geo = "0.31.0" -geozero = "0.14" +geozero = { version = "0.14", features = ["with-wkb", "with-geo"] } rstar = "0.12" zip = "2.2.2" +rand = "0.8" [[bench]] name = "rtree" harness = false + +[[bench]] +name = "distance" +harness = false diff --git a/benches/distance.rs b/benches/distance.rs new file mode 100644 index 0000000..0c880cc --- /dev/null +++ b/benches/distance.rs @@ -0,0 +1,249 @@ +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use geo::algorithm::{Distance, Euclidean}; +use geo::{Geometry, LineString, Point, Polygon}; +use geo_index::rtree::distance::{ + DistanceMetric, EuclideanDistance, HaversineDistance, SliceGeometryAccessor, SpheroidDistance, +}; +use geo_index::rtree::sort::HilbertSort; +use geo_index::rtree::{RTreeBuilder, RTreeIndex}; +use geo_index::IndexableNum; +use geo_types::coord; +use rand::rngs::StdRng; +use rand::{Rng, SeedableRng}; + +// Simple distance metric for benchmarks +struct SimpleMetric; + +impl DistanceMetric for SimpleMetric { + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { + let dx = x2 - x1; + let dy = y2 - y1; + (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) + } + + fn distance_to_bbox(&self, x: N, y: N, min_x: N, min_y: N, max_x: N, max_y: N) -> N { + let dx = if x < min_x { + min_x - x + } else if x > max_x { + x - max_x + } else { + N::zero() + }; + let dy = if y < min_y { + min_y - y + } else if y > max_y { + y - max_y + } else { + N::zero() + }; + (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) + } + + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) + } +} + +fn generate_test_data(n: usize) -> (Vec>, Vec>) { + let mut rng = StdRng::seed_from_u64(42); + let mut points = Vec::with_capacity(n); + let mut geometries = Vec::with_capacity(n); + + for i in 0..n { + let x = rng.gen_range(-180.0..180.0); + let y = rng.gen_range(-90.0..90.0); + let point = Point::new(x, y); + points.push(point); + + // Create a mix of geometry types + let geom = match i % 3 { + 0 => Geometry::Point(point), + 1 => { + // Create a small line around the point + let offset = 0.01; + Geometry::LineString(LineString::new(vec![ + coord! { x: x, y: y }, + coord! { x: x + offset, y: y + offset }, + ])) + } + 2 => { + // Create a small square around the point + let offset = 0.005; + Geometry::Polygon(Polygon::new( + LineString::new(vec![ + coord! { x: x - offset, y: y - offset }, + coord! { x: x + offset, y: y - offset }, + coord! { x: x + offset, y: y + offset }, + coord! { x: x - offset, y: y + offset }, + coord! { x: x - offset, y: y - offset }, + ]), + vec![], + )) + } + _ => unreachable!(), + }; + geometries.push(geom); + } + + (points, geometries) +} + +fn build_rtree(points: &[Point]) -> geo_index::rtree::RTree { + let mut builder = RTreeBuilder::new(points.len() as u32); + for point in points { + builder.add(point.x(), point.y(), point.x(), point.y()); + } + builder.finish::() +} + +fn benchmark_distance_metrics(c: &mut Criterion) { + let sizes = vec![100, 1000]; + + for size in sizes { + let (points, geometries) = generate_test_data(size); + let tree = build_rtree(&points); + let query_point = Point::new(0.0, 0.0); + + let euclidean = EuclideanDistance; + let haversine = HaversineDistance::default(); + let spheroid = SpheroidDistance::default(); + + // Benchmark neighbors_with_distance with different metrics + let mut group = c.benchmark_group("neighbors_with_distance"); + + group.bench_with_input(BenchmarkId::new("euclidean", size), &size, |b, _| { + b.iter(|| { + tree.neighbors_with_distance( + query_point.x(), + query_point.y(), + Some(10), + None, + &euclidean, + ) + }) + }); + + group.bench_with_input(BenchmarkId::new("haversine", size), &size, |b, _| { + b.iter(|| { + tree.neighbors_with_distance( + query_point.x(), + query_point.y(), + Some(10), + None, + &haversine, + ) + }) + }); + + group.bench_with_input(BenchmarkId::new("spheroid", size), &size, |b, _| { + b.iter(|| { + tree.neighbors_with_distance( + query_point.x(), + query_point.y(), + Some(10), + None, + &spheroid, + ) + }) + }); + + group.finish(); + + // Benchmark neighbors_geometry with different metrics + let mut geom_group = c.benchmark_group("neighbors_geometry"); + let query_geometry = Geometry::Point(query_point); + + geom_group.bench_with_input(BenchmarkId::new("euclidean", size), &size, |b, _| { + let metric = SimpleMetric; + let accessor = SliceGeometryAccessor::new(&geometries); + b.iter(|| tree.neighbors_geometry(&query_geometry, Some(10), None, &metric, &accessor)) + }); + + geom_group.finish(); + } +} + +fn benchmark_distance_calculations(c: &mut Criterion) { + let euclidean = EuclideanDistance; + let haversine = HaversineDistance::default(); + let spheroid = SpheroidDistance::default(); + + let p1 = Point::new(-74.0, 40.7); // New York + let p2 = Point::new(-0.1, 51.5); // London + let geom1 = Geometry::Point(p1); + let geom2 = Geometry::Point(p2); + + // Benchmark raw distance calculations + let mut group = c.benchmark_group("distance_calculation"); + + group.bench_function("euclidean_point_to_point", |b| { + b.iter(|| euclidean.distance(p1.x(), p1.y(), p2.x(), p2.y())) + }); + + group.bench_function("haversine_point_to_point", |b| { + b.iter(|| haversine.distance(p1.x(), p1.y(), p2.x(), p2.y())) + }); + + group.bench_function("spheroid_point_to_point", |b| { + b.iter(|| spheroid.distance(p1.x(), p1.y(), p2.x(), p2.y())) + }); + + group.bench_function("euclidean_geometry_to_geometry", |b| { + b.iter(|| Euclidean.distance(&geom1, &geom2)) + }); + + // Benchmark bbox distance calculations + let bbox = (-75.0, 40.0, -73.0, 41.0); // Bbox around New York + + group.bench_function("euclidean_distance_to_bbox", |b| { + b.iter(|| euclidean.distance_to_bbox(p2.x(), p2.y(), bbox.0, bbox.1, bbox.2, bbox.3)) + }); + + group.bench_function("haversine_distance_to_bbox", |b| { + b.iter(|| haversine.distance_to_bbox(p2.x(), p2.y(), bbox.0, bbox.1, bbox.2, bbox.3)) + }); + + group.bench_function("spheroid_distance_to_bbox", |b| { + b.iter(|| spheroid.distance_to_bbox(p2.x(), p2.y(), bbox.0, bbox.1, bbox.2, bbox.3)) + }); + + group.finish(); +} + +fn benchmark_comparison_with_baseline(c: &mut Criterion) { + let (points, _) = generate_test_data(1000); + let tree = build_rtree(&points); + let query_point = Point::new(0.0, 0.0); + + let euclidean = EuclideanDistance; + + let mut group = c.benchmark_group("baseline_comparison"); + + // Original neighbors method (baseline) + group.bench_function("original_neighbors", |b| { + b.iter(|| tree.neighbors(query_point.x(), query_point.y(), Some(10), None)) + }); + + // New neighbors_with_distance method + group.bench_function("neighbors_with_distance_euclidean", |b| { + b.iter(|| { + tree.neighbors_with_distance( + query_point.x(), + query_point.y(), + Some(10), + None, + &euclidean, + ) + }) + }); + + group.finish(); +} + +criterion_group!( + benches, + benchmark_distance_metrics, + benchmark_distance_calculations, + benchmark_comparison_with_baseline +); +criterion_main!(benches); diff --git a/src/rtree/distance.rs b/src/rtree/distance.rs new file mode 100644 index 0000000..fd353cb --- /dev/null +++ b/src/rtree/distance.rs @@ -0,0 +1,465 @@ +//! Distance metrics for spatial queries. +//! +//! This module provides different distance calculation methods for spatial queries, +//! including Euclidean, Haversine, and Spheroid distance calculations. + +use crate::r#type::IndexableNum; +use geo::algorithm::{Distance, Euclidean, Geodesic, Haversine}; +use geo::{Geometry, Point}; + +pub use crate::rtree::r#trait::GeometryAccessor; + +/// A trait for calculating distances between geometries and points. +pub trait DistanceMetric { + /// Calculate the distance between two points (x1, y1) and (x2, y2). + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N; + + /// Calculate the distance from a point to a bounding box. + /// This is used for spatial index optimization during tree traversal. + fn distance_to_bbox(&self, x: N, y: N, min_x: N, min_y: N, max_x: N, max_y: N) -> N; + + /// Calculate the distance between two geometries. + /// This method is used by geometry-based neighbor searches to compute the actual + /// distance between a query geometry and an item geometry. + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N; + + /// Return the maximum distance value for this metric. + fn max_distance(&self) -> N { + N::max_value() + } +} + +/// Euclidean distance metric. +/// +/// This is the standard straight-line distance calculation suitable for +/// planar coordinate systems. When working with longitude/latitude coordinates, +/// the unit of distance will be degrees. +#[derive(Debug, Clone, Copy, Default)] +pub struct EuclideanDistance; + +impl DistanceMetric for EuclideanDistance { + #[inline] + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { + let p1 = Point::new(x1.to_f64().unwrap_or(0.0), y1.to_f64().unwrap_or(0.0)); + let p2 = Point::new(x2.to_f64().unwrap_or(0.0), y2.to_f64().unwrap_or(0.0)); + N::from_f64(Euclidean.distance(p1, p2)).unwrap_or(N::max_value()) + } + + #[inline] + fn distance_to_bbox(&self, x: N, y: N, min_x: N, min_y: N, max_x: N, max_y: N) -> N { + let dx = axis_dist(x, min_x, max_x); + let dy = axis_dist(y, min_y, max_y); + (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) + } + + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) + } +} + +/// Haversine distance metric. +/// +/// This calculates the great-circle distance between two points on a sphere. +/// It's more accurate for geographic distances than Euclidean distance. +/// The input coordinates should be in longitude/latitude (degrees), and +/// the output distance is in meters. +#[derive(Debug, Clone, Copy)] +pub struct HaversineDistance { + /// Earth's radius in meters + pub earth_radius: f64, +} + +impl Default for HaversineDistance { + fn default() -> Self { + Self { + earth_radius: 6378137.0, // WGS84 equatorial radius in meters + } + } +} + +impl HaversineDistance { + /// Create a new Haversine distance metric with custom Earth radius. + pub fn with_radius(earth_radius: f64) -> Self { + Self { earth_radius } + } +} + +impl DistanceMetric for HaversineDistance { + fn distance(&self, lon1: N, lat1: N, lon2: N, lat2: N) -> N { + let p1 = Point::new(lon1.to_f64().unwrap_or(0.0), lat1.to_f64().unwrap_or(0.0)); + let p2 = Point::new(lon2.to_f64().unwrap_or(0.0), lat2.to_f64().unwrap_or(0.0)); + N::from_f64(Haversine.distance(p1, p2)).unwrap_or(N::max_value()) + } + + fn distance_to_bbox( + &self, + lon: N, + lat: N, + min_lon: N, + min_lat: N, + max_lon: N, + max_lat: N, + ) -> N { + // For geographic distance to bbox, find the closest point on the bbox + let lon_f = lon.to_f64().unwrap_or(0.0); + let lat_f = lat.to_f64().unwrap_or(0.0); + let min_lon_f = min_lon.to_f64().unwrap_or(0.0); + let min_lat_f = min_lat.to_f64().unwrap_or(0.0); + let max_lon_f = max_lon.to_f64().unwrap_or(0.0); + let max_lat_f = max_lat.to_f64().unwrap_or(0.0); + + let closest_lon = lon_f.clamp(min_lon_f, max_lon_f); + let closest_lat = lat_f.clamp(min_lat_f, max_lat_f); + + let point = Point::new(lon_f, lat_f); + let closest_point = Point::new(closest_lon, closest_lat); + N::from_f64(Haversine.distance(point, closest_point)).unwrap_or(N::max_value()) + } + + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + // For Haversine, use centroid-to-centroid distance as approximation + use geo::algorithm::Centroid; + let c1 = geom1.centroid().unwrap_or(Point::new(0.0, 0.0)); + let c2 = geom2.centroid().unwrap_or(Point::new(0.0, 0.0)); + N::from_f64(Haversine.distance(c1, c2)).unwrap_or(N::max_value()) + } +} + +/// Spheroid distance metric (using Geodesic/Vincenty's formula). +/// +/// This calculates the shortest distance between two points on the surface +/// of a spheroid (ellipsoid), providing a more accurate Earth model than +/// a simple sphere. The input coordinates should be in longitude/latitude +/// (degrees), and the output distance is in meters. +#[derive(Debug, Clone, Copy, Default)] +pub struct SpheroidDistance; + +impl SpheroidDistance { + /// Create a new Spheroid distance metric for GRS80 ellipsoid. + pub fn grs80() -> Self { + Self + } +} + +impl DistanceMetric for SpheroidDistance { + fn distance(&self, lon1: N, lat1: N, lon2: N, lat2: N) -> N { + let p1 = Point::new(lon1.to_f64().unwrap_or(0.0), lat1.to_f64().unwrap_or(0.0)); + let p2 = Point::new(lon2.to_f64().unwrap_or(0.0), lat2.to_f64().unwrap_or(0.0)); + N::from_f64(Geodesic.distance(p1, p2)).unwrap_or(N::max_value()) + } + + fn distance_to_bbox( + &self, + lon: N, + lat: N, + min_lon: N, + min_lat: N, + max_lon: N, + max_lat: N, + ) -> N { + // Similar to haversine, approximate using closest point on bbox + let lon_f = lon.to_f64().unwrap_or(0.0); + let lat_f = lat.to_f64().unwrap_or(0.0); + let min_lon_f = min_lon.to_f64().unwrap_or(0.0); + let min_lat_f = min_lat.to_f64().unwrap_or(0.0); + let max_lon_f = max_lon.to_f64().unwrap_or(0.0); + let max_lat_f = max_lat.to_f64().unwrap_or(0.0); + + let closest_lon = lon_f.clamp(min_lon_f, max_lon_f); + let closest_lat = lat_f.clamp(min_lat_f, max_lat_f); + + let point = Point::new(lon_f, lat_f); + let closest_point = Point::new(closest_lon, closest_lat); + N::from_f64(Geodesic.distance(point, closest_point)).unwrap_or(N::max_value()) + } + + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + // For Geodesic, use centroid-to-centroid distance as approximation + use geo::algorithm::Centroid; + let c1 = geom1.centroid().unwrap_or(Point::new(0.0, 0.0)); + let c2 = geom2.centroid().unwrap_or(Point::new(0.0, 0.0)); + N::from_f64(Geodesic.distance(c1, c2)).unwrap_or(N::max_value()) + } +} + +/// Simple geometry accessor that wraps a slice of geometries. +/// +/// This accessor provides access to geometries by index for use with distance metrics. +/// +/// # Example +/// ``` +/// use geo_index::rtree::distance::{EuclideanDistance, SliceGeometryAccessor}; +/// use geo::{Geometry, Point}; +/// +/// let geometries = vec![ +/// Geometry::Point(Point::new(0.0, 0.0)), +/// Geometry::Point(Point::new(1.0, 1.0)), +/// ]; +/// +/// let accessor = SliceGeometryAccessor::new(&geometries); +/// let metric = EuclideanDistance; +/// // Now accessor and metric can be used with neighbors_geometry +/// ``` +pub struct SliceGeometryAccessor<'a> { + geometries: &'a [Geometry], +} + +impl<'a> SliceGeometryAccessor<'a> { + /// Create a new accessor with the given geometries. + pub fn new(geometries: &'a [Geometry]) -> Self { + Self { geometries } + } +} + +impl<'a> GeometryAccessor for SliceGeometryAccessor<'a> { + fn get_geometry(&self, item_index: usize) -> Option<&Geometry> { + self.geometries.get(item_index) + } +} + +/// 1D distance from a value to a range. +#[inline] +fn axis_dist(k: N, min: N, max: N) -> N { + if k < min { + min - k + } else if k <= max { + N::zero() + } else { + k - max + } +} + +#[cfg(test)] +mod tests { + use super::*; + use geo::LineString; + + #[test] + fn test_euclidean_distance() { + let metric = EuclideanDistance; + let distance = metric.distance(0.0f64, 0.0f64, 3.0f64, 4.0f64); + assert!((distance - 5.0f64).abs() < 1e-10); + } + + #[test] + fn test_haversine_distance() { + let metric = HaversineDistance::default(); + // Distance between New York and London (approximately) + let distance = metric.distance(-74.0f64, 40.7f64, -0.1f64, 51.5f64); + // Should be approximately 5585 km + assert!((distance - 5585000.0f64).abs() < 50000.0f64); + } + + #[test] + fn test_spheroid_distance() { + let metric = SpheroidDistance; + // Distance between New York and London (approximately) + let distance = metric.distance(-74.0f64, 40.7f64, -0.1f64, 51.5f64); + // Should be approximately 5585 km (slightly different from Haversine) + assert!((distance - 5585000.0f64).abs() < 50000.0f64); + } + + #[test] + fn test_euclidean_geometry_distance() { + // Test Euclidean distance between geometries + let point1 = Geometry::Point(Point::new(0.0, 0.0)); + let point2 = Geometry::Point(Point::new(3.0, 4.0)); + let distance: f64 = Euclidean.distance(&point1, &point2); + assert!((distance - 5.0).abs() < 1e-10); + + // Test distance to line + let line = Geometry::LineString(LineString::new(vec![ + geo_types::coord! { x: 0.0, y: 5.0 }, + geo_types::coord! { x: 10.0, y: 5.0 }, + ])); + let query = Geometry::Point(Point::new(0.0, 0.0)); + let distance: f64 = Euclidean.distance(&query, &line); + assert!((distance - 5.0).abs() < 1e-10); + } + + #[test] + fn test_wkb_decoding_distance_metric() { + use geozero::{wkb, GeozeroGeometry}; + + /// Custom distance metric that stores WKB-encoded geometries and decodes them on-demand + struct WkbDistanceMetric<'a> { + wkb_data: &'a [Vec], // Array of WKB-encoded geometries + } + + impl<'a> WkbDistanceMetric<'a> { + fn new(wkb_data: &'a [Vec]) -> Self { + Self { wkb_data } + } + + /// Decode WKB data on-demand to get geometry + fn decode_geometry(&self, index: usize) -> Option> { + if index < self.wkb_data.len() { + use geozero::geo_types::GeoWriter; + + let mut geo_writer = GeoWriter::new(); + // Pass the byte slice directly to Wkb + if wkb::Wkb(self.wkb_data[index].as_slice()) + .process_geom(&mut geo_writer) + .is_ok() + { + geo_writer.take_geometry() + } else { + None + } + } else { + None + } + } + } + + impl<'a, N: IndexableNum> DistanceMetric for WkbDistanceMetric<'a> { + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { + EuclideanDistance.distance(x1, y1, x2, y2) + } + + fn distance_to_bbox(&self, x: N, y: N, min_x: N, min_y: N, max_x: N, max_y: N) -> N { + EuclideanDistance.distance_to_bbox(x, y, min_x, min_y, max_x, max_y) + } + + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) + } + } + + // Create some test WKB data (encoded points) + let point1 = Geometry::Point(Point::new(0.0, 0.0)); + let point2 = Geometry::Point(Point::new(3.0, 4.0)); + let point3 = Geometry::Point(Point::new(6.0, 8.0)); + + // Encode geometries to WKB using geozero + use geozero::ToWkb; + let wkb1 = point1.to_wkb(geozero::CoordDimensions::default()).unwrap(); + let wkb2 = point2.to_wkb(geozero::CoordDimensions::default()).unwrap(); + let wkb3 = point3.to_wkb(geozero::CoordDimensions::default()).unwrap(); + let wkb_data = vec![wkb1, wkb2, wkb3]; + + // Create the WKB-based distance metric + let wkb_metric = WkbDistanceMetric::new(&wkb_data); + let query = Geometry::Point(Point::new(1.0, 1.0)); + + // Test distance calculation with on-demand WKB decoding + // Decode geometries and compute distances + let geom0 = wkb_metric.decode_geometry(0).unwrap(); + let dist: f64 = wkb_metric.distance_to_geometry(&query, &geom0); + assert!((dist - 1.414).abs() < 0.01); // Distance from (1,1) to (0,0) + + let geom1 = wkb_metric.decode_geometry(1).unwrap(); + let dist: f64 = wkb_metric.distance_to_geometry(&query, &geom1); + assert!((dist - 3.605).abs() < 0.01); // Distance from (1,1) to (3,4) + + let geom2 = wkb_metric.decode_geometry(2).unwrap(); + let dist: f64 = wkb_metric.distance_to_geometry(&query, &geom2); + assert!((dist - 8.602).abs() < 0.01); // Distance from (1,1) to (6,8) + } + + #[test] + fn test_cached_geometry_distance_metric() { + use std::cell::RefCell; + use std::collections::HashMap; + + /// Custom distance metric with geometry caching to avoid repeated calculations + struct CachedDistanceMetric<'a> { + geometries: &'a [Geometry], + cache: RefCell>>, // Cache for decoded geometries + cache_hits: RefCell, // Track cache performance + cache_misses: RefCell, + } + + impl<'a> CachedDistanceMetric<'a> { + fn new(geometries: &'a [Geometry]) -> Self { + Self { + geometries, + cache: RefCell::new(HashMap::new()), + cache_hits: RefCell::new(0), + cache_misses: RefCell::new(0), + } + } + + /// Get geometry with caching - simulates expensive decode operation + fn get_cached_geometry(&self, index: usize) -> Option> { + if index >= self.geometries.len() { + return None; + } + + // Check cache first + if let Some(cached_geom) = self.cache.borrow().get(&index) { + *self.cache_hits.borrow_mut() += 1; + return Some(cached_geom.clone()); + } + + // Cache miss - "expensive" operation simulation + *self.cache_misses.borrow_mut() += 1; + let geometry = self.geometries[index].clone(); + + // Store in cache + self.cache.borrow_mut().insert(index, geometry.clone()); + Some(geometry) + } + + fn get_cache_stats(&self) -> (usize, usize) { + (*self.cache_hits.borrow(), *self.cache_misses.borrow()) + } + } + + impl<'a, N: IndexableNum> DistanceMetric for CachedDistanceMetric<'a> { + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { + EuclideanDistance.distance(x1, y1, x2, y2) + } + + fn distance_to_bbox(&self, x: N, y: N, min_x: N, min_y: N, max_x: N, max_y: N) -> N { + EuclideanDistance.distance_to_bbox(x, y, min_x, min_y, max_x, max_y) + } + + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) + } + } + + // Create test data + let geometries = vec![ + Geometry::Point(Point::new(0.0, 0.0)), + Geometry::Point(Point::new(3.0, 4.0)), + Geometry::Point(Point::new(6.0, 8.0)), + ]; + + let cached_metric = CachedDistanceMetric::new(&geometries); + let query = Geometry::Point(Point::new(1.0, 1.0)); + + // First access - should be cache misses + let geom0 = cached_metric.get_cached_geometry(0).unwrap(); + let dist1: f64 = cached_metric.distance_to_geometry(&query, &geom0); + + let geom1 = cached_metric.get_cached_geometry(1).unwrap(); + let dist2: f64 = cached_metric.distance_to_geometry(&query, &geom1); + + let geom2 = cached_metric.get_cached_geometry(2).unwrap(); + let dist3: f64 = cached_metric.distance_to_geometry(&query, &geom2); + + assert!((dist1 - 1.414).abs() < 0.01); + assert!((dist2 - 3.605).abs() < 0.01); + assert!((dist3 - 8.602).abs() < 0.01); + + let (hits_after_first, misses_after_first) = cached_metric.get_cache_stats(); + assert_eq!(hits_after_first, 0); // No hits yet + assert_eq!(misses_after_first, 3); // 3 misses + + // Second access to same geometries - should be cache hits + let geom0_cached = cached_metric.get_cached_geometry(0).unwrap(); + let dist1_cached: f64 = cached_metric.distance_to_geometry(&query, &geom0_cached); + + let geom1_cached = cached_metric.get_cached_geometry(1).unwrap(); + let dist2_cached: f64 = cached_metric.distance_to_geometry(&query, &geom1_cached); + + assert!((dist1_cached - 1.414).abs() < 0.01); + assert!((dist2_cached - 3.605).abs() < 0.01); + + let (hits_after_second, misses_after_second) = cached_metric.get_cache_stats(); + assert_eq!(hits_after_second, 2); // 2 cache hits + assert_eq!(misses_after_second, 3); // Still 3 misses total + } +} diff --git a/src/rtree/mod.rs b/src/rtree/mod.rs index 08243a7..9897a1f 100644 --- a/src/rtree/mod.rs +++ b/src/rtree/mod.rs @@ -56,6 +56,7 @@ mod builder; mod constants; +pub mod distance; mod index; pub mod sort; mod r#trait; @@ -63,6 +64,7 @@ mod traversal; pub mod util; pub use builder::{RTreeBuilder, DEFAULT_RTREE_NODE_SIZE}; +pub use distance::{DistanceMetric, EuclideanDistance, HaversineDistance, SpheroidDistance}; pub use index::{RTree, RTreeMetadata, RTreeRef}; pub use r#trait::RTreeIndex; pub use traversal::Node; diff --git a/src/rtree/trait.rs b/src/rtree/trait.rs index 7afafce..20e35e9 100644 --- a/src/rtree/trait.rs +++ b/src/rtree/trait.rs @@ -2,17 +2,35 @@ use std::cmp::Reverse; use std::collections::{BinaryHeap, VecDeque}; use std::vec; +use geo::algorithm::BoundingRect; +use geo::Geometry; use geo_traits::{CoordTrait, RectTrait}; use crate::error::Result; use crate::indices::Indices; use crate::r#type::IndexableNum; +use crate::rtree::distance::{DistanceMetric, EuclideanDistance}; use crate::rtree::index::{RTree, RTreeRef}; use crate::rtree::traversal::{IntersectionIterator, Node}; use crate::rtree::util::upper_bound; use crate::rtree::RTreeMetadata; use crate::GeoIndexError; +/// A trait for accessing geometries by index. +/// +/// This trait allows different storage strategies for geometries (direct storage, +/// WKB decoding, caching, etc.) to be used with spatial index queries. +pub trait GeometryAccessor { + /// Get the geometry at the given index. + /// + /// # Arguments + /// * `item_index` - Index of the item to retrieve + /// + /// # Returns + /// A reference to the geometry at the given index, or None if the index is out of bounds + fn get_geometry(&self, item_index: usize) -> Option<&Geometry>; +} + /// A trait for searching and accessing data out of an RTree. pub trait RTreeIndex: Sized { /// A slice representing all the bounding boxes of all elements contained within this tree, @@ -140,6 +158,9 @@ pub trait RTreeIndex: Sized { /// Search items in order of distance from the given point. /// + /// This method uses Euclidean distance by default. For other distance metrics, + /// use [`neighbors_with_distance`]. + /// /// ``` /// use geo_index::rtree::{RTreeBuilder, RTreeIndex, RTreeRef}; /// use geo_index::rtree::sort::HilbertSort; @@ -160,15 +181,48 @@ pub trait RTreeIndex: Sized { y: N, max_results: Option, max_distance: Option, + ) -> Vec { + // Use Euclidean distance by default for backward compatibility + let euclidean_distance = EuclideanDistance; + self.neighbors_with_distance(x, y, max_results, max_distance, &euclidean_distance) + } + + /// Search items in order of distance from the given point using a custom distance metric. + /// + /// This method allows you to specify a custom distance calculation method, such as + /// Euclidean, Haversine, or Spheroid distance. + /// + /// ``` + /// use geo_index::rtree::{RTreeBuilder, RTreeIndex}; + /// use geo_index::rtree::distance::{EuclideanDistance, HaversineDistance}; + /// use geo_index::rtree::sort::HilbertSort; + /// + /// // Create an RTree with geographic coordinates (longitude, latitude) + /// let mut builder = RTreeBuilder::::new(3); + /// builder.add(-74.0, 40.7, -74.0, 40.7); // New York + /// builder.add(-0.1, 51.5, -0.1, 51.5); // London + /// builder.add(139.7, 35.7, 139.7, 35.7); // Tokyo + /// let tree = builder.finish::(); + /// + /// // Find nearest neighbors using Haversine distance (great-circle distance) + /// let haversine = HaversineDistance::default(); + /// let results = tree.neighbors_with_distance(-74.0, 40.7, Some(2), None, &haversine); + /// ``` + fn neighbors_with_distance + ?Sized>( + &self, + x: N, + y: N, + max_results: Option, + max_distance: Option, + distance_metric: &M, ) -> Vec { let boxes = self.boxes(); let indices = self.indices(); - let max_distance = max_distance.unwrap_or(N::max_value()); + let max_distance = max_distance.unwrap_or(distance_metric.max_distance()); let mut outer_node_index = Some(boxes.len() - 4); let mut queue = BinaryHeap::new(); let mut results: Vec = vec![]; - let max_dist_squared = max_distance * max_distance; 'outer: while let Some(node_index) = outer_node_index { // find the end index of the node @@ -179,10 +233,17 @@ pub trait RTreeIndex: Sized { for pos in (node_index..end).step_by(4) { let index = indices.get(pos >> 2); - let dx = axis_dist(x, boxes[pos], boxes[pos + 2]); - let dy = axis_dist(y, boxes[pos + 1], boxes[pos + 3]); - let dist = dx * dx + dy * dy; - if dist > max_dist_squared { + // Use the custom distance metric for bbox distance calculation + let dist = distance_metric.distance_to_bbox( + x, + y, + boxes[pos], + boxes[pos + 1], + boxes[pos + 2], + boxes[pos + 3], + ); + + if dist > max_distance { continue; } @@ -194,6 +255,7 @@ pub trait RTreeIndex: Sized { })); } else { // leaf item (use odd id) + // Use consistent distance calculation for both nodes and leaf items queue.push(Reverse(NeighborNode { id: (index << 1) + 1, dist, @@ -204,7 +266,7 @@ pub trait RTreeIndex: Sized { // pop items from the queue while !queue.is_empty() && queue.peek().is_some_and(|val| (val.0.id & 1) != 0) { let dist = queue.peek().unwrap().0.dist; - if dist > max_dist_squared { + if dist > max_distance { break 'outer; } let item = queue.pop().unwrap(); @@ -234,6 +296,159 @@ pub trait RTreeIndex: Sized { self.neighbors(coord.x(), coord.y(), max_results, max_distance) } + /// Search items in order of distance from the given coordinate using a custom distance metric. + fn neighbors_coord_with_distance + ?Sized>( + &self, + coord: &impl CoordTrait, + max_results: Option, + max_distance: Option, + distance_metric: &M, + ) -> Vec { + self.neighbors_with_distance( + coord.x(), + coord.y(), + max_results, + max_distance, + distance_metric, + ) + } + + /// Search items in order of distance from a query geometry using a distance metric and geometry accessor. + /// + /// This method allows searching with geometry-to-geometry distance calculations. + /// The distance metric defines how distances are computed, and the geometry accessor + /// provides access to the actual geometries by index. + /// + /// ``` + /// use geo_index::rtree::{RTreeBuilder, RTreeIndex}; + /// use geo_index::rtree::distance::{EuclideanDistance, SliceGeometryAccessor}; + /// use geo_index::rtree::sort::HilbertSort; + /// use geo::{Point, Geometry}; + /// + /// // Create an RTree + /// let mut builder = RTreeBuilder::::new(3); + /// builder.add(0., 0., 2., 2.); + /// builder.add(5., 5., 7., 7.); + /// builder.add(10., 10., 12., 12.); + /// let tree = builder.finish::(); + /// + /// // Example geometries + /// let geometries: Vec> = vec![ + /// Geometry::Point(Point::new(1.0, 1.0)), + /// Geometry::Point(Point::new(6.0, 6.0)), + /// Geometry::Point(Point::new(11.0, 11.0)), + /// ]; + /// + /// let metric = EuclideanDistance; + /// let accessor = SliceGeometryAccessor::new(&geometries); + /// let query_geom = Geometry::Point(Point::new(3.0, 3.0)); + /// let results = tree.neighbors_geometry(&query_geom, None, None, &metric, &accessor); + /// ``` + fn neighbors_geometry + ?Sized, A: GeometryAccessor + ?Sized>( + &self, + query_geometry: &geo::Geometry, + max_results: Option, + max_distance: Option, + distance_metric: &M, + accessor: &A, + ) -> Vec { + let boxes = self.boxes(); + let indices = self.indices(); + let max_distance = max_distance.unwrap_or(distance_metric.max_distance()); + + // Get the bounding box of the query geometry + let bounds = query_geometry.bounding_rect(); + let (query_min_x, query_min_y, query_max_x, query_max_y) = if let Some(rect) = bounds { + let min = rect.min(); + let max = rect.max(); + ( + N::from_f64(min.x).unwrap_or(N::zero()), + N::from_f64(min.y).unwrap_or(N::zero()), + N::from_f64(max.x).unwrap_or(N::zero()), + N::from_f64(max.y).unwrap_or(N::zero()), + ) + } else { + // If no bounding box, use origin + (N::zero(), N::zero(), N::zero(), N::zero()) + }; + + let mut outer_node_index = Some(boxes.len() - 4); + let mut queue = BinaryHeap::new(); + let mut results: Vec = vec![]; + + 'outer: while let Some(node_index) = outer_node_index { + // find the end index of the node + let end = (node_index + self.node_size() as usize * 4) + .min(upper_bound(node_index, self.level_bounds())); + + // add child nodes to the queue + for pos in (node_index..end).step_by(4) { + let index = indices.get(pos >> 2); + + let dist = if node_index >= self.num_items() as usize * 4 { + // For internal nodes, use bbox-to-bbox distance as approximation + let center_x = (query_min_x + query_max_x) / (N::one() + N::one()); + let center_y = (query_min_y + query_max_y) / (N::one() + N::one()); + + distance_metric.distance_to_bbox( + center_x, + center_y, + boxes[pos], + boxes[pos + 1], + boxes[pos + 2], + boxes[pos + 3], + ) + } else { + // For leaf items, use geometry-to-geometry distance + if let Some(item_geom) = accessor.get_geometry(index) { + distance_metric.distance_to_geometry(query_geometry, item_geom) + } else { + distance_metric.max_distance() + } + }; + + if dist > max_distance { + continue; + } + + if node_index >= self.num_items() as usize * 4 { + // node (use even id) + queue.push(Reverse(NeighborNode { + id: index << 1, + dist, + })); + } else { + // leaf item (use odd id) + queue.push(Reverse(NeighborNode { + id: (index << 1) + 1, + dist, + })); + } + } + + // pop items from the queue + while !queue.is_empty() && queue.peek().is_some_and(|val| (val.0.id & 1) != 0) { + let dist = queue.peek().unwrap().0.dist; + if dist > max_distance { + break 'outer; + } + let item = queue.pop().unwrap(); + results.push((item.0.id >> 1).try_into().unwrap()); + if max_results.is_some_and(|max_results| results.len() == max_results) { + break 'outer; + } + } + + if let Some(item) = queue.pop() { + outer_node_index = Some(item.0.id >> 1); + } else { + outer_node_index = None; + } + } + + results + } + /// Returns an iterator over the indexes of objects in this and another tree that intersect. /// /// Each returned object is of the form `(u32, u32)`, where the first is the positional @@ -345,4 +560,368 @@ mod test { assert_eq!(results, expected); } } + + mod distance_metrics { + use crate::rtree::distance::{EuclideanDistance, HaversineDistance, SpheroidDistance}; + use crate::rtree::sort::HilbertSort; + use crate::rtree::{RTreeBuilder, RTreeIndex}; + + #[test] + fn test_euclidean_distance_neighbors() { + let mut builder = RTreeBuilder::::new(3); + builder.add(0., 0., 1., 1.); + builder.add(2., 2., 3., 3.); + builder.add(4., 4., 5., 5.); + let tree = builder.finish::(); + + let euclidean = EuclideanDistance; + let results = tree.neighbors_with_distance(0., 0., None, None, &euclidean); + + // Should return items in order of distance from (0,0) + assert_eq!(results, vec![0, 1, 2]); + } + + #[test] + fn test_haversine_distance_neighbors() { + let mut builder = RTreeBuilder::::new(3); + // Add some geographic points (longitude, latitude) + builder.add(-74.0, 40.7, -74.0, 40.7); // New York + builder.add(-0.1, 51.5, -0.1, 51.5); // London + builder.add(139.7, 35.7, 139.7, 35.7); // Tokyo + let tree = builder.finish::(); + + let haversine = HaversineDistance::default(); + let results = tree.neighbors_with_distance(-74.0, 40.7, None, None, &haversine); + + // From New York, should find New York first, then London, then Tokyo + assert_eq!(results, vec![0, 1, 2]); + } + + #[test] + fn test_spheroid_distance_neighbors() { + let mut builder = RTreeBuilder::::new(3); + // Add some geographic points (longitude, latitude) + builder.add(-74.0, 40.7, -74.0, 40.7); // New York + builder.add(-0.1, 51.5, -0.1, 51.5); // London + builder.add(139.7, 35.7, 139.7, 35.7); // Tokyo + let tree = builder.finish::(); + + let spheroid = SpheroidDistance; + let results = tree.neighbors_with_distance(-74.0, 40.7, None, None, &spheroid); + + // From New York, should find New York first, then London, then Tokyo + assert_eq!(results, vec![0, 1, 2]); + } + + #[test] + fn test_backward_compatibility() { + let mut builder = RTreeBuilder::::new(3); + builder.add(0., 0., 1., 1.); + builder.add(2., 2., 3., 3.); + builder.add(4., 4., 5., 5.); + let tree = builder.finish::(); + + // Test that original neighbors method still works + let results_original = tree.neighbors(0., 0., None, None); + + // Test that new method with Euclidean distance gives same results + let euclidean = EuclideanDistance; + let results_new = tree.neighbors_with_distance(0., 0., None, None, &euclidean); + + assert_eq!(results_original, results_new); + } + + #[test] + fn test_max_distance_filtering() { + let mut builder = RTreeBuilder::::new(3); + builder.add(0., 0., 1., 1.); + builder.add(2., 2., 3., 3.); + builder.add(10., 10., 11., 11.); + let tree = builder.finish::(); + + let euclidean = EuclideanDistance; + // Only find neighbors within distance 5 + let results = tree.neighbors_with_distance(0., 0., None, Some(5.0), &euclidean); + + // Should only find first two items, not the distant third one + assert_eq!(results.len(), 2); + assert_eq!(results, vec![0, 1]); + } + + #[test] + fn test_geometry_neighbors_euclidean() { + use crate::r#type::IndexableNum; + use crate::rtree::distance::{DistanceMetric, SliceGeometryAccessor}; + use geo::algorithm::{Distance, Euclidean}; + use geo::{Geometry, Point}; + + let mut builder = RTreeBuilder::::new(3); + builder.add(0., 0., 2., 2.); // Item 0 + builder.add(5., 5., 7., 7.); // Item 1 + builder.add(10., 10., 12., 12.); // Item 2 + let tree = builder.finish::(); + + // Geometries corresponding to the bboxes + let geometries: Vec> = vec![ + Geometry::Point(Point::new(1.0, 1.0)), // Item 0 + Geometry::Point(Point::new(6.0, 6.0)), // Item 1 + Geometry::Point(Point::new(11.0, 11.0)), // Item 2 + ]; + + struct SimpleMetric; + impl DistanceMetric for SimpleMetric { + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { + let dx = x2 - x1; + let dy = y2 - y1; + (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) + } + fn distance_to_bbox( + &self, + x: N, + y: N, + min_x: N, + min_y: N, + max_x: N, + max_y: N, + ) -> N { + let dx = if x < min_x { + min_x - x + } else if x > max_x { + x - max_x + } else { + N::zero() + }; + let dy = if y < min_y { + min_y - y + } else if y > max_y { + y - max_y + } else { + N::zero() + }; + (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) + } + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) + } + } + + let query_geom = Geometry::Point(Point::new(3.0, 3.0)); + let metric = SimpleMetric; + let accessor = SliceGeometryAccessor::new(&geometries); + let results = tree.neighbors_geometry(&query_geom, None, None, &metric, &accessor); + + // Item 0 should be closest to query point (3,3) + assert_eq!(results[0], 0); + assert_eq!(results[1], 1); + assert_eq!(results[2], 2); + } + + #[test] + fn test_geometry_neighbors_linestring() { + use crate::r#type::IndexableNum; + use crate::rtree::distance::{DistanceMetric, SliceGeometryAccessor}; + use geo::algorithm::{Distance, Euclidean}; + use geo::{Geometry, LineString, Point}; + use geo_types::coord; + + let mut builder = RTreeBuilder::::new(3); + builder.add(0., 0., 10., 0.); // Item 0 - horizontal line + builder.add(5., 5., 15., 5.); // Item 1 - horizontal line higher up + builder.add(0., 10., 10., 10.); // Item 2 - horizontal line at top + let tree = builder.finish::(); + + // Geometries corresponding to the bboxes + let geometries: Vec> = vec![ + Geometry::LineString(LineString::new(vec![ + coord! { x: 0.0, y: 0.0 }, + coord! { x: 10.0, y: 0.0 }, + ])), + Geometry::LineString(LineString::new(vec![ + coord! { x: 5.0, y: 5.0 }, + coord! { x: 15.0, y: 5.0 }, + ])), + Geometry::LineString(LineString::new(vec![ + coord! { x: 0.0, y: 10.0 }, + coord! { x: 10.0, y: 10.0 }, + ])), + ]; + + struct SimpleMetric; + impl DistanceMetric for SimpleMetric { + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { + let dx = x2 - x1; + let dy = y2 - y1; + (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) + } + fn distance_to_bbox( + &self, + x: N, + y: N, + min_x: N, + min_y: N, + max_x: N, + max_y: N, + ) -> N { + let dx = if x < min_x { + min_x - x + } else if x > max_x { + x - max_x + } else { + N::zero() + }; + let dy = if y < min_y { + min_y - y + } else if y > max_y { + y - max_y + } else { + N::zero() + }; + (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) + } + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) + } + } + + let query_geom = Geometry::Point(Point::new(5.0, 2.0)); + let metric = SimpleMetric; + let accessor = SliceGeometryAccessor::new(&geometries); + let results = tree.neighbors_geometry(&query_geom, None, None, &metric, &accessor); + + // Item 0 (bottom line) should be closest to point (5, 2) + assert_eq!(results[0], 0); + } + + #[test] + fn test_geometry_neighbors_with_max_results() { + use crate::r#type::IndexableNum; + use crate::rtree::distance::{DistanceMetric, SliceGeometryAccessor}; + use geo::algorithm::{Distance, Euclidean}; + use geo::{Geometry, Point}; + + let mut builder = RTreeBuilder::::new(5); + for i in 0..5 { + let x = (i * 3) as f64; + builder.add(x, x, x + 1., x + 1.); + } + let tree = builder.finish::(); + + // Create geometries for each bbox + let geometries: Vec> = (0..5) + .map(|i| { + let x = (i * 3) as f64; + Geometry::Point(Point::new(x + 0.5, x + 0.5)) + }) + .collect(); + + struct SimpleMetric; + impl DistanceMetric for SimpleMetric { + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { + let dx = x2 - x1; + let dy = y2 - y1; + (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) + } + fn distance_to_bbox( + &self, + x: N, + y: N, + min_x: N, + min_y: N, + max_x: N, + max_y: N, + ) -> N { + let dx = if x < min_x { + min_x - x + } else if x > max_x { + x - max_x + } else { + N::zero() + }; + let dy = if y < min_y { + min_y - y + } else if y > max_y { + y - max_y + } else { + N::zero() + }; + (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) + } + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) + } + } + + let query_geom = Geometry::Point(Point::new(5.0, 5.0)); + let metric = SimpleMetric; + let accessor = SliceGeometryAccessor::new(&geometries); + let results = tree.neighbors_geometry(&query_geom, Some(3), None, &metric, &accessor); + + assert_eq!(results.len(), 3); + // Should get the 3 closest items + } + + #[test] + fn test_geometry_neighbors_haversine() { + use crate::r#type::IndexableNum; + use crate::rtree::distance::{DistanceMetric, SliceGeometryAccessor}; + use geo::algorithm::{Centroid, Distance, Haversine}; + use geo::{Geometry, Point}; + + let mut builder = RTreeBuilder::::new(3); + // Geographic bounding boxes (lon, lat) + builder.add(-74.1, 40.6, -74.0, 40.7); // New York area + builder.add(-0.2, 51.4, -0.1, 51.5); // London area + builder.add(139.6, 35.6, 139.7, 35.7); // Tokyo area + let tree = builder.finish::(); + + let geometries: Vec> = vec![ + Geometry::Point(Point::new(-74.0, 40.7)), // New York + Geometry::Point(Point::new(-0.1, 51.5)), // London + Geometry::Point(Point::new(139.7, 35.7)), // Tokyo + ]; + + struct HaversineMetric; + impl DistanceMetric for HaversineMetric { + fn distance(&self, lon1: N, lat1: N, lon2: N, lat2: N) -> N { + let p1 = Point::new(lon1.to_f64().unwrap_or(0.0), lat1.to_f64().unwrap_or(0.0)); + let p2 = Point::new(lon2.to_f64().unwrap_or(0.0), lat2.to_f64().unwrap_or(0.0)); + N::from_f64(Haversine.distance(p1, p2)).unwrap_or(N::max_value()) + } + fn distance_to_bbox( + &self, + lon: N, + lat: N, + min_lon: N, + min_lat: N, + max_lon: N, + max_lat: N, + ) -> N { + let lon_f = lon.to_f64().unwrap_or(0.0); + let lat_f = lat.to_f64().unwrap_or(0.0); + let min_lon_f = min_lon.to_f64().unwrap_or(0.0); + let min_lat_f = min_lat.to_f64().unwrap_or(0.0); + let max_lon_f = max_lon.to_f64().unwrap_or(0.0); + let max_lat_f = max_lat.to_f64().unwrap_or(0.0); + let closest_lon = lon_f.clamp(min_lon_f, max_lon_f); + let closest_lat = lat_f.clamp(min_lat_f, max_lat_f); + let point = Point::new(lon_f, lat_f); + let closest_point = Point::new(closest_lon, closest_lat); + N::from_f64(Haversine.distance(point, closest_point)).unwrap_or(N::max_value()) + } + fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { + let c1 = geom1.centroid().unwrap_or(Point::new(0.0, 0.0)); + let c2 = geom2.centroid().unwrap_or(Point::new(0.0, 0.0)); + N::from_f64(Haversine.distance(c1, c2)).unwrap_or(N::max_value()) + } + } + + let query_geom = Geometry::Point(Point::new(-74.0, 40.7)); // New York + let metric = HaversineMetric; + let accessor = SliceGeometryAccessor::new(&geometries); + let results = tree.neighbors_geometry(&query_geom, None, None, &metric, &accessor); + + // New York should be closest (distance 0) + assert_eq!(results[0], 0); + } + } } diff --git a/src/type.rs b/src/type.rs index 3330f5e..ae4c6f1 100644 --- a/src/type.rs +++ b/src/type.rs @@ -13,12 +13,35 @@ use crate::GeoIndexError; /// JavaScript ([rtree](https://github.com/mourner/flatbush), /// [kdtree](https://github.com/mourner/kdbush)) pub trait IndexableNum: - private::Sealed + Num + NumCast + PartialOrd + Debug + Send + Sync + bytemuck::Pod + Bounded +private::Sealed + Num + NumCast + PartialOrd + Debug + Send + Sync + bytemuck::Pod + Bounded { /// The type index to match the array order of `ARRAY_TYPES` in flatbush JS const TYPE_INDEX: u8; /// The number of bytes per element const BYTES_PER_ELEMENT: usize; + + /// Convert to f64 for distance calculations + fn to_f64(self) -> Option { + NumCast::from(self) + } + + /// Convert from f64 for distance calculations + fn from_f64(value: f64) -> Option { + NumCast::from(value) + } + + /// Get the square root of this value + fn sqrt(self) -> Option { + self.to_f64() + .and_then(|value| { + if value >= 0.0 { + Some(value.sqrt()) + } else { + None + } + }) + .and_then(NumCast::from) + } } impl IndexableNum for i8 { @@ -122,7 +145,7 @@ impl CoordType { u32::TYPE_INDEX => CoordType::UInt32, f32::TYPE_INDEX => CoordType::Float32, f64::TYPE_INDEX => CoordType::Float64, - t => return Err(GeoIndexError::General(format!("Unexpected type {}.", t))), + t => return Err(GeoIndexError::General(format!("Unexpected type {t}."))), }; Ok(result) } From 891bce646b0d8fc973fd7c8eb99eee7f71fb24f1 Mon Sep 17 00:00:00 2001 From: zhangfengcdt Date: Wed, 8 Oct 2025 08:57:23 -0700 Subject: [PATCH 2/5] fix cargo fmt --- src/type.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/type.rs b/src/type.rs index ae4c6f1..5a773d4 100644 --- a/src/type.rs +++ b/src/type.rs @@ -13,7 +13,7 @@ use crate::GeoIndexError; /// JavaScript ([rtree](https://github.com/mourner/flatbush), /// [kdtree](https://github.com/mourner/kdbush)) pub trait IndexableNum: -private::Sealed + Num + NumCast + PartialOrd + Debug + Send + Sync + bytemuck::Pod + Bounded + private::Sealed + Num + NumCast + PartialOrd + Debug + Send + Sync + bytemuck::Pod + Bounded { /// The type index to match the array order of `ARRAY_TYPES` in flatbush JS const TYPE_INDEX: u8; From 14a909e4ebed1db3907bb3e57c6dd22bfd93eac6 Mon Sep 17 00:00:00 2001 From: zhangfengcdt Date: Wed, 8 Oct 2025 09:00:13 -0700 Subject: [PATCH 3/5] fix clippy fmt --- src/type.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/type.rs b/src/type.rs index 5a773d4..aa79f33 100644 --- a/src/type.rs +++ b/src/type.rs @@ -145,7 +145,7 @@ impl CoordType { u32::TYPE_INDEX => CoordType::UInt32, f32::TYPE_INDEX => CoordType::Float32, f64::TYPE_INDEX => CoordType::Float64, - t => return Err(GeoIndexError::General(format!("Unexpected type {t}."))), + t => return Err(GeoIndexError::General(format!("Unexpected type {}.", t))), }; Ok(result) } From 615aa209583d1c85f3b746b45e7f80b6013ed3a2 Mon Sep 17 00:00:00 2001 From: zhangfengcdt Date: Tue, 21 Oct 2025 08:59:07 -0700 Subject: [PATCH 4/5] Address PR review comments - Feature Flag Implementation (geo-types pattern) - Removed top-level re-exports from rtree/mod.rs - Removed redundant geo-types dependency - Removed duplicate axis_dist function (now in trait.rs) - Made entire distance module conditional on use-geo_0_31 feature - Trait architecture change: created SimpleDistanceMetric (no geo dependency) - Tests run successfully with and without use-geo_0_31 feature flag --- Cargo.lock | 639 ++++++++++++++++++++++------------------- Cargo.toml | 10 +- benches/distance.rs | 11 +- src/rtree/distance.rs | 69 ++--- src/rtree/mod.rs | 4 +- src/rtree/trait.rs | 150 +++++++--- src/rtree/traversal.rs | 4 +- 7 files changed, 493 insertions(+), 394 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fa90674..ad4d7ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aes" @@ -21,9 +21,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -42,9 +42,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "approx" @@ -57,24 +57,18 @@ dependencies = [ [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" dependencies = [ "derive_arbitrary", ] [[package]] name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bitflags" -version = "2.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "block-buffer" @@ -87,15 +81,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.14.0" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] name = "byteorder" @@ -105,22 +99,20 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bzip2" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" dependencies = [ "bzip2-sys", - "libc", ] [[package]] name = "bzip2-sys" -version = "0.1.11+1.0.8" +version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ "cc", - "libc", "pkg-config", ] @@ -132,10 +124,11 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.6" +version = "1.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -143,15 +136,15 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "ciborium" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", @@ -160,15 +153,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", @@ -186,18 +179,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.6" +version = "4.5.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.4.6" +version = "4.5.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0" dependencies = [ "anstyle", "clap_lex", @@ -205,9 +198,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "constant_time_eq" @@ -217,18 +210,18 @@ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] @@ -241,9 +234,9 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -286,26 +279,21 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] @@ -314,6 +302,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.6" @@ -326,24 +320,24 @@ dependencies = [ [[package]] name = "deflate64" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" +checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" [[package]] name = "deranged" -version = "0.3.11" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", ] [[package]] name = "derive_arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", @@ -384,31 +378,27 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "errno" -version = "0.3.5" +name = "find-msvc-tools" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" -dependencies = [ - "libc", - "windows-sys", -] +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "flate2" -version = "1.0.35" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" dependencies = [ "crc32fast", "miniz_oxide", @@ -428,9 +418,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -463,13 +453,12 @@ dependencies = [ "float_next_after", "geo", "geo-traits", - "geo-types", "geozero", "num-traits", "rand", "rayon", "rstar", - "thiserror 1.0.49", + "thiserror 1.0.69", "tinyvec", "zip", ] @@ -498,23 +487,23 @@ dependencies = [ [[package]] name = "geographiclib-rs" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e5ed84f8089c70234b0a8e0aedb6dc733671612ddc0d37c6066052f9781960" +checksum = "f611040a2bb37eaa29a78a128d1e92a378a03e0b6e66ae27398d42b1ba9a7841" dependencies = [ "libm", ] [[package]] name = "geojson" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d728c1df1fbf328d74151efe6cb0586f79ee813346ea981add69bd22c9241b" +checksum = "e26f3c45b36fccc9cf2805e61d4da6bc4bbd5a3a9589b01afa3a40eff703bd79" dependencies = [ "log", "serde", "serde_json", - "thiserror 1.0.49", + "thiserror 2.0.17", ] [[package]] @@ -528,26 +517,45 @@ dependencies = [ "log", "scroll", "serde_json", - "thiserror 1.0.49", + "thiserror 1.0.69", "wkt", ] [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + [[package]] name = "half" -version = "1.8.2" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] [[package]] name = "hash32" @@ -560,15 +568,21 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + [[package]] name = "heapless" version = "0.8.0" @@ -581,9 +595,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hmac" @@ -611,9 +625,9 @@ checksum = "9190f86706ca38ac8add223b2aed8b1330002b5cdbbce28fb58b10914d38fc27" [[package]] name = "i_overlay" -version = "4.0.2" +version = "4.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c291f5c15a84f0e9126ff050719c5ca50227b27947b52526ee8370287dfc9e" +checksum = "0fcccbd4e4274e0f80697f5fbc6540fdac533cce02f2081b328e68629cce24f9" dependencies = [ "i_float", "i_key_sort", @@ -639,32 +653,32 @@ checksum = "35e6d558e6d4c7b82bc51d9c771e7a927862a161a7d87bf2b0541450e0e20915" [[package]] name = "indexmap" -version = "2.7.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.0", ] [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "generic-array", ] [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", - "rustix", - "windows-sys", + "libc", + "windows-sys 0.59.0", ] [[package]] @@ -687,57 +701,47 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.169" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - -[[package]] -name = "linux-raw-sys" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" - -[[package]] -name = "lockfree-object-pool" -version = "0.1.6" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "log" -version = "0.4.22" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lzma-rs" @@ -750,27 +754,30 @@ dependencies = [ ] [[package]] -name = "memchr" -version = "2.7.4" +name = "lzma-sys" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] [[package]] -name = "memoffset" -version = "0.9.0" +name = "memchr" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "miniz_oxide" -version = "0.8.2" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -781,9 +788,9 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -797,9 +804,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "pbkdf2" @@ -813,15 +820,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plotters" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -832,15 +839,15 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] @@ -853,31 +860,37 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -905,14 +918,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.16", ] [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -920,9 +933,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -930,9 +943,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaac441002f822bc9705a681810a4dd2963094b9ca0ddc41cb963a4c189189ea" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -942,9 +955,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.2" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5011c7e263a695dc8ca064cddb722af1be54e517a280b12a5356f98366899e5d" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -953,21 +966,21 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "robust" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf4a6aa5f6d6888f39e980649f3ad6b666acdce1d78e95b8a2cb076e687ae30" +checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839" [[package]] name = "rstar" -version = "0.12.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133315eb94c7b1e8d0cb097e5a710d850263372fd028fff18969de708afc7008" +checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb" dependencies = [ "heapless", "num-traits", @@ -975,23 +988,16 @@ dependencies = [ ] [[package]] -name = "rustix" -version = "0.38.19" +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -1002,12 +1008,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "scroll" version = "0.11.0" @@ -1016,18 +1016,28 @@ checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" [[package]] name = "serde" -version = "1.0.189" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.189" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1036,13 +1046,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", + "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -1076,11 +1088,11 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "spade" -version = "2.13.1" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ece03ff43cd2a9b57ebf776ea5e78bd30b3b4185a619f041079f4109f385034" +checksum = "fb313e1c8afee5b5647e00ee0fe6855e3d529eb863a0fdae1d60006c4d1e9990" dependencies = [ - "hashbrown", + "hashbrown 0.15.5", "num-traits", "robust", "smallvec", @@ -1088,9 +1100,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "subtle" @@ -1100,9 +1112,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.92" +version = "2.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ae51629bf965c5c098cc9e87908a3df5301051a9e087d6f9bef5c9771ed126" +checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b" dependencies = [ "proc-macro2", "quote", @@ -1111,27 +1123,27 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.49" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.49", + "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" -version = "2.0.9" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.9", + "thiserror-impl 2.0.17", ] [[package]] name = "thiserror-impl" -version = "1.0.49" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -1140,9 +1152,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.9" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -1151,9 +1163,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "num-conv", @@ -1164,9 +1176,9 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "tinytemplate" @@ -1180,9 +1192,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -1195,15 +1207,15 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "typenum" -version = "1.17.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" [[package]] name = "version_check" @@ -1213,9 +1225,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -1223,29 +1235,40 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", + "once_cell", + "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -1254,9 +1277,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1264,9 +1287,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -1277,69 +1300,66 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "winapi" -version = "0.3.9" +name = "winapi-util" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "windows-sys 0.61.2", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "winapi-util" -version = "0.1.6" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "winapi", + "windows-targets", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets", + "windows-link", ] [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", + "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", @@ -1348,45 +1368,57 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "wkt" @@ -1397,24 +1429,32 @@ dependencies = [ "geo-types", "log", "num-traits", - "thiserror 1.0.49", + "thiserror 1.0.69", +] + +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", ] [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", @@ -1423,9 +1463,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] @@ -1443,9 +1483,9 @@ dependencies = [ [[package]] name = "zip" -version = "2.2.2" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" dependencies = [ "aes", "arbitrary", @@ -1456,15 +1496,16 @@ dependencies = [ "deflate64", "displaydoc", "flate2", + "getrandom 0.3.4", "hmac", "indexmap", "lzma-rs", "memchr", "pbkdf2", - "rand", "sha1", - "thiserror 2.0.9", + "thiserror 2.0.17", "time", + "xz2", "zeroize", "zopfli", "zstd", @@ -1472,41 +1513,39 @@ dependencies = [ [[package]] name = "zopfli" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" dependencies = [ "bumpalo", "crc32fast", - "lockfree-object-pool", "log", - "once_cell", "simd-adler32", ] [[package]] name = "zstd" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.1" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 822d209..8c5c9c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,12 +12,15 @@ categories = ["data-structures", "algorithms", "science::geo"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = [] +use-geo_0_31 = ["geo_0_31"] + [dependencies] bytemuck = "1" float_next_after = "1" +geo_0_31 = { package = "geo", version = "0.31", optional = true } geo-traits = "0.3" -geo-types = "0.7.17" -geo = "0.31.0" num-traits = "0.2" rayon = { version = "1.8.0", optional = true } thiserror = "1" @@ -25,7 +28,7 @@ tinyvec = { version = "1", features = ["alloc", "rustc_1_40"] } [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } -geo = "0.31.0" +geo_0_31 = { package = "geo", version = "0.31.0" } geozero = { version = "0.14", features = ["with-wkb", "with-geo"] } rstar = "0.12" zip = "2.2.2" @@ -38,3 +41,4 @@ harness = false [[bench]] name = "distance" harness = false +required-features = ["use-geo_0_31"] diff --git a/benches/distance.rs b/benches/distance.rs index 0c880cc..23c291d 100644 --- a/benches/distance.rs +++ b/benches/distance.rs @@ -1,20 +1,19 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use geo::algorithm::{Distance, Euclidean}; -use geo::{Geometry, LineString, Point, Polygon}; +use geo_0_31::algorithm::{Distance, Euclidean}; +use geo_0_31::{coord, Geometry, LineString, Point, Polygon}; use geo_index::rtree::distance::{ DistanceMetric, EuclideanDistance, HaversineDistance, SliceGeometryAccessor, SpheroidDistance, }; use geo_index::rtree::sort::HilbertSort; -use geo_index::rtree::{RTreeBuilder, RTreeIndex}; +use geo_index::rtree::{RTreeBuilder, RTreeIndex, SimpleDistanceMetric}; use geo_index::IndexableNum; -use geo_types::coord; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; // Simple distance metric for benchmarks struct SimpleMetric; -impl DistanceMetric for SimpleMetric { +impl SimpleDistanceMetric for SimpleMetric { fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { let dx = x2 - x1; let dy = y2 - y1; @@ -38,7 +37,9 @@ impl DistanceMetric for SimpleMetric { }; (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) } +} +impl DistanceMetric for SimpleMetric { fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) } diff --git a/src/rtree/distance.rs b/src/rtree/distance.rs index fd353cb..7d0afd3 100644 --- a/src/rtree/distance.rs +++ b/src/rtree/distance.rs @@ -4,29 +4,24 @@ //! including Euclidean, Haversine, and Spheroid distance calculations. use crate::r#type::IndexableNum; -use geo::algorithm::{Distance, Euclidean, Geodesic, Haversine}; -use geo::{Geometry, Point}; +use crate::rtree::r#trait::{axis_dist, SimpleDistanceMetric}; +use geo_0_31::algorithm::{Distance, Euclidean, Geodesic, Haversine}; +use geo_0_31::{Geometry, Point}; pub use crate::rtree::r#trait::GeometryAccessor; /// A trait for calculating distances between geometries and points. -pub trait DistanceMetric { - /// Calculate the distance between two points (x1, y1) and (x2, y2). - fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N; - - /// Calculate the distance from a point to a bounding box. - /// This is used for spatial index optimization during tree traversal. - fn distance_to_bbox(&self, x: N, y: N, min_x: N, min_y: N, max_x: N, max_y: N) -> N; - +/// +/// This trait extends `SimpleDistanceMetric` to add geometry-to-geometry distance calculations. +pub trait DistanceMetric: SimpleDistanceMetric { /// Calculate the distance between two geometries. /// This method is used by geometry-based neighbor searches to compute the actual /// distance between a query geometry and an item geometry. + /// + /// TODO: Consider changing to accept `&impl GeometryTrait` instead of concrete + /// `Geometry` type for better flexibility and integration with geo-traits. + /// This would be a non-breaking change since Geometry implements GeometryTrait. fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N; - - /// Return the maximum distance value for this metric. - fn max_distance(&self) -> N { - N::max_value() - } } /// Euclidean distance metric. @@ -37,7 +32,7 @@ pub trait DistanceMetric { #[derive(Debug, Clone, Copy, Default)] pub struct EuclideanDistance; -impl DistanceMetric for EuclideanDistance { +impl SimpleDistanceMetric for EuclideanDistance { #[inline] fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { let p1 = Point::new(x1.to_f64().unwrap_or(0.0), y1.to_f64().unwrap_or(0.0)); @@ -51,7 +46,9 @@ impl DistanceMetric for EuclideanDistance { let dy = axis_dist(y, min_y, max_y); (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) } +} +impl DistanceMetric for EuclideanDistance { fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) } @@ -84,7 +81,7 @@ impl HaversineDistance { } } -impl DistanceMetric for HaversineDistance { +impl SimpleDistanceMetric for HaversineDistance { fn distance(&self, lon1: N, lat1: N, lon2: N, lat2: N) -> N { let p1 = Point::new(lon1.to_f64().unwrap_or(0.0), lat1.to_f64().unwrap_or(0.0)); let p2 = Point::new(lon2.to_f64().unwrap_or(0.0), lat2.to_f64().unwrap_or(0.0)); @@ -115,10 +112,12 @@ impl DistanceMetric for HaversineDistance { let closest_point = Point::new(closest_lon, closest_lat); N::from_f64(Haversine.distance(point, closest_point)).unwrap_or(N::max_value()) } +} +impl DistanceMetric for HaversineDistance { fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { // For Haversine, use centroid-to-centroid distance as approximation - use geo::algorithm::Centroid; + use geo_0_31::algorithm::Centroid; let c1 = geom1.centroid().unwrap_or(Point::new(0.0, 0.0)); let c2 = geom2.centroid().unwrap_or(Point::new(0.0, 0.0)); N::from_f64(Haversine.distance(c1, c2)).unwrap_or(N::max_value()) @@ -141,7 +140,7 @@ impl SpheroidDistance { } } -impl DistanceMetric for SpheroidDistance { +impl SimpleDistanceMetric for SpheroidDistance { fn distance(&self, lon1: N, lat1: N, lon2: N, lat2: N) -> N { let p1 = Point::new(lon1.to_f64().unwrap_or(0.0), lat1.to_f64().unwrap_or(0.0)); let p2 = Point::new(lon2.to_f64().unwrap_or(0.0), lat2.to_f64().unwrap_or(0.0)); @@ -172,10 +171,12 @@ impl DistanceMetric for SpheroidDistance { let closest_point = Point::new(closest_lon, closest_lat); N::from_f64(Geodesic.distance(point, closest_point)).unwrap_or(N::max_value()) } +} +impl DistanceMetric for SpheroidDistance { fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { // For Geodesic, use centroid-to-centroid distance as approximation - use geo::algorithm::Centroid; + use geo_0_31::algorithm::Centroid; let c1 = geom1.centroid().unwrap_or(Point::new(0.0, 0.0)); let c2 = geom2.centroid().unwrap_or(Point::new(0.0, 0.0)); N::from_f64(Geodesic.distance(c1, c2)).unwrap_or(N::max_value()) @@ -189,7 +190,7 @@ impl DistanceMetric for SpheroidDistance { /// # Example /// ``` /// use geo_index::rtree::distance::{EuclideanDistance, SliceGeometryAccessor}; -/// use geo::{Geometry, Point}; +/// use geo_0_31::{Geometry, Point}; /// /// let geometries = vec![ /// Geometry::Point(Point::new(0.0, 0.0)), @@ -217,22 +218,10 @@ impl<'a> GeometryAccessor for SliceGeometryAccessor<'a> { } } -/// 1D distance from a value to a range. -#[inline] -fn axis_dist(k: N, min: N, max: N) -> N { - if k < min { - min - k - } else if k <= max { - N::zero() - } else { - k - max - } -} - #[cfg(test)] mod tests { use super::*; - use geo::LineString; + use geo_0_31::{coord, LineString}; #[test] fn test_euclidean_distance() { @@ -269,8 +258,8 @@ mod tests { // Test distance to line let line = Geometry::LineString(LineString::new(vec![ - geo_types::coord! { x: 0.0, y: 5.0 }, - geo_types::coord! { x: 10.0, y: 5.0 }, + coord! { x: 0.0, y: 5.0 }, + coord! { x: 10.0, y: 5.0 }, ])); let query = Geometry::Point(Point::new(0.0, 0.0)); let distance: f64 = Euclidean.distance(&query, &line); @@ -312,7 +301,7 @@ mod tests { } } - impl<'a, N: IndexableNum> DistanceMetric for WkbDistanceMetric<'a> { + impl<'a, N: IndexableNum> SimpleDistanceMetric for WkbDistanceMetric<'a> { fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { EuclideanDistance.distance(x1, y1, x2, y2) } @@ -320,7 +309,9 @@ mod tests { fn distance_to_bbox(&self, x: N, y: N, min_x: N, min_y: N, max_x: N, max_y: N) -> N { EuclideanDistance.distance_to_bbox(x, y, min_x, min_y, max_x, max_y) } + } + impl<'a, N: IndexableNum> DistanceMetric for WkbDistanceMetric<'a> { fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) } @@ -406,7 +397,7 @@ mod tests { } } - impl<'a, N: IndexableNum> DistanceMetric for CachedDistanceMetric<'a> { + impl<'a, N: IndexableNum> SimpleDistanceMetric for CachedDistanceMetric<'a> { fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { EuclideanDistance.distance(x1, y1, x2, y2) } @@ -414,7 +405,9 @@ mod tests { fn distance_to_bbox(&self, x: N, y: N, min_x: N, min_y: N, max_x: N, max_y: N) -> N { EuclideanDistance.distance_to_bbox(x, y, min_x, min_y, max_x, max_y) } + } + impl<'a, N: IndexableNum> DistanceMetric for CachedDistanceMetric<'a> { fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) } diff --git a/src/rtree/mod.rs b/src/rtree/mod.rs index 9897a1f..dd59622 100644 --- a/src/rtree/mod.rs +++ b/src/rtree/mod.rs @@ -56,6 +56,7 @@ mod builder; mod constants; +#[cfg(feature = "use-geo_0_31")] pub mod distance; mod index; pub mod sort; @@ -64,7 +65,6 @@ mod traversal; pub mod util; pub use builder::{RTreeBuilder, DEFAULT_RTREE_NODE_SIZE}; -pub use distance::{DistanceMetric, EuclideanDistance, HaversineDistance, SpheroidDistance}; pub use index::{RTree, RTreeMetadata, RTreeRef}; -pub use r#trait::RTreeIndex; +pub use r#trait::{RTreeIndex, SimpleDistanceMetric}; pub use traversal::Node; diff --git a/src/rtree/trait.rs b/src/rtree/trait.rs index 20e35e9..9b8d05c 100644 --- a/src/rtree/trait.rs +++ b/src/rtree/trait.rs @@ -2,24 +2,44 @@ use std::cmp::Reverse; use std::collections::{BinaryHeap, VecDeque}; use std::vec; -use geo::algorithm::BoundingRect; -use geo::Geometry; +#[cfg(feature = "use-geo_0_31")] +use geo_0_31::algorithm::BoundingRect; +#[cfg(feature = "use-geo_0_31")] +use geo_0_31::Geometry; use geo_traits::{CoordTrait, RectTrait}; use crate::error::Result; use crate::indices::Indices; use crate::r#type::IndexableNum; -use crate::rtree::distance::{DistanceMetric, EuclideanDistance}; +#[cfg(feature = "use-geo_0_31")] +use crate::rtree::distance::DistanceMetric; use crate::rtree::index::{RTree, RTreeRef}; use crate::rtree::traversal::{IntersectionIterator, Node}; use crate::rtree::util::upper_bound; use crate::rtree::RTreeMetadata; use crate::GeoIndexError; +/// A simple distance metric trait that doesn't depend on geo. +/// +/// This trait is used for basic distance calculations without geometry support. +pub trait SimpleDistanceMetric { + /// Calculate the distance between two points (x1, y1) and (x2, y2). + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N; + + /// Calculate the distance from a point to a bounding box. + fn distance_to_bbox(&self, x: N, y: N, min_x: N, min_y: N, max_x: N, max_y: N) -> N; + + /// Return the maximum distance value for this metric. + fn max_distance(&self) -> N { + N::max_value() + } +} + /// A trait for accessing geometries by index. /// /// This trait allows different storage strategies for geometries (direct storage, /// WKB decoding, caching, etc.) to be used with spatial index queries. +#[cfg(feature = "use-geo_0_31")] pub trait GeometryAccessor { /// Get the geometry at the given index. /// @@ -182,33 +202,28 @@ pub trait RTreeIndex: Sized { max_results: Option, max_distance: Option, ) -> Vec { - // Use Euclidean distance by default for backward compatibility - let euclidean_distance = EuclideanDistance; - self.neighbors_with_distance(x, y, max_results, max_distance, &euclidean_distance) + // Use simple squared distance for backward compatibility + struct SimpleSquaredDistance; + impl SimpleDistanceMetric for SimpleSquaredDistance { + fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { + let dx = x2 - x1; + let dy = y2 - y1; + dx * dx + dy * dy + } + fn distance_to_bbox(&self, x: N, y: N, min_x: N, min_y: N, max_x: N, max_y: N) -> N { + let dx = axis_dist(x, min_x, max_x); + let dy = axis_dist(y, min_y, max_y); + dx * dx + dy * dy + } + } + let simple_distance = SimpleSquaredDistance; + self.neighbors_with_simple_distance(x, y, max_results, max_distance, &simple_distance) } - /// Search items in order of distance from the given point using a custom distance metric. + /// Search items in order of distance from the given point using a simple distance metric. /// - /// This method allows you to specify a custom distance calculation method, such as - /// Euclidean, Haversine, or Spheroid distance. - /// - /// ``` - /// use geo_index::rtree::{RTreeBuilder, RTreeIndex}; - /// use geo_index::rtree::distance::{EuclideanDistance, HaversineDistance}; - /// use geo_index::rtree::sort::HilbertSort; - /// - /// // Create an RTree with geographic coordinates (longitude, latitude) - /// let mut builder = RTreeBuilder::::new(3); - /// builder.add(-74.0, 40.7, -74.0, 40.7); // New York - /// builder.add(-0.1, 51.5, -0.1, 51.5); // London - /// builder.add(139.7, 35.7, 139.7, 35.7); // Tokyo - /// let tree = builder.finish::(); - /// - /// // Find nearest neighbors using Haversine distance (great-circle distance) - /// let haversine = HaversineDistance::default(); - /// let results = tree.neighbors_with_distance(-74.0, 40.7, Some(2), None, &haversine); - /// ``` - fn neighbors_with_distance + ?Sized>( + /// This is the base method for distance-based neighbor searches that works without the geo feature. + fn neighbors_with_simple_distance + ?Sized>( &self, x: N, y: N, @@ -286,6 +301,39 @@ pub trait RTreeIndex: Sized { results } + /// Search items in order of distance from the given point using a custom distance metric. + /// + /// This method allows you to specify a custom distance calculation method, such as + /// Euclidean, Haversine, or Spheroid distance. + /// + /// ``` + /// use geo_index::rtree::{RTreeBuilder, RTreeIndex}; + /// use geo_index::rtree::distance::{EuclideanDistance, HaversineDistance}; + /// use geo_index::rtree::sort::HilbertSort; + /// + /// // Create an RTree with geographic coordinates (longitude, latitude) + /// let mut builder = RTreeBuilder::::new(3); + /// builder.add(-74.0, 40.7, -74.0, 40.7); // New York + /// builder.add(-0.1, 51.5, -0.1, 51.5); // London + /// builder.add(139.7, 35.7, 139.7, 35.7); // Tokyo + /// let tree = builder.finish::(); + /// + /// // Find nearest neighbors using Haversine distance (great-circle distance) + /// let haversine = HaversineDistance::default(); + /// let results = tree.neighbors_with_distance(-74.0, 40.7, Some(2), None, &haversine); + /// ``` + #[cfg(feature = "use-geo_0_31")] + fn neighbors_with_distance + ?Sized>( + &self, + x: N, + y: N, + max_results: Option, + max_distance: Option, + distance_metric: &M, + ) -> Vec { + self.neighbors_with_simple_distance(x, y, max_results, max_distance, distance_metric) + } + /// Search items in order of distance from the given coordinate. fn neighbors_coord( &self, @@ -297,6 +345,7 @@ pub trait RTreeIndex: Sized { } /// Search items in order of distance from the given coordinate using a custom distance metric. + #[cfg(feature = "use-geo_0_31")] fn neighbors_coord_with_distance + ?Sized>( &self, coord: &impl CoordTrait, @@ -323,7 +372,7 @@ pub trait RTreeIndex: Sized { /// use geo_index::rtree::{RTreeBuilder, RTreeIndex}; /// use geo_index::rtree::distance::{EuclideanDistance, SliceGeometryAccessor}; /// use geo_index::rtree::sort::HilbertSort; - /// use geo::{Point, Geometry}; + /// use geo_0_31::{Point, Geometry}; /// /// // Create an RTree /// let mut builder = RTreeBuilder::::new(3); @@ -344,9 +393,10 @@ pub trait RTreeIndex: Sized { /// let query_geom = Geometry::Point(Point::new(3.0, 3.0)); /// let results = tree.neighbors_geometry(&query_geom, None, None, &metric, &accessor); /// ``` + #[cfg(feature = "use-geo_0_31")] fn neighbors_geometry + ?Sized, A: GeometryAccessor + ?Sized>( &self, - query_geometry: &geo::Geometry, + query_geometry: &Geometry, max_results: Option, max_distance: Option, distance_metric: &M, @@ -517,9 +567,8 @@ impl RTreeIndex for RTreeRef<'_, N> { } /// 1D distance from a value to a range. -#[allow(dead_code)] #[inline] -fn axis_dist(k: N, min: N, max: N) -> N { +pub(crate) fn axis_dist(k: N, min: N, max: N) -> N { if k < min { min - k } else if k <= max { @@ -561,8 +610,10 @@ mod test { } } + #[cfg(feature = "use-geo_0_31")] mod distance_metrics { use crate::rtree::distance::{EuclideanDistance, HaversineDistance, SpheroidDistance}; + use crate::rtree::r#trait::SimpleDistanceMetric; use crate::rtree::sort::HilbertSort; use crate::rtree::{RTreeBuilder, RTreeIndex}; @@ -649,11 +700,12 @@ mod test { } #[test] + #[cfg(feature = "use-geo_0_31")] fn test_geometry_neighbors_euclidean() { use crate::r#type::IndexableNum; use crate::rtree::distance::{DistanceMetric, SliceGeometryAccessor}; - use geo::algorithm::{Distance, Euclidean}; - use geo::{Geometry, Point}; + use geo_0_31::algorithm::{Distance, Euclidean}; + use geo_0_31::{Geometry, Point}; let mut builder = RTreeBuilder::::new(3); builder.add(0., 0., 2., 2.); // Item 0 @@ -669,7 +721,7 @@ mod test { ]; struct SimpleMetric; - impl DistanceMetric for SimpleMetric { + impl SimpleDistanceMetric for SimpleMetric { fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { let dx = x2 - x1; let dy = y2 - y1; @@ -700,6 +752,8 @@ mod test { }; (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) } + } + impl DistanceMetric for SimpleMetric { fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) } @@ -717,12 +771,12 @@ mod test { } #[test] + #[cfg(feature = "use-geo_0_31")] fn test_geometry_neighbors_linestring() { use crate::r#type::IndexableNum; use crate::rtree::distance::{DistanceMetric, SliceGeometryAccessor}; - use geo::algorithm::{Distance, Euclidean}; - use geo::{Geometry, LineString, Point}; - use geo_types::coord; + use geo_0_31::algorithm::{Distance, Euclidean}; + use geo_0_31::{coord, Geometry, LineString, Point}; let mut builder = RTreeBuilder::::new(3); builder.add(0., 0., 10., 0.); // Item 0 - horizontal line @@ -747,7 +801,7 @@ mod test { ]; struct SimpleMetric; - impl DistanceMetric for SimpleMetric { + impl SimpleDistanceMetric for SimpleMetric { fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { let dx = x2 - x1; let dy = y2 - y1; @@ -778,6 +832,8 @@ mod test { }; (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) } + } + impl DistanceMetric for SimpleMetric { fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) } @@ -793,11 +849,12 @@ mod test { } #[test] + #[cfg(feature = "use-geo_0_31")] fn test_geometry_neighbors_with_max_results() { use crate::r#type::IndexableNum; use crate::rtree::distance::{DistanceMetric, SliceGeometryAccessor}; - use geo::algorithm::{Distance, Euclidean}; - use geo::{Geometry, Point}; + use geo_0_31::algorithm::{Distance, Euclidean}; + use geo_0_31::{Geometry, Point}; let mut builder = RTreeBuilder::::new(5); for i in 0..5 { @@ -815,7 +872,7 @@ mod test { .collect(); struct SimpleMetric; - impl DistanceMetric for SimpleMetric { + impl SimpleDistanceMetric for SimpleMetric { fn distance(&self, x1: N, y1: N, x2: N, y2: N) -> N { let dx = x2 - x1; let dy = y2 - y1; @@ -846,6 +903,8 @@ mod test { }; (dx * dx + dy * dy).sqrt().unwrap_or(N::max_value()) } + } + impl DistanceMetric for SimpleMetric { fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) } @@ -861,11 +920,12 @@ mod test { } #[test] + #[cfg(feature = "use-geo_0_31")] fn test_geometry_neighbors_haversine() { use crate::r#type::IndexableNum; use crate::rtree::distance::{DistanceMetric, SliceGeometryAccessor}; - use geo::algorithm::{Centroid, Distance, Haversine}; - use geo::{Geometry, Point}; + use geo_0_31::algorithm::{Centroid, Distance, Haversine}; + use geo_0_31::{Geometry, Point}; let mut builder = RTreeBuilder::::new(3); // Geographic bounding boxes (lon, lat) @@ -881,7 +941,7 @@ mod test { ]; struct HaversineMetric; - impl DistanceMetric for HaversineMetric { + impl SimpleDistanceMetric for HaversineMetric { fn distance(&self, lon1: N, lat1: N, lon2: N, lat2: N) -> N { let p1 = Point::new(lon1.to_f64().unwrap_or(0.0), lat1.to_f64().unwrap_or(0.0)); let p2 = Point::new(lon2.to_f64().unwrap_or(0.0), lat2.to_f64().unwrap_or(0.0)); @@ -908,6 +968,8 @@ mod test { let closest_point = Point::new(closest_lon, closest_lat); N::from_f64(Haversine.distance(point, closest_point)).unwrap_or(N::max_value()) } + } + impl DistanceMetric for HaversineMetric { fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { let c1 = geom1.centroid().unwrap_or(Point::new(0.0, 0.0)); let c2 = geom2.centroid().unwrap_or(Point::new(0.0, 0.0)); diff --git a/src/rtree/traversal.rs b/src/rtree/traversal.rs index f2f2962..37cb3db 100644 --- a/src/rtree/traversal.rs +++ b/src/rtree/traversal.rs @@ -404,8 +404,8 @@ mod test_issue_42 { use crate::rtree::sort::HilbertSort; use crate::rtree::{RTreeBuilder, RTreeIndex}; - use geo::Polygon; - use geo::{BoundingRect, Geometry}; + use geo_0_31::Polygon; + use geo_0_31::{BoundingRect, Geometry}; use geozero::geo_types::GeoWriter; use geozero::geojson::read_geojson_fc; use rstar::primitives::GeomWithData; From 97c49f5cbd0097af3b1d7b67ea292ca2388d8430 Mon Sep 17 00:00:00 2001 From: zhangfengcdt Date: Mon, 3 Nov 2025 13:59:01 -0800 Subject: [PATCH 5/5] remove the vincenty (Geodesic) and GRS80 distance metrics --- benches/distance.rs | 24 +-------------- src/rtree/distance.rs | 72 ++----------------------------------------- src/rtree/trait.rs | 18 +---------- 3 files changed, 4 insertions(+), 110 deletions(-) diff --git a/benches/distance.rs b/benches/distance.rs index 23c291d..8e3a8cc 100644 --- a/benches/distance.rs +++ b/benches/distance.rs @@ -2,7 +2,7 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use geo_0_31::algorithm::{Distance, Euclidean}; use geo_0_31::{coord, Geometry, LineString, Point, Polygon}; use geo_index::rtree::distance::{ - DistanceMetric, EuclideanDistance, HaversineDistance, SliceGeometryAccessor, SpheroidDistance, + DistanceMetric, EuclideanDistance, HaversineDistance, SliceGeometryAccessor, }; use geo_index::rtree::sort::HilbertSort; use geo_index::rtree::{RTreeBuilder, RTreeIndex, SimpleDistanceMetric}; @@ -107,7 +107,6 @@ fn benchmark_distance_metrics(c: &mut Criterion) { let euclidean = EuclideanDistance; let haversine = HaversineDistance::default(); - let spheroid = SpheroidDistance::default(); // Benchmark neighbors_with_distance with different metrics let mut group = c.benchmark_group("neighbors_with_distance"); @@ -136,18 +135,6 @@ fn benchmark_distance_metrics(c: &mut Criterion) { }) }); - group.bench_with_input(BenchmarkId::new("spheroid", size), &size, |b, _| { - b.iter(|| { - tree.neighbors_with_distance( - query_point.x(), - query_point.y(), - Some(10), - None, - &spheroid, - ) - }) - }); - group.finish(); // Benchmark neighbors_geometry with different metrics @@ -167,7 +154,6 @@ fn benchmark_distance_metrics(c: &mut Criterion) { fn benchmark_distance_calculations(c: &mut Criterion) { let euclidean = EuclideanDistance; let haversine = HaversineDistance::default(); - let spheroid = SpheroidDistance::default(); let p1 = Point::new(-74.0, 40.7); // New York let p2 = Point::new(-0.1, 51.5); // London @@ -185,10 +171,6 @@ fn benchmark_distance_calculations(c: &mut Criterion) { b.iter(|| haversine.distance(p1.x(), p1.y(), p2.x(), p2.y())) }); - group.bench_function("spheroid_point_to_point", |b| { - b.iter(|| spheroid.distance(p1.x(), p1.y(), p2.x(), p2.y())) - }); - group.bench_function("euclidean_geometry_to_geometry", |b| { b.iter(|| Euclidean.distance(&geom1, &geom2)) }); @@ -204,10 +186,6 @@ fn benchmark_distance_calculations(c: &mut Criterion) { b.iter(|| haversine.distance_to_bbox(p2.x(), p2.y(), bbox.0, bbox.1, bbox.2, bbox.3)) }); - group.bench_function("spheroid_distance_to_bbox", |b| { - b.iter(|| spheroid.distance_to_bbox(p2.x(), p2.y(), bbox.0, bbox.1, bbox.2, bbox.3)) - }); - group.finish(); } diff --git a/src/rtree/distance.rs b/src/rtree/distance.rs index 7d0afd3..e927dd6 100644 --- a/src/rtree/distance.rs +++ b/src/rtree/distance.rs @@ -1,11 +1,11 @@ //! Distance metrics for spatial queries. //! //! This module provides different distance calculation methods for spatial queries, -//! including Euclidean, Haversine, and Spheroid distance calculations. +//! including Euclidean and Haversine distance calculations. use crate::r#type::IndexableNum; use crate::rtree::r#trait::{axis_dist, SimpleDistanceMetric}; -use geo_0_31::algorithm::{Distance, Euclidean, Geodesic, Haversine}; +use geo_0_31::algorithm::{Distance, Euclidean, Haversine}; use geo_0_31::{Geometry, Point}; pub use crate::rtree::r#trait::GeometryAccessor; @@ -124,65 +124,6 @@ impl DistanceMetric for HaversineDistance { } } -/// Spheroid distance metric (using Geodesic/Vincenty's formula). -/// -/// This calculates the shortest distance between two points on the surface -/// of a spheroid (ellipsoid), providing a more accurate Earth model than -/// a simple sphere. The input coordinates should be in longitude/latitude -/// (degrees), and the output distance is in meters. -#[derive(Debug, Clone, Copy, Default)] -pub struct SpheroidDistance; - -impl SpheroidDistance { - /// Create a new Spheroid distance metric for GRS80 ellipsoid. - pub fn grs80() -> Self { - Self - } -} - -impl SimpleDistanceMetric for SpheroidDistance { - fn distance(&self, lon1: N, lat1: N, lon2: N, lat2: N) -> N { - let p1 = Point::new(lon1.to_f64().unwrap_or(0.0), lat1.to_f64().unwrap_or(0.0)); - let p2 = Point::new(lon2.to_f64().unwrap_or(0.0), lat2.to_f64().unwrap_or(0.0)); - N::from_f64(Geodesic.distance(p1, p2)).unwrap_or(N::max_value()) - } - - fn distance_to_bbox( - &self, - lon: N, - lat: N, - min_lon: N, - min_lat: N, - max_lon: N, - max_lat: N, - ) -> N { - // Similar to haversine, approximate using closest point on bbox - let lon_f = lon.to_f64().unwrap_or(0.0); - let lat_f = lat.to_f64().unwrap_or(0.0); - let min_lon_f = min_lon.to_f64().unwrap_or(0.0); - let min_lat_f = min_lat.to_f64().unwrap_or(0.0); - let max_lon_f = max_lon.to_f64().unwrap_or(0.0); - let max_lat_f = max_lat.to_f64().unwrap_or(0.0); - - let closest_lon = lon_f.clamp(min_lon_f, max_lon_f); - let closest_lat = lat_f.clamp(min_lat_f, max_lat_f); - - let point = Point::new(lon_f, lat_f); - let closest_point = Point::new(closest_lon, closest_lat); - N::from_f64(Geodesic.distance(point, closest_point)).unwrap_or(N::max_value()) - } -} - -impl DistanceMetric for SpheroidDistance { - fn distance_to_geometry(&self, geom1: &Geometry, geom2: &Geometry) -> N { - // For Geodesic, use centroid-to-centroid distance as approximation - use geo_0_31::algorithm::Centroid; - let c1 = geom1.centroid().unwrap_or(Point::new(0.0, 0.0)); - let c2 = geom2.centroid().unwrap_or(Point::new(0.0, 0.0)); - N::from_f64(Geodesic.distance(c1, c2)).unwrap_or(N::max_value()) - } -} - /// Simple geometry accessor that wraps a slice of geometries. /// /// This accessor provides access to geometries by index for use with distance metrics. @@ -239,15 +180,6 @@ mod tests { assert!((distance - 5585000.0f64).abs() < 50000.0f64); } - #[test] - fn test_spheroid_distance() { - let metric = SpheroidDistance; - // Distance between New York and London (approximately) - let distance = metric.distance(-74.0f64, 40.7f64, -0.1f64, 51.5f64); - // Should be approximately 5585 km (slightly different from Haversine) - assert!((distance - 5585000.0f64).abs() < 50000.0f64); - } - #[test] fn test_euclidean_geometry_distance() { // Test Euclidean distance between geometries diff --git a/src/rtree/trait.rs b/src/rtree/trait.rs index 9b8d05c..d513464 100644 --- a/src/rtree/trait.rs +++ b/src/rtree/trait.rs @@ -612,7 +612,7 @@ mod test { #[cfg(feature = "use-geo_0_31")] mod distance_metrics { - use crate::rtree::distance::{EuclideanDistance, HaversineDistance, SpheroidDistance}; + use crate::rtree::distance::{EuclideanDistance, HaversineDistance}; use crate::rtree::r#trait::SimpleDistanceMetric; use crate::rtree::sort::HilbertSort; use crate::rtree::{RTreeBuilder, RTreeIndex}; @@ -648,22 +648,6 @@ mod test { assert_eq!(results, vec![0, 1, 2]); } - #[test] - fn test_spheroid_distance_neighbors() { - let mut builder = RTreeBuilder::::new(3); - // Add some geographic points (longitude, latitude) - builder.add(-74.0, 40.7, -74.0, 40.7); // New York - builder.add(-0.1, 51.5, -0.1, 51.5); // London - builder.add(139.7, 35.7, 139.7, 35.7); // Tokyo - let tree = builder.finish::(); - - let spheroid = SpheroidDistance; - let results = tree.neighbors_with_distance(-74.0, 40.7, None, None, &spheroid); - - // From New York, should find New York first, then London, then Tokyo - assert_eq!(results, vec![0, 1, 2]); - } - #[test] fn test_backward_compatibility() { let mut builder = RTreeBuilder::::new(3);