Skip to main content

geometry_algorithm/
dyn_distance.rs

1//! `distance_dyn` — runtime-dispatched distance between two
2//! [`DynGeometry`] inputs.
3//!
4//! Mirrors the variant-aware overload of
5//! `boost::geometry::distance(g1, g2)` reached when at least one
6//! argument has tag `dynamic_geometry_tag`
7//! (`algorithms/distance.hpp` + `geometries/adapted/boost_variant.hpp`).
8//! On the Rust side we make the dispatch explicit:
9//!
10//! 1. `match (a, b)` on the kind pairs.
11//! 2. Each arm calls the static distance kernels from `crate::distance`
12//!    on the unwrapped concrete types — dispatch inside the arm is
13//!    monomorphic and produces the same code as a direct call would.
14//! 3. Pairs with no static impl (e.g. `Polygon × Polygon` in v1) return
15//!    `Err(DynKindMismatch)` rather than panicking.
16
17use geometry_coords::CoordinateScalar;
18use geometry_cs::{CartesianFamily, CoordinateSystem};
19use geometry_model::{DynGeometry, DynKind, Linestring, Point, Segment};
20use geometry_strategy::{
21    DefaultDistance, DefaultDistanceStrategy, DistanceStrategy, PointToSegment, Pythagoras,
22};
23use geometry_tag::SameAs;
24use geometry_trait::Linestring as LinestringTrait;
25
26use crate::distance::{distance, distance_with};
27use crate::dyn_error::DynKindMismatch;
28
29/// The supported `(left, right)` kind combinations for `distance_dyn`.
30/// Mirrors the per-tag-pair static specialisations in
31/// `algorithms/distance.hpp` + the leaf strategies under
32/// `strategies/cartesian/distance_*.hpp`.
33const SUPPORTED: &[&[DynKind]] = &[
34    &[DynKind::Point, DynKind::Point],
35    &[DynKind::Point, DynKind::LineString], // via PointToSegment per segment
36    &[DynKind::LineString, DynKind::Point], // reversed
37];
38
39/// Runtime-dispatched distance.
40///
41/// Returns `Ok(d)` for any pair v1's static `distance` supports
42/// (point-point, and point-linestring via `PointToSegment`).
43///
44/// The Cartesian family bound reflects that `PointToSegment` (T24) is
45/// Cartesian-only in v1; point-point works for any family, but the
46/// shared bound keeps the wrapper's `where` clause a single line.
47///
48/// # Errors
49///
50/// Returns `Err(DynKindMismatch)` for any kind pair with no static
51/// distance impl (e.g. polygon-polygon).
52#[allow(
53    clippy::match_same_arms,
54    reason = "The two point↔linestring arms differ by argument order; keeping them separate documents both directions."
55)]
56pub fn distance_dyn<S, Cs>(
57    a: &DynGeometry<S, Cs>,
58    b: &DynGeometry<S, Cs>,
59) -> Result<S, DynKindMismatch>
60where
61    S: CoordinateScalar,
62    Cs: CoordinateSystem + Copy,
63    Cs::Family: SameAs<CartesianFamily> + DefaultDistance<Cs::Family>,
64    DefaultDistanceStrategy<Point<S, 2, Cs>, Point<S, 2, Cs>>:
65        DistanceStrategy<Point<S, 2, Cs>, Point<S, 2, Cs>, Out = S> + Default,
66    PointToSegment<Pythagoras>:
67        DistanceStrategy<Point<S, 2, Cs>, Segment<Point<S, 2, Cs>>, Out = S>,
68{
69    use DynGeometry::{LineString, Point as PointArm};
70    match (a, b) {
71        (PointArm(p), PointArm(q)) => Ok(distance(p, q)),
72        (PointArm(p), LineString(ls)) => Ok(point_to_linestring(p, ls)),
73        (LineString(ls), PointArm(p)) => Ok(point_to_linestring(p, ls)),
74        _ => Err(DynKindMismatch {
75            got: alloc::vec![a.kind(), b.kind()],
76            expected: SUPPORTED,
77        }),
78    }
79}
80
81/// Minimum distance from a point to any segment of a linestring.
82///
83/// Mirrors `algorithms/detail/distance/point_to_geometry.hpp`: the
84/// point-to-linestring distance is the minimum point-to-segment
85/// distance over the linestring's segments.
86fn point_to_linestring<S, Cs>(p: &Point<S, 2, Cs>, ls: &Linestring<Point<S, 2, Cs>>) -> S
87where
88    S: CoordinateScalar,
89    Cs: CoordinateSystem + Copy,
90    Cs::Family: SameAs<CartesianFamily>,
91    PointToSegment<Pythagoras>:
92        DistanceStrategy<Point<S, 2, Cs>, Segment<Point<S, 2, Cs>>, Out = S>,
93{
94    let strategy = PointToSegment::<Pythagoras>::default();
95    let mut best: Option<S> = None;
96    let mut prev: Option<Point<S, 2, Cs>> = None;
97    for pt in ls.points() {
98        if let Some(start) = prev {
99            let seg = Segment::new(start, *pt);
100            let d = distance_with(p, &seg, strategy);
101            best = Some(match best {
102                Some(m) if m <= d => m,
103                _ => d,
104            });
105        }
106        prev = Some(*pt);
107    }
108    // Degenerate: fewer than two points. A single vertex is handled as
109    // a zero-length segment (its point-to-segment distance is the
110    // point-to-vertex distance); an empty linestring has distance 0.
111    best.unwrap_or_else(|| match ls.points().next() {
112        Some(v) => distance_with(p, &Segment::new(*v, *v), strategy),
113        None => S::ZERO,
114    })
115}