1use crate::core::{GeoBounds, Location};
27use rstar::{PointDistance, RTree, RTreeObject, AABB};
28
29#[derive(Debug, Clone)]
31pub struct IndexedLocation {
32 pub location: Location,
34 pub index: usize,
36}
37
38impl IndexedLocation {
39 pub fn new(location: Location, index: usize) -> Self {
41 Self { location, index }
42 }
43}
44
45impl RTreeObject for IndexedLocation {
46 type Envelope = AABB<[f64; 2]>;
47
48 fn envelope(&self) -> Self::Envelope {
49 AABB::from_point([self.location.lon, self.location.lat])
50 }
51}
52
53impl PointDistance for IndexedLocation {
54 fn distance_2(&self, point: &[f64; 2]) -> f64 {
55 let dx = self.location.lon - point[0];
56 let dy = self.location.lat - point[1];
57 dx * dx + dy * dy
58 }
59}
60
61#[derive(Debug)]
65pub struct SpatialIndex<T> {
66 tree: RTree<IndexedLocation>,
67 items: Vec<T>,
68}
69
70impl<T: Clone> SpatialIndex<T> {
71 pub fn new() -> Self {
73 Self {
74 tree: RTree::new(),
75 items: Vec::new(),
76 }
77 }
78
79 pub fn from_iter<I, F>(iter: I, location_fn: F) -> Self
81 where
82 I: IntoIterator<Item = T>,
83 F: Fn(&T) -> &Location,
84 {
85 let items: Vec<T> = iter.into_iter().collect();
86 let indexed: Vec<IndexedLocation> = items
87 .iter()
88 .enumerate()
89 .map(|(i, item)| IndexedLocation::new(location_fn(item).clone(), i))
90 .collect();
91
92 Self {
93 tree: RTree::bulk_load(indexed),
94 items,
95 }
96 }
97
98 pub fn insert(&mut self, item: T, location: &Location) {
100 let index = self.items.len();
101 self.items.push(item);
102 self.tree
103 .insert(IndexedLocation::new(location.clone(), index));
104 }
105
106 pub fn query_bbox(&self, min_lat: f64, min_lon: f64, max_lat: f64, max_lon: f64) -> Vec<&T> {
114 let envelope = AABB::from_corners([min_lon, min_lat], [max_lon, max_lat]);
115 self.tree
116 .locate_in_envelope(&envelope)
117 .map(|indexed| &self.items[indexed.index])
118 .collect()
119 }
120
121 pub fn query_bounds(&self, bounds: &GeoBounds) -> Vec<&T> {
123 self.query_bbox(
124 bounds.min_lat,
125 bounds.min_lon,
126 bounds.max_lat,
127 bounds.max_lon,
128 )
129 }
130
131 pub fn query_radius(&self, lat: f64, lon: f64, radius_degrees: f64) -> Vec<&T> {
136 let radius_sq = radius_degrees * radius_degrees;
137 self.tree
138 .locate_within_distance([lon, lat], radius_sq)
139 .map(|indexed| &self.items[indexed.index])
140 .collect()
141 }
142
143 pub fn query_radius_meters(&self, lat: f64, lon: f64, radius_meters: f64) -> Vec<&T> {
147 let degree_radius = radius_meters / 111_320.0 * 1.5; let candidates = self.query_radius(lat, lon, degree_radius);
153
154 candidates
157 }
158
159 pub fn nearest(&self, lat: f64, lon: f64, k: usize) -> Vec<&T> {
161 self.tree
162 .nearest_neighbor_iter(&[lon, lat])
163 .take(k)
164 .map(|indexed| &self.items[indexed.index])
165 .collect()
166 }
167
168 pub fn nearest_one(&self, lat: f64, lon: f64) -> Option<&T> {
170 self.tree
171 .nearest_neighbor(&[lon, lat])
172 .map(|indexed| &self.items[indexed.index])
173 }
174
175 pub fn len(&self) -> usize {
177 self.items.len()
178 }
179
180 pub fn is_empty(&self) -> bool {
182 self.items.is_empty()
183 }
184
185 pub fn items(&self) -> &[T] {
187 &self.items
188 }
189}
190
191impl<T: Clone> Default for SpatialIndex<T> {
192 fn default() -> Self {
193 Self::new()
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use crate::core::Location;
201
202 #[test]
203 fn test_spatial_index_new() {
204 let index: SpatialIndex<String> = SpatialIndex::new();
205 assert!(index.is_empty());
206 }
207
208 #[test]
209 fn test_spatial_index_insert() {
210 let mut index = SpatialIndex::new();
211 index.insert("NYC".to_string(), &Location::new(40.7128, -74.0060));
212 index.insert("LA".to_string(), &Location::new(34.0522, -118.2437));
213
214 assert_eq!(index.len(), 2);
215 }
216
217 #[test]
218 fn test_spatial_index_query_bbox() {
219 let items = vec![
220 ("NYC", Location::new(40.7128, -74.0060)),
221 ("LA", Location::new(34.0522, -118.2437)),
222 ("Chicago", Location::new(41.8781, -87.6298)),
223 ];
224
225 let mut index: SpatialIndex<&str> = SpatialIndex::new();
227 for (name, loc) in &items {
228 index.insert(*name, loc);
229 }
230
231 let results = index.query_bbox(35.0, -80.0, 45.0, -70.0);
233 assert_eq!(results.len(), 1);
234 assert_eq!(*results[0], "NYC");
235 }
236
237 #[test]
238 fn test_spatial_index_nearest() {
239 let mut index: SpatialIndex<&str> = SpatialIndex::new();
240 index.insert("NYC", &Location::new(40.7128, -74.0060));
241 index.insert("LA", &Location::new(34.0522, -118.2437));
242 index.insert("Chicago", &Location::new(41.8781, -87.6298));
243
244 let nearest = index.nearest_one(39.9526, -75.1652);
246 assert_eq!(nearest, Some(&"NYC"));
247 }
248
249 #[test]
250 fn test_spatial_index_k_nearest() {
251 let mut index: SpatialIndex<&str> = SpatialIndex::new();
252 index.insert("NYC", &Location::new(40.7128, -74.0060));
253 index.insert("Boston", &Location::new(42.3601, -71.0589));
254 index.insert("Philadelphia", &Location::new(39.9526, -75.1652));
255 index.insert("LA", &Location::new(34.0522, -118.2437));
256
257 let results = index.nearest(40.7128, -74.0060, 2);
259 assert_eq!(results.len(), 2);
260 }
261}