use nodedb_types::geometry::{Geometry, haversine_distance, point_in_polygon};
use nodedb_types::geometry_bbox;
use super::edge::{point_on_ring_boundary, ring_edges, segment_to_segment_dist_sq};
pub fn st_distance(a: &Geometry, b: &Geometry) -> f64 {
match (a, b) {
(Geometry::Point { coordinates: ca }, Geometry::Point { coordinates: cb }) => {
haversine_distance(ca[0], ca[1], cb[0], cb[1])
}
(Geometry::Point { coordinates: pt }, Geometry::LineString { coordinates: line })
| (Geometry::LineString { coordinates: line }, Geometry::Point { coordinates: pt }) => {
point_to_linestring_distance(*pt, line)
}
(Geometry::Point { coordinates: pt }, Geometry::Polygon { coordinates: rings })
| (Geometry::Polygon { coordinates: rings }, Geometry::Point { coordinates: pt }) => {
point_to_polygon_distance(*pt, rings)
}
(Geometry::LineString { coordinates: la }, Geometry::LineString { coordinates: lb }) => {
linestring_to_linestring_distance(la, lb)
}
(Geometry::LineString { coordinates: line }, Geometry::Polygon { coordinates: rings })
| (Geometry::Polygon { coordinates: rings }, Geometry::LineString { coordinates: line }) => {
linestring_to_polygon_distance(line, rings)
}
(Geometry::Polygon { coordinates: ra }, Geometry::Polygon { coordinates: rb }) => {
polygon_to_polygon_distance(ra, rb)
}
(Geometry::MultiPoint { coordinates }, other)
| (other, Geometry::MultiPoint { coordinates }) => coordinates
.iter()
.map(|pt| st_distance(&Geometry::Point { coordinates: *pt }, other))
.fold(f64::INFINITY, f64::min),
(Geometry::MultiLineString { coordinates }, other)
| (other, Geometry::MultiLineString { coordinates }) => coordinates
.iter()
.map(|ls| {
st_distance(
&Geometry::LineString {
coordinates: ls.clone(),
},
other,
)
})
.fold(f64::INFINITY, f64::min),
(Geometry::MultiPolygon { coordinates }, other)
| (other, Geometry::MultiPolygon { coordinates }) => coordinates
.iter()
.map(|poly| {
st_distance(
&Geometry::Polygon {
coordinates: poly.clone(),
},
other,
)
})
.fold(f64::INFINITY, f64::min),
(Geometry::GeometryCollection { geometries }, other)
| (other, Geometry::GeometryCollection { geometries }) => geometries
.iter()
.map(|g| st_distance(g, other))
.fold(f64::INFINITY, f64::min),
}
}
pub fn st_dwithin(a: &Geometry, b: &Geometry, distance_meters: f64) -> bool {
if let (Geometry::Point { coordinates: ca }, Geometry::Point { coordinates: cb }) = (a, b) {
return haversine_distance(ca[0], ca[1], cb[0], cb[1]) <= distance_meters;
}
let a_bb = geometry_bbox(a).expand_meters(distance_meters);
let b_bb = geometry_bbox(b);
if !a_bb.intersects(&b_bb) {
return false;
}
st_distance(a, b) <= distance_meters
}
fn point_to_linestring_distance(pt: [f64; 2], line: &[[f64; 2]]) -> f64 {
if line.is_empty() {
return f64::INFINITY;
}
if line.len() == 1 {
return haversine_distance(pt[0], pt[1], line[0][0], line[0][1]);
}
let mut min_dist = f64::INFINITY;
for i in 0..line.len() - 1 {
let d = point_to_segment_meters(pt, line[i], line[i + 1]);
min_dist = min_dist.min(d);
}
min_dist
}
fn point_to_polygon_distance(pt: [f64; 2], rings: &[Vec<[f64; 2]>]) -> f64 {
let Some(exterior) = rings.first() else {
return f64::INFINITY;
};
if point_in_polygon(pt[0], pt[1], exterior) || point_on_ring_boundary(pt, exterior) {
let in_hole = rings[1..]
.iter()
.any(|hole| point_in_polygon(pt[0], pt[1], hole) && !point_on_ring_boundary(pt, hole));
if !in_hole {
return 0.0;
}
}
let mut min_dist = f64::INFINITY;
for ring in rings {
for i in 0..ring.len().saturating_sub(1) {
let d = point_to_segment_meters(pt, ring[i], ring[i + 1]);
min_dist = min_dist.min(d);
}
}
min_dist
}
fn linestring_to_linestring_distance(la: &[[f64; 2]], lb: &[[f64; 2]]) -> f64 {
let mut min_dist = f64::INFINITY;
for i in 0..la.len().saturating_sub(1) {
for j in 0..lb.len().saturating_sub(1) {
let d_sq = segment_to_segment_dist_sq(la[i], la[i + 1], lb[j], lb[j + 1]);
if d_sq < 1e-20 {
return 0.0;
}
let d = coord_dist_to_meters(d_sq.sqrt(), la[i], lb[j]);
min_dist = min_dist.min(d);
}
}
min_dist
}
fn linestring_to_polygon_distance(line: &[[f64; 2]], rings: &[Vec<[f64; 2]>]) -> f64 {
let Some(exterior) = rings.first() else {
return f64::INFINITY;
};
for pt in line {
if point_in_polygon(pt[0], pt[1], exterior) || point_on_ring_boundary(*pt, exterior) {
return 0.0;
}
}
let mut min_dist = f64::INFINITY;
for ring in rings {
let edges = ring_edges(ring);
for i in 0..line.len().saturating_sub(1) {
for &(re_a, re_b) in &edges {
let d_sq = segment_to_segment_dist_sq(line[i], line[i + 1], re_a, re_b);
if d_sq < 1e-20 {
return 0.0;
}
let d = coord_dist_to_meters(d_sq.sqrt(), line[i], re_a);
min_dist = min_dist.min(d);
}
}
}
min_dist
}
fn polygon_to_polygon_distance(ra: &[Vec<[f64; 2]>], rb: &[Vec<[f64; 2]>]) -> f64 {
let Some(ext_a) = ra.first() else {
return f64::INFINITY;
};
let Some(ext_b) = rb.first() else {
return f64::INFINITY;
};
for pt in ext_b {
if point_in_polygon(pt[0], pt[1], ext_a) || point_on_ring_boundary(*pt, ext_a) {
return 0.0;
}
}
for pt in ext_a {
if point_in_polygon(pt[0], pt[1], ext_b) || point_on_ring_boundary(*pt, ext_b) {
return 0.0;
}
}
let mut min_dist = f64::INFINITY;
let a_edges = ring_edges(ext_a);
let b_edges = ring_edges(ext_b);
for &(a1, a2) in &a_edges {
for &(b1, b2) in &b_edges {
let d_sq = segment_to_segment_dist_sq(a1, a2, b1, b2);
if d_sq < 1e-20 {
return 0.0;
}
let d = coord_dist_to_meters(d_sq.sqrt(), a1, b1);
min_dist = min_dist.min(d);
}
}
min_dist
}
fn point_to_segment_meters(pt: [f64; 2], seg_a: [f64; 2], seg_b: [f64; 2]) -> f64 {
let dx = seg_b[0] - seg_a[0];
let dy = seg_b[1] - seg_a[1];
let len_sq = dx * dx + dy * dy;
let nearest = if len_sq < 1e-20 {
seg_a
} else {
let t = ((pt[0] - seg_a[0]) * dx + (pt[1] - seg_a[1]) * dy) / len_sq;
let t = t.clamp(0.0, 1.0);
[seg_a[0] + t * dx, seg_a[1] + t * dy]
};
haversine_distance(pt[0], pt[1], nearest[0], nearest[1])
}
fn coord_dist_to_meters(coord_dist: f64, a: [f64; 2], b: [f64; 2]) -> f64 {
let mid_lat = ((a[1] + b[1]) / 2.0).to_radians();
let meters_per_deg_lat = 110_540.0;
let meters_per_deg_lng = 111_320.0 * mid_lat.cos();
let avg_scale = (meters_per_deg_lat + meters_per_deg_lng) / 2.0;
coord_dist * avg_scale
}
#[cfg(test)]
mod tests {
use super::*;
fn square() -> Geometry {
Geometry::polygon(vec![vec![
[0.0, 0.0],
[1.0, 0.0],
[1.0, 1.0],
[0.0, 1.0],
[0.0, 0.0],
]])
}
#[test]
fn point_to_point_haversine() {
let d = st_distance(&Geometry::point(0.0, 0.0), &Geometry::point(0.0, 1.0));
assert!((d - 111_195.0).abs() < 500.0, "got {d}");
}
#[test]
fn point_to_point_same() {
let d = st_distance(&Geometry::point(5.0, 5.0), &Geometry::point(5.0, 5.0));
assert!(d < 0.01);
}
#[test]
fn point_inside_polygon_zero_distance() {
let d = st_distance(&Geometry::point(0.5, 0.5), &square());
assert!(d < 0.01);
}
#[test]
fn point_on_edge_zero_distance() {
let d = st_distance(&Geometry::point(0.5, 0.0), &square());
assert!(d < 0.01);
}
#[test]
fn point_to_polygon_outside() {
let d = st_distance(&Geometry::point(2.0, 0.5), &square());
assert!(d > 100_000.0, "got {d}");
}
#[test]
fn point_to_linestring() {
let line = Geometry::line_string(vec![[0.0, 0.0], [10.0, 0.0]]);
let d = st_distance(&Geometry::point(5.0, 1.0), &line);
assert!((d - 111_195.0).abs() < 500.0, "got {d}");
}
#[test]
fn linestring_to_linestring_crossing() {
let a = Geometry::line_string(vec![[0.0, 0.0], [10.0, 10.0]]);
let b = Geometry::line_string(vec![[0.0, 10.0], [10.0, 0.0]]);
let d = st_distance(&a, &b);
assert!(d < 0.01);
}
#[test]
fn dwithin_points_close() {
let a = Geometry::point(0.0, 0.0);
let b = Geometry::point(0.001, 0.0);
assert!(st_dwithin(&a, &b, 200.0));
assert!(!st_dwithin(&a, &b, 50.0));
}
#[test]
fn dwithin_point_polygon() {
let pt = Geometry::point(2.0, 0.5);
assert!(st_dwithin(&pt, &square(), 200_000.0)); assert!(!st_dwithin(&pt, &square(), 50_000.0)); }
#[test]
fn dwithin_bbox_prefilter_rejects() {
let a = Geometry::point(0.0, 0.0);
let b = Geometry::point(90.0, 45.0);
assert!(!st_dwithin(&a, &b, 1000.0));
}
#[test]
fn polygon_to_polygon_overlapping() {
let other = Geometry::polygon(vec![vec![
[0.5, 0.5],
[1.5, 0.5],
[1.5, 1.5],
[0.5, 1.5],
[0.5, 0.5],
]]);
let d = st_distance(&square(), &other);
assert!(d < 0.01);
}
#[test]
fn polygon_to_polygon_separated() {
let far = Geometry::polygon(vec![vec![
[5.0, 5.0],
[6.0, 5.0],
[6.0, 6.0],
[5.0, 6.0],
[5.0, 5.0],
]]);
let d = st_distance(&square(), &far);
assert!(d > 100_000.0, "got {d}");
}
}