Skip to content

Commit 35331b8

Browse files
[EWT-2845] Add Pluggable Distance Metrics to RTree Neighbors Search (#1)
* fix current clippy errors * Implement pluggable distance calclations for spatial indexes * enable run rust and test for pr * fix cargo fmt * fix clippy issue * Apply suggestion from @Copilot Co-authored-by: Copilot <[email protected]> * Update src/type.rs Co-authored-by: Copilot <[email protected]> * Update src/rtree/distance.rs Co-authored-by: Copilot <[email protected]> * Update src/rtree/distance.rs Co-authored-by: Copilot <[email protected]> * Update src/rtree/distance.rs Co-authored-by: Copilot <[email protected]> * Removed the redundant distance check * fix compiling error --------- Co-authored-by: Copilot <[email protected]>
1 parent 6fe0c2c commit 35331b8

File tree

11 files changed

+670
-20
lines changed

11 files changed

+670
-20
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ on:
55
branches:
66
- main
77
pull_request:
8+
branches:
9+
- main
810

911
jobs:
1012
lint-test:

examples/distance_metrics.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
//! Example demonstrating different distance metrics for spatial queries.
2+
//!
3+
//! This example shows how to use Euclidean, Haversine, and Spheroid distance metrics
4+
//! for finding nearest neighbors in spatial datasets.
5+
6+
use geo_index::rtree::distance::{EuclideanDistance, HaversineDistance, SpheroidDistance};
7+
use geo_index::rtree::sort::HilbertSort;
8+
use geo_index::rtree::{RTreeBuilder, RTreeIndex};
9+
10+
fn main() {
11+
println!("=== Distance Metrics Example ===\n");
12+
13+
// Example 1: Euclidean Distance (for planar coordinates)
14+
println!("1. Euclidean Distance (planar coordinates):");
15+
euclidean_distance_example();
16+
17+
// Example 2: Haversine Distance (for geographic coordinates)
18+
println!("\n2. Haversine Distance (geographic coordinates):");
19+
haversine_distance_example();
20+
21+
// Example 3: Spheroid Distance (for high-precision geographic coordinates)
22+
println!("\n3. Spheroid Distance (high-precision geographic coordinates):");
23+
spheroid_distance_example();
24+
25+
// Example 4: Comparison of different metrics
26+
println!("\n4. Comparison of different distance metrics:");
27+
comparison_example();
28+
}
29+
30+
fn euclidean_distance_example() {
31+
let mut builder = RTreeBuilder::<f64>::new(4);
32+
33+
// Add some points in a planar coordinate system
34+
builder.add(0., 0., 1., 1.); // Point A
35+
builder.add(3., 4., 4., 5.); // Point B
36+
builder.add(6., 8., 7., 9.); // Point C
37+
builder.add(1., 1., 2., 2.); // Point D
38+
39+
let tree = builder.finish::<HilbertSort>();
40+
41+
let euclidean = EuclideanDistance;
42+
let query_point = (0., 0.);
43+
44+
println!(" Query point: {:?}", query_point);
45+
let results =
46+
tree.neighbors_with_distance(query_point.0, query_point.1, Some(3), None, &euclidean);
47+
48+
println!(" Nearest neighbors (by insertion order): {:?}", results);
49+
println!(" Distance metric: Euclidean (straight-line distance)");
50+
}
51+
52+
fn haversine_distance_example() {
53+
let mut builder = RTreeBuilder::<f64>::new(5);
54+
55+
// Add some major cities (longitude, latitude)
56+
builder.add(-74.0, 40.7, -74.0, 40.7); // New York
57+
builder.add(-0.1, 51.5, -0.1, 51.5); // London
58+
builder.add(139.7, 35.7, 139.7, 35.7); // Tokyo
59+
builder.add(-118.2, 34.1, -118.2, 34.1); // Los Angeles
60+
builder.add(2.3, 48.9, 2.3, 48.9); // Paris
61+
62+
let tree = builder.finish::<HilbertSort>();
63+
64+
let haversine = HaversineDistance::default();
65+
let query_point = (-74.0, 40.7); // New York
66+
67+
println!(" Query point: New York {:?}", query_point);
68+
let results =
69+
tree.neighbors_with_distance(query_point.0, query_point.1, Some(3), None, &haversine);
70+
71+
println!(" Nearest neighbors (by insertion order): {:?}", results);
72+
println!(" Distance metric: Haversine (great-circle distance on sphere)");
73+
println!(" Earth radius: {} meters", haversine.earth_radius);
74+
}
75+
76+
fn spheroid_distance_example() {
77+
let mut builder = RTreeBuilder::<f64>::new(5);
78+
79+
// Add some major cities (longitude, latitude)
80+
builder.add(-74.0, 40.7, -74.0, 40.7); // New York
81+
builder.add(-0.1, 51.5, -0.1, 51.5); // London
82+
builder.add(139.7, 35.7, 139.7, 35.7); // Tokyo
83+
builder.add(-118.2, 34.1, -118.2, 34.1); // Los Angeles
84+
builder.add(2.3, 48.9, 2.3, 48.9); // Paris
85+
86+
let tree = builder.finish::<HilbertSort>();
87+
88+
let spheroid = SpheroidDistance::default(); // WGS84 ellipsoid
89+
let query_point = (-74.0, 40.7); // New York
90+
91+
println!(" Query point: New York {:?}", query_point);
92+
let results =
93+
tree.neighbors_with_distance(query_point.0, query_point.1, Some(3), None, &spheroid);
94+
95+
println!(" Nearest neighbors (by insertion order): {:?}", results);
96+
println!(" Distance metric: Spheroid (distance on ellipsoid)");
97+
println!(" Semi-major axis: {} meters", spheroid.semi_major_axis);
98+
println!(" Semi-minor axis: {} meters", spheroid.semi_minor_axis);
99+
}
100+
101+
fn comparison_example() {
102+
let mut builder = RTreeBuilder::<f64>::new(3);
103+
104+
// Add points with different characteristics
105+
builder.add(0., 0., 1., 1.); // Origin
106+
builder.add(1., 1., 2., 2.); // Close point
107+
builder.add(10., 10., 11., 11.); // Distant point
108+
109+
let tree = builder.finish::<HilbertSort>();
110+
111+
let query_point = (0., 0.);
112+
113+
// Test with different distance metrics
114+
let euclidean = EuclideanDistance;
115+
let haversine = HaversineDistance::default();
116+
let spheroid = SpheroidDistance::default();
117+
118+
println!(" Query point: {:?}", query_point);
119+
120+
let euclidean_results =
121+
tree.neighbors_with_distance(query_point.0, query_point.1, Some(2), None, &euclidean);
122+
println!(" Euclidean results: {:?}", euclidean_results);
123+
124+
let haversine_results =
125+
tree.neighbors_with_distance(query_point.0, query_point.1, Some(2), None, &haversine);
126+
println!(" Haversine results: {:?}", haversine_results);
127+
128+
let spheroid_results =
129+
tree.neighbors_with_distance(query_point.0, query_point.1, Some(2), None, &spheroid);
130+
println!(" Spheroid results: {:?}", spheroid_results);
131+
132+
// Test backward compatibility
133+
let original_results = tree.neighbors(query_point.0, query_point.1, Some(2), None);
134+
println!(" Original method results: {:?}", original_results);
135+
136+
println!(" Note: For small distances, all metrics should give similar ordering.");
137+
}

src/indices.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ impl MutableIndices<'_> {
5858
}
5959

