-
Notifications
You must be signed in to change notification settings - Fork 14
feat: Add distance metric support for RTree neighbor queries #141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
zhangfengcdt
wants to merge
6
commits into
kylebarron:main
Choose a base branch
from
wherobots:feature/distance_metric_extension_upstream
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 3 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
c3fdeef
feat: Add distance metric support for RTree neighbor queries
zhangfengcdt 891bce6
fix cargo fmt
zhangfengcdt 14a909e
fix clippy fmt
zhangfengcdt 615aa20
Address PR review comments
zhangfengcdt 74792d5
Merge branch 'main' of github.com:kylebarron/geo-index into feature/d…
zhangfengcdt 97c49f5
remove the vincenty (Geodesic) and GRS80 distance metrics
zhangfengcdt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,3 +25,5 @@ target/ | |
| # Added by cargo | ||
|
|
||
| /target | ||
|
|
||
| .idea/ | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<N: IndexableNum> DistanceMetric<N> 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<f64>, geom2: &Geometry<f64>) -> N { | ||
| N::from_f64(Euclidean.distance(geom1, geom2)).unwrap_or(N::max_value()) | ||
| } | ||
| } | ||
|
|
||
| fn generate_test_data(n: usize) -> (Vec<Point<f64>>, Vec<Geometry<f64>>) { | ||
| 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<f64>]) -> geo_index::rtree::RTree<f64> { | ||
| 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::<HilbertSort>() | ||
| } | ||
|
|
||
| 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); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.