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