6060
#[allow(dead_code)]
61-
pub(crate) fn split_at_mut(&mut self, mid: usize) -> (MutableIndices, MutableIndices) {
61+
pub(crate) fn split_at_mut(&mut self, mid: usize) -> (MutableIndices<'_>, MutableIndices<'_>) {
6262
match self {
6363
Self::U16(arr) => {
6464
let (left, right) = arr.split_at_mut(mid);
@@ -72,7 +72,7 @@ impl MutableIndices<'_> {
7272
}
7373

7474
#[allow(dead_code)]
75-
pub(crate) fn chunks_mut(&mut self, chunk_size: usize) -> Vec<MutableIndices> {
75+
pub(crate) fn chunks_mut(&mut self, chunk_size: usize) -> Vec<MutableIndices<'_>> {
7676
match self {
7777
Self::U16(arr) => arr
7878
.chunks_mut(chunk_size)

src/kdtree/builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ impl<N: IndexableNum> KDTreeBuilder<N> {
131131
fn split_data_borrow<N: IndexableNum>(
132132
data: &mut [u8],
133133
metadata: KDTreeMetadata<N>,
134-
) -> (&mut [N], MutableIndices) {
134+
) -> (&mut [N], MutableIndices<'_>) {
135135
let (ids_buf, padded_coords_buf) =
136136
data[KDBUSH_HEADER_SIZE..].split_at_mut(metadata.indices_byte_size);
137137
let coords_buf = &mut padded_coords_buf[metadata.pad_coords_byte_size..];

src/kdtree/index.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ impl<N: IndexableNum> KDTreeMetadata<N> {
5454
let version = version_and_type >> 4;
5555
if version != KDBUSH_VERSION {
5656
return Err(GeoIndexError::General(
57-
format!("Got v{} data when expected v{}.", version, KDBUSH_VERSION).to_string(),
57+
format!("Got v{version} data when expected v{KDBUSH_VERSION}.").to_string(),
5858
));
5959
}
6060

src/kdtree/trait.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub trait KDTreeIndex<N: IndexableNum>: Sized {
1111
fn coords(&self) -> &[N];
1212

1313
/// The underlying raw indices buffer of this tree
14-
fn indices(&self) -> Indices;
14+
fn indices(&self) -> Indices<'_>;
1515

1616
/// Access the metadata describing this KDTree
1717
fn metadata(&self) -> &KDTreeMetadata<N>;
@@ -194,7 +194,7 @@ impl<N: IndexableNum> KDTreeIndex<N> for KDTree<N> {
194194
self.metadata.coords_slice(&self.buffer)
195195
}
196196

197-
fn indices(&self) -> Indices {
197+
fn indices(&self) -> Indices<'_> {
198198
self.metadata.indices_slice(&self.buffer)
199199
}
200200

@@ -208,7 +208,7 @@ impl<N: IndexableNum> KDTreeIndex<N> for KDTreeRef<'_, N> {
208208
self.coords
209209
}
210210

211-
fn indices(&self) -> Indices {
211+
fn indices(&self) -> Indices<'_> {
212212
self.indices
213213
}
214214

0 commit comments

Comments
 (0)