gistools/geometry/tools/lines/
point_to_line_distance.rs1use crate::{
2 geometry::{euclidean_distance, haversine_distance},
3 proj::Coords,
4};
5use libm::{fmax, fmin, hypot};
6use s2json::{GetXY, GetZ};
7
8#[derive(Debug, Default, Copy, Clone, PartialEq)]
10pub enum DistanceMethod {
11 #[default]
13 Euclidean,
14 Haversine,
16}
17
18#[derive(Debug, Default, Copy, Clone, PartialEq)]
19struct ClosestIndex {
20 index: usize,
21 dist: f64,
22}
23
24pub fn point_to_line_distance<P: GetXY + GetZ, Q: GetXY + GetZ>(
34 line: &[P],
35 point: &Q,
36 method: Option<DistanceMethod>,
37) -> f64 {
38 let method = method.unwrap_or(DistanceMethod::Euclidean);
39 let haversine = method == DistanceMethod::Haversine;
40
41 let mut closest_index: Option<ClosestIndex> = None;
42 for (i, line) in line.iter().enumerate() {
43 let dist = if haversine {
45 haversine_distance(point, line)
46 } else {
47 euclidean_distance(point, line)
48 };
49 if dist == 0. {
50 return 0.;
51 }
52 if closest_index.is_none() || dist < closest_index.unwrap().dist {
53 closest_index = Some(ClosestIndex { index: i, dist });
54 }
55 }
56
57 if closest_index.is_none() {
59 return -1.;
60 }
61 let closest_index = closest_index.unwrap();
62
63 if line.len() == 1 {
65 return closest_index.dist;
66 }
67
68 let curr = &line[closest_index.index];
69 if closest_index.index == 0 {
71 return distance_point_to_segment(curr, &line[closest_index.index + 1], point, method);
72 }
73 if closest_index.index == line.len() - 1 {
74 return distance_point_to_segment(curr, &line[closest_index.index - 1], point, method);
75 }
76 let prev = &line[closest_index.index - 1];
77 let next = &line[closest_index.index + 1];
78
79 let dist1 = distance_point_to_segment(curr, prev, point, method);
81 let dist2 = distance_point_to_segment(curr, next, point, method);
82
83 if dist1 < dist2 { dist1 } else { dist2 }
84}
85
86fn distance_point_to_segment<A: GetXY, B: GetXY, P: GetXY>(
94 a: &A,
95 b: &B,
96 p: &P,
97 method: DistanceMethod,
98) -> f64 {
99 if method == DistanceMethod::Haversine {
100 let abx = b.x() - a.x();
104 let aby = b.y() - a.y();
105 let apx = p.x() - a.x();
106 let apy = p.y() - a.y();
107 let ab_len_sq = abx * abx + aby * aby;
108
109 if ab_len_sq == 0. {
110 return haversine_distance(p, a);
111 }
112
113 let mut t = (apx * abx + apy * aby) / ab_len_sq;
114 t = fmax(0., fmin(1., t));
115
116 let proj = Coords::new_xy(a.x() + t * abx, a.y() + t * aby);
117 return haversine_distance(p, &proj);
118 }
119
120 let ab = Coords::new_xy(b.x() - a.x(), b.y() - a.y());
122 let ap = Coords::new_xy(p.x() - a.x(), p.y() - a.y());
123 let ab_len_sq = ab.x() * ab.x() + ab.y() * ab.y();
124
125 if ab_len_sq == 0. {
126 return hypot(p.x() - a.x(), p.y() - a.y());
127 }
128
129 let mut t = (ap.x() * ab.x() + ap.y() * ab.y()) / ab_len_sq;
130 t = fmax(0., fmin(1., t));
131
132 let closest = Coords::new_xy(a.x() + t * ab.x(), a.y() + t * ab.y());
133 hypot(p.x() - closest.x(), p.y() - closest.y())
134}