1use crate::error::{AlgorithmError, Result};
28use oxigdal_core::vector::{Coordinate, LineString, Point, Polygon};
29
30#[cfg(feature = "std")]
31use std::f64::consts::PI;
32
33#[cfg(not(feature = "std"))]
34use core::f64::consts::PI;
35
36const WGS84_A: f64 = 6_378_137.0;
38
39const WGS84_B: f64 = 6_356_752.314_245;
41
42const WGS84_F: f64 = (WGS84_A - WGS84_B) / WGS84_A;
44
45const VINCENTY_MAX_ITER: usize = 200;
47
48const VINCENTY_THRESHOLD: f64 = 1e-12;
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum DistanceMethod {
54 Euclidean,
56 Haversine,
58 Vincenty,
60}
61
62pub fn distance_point_to_point(p1: &Point, p2: &Point, method: DistanceMethod) -> Result<f64> {
78 match method {
79 DistanceMethod::Euclidean => Ok(euclidean_distance(&p1.coord, &p2.coord)),
80 DistanceMethod::Haversine => haversine_distance(&p1.coord, &p2.coord),
81 DistanceMethod::Vincenty => vincenty_distance(&p1.coord, &p2.coord),
82 }
83}
84
85pub fn distance_point_to_linestring(
101 point: &Point,
102 linestring: &LineString,
103 method: DistanceMethod,
104) -> Result<f64> {
105 if linestring.coords.is_empty() {
106 return Err(AlgorithmError::EmptyInput {
107 operation: "distance_point_to_linestring",
108 });
109 }
110
111 if linestring.coords.len() == 1 {
112 return distance_point_to_point(point, &Point::from_coord(linestring.coords[0]), method);
113 }
114
115 let mut min_dist = f64::INFINITY;
116
117 for i in 0..linestring.coords.len() - 1 {
118 let seg_start = &linestring.coords[i];
119 let seg_end = &linestring.coords[i + 1];
120
121 let dist = distance_point_to_segment(&point.coord, seg_start, seg_end, method)?;
122 min_dist = min_dist.min(dist);
123 }
124
125 Ok(min_dist)
126}
127
128pub fn distance_point_to_polygon(
144 point: &Point,
145 polygon: &Polygon,
146 method: DistanceMethod,
147) -> Result<f64> {
148 if polygon.exterior.coords.len() < 3 {
149 return Err(AlgorithmError::InsufficientData {
150 operation: "distance_point_to_polygon",
151 message: "polygon must have at least 3 coordinates".to_string(),
152 });
153 }
154
155 if point_in_polygon_boundary(&point.coord, polygon) {
157 return Ok(0.0);
158 }
159
160 let mut min_dist = distance_point_to_linestring(point, &polygon.exterior, method)?;
162
163 for hole in &polygon.interiors {
165 let hole_dist = distance_point_to_linestring(point, hole, method)?;
166 min_dist = min_dist.min(hole_dist);
167 }
168
169 Ok(min_dist)
170}
171
172fn euclidean_distance(c1: &Coordinate, c2: &Coordinate) -> f64 {
174 let dx = c2.x - c1.x;
175 let dy = c2.y - c1.y;
176
177 if let (Some(z1), Some(z2)) = (c1.z, c2.z) {
178 let dz = z2 - z1;
179 (dx * dx + dy * dy + dz * dz).sqrt()
180 } else {
181 (dx * dx + dy * dy).sqrt()
182 }
183}
184
185fn haversine_distance(c1: &Coordinate, c2: &Coordinate) -> Result<f64> {
187 let lat1 = c1.y * PI / 180.0;
188 let lon1 = c1.x * PI / 180.0;
189 let lat2 = c2.y * PI / 180.0;
190 let lon2 = c2.x * PI / 180.0;
191
192 if lat1.abs() > PI / 2.0 || lat2.abs() > PI / 2.0 {
194 return Err(AlgorithmError::InvalidParameter {
195 parameter: "latitude",
196 message: "latitude must be between -90 and 90 degrees".to_string(),
197 });
198 }
199
200 let dlat = lat2 - lat1;
201 let dlon = lon2 - lon1;
202
203 let a = (dlat / 2.0).sin().powi(2) + lat1.cos() * lat2.cos() * (dlon / 2.0).sin().powi(2);
204
205 let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
206
207 let radius = (WGS84_A + WGS84_B) / 2.0;
209 Ok(radius * c)
210}
211
212fn vincenty_distance(c1: &Coordinate, c2: &Coordinate) -> Result<f64> {
216 let lat1 = c1.y * PI / 180.0;
217 let lon1 = c1.x * PI / 180.0;
218 let lat2 = c2.y * PI / 180.0;
219 let lon2 = c2.x * PI / 180.0;
220
221 if lat1.abs() > PI / 2.0 || lat2.abs() > PI / 2.0 {
223 return Err(AlgorithmError::InvalidParameter {
224 parameter: "latitude",
225 message: "latitude must be between -90 and 90 degrees".to_string(),
226 });
227 }
228
229 let l = lon2 - lon1;
230
231 let u1 = ((1.0 - WGS84_F) * lat1.tan()).atan();
232 let u2 = ((1.0 - WGS84_F) * lat2.tan()).atan();
233
234 let sin_u1 = u1.sin();
235 let cos_u1 = u1.cos();
236 let sin_u2 = u2.sin();
237 let cos_u2 = u2.cos();
238
239 let mut lambda = l;
240 let mut lambda_prev;
241 let mut iter_count = 0;
242
243 let (sin_sigma, cos_sigma, sigma, cos_sq_alpha, cos_2sigma_m);
244
245 loop {
246 let sin_lambda = lambda.sin();
247 let cos_lambda = lambda.cos();
248
249 let sin_sigma_temp = ((cos_u2 * sin_lambda).powi(2)
250 + (cos_u1 * sin_u2 - sin_u1 * cos_u2 * cos_lambda).powi(2))
251 .sqrt();
252
253 if sin_sigma_temp.abs() < f64::EPSILON {
254 return Ok(0.0);
256 }
257
258 let cos_sigma_temp = sin_u1 * sin_u2 + cos_u1 * cos_u2 * cos_lambda;
259 let sigma_temp = sin_sigma_temp.atan2(cos_sigma_temp);
260
261 let sin_alpha_temp = cos_u1 * cos_u2 * sin_lambda / sin_sigma_temp;
262 let cos_sq_alpha_temp = 1.0 - sin_alpha_temp * sin_alpha_temp;
263
264 let cos_2sigma_m_temp = if cos_sq_alpha_temp.abs() < f64::EPSILON {
265 0.0
266 } else {
267 cos_sigma_temp - 2.0 * sin_u1 * sin_u2 / cos_sq_alpha_temp
268 };
269
270 let c =
271 WGS84_F / 16.0 * cos_sq_alpha_temp * (4.0 + WGS84_F * (4.0 - 3.0 * cos_sq_alpha_temp));
272
273 lambda_prev = lambda;
274 lambda = l
275 + (1.0 - c)
276 * WGS84_F
277 * sin_alpha_temp
278 * (sigma_temp
279 + c * sin_sigma_temp
280 * (cos_2sigma_m_temp
281 + c * cos_sigma_temp
282 * (-1.0 + 2.0 * cos_2sigma_m_temp * cos_2sigma_m_temp)));
283
284 iter_count += 1;
285 if (lambda - lambda_prev).abs() < VINCENTY_THRESHOLD || iter_count >= VINCENTY_MAX_ITER {
286 sin_sigma = sin_sigma_temp;
287 cos_sigma = cos_sigma_temp;
288 sigma = sigma_temp;
289 cos_sq_alpha = cos_sq_alpha_temp;
290 cos_2sigma_m = cos_2sigma_m_temp;
291 break;
292 }
293 }
294
295 if iter_count >= VINCENTY_MAX_ITER {
296 return Err(AlgorithmError::NumericalError {
297 operation: "vincenty_distance",
298 message: "failed to converge".to_string(),
299 });
300 }
301
302 let u_sq = cos_sq_alpha * (WGS84_A * WGS84_A - WGS84_B * WGS84_B) / (WGS84_B * WGS84_B);
303 let a = 1.0 + u_sq / 16384.0 * (4096.0 + u_sq * (-768.0 + u_sq * (320.0 - 175.0 * u_sq)));
304 let b = u_sq / 1024.0 * (256.0 + u_sq * (-128.0 + u_sq * (74.0 - 47.0 * u_sq)));
305
306 let delta_sigma = b
307 * sin_sigma
308 * (cos_2sigma_m
309 + b / 4.0
310 * (cos_sigma * (-1.0 + 2.0 * cos_2sigma_m * cos_2sigma_m)
311 - b / 6.0
312 * cos_2sigma_m
313 * (-3.0 + 4.0 * sin_sigma * sin_sigma)
314 * (-3.0 + 4.0 * cos_2sigma_m * cos_2sigma_m)));
315
316 let distance = WGS84_B * a * (sigma - delta_sigma);
317
318 Ok(distance)
319}
320
321fn distance_point_to_segment(
323 point: &Coordinate,
324 seg_start: &Coordinate,
325 seg_end: &Coordinate,
326 method: DistanceMethod,
327) -> Result<f64> {
328 match method {
329 DistanceMethod::Euclidean => Ok(distance_point_to_segment_euclidean(
330 point, seg_start, seg_end,
331 )),
332 DistanceMethod::Haversine | DistanceMethod::Vincenty => {
333 distance_point_to_segment_geodetic(point, seg_start, seg_end, method)
335 }
336 }
337}
338
339fn distance_point_to_segment_euclidean(
341 point: &Coordinate,
342 seg_start: &Coordinate,
343 seg_end: &Coordinate,
344) -> f64 {
345 let dx = seg_end.x - seg_start.x;
346 let dy = seg_end.y - seg_start.y;
347
348 let len_sq = dx * dx + dy * dy;
349
350 if len_sq < f64::EPSILON {
351 return euclidean_distance(point, seg_start);
353 }
354
355 let t = ((point.x - seg_start.x) * dx + (point.y - seg_start.y) * dy) / len_sq;
357
358 let t_clamped = t.clamp(0.0, 1.0);
359
360 let closest_x = seg_start.x + t_clamped * dx;
362 let closest_y = seg_start.y + t_clamped * dy;
363
364 let closest = Coordinate::new_2d(closest_x, closest_y);
365
366 euclidean_distance(point, &closest)
367}
368
369fn distance_point_to_segment_geodetic(
371 point: &Coordinate,
372 seg_start: &Coordinate,
373 seg_end: &Coordinate,
374 method: DistanceMethod,
375) -> Result<f64> {
376 const NUM_SAMPLES: usize = 10;
378
379 let mut min_dist = f64::INFINITY;
380
381 for i in 0..=NUM_SAMPLES {
382 let t = i as f64 / NUM_SAMPLES as f64;
383
384 let sample_x = seg_start.x + t * (seg_end.x - seg_start.x);
385 let sample_y = seg_start.y + t * (seg_end.y - seg_start.y);
386 let sample = Coordinate::new_2d(sample_x, sample_y);
387
388 let dist = match method {
389 DistanceMethod::Haversine => haversine_distance(point, &sample)?,
390 DistanceMethod::Vincenty => vincenty_distance(point, &sample)?,
391 _ => euclidean_distance(point, &sample),
392 };
393
394 min_dist = min_dist.min(dist);
395 }
396
397 Ok(min_dist)
398}
399
400fn point_in_polygon_boundary(point: &Coordinate, polygon: &Polygon) -> bool {
402 ray_casting_test(point, &polygon.exterior.coords)
403}
404
405fn ray_casting_test(point: &Coordinate, ring: &[Coordinate]) -> bool {
407 let mut inside = false;
408 let n = ring.len();
409
410 let mut j = n - 1;
411 for i in 0..n {
412 let xi = ring[i].x;
413 let yi = ring[i].y;
414 let xj = ring[j].x;
415 let yj = ring[j].y;
416
417 let intersect = ((yi > point.y) != (yj > point.y))
418 && (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);
419
420 if intersect {
421 inside = !inside;
422 }
423
424 j = i;
425 }
426
427 inside
428}
429
430#[inline(always)]
436fn point_dist(p: (f64, f64), q: (f64, f64)) -> f64 {
437 let dx = p.0 - q.0;
438 let dy = p.1 - q.1;
439 (dx * dx + dy * dy).sqrt()
440}
441
442fn point_to_segment_dist(p: (f64, f64), a: (f64, f64), b: (f64, f64)) -> f64 {
444 let ab = (b.0 - a.0, b.1 - a.1);
445 let len2 = ab.0 * ab.0 + ab.1 * ab.1;
446 if len2 < 1e-14 {
447 return point_dist(p, a);
448 }
449 let t = ((p.0 - a.0) * ab.0 + (p.1 - a.1) * ab.1) / len2;
450 let t = t.clamp(0.0, 1.0);
451 point_dist(p, (a.0 + t * ab.0, a.1 + t * ab.1))
452}
453
454pub fn discrete_frechet_distance(a: &[(f64, f64)], b: &[(f64, f64)]) -> f64 {
464 if a.is_empty() || b.is_empty() {
465 return 0.0;
466 }
467 let n = a.len();
468 let m = b.len();
469 let mut ca = vec![vec![f64::INFINITY; m]; n];
470 for i in 0..n {
471 for j in 0..m {
472 let d = point_dist(a[i], b[j]);
473 ca[i][j] = if i == 0 && j == 0 {
474 d
475 } else if i == 0 {
476 ca[0][j - 1].max(d)
477 } else if j == 0 {
478 ca[i - 1][0].max(d)
479 } else {
480 ca[i - 1][j].min(ca[i][j - 1]).min(ca[i - 1][j - 1]).max(d)
481 };
482 }
483 }
484 ca[n - 1][m - 1]
485}
486
487pub fn directed_hausdorff(a: &[(f64, f64)], b: &[(f64, f64)]) -> f64 {
492 if a.is_empty() || b.is_empty() {
493 return 0.0;
494 }
495 a.iter()
496 .map(|&p| {
497 b.iter()
498 .map(|&q| point_dist(p, q))
499 .fold(f64::INFINITY, f64::min)
500 })
501 .fold(0.0_f64, f64::max)
502}
503
504pub fn hausdorff_distance(a: &[(f64, f64)], b: &[(f64, f64)]) -> f64 {
510 directed_hausdorff(a, b).max(directed_hausdorff(b, a))
511}
512
513fn directed_hausdorff_to_segs(from: &[(f64, f64)], to: &[(f64, f64)]) -> f64 {
515 if from.is_empty() || to.is_empty() {
516 return 0.0;
517 }
518 from.iter()
519 .map(|&p| {
520 if to.len() == 1 {
521 point_dist(p, to[0])
522 } else {
523 to.windows(2)
524 .map(|seg| point_to_segment_dist(p, seg[0], seg[1]))
525 .fold(f64::INFINITY, f64::min)
526 }
527 })
528 .fold(0.0_f64, f64::max)
529}
530
531pub fn hausdorff_distance_to_segments(a: &[(f64, f64)], b: &[(f64, f64)]) -> f64 {
538 directed_hausdorff_to_segs(a, b).max(directed_hausdorff_to_segs(b, a))
539}
540
541#[cfg(test)]
542mod tests {
543 use super::*;
544
545 #[test]
546 fn test_distance_point_to_point_euclidean() {
547 let p1 = Point::new(0.0, 0.0);
548 let p2 = Point::new(3.0, 4.0);
549
550 let result = distance_point_to_point(&p1, &p2, DistanceMethod::Euclidean);
551 assert!(result.is_ok());
552
553 if let Ok(dist) = result {
554 assert!((dist - 5.0).abs() < 1e-10);
555 }
556 }
557
558 #[test]
559 fn test_distance_point_to_point_3d() {
560 let p1 = Point::new_3d(0.0, 0.0, 0.0);
561 let p2 = Point::new_3d(1.0, 1.0, 1.0);
562
563 let result = distance_point_to_point(&p1, &p2, DistanceMethod::Euclidean);
564 assert!(result.is_ok());
565
566 if let Ok(dist) = result {
567 assert!((dist - 3.0_f64.sqrt()).abs() < 1e-10);
568 }
569 }
570
571 #[test]
572 fn test_distance_point_to_point_haversine() {
573 let nyc = Point::new(-74.0, 40.7); let lon = Point::new(-0.1, 51.5); let result = distance_point_to_point(&nyc, &lon, DistanceMethod::Haversine);
578 assert!(result.is_ok());
579
580 if let Ok(dist) = result {
581 assert!(dist > 5_000_000.0);
583 assert!(dist < 6_000_000.0);
584 }
585 }
586
587 #[test]
588 fn test_distance_point_to_linestring() {
589 let point = Point::new(1.0, 1.0);
590
591 let coords = vec![
592 Coordinate::new_2d(0.0, 0.0),
593 Coordinate::new_2d(2.0, 0.0),
594 Coordinate::new_2d(2.0, 2.0),
595 ];
596 let linestring = LineString::new(coords);
597 assert!(linestring.is_ok());
598
599 if let Ok(ls) = linestring {
600 let result = distance_point_to_linestring(&point, &ls, DistanceMethod::Euclidean);
601 assert!(result.is_ok());
602
603 if let Ok(dist) = result {
604 assert!((dist - 1.0).abs() < 1e-10);
606 }
607 }
608 }
609
610 #[test]
611 fn test_distance_point_to_polygon() {
612 let coords = vec![
614 Coordinate::new_2d(0.0, 0.0),
615 Coordinate::new_2d(4.0, 0.0),
616 Coordinate::new_2d(4.0, 4.0),
617 Coordinate::new_2d(0.0, 4.0),
618 Coordinate::new_2d(0.0, 0.0),
619 ];
620 let exterior = LineString::new(coords);
621 assert!(exterior.is_ok());
622
623 if let Ok(ext) = exterior {
624 let polygon = Polygon::new(ext, vec![]);
625 assert!(polygon.is_ok());
626
627 if let Ok(poly) = polygon {
628 let inside = Point::new(2.0, 2.0);
630 let result1 = distance_point_to_polygon(&inside, &poly, DistanceMethod::Euclidean);
631 assert!(result1.is_ok());
632
633 if let Ok(dist) = result1 {
634 assert_eq!(dist, 0.0);
635 }
636
637 let outside = Point::new(5.0, 5.0);
639 let result2 = distance_point_to_polygon(&outside, &poly, DistanceMethod::Euclidean);
640 assert!(result2.is_ok());
641
642 if let Ok(dist) = result2 {
643 assert!((dist - 2.0_f64.sqrt()).abs() < 1e-10);
645 }
646 }
647 }
648 }
649
650 #[test]
651 fn test_euclidean_distance() {
652 let c1 = Coordinate::new_2d(0.0, 0.0);
653 let c2 = Coordinate::new_2d(3.0, 4.0);
654
655 let dist = euclidean_distance(&c1, &c2);
656 assert!((dist - 5.0).abs() < 1e-10);
657 }
658
659 #[test]
660 fn test_haversine_same_point() {
661 let c1 = Coordinate::new_2d(0.0, 0.0);
662 let c2 = Coordinate::new_2d(0.0, 0.0);
663
664 let result = haversine_distance(&c1, &c2);
665 assert!(result.is_ok());
666
667 if let Ok(dist) = result {
668 assert!(dist.abs() < 1e-10);
669 }
670 }
671
672 #[test]
673 fn test_vincenty_same_point() {
674 let c1 = Coordinate::new_2d(0.0, 0.0);
675 let c2 = Coordinate::new_2d(0.0, 0.0);
676
677 let result = vincenty_distance(&c1, &c2);
678 assert!(result.is_ok());
679
680 if let Ok(dist) = result {
681 assert!(dist.abs() < 1e-10);
682 }
683 }
684
685 #[test]
686 fn test_distance_point_to_segment_euclidean() {
687 let point = Coordinate::new_2d(1.0, 1.0);
688 let seg_start = Coordinate::new_2d(0.0, 0.0);
689 let seg_end = Coordinate::new_2d(2.0, 0.0);
690
691 let dist = distance_point_to_segment_euclidean(&point, &seg_start, &seg_end);
692 assert!((dist - 1.0).abs() < 1e-10);
693 }
694
695 #[test]
696 fn test_ray_casting() {
697 let ring = vec![
698 Coordinate::new_2d(0.0, 0.0),
699 Coordinate::new_2d(4.0, 0.0),
700 Coordinate::new_2d(4.0, 4.0),
701 Coordinate::new_2d(0.0, 4.0),
702 Coordinate::new_2d(0.0, 0.0),
703 ];
704
705 let inside = Coordinate::new_2d(2.0, 2.0);
707 assert!(ray_casting_test(&inside, &ring));
708
709 let outside = Coordinate::new_2d(5.0, 5.0);
711 assert!(!ray_casting_test(&outside, &ring));
712 }
713
714 #[test]
715 fn test_invalid_latitude() {
716 let c1 = Coordinate::new_2d(0.0, 95.0); let c2 = Coordinate::new_2d(0.0, 0.0);
718
719 let result = haversine_distance(&c1, &c2);
720 assert!(result.is_err());
721 }
722}