1use crate::error::{Result, SpatioError};
4use geo::{
5 BoundingRect, ChamberlainDuquetteArea, Contains, ConvexHull, Distance, GeodesicArea,
6 Intersects, Rect, Rhumb,
7};
8use spatio_types::geo::{Point, Polygon};
9use std::cmp::Ordering;
10use std::collections::BinaryHeap;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
14pub enum DistanceMetric {
15 #[default]
16 Haversine,
17 Geodesic,
18 Rhumb,
19 Euclidean,
20}
21
22pub fn distance_between(point1: &Point, point2: &Point, metric: DistanceMetric) -> f64 {
24 match metric {
25 DistanceMetric::Haversine => point1.haversine_distance(point2),
26 DistanceMetric::Geodesic => point1.geodesic_distance(point2),
27 DistanceMetric::Rhumb => Rhumb.distance(*point1.inner(), *point2.inner()),
28 DistanceMetric::Euclidean => point1.euclidean_distance(point2),
29 }
30}
31
32#[derive(Clone)]
34struct KnnEntry<'a, T> {
35 point: Point,
36 distance: f64,
37 data: &'a T,
38}
39
40impl<'a, T> PartialEq for KnnEntry<'a, T> {
41 fn eq(&self, other: &Self) -> bool {
42 self.distance == other.distance
43 }
44}
45
46impl<'a, T> Eq for KnnEntry<'a, T> {}
47
48impl<'a, T> PartialOrd for KnnEntry<'a, T> {
49 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
50 Some(self.cmp(other))
51 }
52}
53
54impl<'a, T> Ord for KnnEntry<'a, T> {
55 fn cmp(&self, other: &Self) -> Ordering {
56 self.distance
58 .partial_cmp(&other.distance)
59 .unwrap_or(Ordering::Equal)
60 }
61}
62
63pub fn knn<T: Clone>(
98 center: &Point,
99 points: &[(Point, T)],
100 k: usize,
101 metric: DistanceMetric,
102) -> Vec<(Point, f64, T)> {
103 if k == 0 || points.is_empty() {
104 return Vec::new();
105 }
106
107 let mut heap = BinaryHeap::with_capacity(k.min(points.len()));
109
110 for (pt, data) in points.iter() {
111 let dist = distance_between(center, pt, metric);
112
113 if !dist.is_finite() {
116 continue;
117 }
118
119 if heap.len() < k {
120 heap.push(KnnEntry {
121 point: *pt,
122 distance: dist,
123 data,
124 });
125 } else if let Some(worst) = heap.peek()
126 && dist < worst.distance
127 {
128 heap.pop();
129 heap.push(KnnEntry {
130 point: *pt,
131 distance: dist,
132 data,
133 });
134 }
135 }
136
137 let mut results = Vec::with_capacity(heap.len());
139 while let Some(entry) = heap.pop() {
140 results.push((entry.point, entry.distance, entry.data.clone()));
141 }
142 results.reverse();
143 results
144}
145
146pub fn bounding_box(min_lon: f64, min_lat: f64, max_lon: f64, max_lat: f64) -> Result<Rect> {
147 if min_lon > max_lon {
148 return Err(SpatioError::InvalidInput(format!(
149 "min_lon ({}) must be <= max_lon ({})",
150 min_lon, max_lon
151 )));
152 }
153 if min_lat > max_lat {
154 return Err(SpatioError::InvalidInput(format!(
155 "min_lat ({}) must be <= max_lat ({})",
156 min_lat, max_lat
157 )));
158 }
159
160 Ok(Rect::new(
161 geo::coord! { x: min_lon, y: min_lat },
162 geo::coord! { x: max_lon, y: max_lat },
163 ))
164}
165
166pub fn point_in_polygon(polygon: &Polygon, point: &Point) -> bool {
167 polygon.contains(point)
168}
169
170pub fn point_in_bbox(bbox: &Rect, point: &Point) -> bool {
171 bbox.contains(point.inner())
172}
173
174pub fn polygon_area(polygon: &Polygon) -> f64 {
209 polygon.inner().chamberlain_duquette_unsigned_area()
210}
211
212pub fn geodesic_polygon_area(polygon: &Polygon) -> f64 {
247 polygon.inner().geodesic_area_unsigned()
248}
249
250pub fn convex_hull(points: &[Point]) -> Option<Polygon> {
251 if points.is_empty() {
252 return None;
253 }
254 let geo_points: Vec<geo::Point> = points.iter().map(|p| (*p).into()).collect();
255 let multi_point = geo::MultiPoint::new(geo_points);
256 Some(multi_point.convex_hull().into())
257}
258
259pub fn bounding_rect_for_points(points: &[Point]) -> Option<Rect> {
260 if points.is_empty() {
261 return None;
262 }
263
264 let geo_points: Vec<geo::Point> = points.iter().map(|p| (*p).into()).collect();
265 let multi_point = geo::MultiPoint::new(geo_points);
266 multi_point.bounding_rect()
267}
268
269pub fn bboxes_intersect(bbox1: &Rect, bbox2: &Rect) -> bool {
270 bbox1.intersects(bbox2)
271}
272
273pub fn expand_bbox(bbox: &Rect, distance_meters: f64) -> Rect {
314 let lat_offset = distance_meters / 111_000.0;
316
317 let min_y = (bbox.min().y - lat_offset).max(-90.0);
319 let max_y = (bbox.max().y + lat_offset).min(90.0);
320
321 let max_abs_lat = bbox.min().y.abs().max(bbox.max().y.abs());
324
325 let calc_lat = max_abs_lat.min(89.9);
329
330 let lon_offset = distance_meters / (111_000.0 * calc_lat.to_radians().cos());
333
334 Rect::new(
336 geo::coord! {
337 x: bbox.min().x - lon_offset,
338 y: min_y
339 },
340 geo::coord! {
341 x: bbox.max().x + lon_offset,
342 y: max_y
343 },
344 )
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350
351 #[test]
352 fn test_distance_between() {
353 let p1 = Point::new(-74.0060, 40.7128); let p2 = Point::new(-118.2437, 34.0522); let dist_haversine = distance_between(&p1, &p2, DistanceMetric::Haversine);
357 let dist_geodesic = distance_between(&p1, &p2, DistanceMetric::Geodesic);
358
359 assert!(dist_haversine > 3_900_000.0 && dist_haversine < 4_000_000.0);
360 assert!(dist_geodesic > 3_900_000.0 && dist_geodesic < 4_000_000.0);
361
362 let diff = (dist_haversine - dist_geodesic).abs();
363 assert!(diff < 10_000.0);
364 }
365
366 #[test]
367 fn test_knn() {
368 let center = Point::new(-74.0060, 40.7128);
369 let candidates = vec![
370 (Point::new(-73.9442, 40.6782), "Brooklyn"),
371 (Point::new(-73.9356, 40.7306), "Queens"),
372 (Point::new(-118.2437, 34.0522), "LA"),
373 (Point::new(-73.9712, 40.7831), "Upper West Side"),
374 ];
375
376 let nearest = knn(¢er, &candidates, 2, DistanceMetric::Haversine);
377
378 assert_eq!(nearest.len(), 2);
379 assert_ne!(nearest[0].2, "LA");
380 assert_ne!(nearest[1].2, "LA");
381 }
382
383 #[test]
384 fn test_bounding_box() {
385 let bbox = bounding_box(-74.0, 40.7, -73.9, 40.8).unwrap();
386
387 assert_eq!(bbox.min().x, -74.0);
388 assert_eq!(bbox.min().y, 40.7);
389 assert_eq!(bbox.max().x, -73.9);
390 assert_eq!(bbox.max().y, 40.8);
391 }
392
393 #[test]
394 fn test_bounding_box_invalid() {
395 let result = bounding_box(-73.9, 40.7, -74.0, 40.8);
396 assert!(result.is_err());
397 }
398
399 #[test]
400 fn test_point_in_bbox() {
401 let bbox = bounding_box(-74.0, 40.7, -73.9, 40.8).unwrap();
402
403 assert!(point_in_bbox(&bbox, &Point::new(-73.95, 40.75)));
404 assert!(!point_in_bbox(&bbox, &Point::new(-73.85, 40.75)));
405 }
406
407 #[test]
408 fn test_bboxes_intersect() {
409 let bbox1 = bounding_box(-74.0, 40.7, -73.9, 40.8).unwrap();
410 let bbox2 = bounding_box(-73.95, 40.75, -73.85, 40.85).unwrap();
411 let bbox3 = bounding_box(-73.0, 40.0, -72.9, 40.1).unwrap();
412
413 assert!(bboxes_intersect(&bbox1, &bbox2));
414 assert!(!bboxes_intersect(&bbox1, &bbox3));
415 }
416
417 #[test]
418 fn test_convex_hull() {
419 let points = vec![
420 Point::new(-74.0, 40.7),
421 Point::new(-73.9, 40.7),
422 Point::new(-73.95, 40.8),
423 Point::new(-73.95, 40.75), ];
425
426 let hull = convex_hull(&points).unwrap();
427 assert_eq!(hull.exterior().0.len(), 4);
428 }
429
430 #[test]
431 fn test_bounding_rect_for_points() {
432 let points = vec![
433 Point::new(-74.0, 40.7),
434 Point::new(-73.9, 40.8),
435 Point::new(-73.95, 40.75),
436 ];
437
438 let bbox = bounding_rect_for_points(&points).unwrap();
439 assert_eq!(bbox.min().x, -74.0);
440 assert_eq!(bbox.min().y, 40.7);
441 assert_eq!(bbox.max().x, -73.9);
442 assert_eq!(bbox.max().y, 40.8);
443 }
444
445 #[test]
446 fn test_expand_bbox() {
447 let bbox = bounding_box(-74.0, 40.7, -73.9, 40.8).unwrap();
448 let expanded = expand_bbox(&bbox, 1000.0);
449 assert!(expanded.min().x < bbox.min().x);
450 assert!(expanded.min().y < bbox.min().y);
451 assert!(expanded.max().x > bbox.max().x);
452 assert!(expanded.max().y > bbox.max().y);
453 }
454}