1use nodedb_types::geometry::{Geometry, haversine_distance, point_in_polygon};
12use nodedb_types::geometry_bbox;
13
14use super::edge::{point_on_ring_boundary, ring_edges, segment_to_segment_dist_sq};
15
16pub fn st_distance(a: &Geometry, b: &Geometry) -> f64 {
21 match (a, b) {
22 (Geometry::Point { coordinates: ca }, Geometry::Point { coordinates: cb }) => {
24 haversine_distance(ca[0], ca[1], cb[0], cb[1])
25 }
26
27 (Geometry::Point { coordinates: pt }, Geometry::LineString { coordinates: line })
29 | (Geometry::LineString { coordinates: line }, Geometry::Point { coordinates: pt }) => {
30 point_to_linestring_distance(*pt, line)
31 }
32
33 (Geometry::Point { coordinates: pt }, Geometry::Polygon { coordinates: rings })
35 | (Geometry::Polygon { coordinates: rings }, Geometry::Point { coordinates: pt }) => {
36 point_to_polygon_distance(*pt, rings)
37 }
38
39 (Geometry::LineString { coordinates: la }, Geometry::LineString { coordinates: lb }) => {
41 linestring_to_linestring_distance(la, lb)
42 }
43
44 (Geometry::LineString { coordinates: line }, Geometry::Polygon { coordinates: rings })
46 | (Geometry::Polygon { coordinates: rings }, Geometry::LineString { coordinates: line }) => {
47 linestring_to_polygon_distance(line, rings)
48 }
49
50 (Geometry::Polygon { coordinates: ra }, Geometry::Polygon { coordinates: rb }) => {
52 polygon_to_polygon_distance(ra, rb)
53 }
54
55 (Geometry::MultiPoint { coordinates }, other)
57 | (other, Geometry::MultiPoint { coordinates }) => coordinates
58 .iter()
59 .map(|pt| st_distance(&Geometry::Point { coordinates: *pt }, other))
60 .fold(f64::INFINITY, f64::min),
61
62 (Geometry::MultiLineString { coordinates }, other)
63 | (other, Geometry::MultiLineString { coordinates }) => coordinates
64 .iter()
65 .map(|ls| {
66 st_distance(
67 &Geometry::LineString {
68 coordinates: ls.clone(),
69 },
70 other,
71 )
72 })
73 .fold(f64::INFINITY, f64::min),
74
75 (Geometry::MultiPolygon { coordinates }, other)
76 | (other, Geometry::MultiPolygon { coordinates }) => coordinates
77 .iter()
78 .map(|poly| {
79 st_distance(
80 &Geometry::Polygon {
81 coordinates: poly.clone(),
82 },
83 other,
84 )
85 })
86 .fold(f64::INFINITY, f64::min),
87
88 (Geometry::GeometryCollection { geometries }, other)
89 | (other, Geometry::GeometryCollection { geometries }) => geometries
90 .iter()
91 .map(|g| st_distance(g, other))
92 .fold(f64::INFINITY, f64::min),
93 }
94}
95
96pub fn st_dwithin(a: &Geometry, b: &Geometry, distance_meters: f64) -> bool {
101 if let (Geometry::Point { coordinates: ca }, Geometry::Point { coordinates: cb }) = (a, b) {
103 return haversine_distance(ca[0], ca[1], cb[0], cb[1]) <= distance_meters;
104 }
105
106 let a_bb = geometry_bbox(a).expand_meters(distance_meters);
108 let b_bb = geometry_bbox(b);
109 if !a_bb.intersects(&b_bb) {
110 return false;
111 }
112
113 st_distance(a, b) <= distance_meters
115}
116
117fn point_to_linestring_distance(pt: [f64; 2], line: &[[f64; 2]]) -> f64 {
121 if line.is_empty() {
122 return f64::INFINITY;
123 }
124 if line.len() == 1 {
125 return haversine_distance(pt[0], pt[1], line[0][0], line[0][1]);
126 }
127
128 let mut min_dist = f64::INFINITY;
129 for i in 0..line.len() - 1 {
130 let d = point_to_segment_meters(pt, line[i], line[i + 1]);
131 min_dist = min_dist.min(d);
132 }
133 min_dist
134}
135
136fn point_to_polygon_distance(pt: [f64; 2], rings: &[Vec<[f64; 2]>]) -> f64 {
138 let Some(exterior) = rings.first() else {
139 return f64::INFINITY;
140 };
141
142 if point_in_polygon(pt[0], pt[1], exterior) || point_on_ring_boundary(pt, exterior) {
144 let in_hole = rings[1..]
145 .iter()
146 .any(|hole| point_in_polygon(pt[0], pt[1], hole) && !point_on_ring_boundary(pt, hole));
147 if !in_hole {
148 return 0.0;
149 }
150 }
151
152 let mut min_dist = f64::INFINITY;
154 for ring in rings {
155 for i in 0..ring.len().saturating_sub(1) {
156 let d = point_to_segment_meters(pt, ring[i], ring[i + 1]);
157 min_dist = min_dist.min(d);
158 }
159 }
160 min_dist
161}
162
163fn linestring_to_linestring_distance(la: &[[f64; 2]], lb: &[[f64; 2]]) -> f64 {
164 let mut min_dist = f64::INFINITY;
165 for i in 0..la.len().saturating_sub(1) {
166 for j in 0..lb.len().saturating_sub(1) {
167 let d_sq = segment_to_segment_dist_sq(la[i], la[i + 1], lb[j], lb[j + 1]);
168 if d_sq < 1e-20 {
169 return 0.0;
170 }
171 let d = coord_dist_to_meters(d_sq.sqrt(), la[i], lb[j]);
172 min_dist = min_dist.min(d);
173 }
174 }
175 min_dist
176}
177
178fn linestring_to_polygon_distance(line: &[[f64; 2]], rings: &[Vec<[f64; 2]>]) -> f64 {
179 let Some(exterior) = rings.first() else {
181 return f64::INFINITY;
182 };
183 for pt in line {
184 if point_in_polygon(pt[0], pt[1], exterior) || point_on_ring_boundary(*pt, exterior) {
185 return 0.0;
186 }
187 }
188
189 let mut min_dist = f64::INFINITY;
191 for ring in rings {
192 let edges = ring_edges(ring);
193 for i in 0..line.len().saturating_sub(1) {
194 for &(re_a, re_b) in &edges {
195 let d_sq = segment_to_segment_dist_sq(line[i], line[i + 1], re_a, re_b);
196 if d_sq < 1e-20 {
197 return 0.0;
198 }
199 let d = coord_dist_to_meters(d_sq.sqrt(), line[i], re_a);
200 min_dist = min_dist.min(d);
201 }
202 }
203 }
204 min_dist
205}
206
207fn polygon_to_polygon_distance(ra: &[Vec<[f64; 2]>], rb: &[Vec<[f64; 2]>]) -> f64 {
208 let Some(ext_a) = ra.first() else {
209 return f64::INFINITY;
210 };
211 let Some(ext_b) = rb.first() else {
212 return f64::INFINITY;
213 };
214
215 for pt in ext_b {
217 if point_in_polygon(pt[0], pt[1], ext_a) || point_on_ring_boundary(*pt, ext_a) {
218 return 0.0;
219 }
220 }
221 for pt in ext_a {
222 if point_in_polygon(pt[0], pt[1], ext_b) || point_on_ring_boundary(*pt, ext_b) {
223 return 0.0;
224 }
225 }
226
227 let mut min_dist = f64::INFINITY;
229 let a_edges = ring_edges(ext_a);
230 let b_edges = ring_edges(ext_b);
231 for &(a1, a2) in &a_edges {
232 for &(b1, b2) in &b_edges {
233 let d_sq = segment_to_segment_dist_sq(a1, a2, b1, b2);
234 if d_sq < 1e-20 {
235 return 0.0;
236 }
237 let d = coord_dist_to_meters(d_sq.sqrt(), a1, b1);
238 min_dist = min_dist.min(d);
239 }
240 }
241 min_dist
242}
243
244fn point_to_segment_meters(pt: [f64; 2], seg_a: [f64; 2], seg_b: [f64; 2]) -> f64 {
249 let dx = seg_b[0] - seg_a[0];
250 let dy = seg_b[1] - seg_a[1];
251 let len_sq = dx * dx + dy * dy;
252
253 let nearest = if len_sq < 1e-20 {
254 seg_a
255 } else {
256 let t = ((pt[0] - seg_a[0]) * dx + (pt[1] - seg_a[1]) * dy) / len_sq;
257 let t = t.clamp(0.0, 1.0);
258 [seg_a[0] + t * dx, seg_a[1] + t * dy]
259 };
260
261 haversine_distance(pt[0], pt[1], nearest[0], nearest[1])
262}
263
264fn coord_dist_to_meters(coord_dist: f64, a: [f64; 2], b: [f64; 2]) -> f64 {
268 let mid_lat = ((a[1] + b[1]) / 2.0).to_radians();
269 let meters_per_deg_lat = 110_540.0;
270 let meters_per_deg_lng = 111_320.0 * mid_lat.cos();
271 let avg_scale = (meters_per_deg_lat + meters_per_deg_lng) / 2.0;
274 coord_dist * avg_scale
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 fn square() -> Geometry {
282 Geometry::polygon(vec![vec![
283 [0.0, 0.0],
284 [1.0, 0.0],
285 [1.0, 1.0],
286 [0.0, 1.0],
287 [0.0, 0.0],
288 ]])
289 }
290
291 #[test]
292 fn point_to_point_haversine() {
293 let d = st_distance(&Geometry::point(0.0, 0.0), &Geometry::point(0.0, 1.0));
294 assert!((d - 111_195.0).abs() < 500.0, "got {d}");
296 }
297
298 #[test]
299 fn point_to_point_same() {
300 let d = st_distance(&Geometry::point(5.0, 5.0), &Geometry::point(5.0, 5.0));
301 assert!(d < 0.01);
302 }
303
304 #[test]
305 fn point_inside_polygon_zero_distance() {
306 let d = st_distance(&Geometry::point(0.5, 0.5), &square());
307 assert!(d < 0.01);
308 }
309
310 #[test]
311 fn point_on_edge_zero_distance() {
312 let d = st_distance(&Geometry::point(0.5, 0.0), &square());
313 assert!(d < 0.01);
314 }
315
316 #[test]
317 fn point_to_polygon_outside() {
318 let d = st_distance(&Geometry::point(2.0, 0.5), &square());
319 assert!(d > 100_000.0, "got {d}");
321 }
322
323 #[test]
324 fn point_to_linestring() {
325 let line = Geometry::line_string(vec![[0.0, 0.0], [10.0, 0.0]]);
326 let d = st_distance(&Geometry::point(5.0, 1.0), &line);
328 assert!((d - 111_195.0).abs() < 500.0, "got {d}");
329 }
330
331 #[test]
332 fn linestring_to_linestring_crossing() {
333 let a = Geometry::line_string(vec![[0.0, 0.0], [10.0, 10.0]]);
334 let b = Geometry::line_string(vec![[0.0, 10.0], [10.0, 0.0]]);
335 let d = st_distance(&a, &b);
336 assert!(d < 0.01);
337 }
338
339 #[test]
340 fn dwithin_points_close() {
341 let a = Geometry::point(0.0, 0.0);
342 let b = Geometry::point(0.001, 0.0);
343 assert!(st_dwithin(&a, &b, 200.0));
345 assert!(!st_dwithin(&a, &b, 50.0));
346 }
347
348 #[test]
349 fn dwithin_point_polygon() {
350 let pt = Geometry::point(2.0, 0.5);
351 assert!(st_dwithin(&pt, &square(), 200_000.0)); assert!(!st_dwithin(&pt, &square(), 50_000.0)); }
355
356 #[test]
357 fn dwithin_bbox_prefilter_rejects() {
358 let a = Geometry::point(0.0, 0.0);
360 let b = Geometry::point(90.0, 45.0);
361 assert!(!st_dwithin(&a, &b, 1000.0));
362 }
363
364 #[test]
365 fn polygon_to_polygon_overlapping() {
366 let other = Geometry::polygon(vec![vec![
367 [0.5, 0.5],
368 [1.5, 0.5],
369 [1.5, 1.5],
370 [0.5, 1.5],
371 [0.5, 0.5],
372 ]]);
373 let d = st_distance(&square(), &other);
374 assert!(d < 0.01);
375 }
376
377 #[test]
378 fn polygon_to_polygon_separated() {
379 let far = Geometry::polygon(vec![vec![
380 [5.0, 5.0],
381 [6.0, 5.0],
382 [6.0, 6.0],
383 [5.0, 6.0],
384 [5.0, 5.0],
385 ]]);
386 let d = st_distance(&square(), &far);
387 assert!(d > 100_000.0, "got {d}");
388 }
389}