use rapidgeo_distance::{geodesic, LngLat};
pub trait PerpDistance {
fn d_perp_m(&self, a: LngLat, b: LngLat, p: LngLat) -> f64;
}
pub struct XtGreatCircle;
impl PerpDistance for XtGreatCircle {
fn d_perp_m(&self, a: LngLat, b: LngLat, p: LngLat) -> f64 {
geodesic::great_circle_point_to_seg(p, (a, b))
}
}
pub struct XtEnu {
pub origin: LngLat,
}
impl PerpDistance for XtEnu {
fn d_perp_m(&self, a: LngLat, b: LngLat, p: LngLat) -> f64 {
geodesic::point_to_segment_enu_m(p, (a, b))
}
}
pub struct XtEuclid;
impl PerpDistance for XtEuclid {
fn d_perp_m(&self, a: LngLat, b: LngLat, p: LngLat) -> f64 {
let (ax, ay) = (a.lng_deg, a.lat_deg);
let (bx, by) = (b.lng_deg, b.lat_deg);
let (px, py) = (p.lng_deg, p.lat_deg);
if ax == bx && ay == by {
let dx = px - ax;
let dy = py - ay;
return (dx * dx + dy * dy).sqrt();
}
let dx = bx - ax;
let dy = by - ay;
let t = ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy);
let t = t.clamp(0.0, 1.0);
let proj_x = ax + t * dx;
let proj_y = ay + t * dy;
let dpx = px - proj_x;
let dpy = py - proj_y;
(dpx * dpx + dpy * dpy).sqrt()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_euclid_perpendicular_distance() {
let backend = XtEuclid;
let a = LngLat::new_deg(0.0, 0.0);
let b = LngLat::new_deg(10.0, 0.0);
let p = LngLat::new_deg(5.0, 3.0);
let dist = backend.d_perp_m(a, b, p);
assert!((dist - 3.0).abs() < 0.001);
}
#[test]
fn test_euclid_point_on_line() {
let backend = XtEuclid;
let a = LngLat::new_deg(0.0, 0.0);
let b = LngLat::new_deg(10.0, 0.0);
let p = LngLat::new_deg(5.0, 0.0);
let dist = backend.d_perp_m(a, b, p);
assert!(dist.abs() < 0.001);
}
#[test]
fn test_euclid_point_at_endpoint() {
let backend = XtEuclid;
let a = LngLat::new_deg(0.0, 0.0);
let b = LngLat::new_deg(10.0, 0.0);
let dist_a = backend.d_perp_m(a, b, a);
let dist_b = backend.d_perp_m(a, b, b);
assert!(dist_a.abs() < 0.001);
assert!(dist_b.abs() < 0.001);
}
#[test]
fn test_euclid_degenerate_segment() {
let backend = XtEuclid;
let a = LngLat::new_deg(5.0, 5.0);
let b = LngLat::new_deg(5.0, 5.0);
let p = LngLat::new_deg(8.0, 9.0);
let dist = backend.d_perp_m(a, b, p);
let expected = (3.0_f64 * 3.0 + 4.0 * 4.0).sqrt();
assert!((dist - expected).abs() < 0.001);
}
#[test]
fn test_euclid_projection_beyond_segment() {
let backend = XtEuclid;
let a = LngLat::new_deg(0.0, 0.0);
let b = LngLat::new_deg(5.0, 0.0);
let p = LngLat::new_deg(10.0, 3.0);
let dist = backend.d_perp_m(a, b, p);
let expected = (5.0_f64 * 5.0 + 3.0 * 3.0).sqrt();
assert!((dist - expected).abs() < 0.001);
}
#[test]
fn test_great_circle_basic() {
let backend = XtGreatCircle;
let a = LngLat::new_deg(-122.0, 37.0);
let b = LngLat::new_deg(-121.0, 37.0);
let p = LngLat::new_deg(-121.5, 37.1);
let dist = backend.d_perp_m(a, b, p);
assert!(dist > 10000.0 && dist < 12000.0);
}
#[test]
fn test_great_circle_point_on_line() {
let backend = XtGreatCircle;
let a = LngLat::new_deg(-122.0, 37.0);
let b = LngLat::new_deg(-121.0, 37.0);
let p = LngLat::new_deg(-121.5, 37.0);
let dist = backend.d_perp_m(a, b, p);
assert!(dist < 1000.0);
}
#[test]
fn test_enu_basic() {
let origin = LngLat::new_deg(-121.5, 37.0);
let backend = XtEnu { origin };
let a = LngLat::new_deg(-122.0, 37.0);
let b = LngLat::new_deg(-121.0, 37.0);
let p = LngLat::new_deg(-121.5, 37.1);
let dist = backend.d_perp_m(a, b, p);
assert!(dist > 10000.0 && dist < 12000.0);
}
}