geometry_strategy/distance.rs
1//! The [`DistanceStrategy`] trait, default strategy selection, and
2//! the [`Reversed`] argument-swapping adapter.
3//!
4//! Mirrors three pieces of Boost.Geometry that collaborate to make
5//! `boost::geometry::distance(a, b)` work for any geometry pair in
6//! any coordinate system, with implicit or explicit strategy
7//! selection:
8//!
9//! * `boost/geometry/strategies/distance.hpp` — the
10//! `boost::geometry::strategy::distance` namespace and the per-CS
11//! strategy concept.
12//! * `boost/geometry/strategies/distance/services.hpp` — the
13//! `services::default_strategy<G1, G2>` metafunction that picks
14//! the right strategy for a CS pair (Pythagoras for cartesian,
15//! Haversine / Andoyer for spherical / geographic, …).
16//! * `boost/geometry/strategies/default_strategy.hpp` plus
17//! `boost/geometry/core/reverse_dispatch.hpp` and the partial
18//! specialisation at
19//! `boost/geometry/algorithms/detail/distance/interface.hpp:53-77`
20//! — the symmetry-collapsing layer that gives you
21//! `distance(segment, point)` for free once you have
22//! `distance(point, segment)`.
23//!
24//! T21 lands the *trait surface* only — no concrete strategies yet.
25//! T22 adds `Pythagoras` / `ComparablePythagoras` and the first
26//! [`DefaultDistance`] impls; T23 wires this into the free-function
27//! `geometry-algorithm::distance` entry point.
28
29use geometry_coords::CoordinateScalar;
30use geometry_cs::CoordinateSystem;
31use geometry_trait::{Geometry, Point};
32
33/// A strategy for computing the distance between two geometries.
34///
35/// Mirrors the per-CS strategy concept declared in
36/// `boost/geometry/strategies/distance.hpp` and refined per
37/// coordinate system in `strategies/{cartesian,spherical,geographic}/
38/// distance_*.hpp`. The Boost concept exposes
39/// `apply(g1, g2)` plus a `return_type<G1, G2>` metafunction plus a
40/// `comparable_type<...>` sibling; we collapse those three onto one
41/// Rust trait via associated types.
42///
43/// # Associated items
44///
45/// * [`Self::Out`] — the scalar the distance comes back as.
46/// Equivalent to Boost's
47/// `boost::geometry::strategy::distance::services::return_type<...>`
48/// (`strategies/distance/services.hpp`). Typically the wider of
49/// `A`'s and `B`'s coordinate scalars after promotion.
50/// * [`Self::Comparable`] — the "skip the sqrt" form of
51/// this strategy. For Pythagoras this is `ComparablePythagoras`;
52/// for strategies where there is nothing to save by deferring a
53/// square root (Andoyer, Vincenty), set `type Comparable = Self;`
54/// and the optimiser collapses the indirection. Mirrors
55/// `comparable_type<...>` from the same Boost services header.
56///
57/// # Method shape
58///
59/// `distance(&self, &A, &B)` mirrors `apply(g1, g2)` on Boost's
60/// strategy structs (e.g.
61/// `strategies/cartesian/distance_pythagoras.hpp:75-95` for the
62/// comparable form, `:99-115` for the sqrt-paying form).
63/// `comparable(&self)` mirrors `get_comparable(strategy)` from
64/// `strategies/distance/services.hpp`.
65pub trait DistanceStrategy<A: Geometry, B: Geometry> {
66 /// The output scalar type. Typically the promoted scalar of
67 /// `A` and `B`. Mirrors
68 /// `services::return_type<Strategy, P1, P2>::type` from
69 /// `strategies/distance/services.hpp`.
70 type Out: CoordinateScalar;
71
72 /// The "skip the sqrt" form of this strategy.
73 ///
74 /// For Pythagoras → `ComparablePythagoras` (returns squared
75 /// distance). For strategies where there is no win from a
76 /// comparable form (Vincenty, Andoyer), `type Comparable = Self;`
77 /// — the optimiser collapses it. Mirrors
78 /// `comparable_type<Strategy>::type` from
79 /// `strategies/distance/services.hpp`.
80 ///
81 /// The bound `DistanceStrategy<A, B, Out = Self::Out>` enforces
82 /// the invariant that comparing two comparable distances gives
83 /// the same ordering as comparing two real distances — both
84 /// sides of the comparison must speak the same scalar type.
85 type Comparable: DistanceStrategy<A, B, Out = Self::Out>;
86
87 /// Compute the distance between `a` and `b`.
88 ///
89 /// Mirrors `apply(g1, g2)` on Boost strategy structs (e.g.
90 /// `strategies/cartesian/distance_pythagoras.hpp:99-115`).
91 fn distance(&self, a: &A, b: &B) -> Self::Out;
92
93 /// Return the comparable-form companion of this strategy.
94 ///
95 /// Mirrors `services::get_comparable(strategy)` from
96 /// `strategies/distance/services.hpp` — most concrete strategies
97 /// implement it as a no-op constructor of the sibling
98 /// `comparable::*` type.
99 fn comparable(&self) -> Self::Comparable;
100}
101
102/// "Which distance strategy do we pick by default for a pair of
103/// coordinate-system *families* `Self` (for the first geometry's
104/// CS family) and `Other` (for the second)?"
105///
106/// Mirrors `boost::geometry::strategy::distance::services::
107/// default_strategy<Tag1, Tag2, Point1, Point2, CsTag1, CsTag2>`
108/// from `boost/geometry/strategies/distance/services.hpp`, together
109/// with the per-CS specialisations in
110/// `strategies/{cartesian,spherical,geographic}/distance.hpp` and
111/// the umbrella `strategies/default_strategy.hpp`.
112///
113/// In Boost the dispatch keys on the *coordinate-system tag*
114/// (`cs_tag<Point>::type`). The Rust analogue is the
115/// [`CoordinateSystem::Family`] associated type defined in
116/// `geometry-cs` — every concrete CS (`Cartesian`,
117/// `Spherical<Degree>`, `Spherical<Radian>`, `Geographic<Degree>`,
118/// …) reports one of the family marker types
119/// (`CartesianFamily`, `SphericalFamily`, `GeographicFamily`,
120/// `PolarFamily`).
121///
122/// Implementations land in `geometry-strategy` alongside each
123/// concrete strategy:
124///
125/// ```ignore
126/// impl DefaultDistance<CartesianFamily> for CartesianFamily { type Strategy = Pythagoras; }
127/// impl DefaultDistance<SphericalFamily> for SphericalFamily { type Strategy = Haversine; }
128/// impl DefaultDistance<GeographicFamily> for GeographicFamily { type Strategy = Andoyer; }
129/// ```
130///
131/// The `Strategy: Default` bound matches Boost's expectation that
132/// `services::default_strategy<...>::type` is default-constructible
133/// (every `strategies/.../distance_*.hpp` concrete strategy has a
134/// no-arg constructor).
135pub trait DefaultDistance<Other> {
136 /// The strategy chosen for `(Self, Other)`. Must implement
137 /// `Default` because the free-function `distance(a, b)` builds
138 /// it without arguments — same contract as Boost's
139 /// default-constructed `services::default_strategy<...>::type`.
140 type Strategy: Default;
141}
142
143/// Type alias resolving the default distance strategy for the pair
144/// `(A, B)` by walking
145/// `A -> A::Point -> Cs -> Family -> DefaultDistance<…B's Family…>::Strategy`.
146///
147/// Replaces the C++ free function
148/// `boost::geometry::strategy::distance::services::
149/// default_strategy<G1, G2>::type` from
150/// `strategies/distance/services.hpp`, plumbed through the
151/// per-CS files.
152///
153/// At call sites in `geometry-algorithm::distance`, this is the
154/// type the strategy-less `distance(a, b)` overload monomorphises
155/// against:
156///
157/// ```ignore
158/// pub fn distance<A: Geometry, B: Geometry>(a: &A, b: &B)
159/// -> <DefaultDistanceStrategy<A, B> as DistanceStrategy<A, B>>::Out
160/// where
161/// DefaultDistanceStrategy<A, B>: DistanceStrategy<A, B>,
162/// {
163/// DefaultDistanceStrategy::<A, B>::default().distance(a, b)
164/// }
165/// ```
166pub type DefaultDistanceStrategy<A, B> =
167 <<<<A as Geometry>::Point as Point>::Cs as CoordinateSystem>::Family as DefaultDistance<
168 <<<B as Geometry>::Point as Point>::Cs as CoordinateSystem>::Family,
169 >>::Strategy;
170
171/// `Reversed<S>` lifts a `DistanceStrategy<A, B>` into a
172/// `DistanceStrategy<B, A>` by calling the inner strategy with the
173/// arguments swapped.
174///
175/// Replaces `boost::geometry::reverse_dispatch`
176/// (`boost/geometry/core/reverse_dispatch.hpp`) together with the
177/// partial specialisation that uses it in
178/// `boost/geometry/algorithms/detail/distance/interface.hpp:53-77`.
179/// Boost has one such specialisation *per algorithm*; in Rust the
180/// symmetry is expressed once, at the strategy-trait layer, with a
181/// single blanket impl below — every algorithm that goes through a
182/// `DistanceStrategy<A, B>` automatically gets the swap.
183///
184/// # Cost
185///
186/// `#[repr(transparent)]` is deliberately *not* applied — `Reversed`
187/// is a one-field tuple struct and monomorphisation collapses the
188/// indirection. There is no runtime overhead: the wrapper exists
189/// solely to give the trait system a *different* `Self` type to
190/// dispatch on, the same way `boost::geometry::detail::distance::
191/// reverse_dispatch_call` exists in C++ only to give the partial
192/// specialisation something to bind to.
193#[derive(Debug, Default, Clone, Copy)]
194pub struct Reversed<S>(pub S);
195
196impl<A, B, S> DistanceStrategy<B, A> for Reversed<S>
197where
198 A: Geometry,
199 B: Geometry,
200 S: DistanceStrategy<A, B>,
201{
202 type Out = S::Out;
203 type Comparable = Reversed<S::Comparable>;
204
205 #[inline]
206 fn distance(&self, b: &B, a: &A) -> S::Out {
207 self.0.distance(a, b)
208 }
209
210 #[inline]
211 fn comparable(&self) -> Self::Comparable {
212 Reversed(self.0.comparable())
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 //! Compile-time witnesses for the trait surface that the
219 //! algorithm crate (T23) will rely on. No runtime assertions —
220 //! a concrete strategy lands in T22.
221
222 use super::{DefaultDistance, DefaultDistanceStrategy, DistanceStrategy, Reversed};
223 use geometry_coords::CoordinateScalar;
224 use geometry_cs::CoordinateSystem;
225 use geometry_trait::{Geometry, Point};
226
227 /// Witness: any type implementing `DistanceStrategy<A, B>` must
228 /// expose `Out`, `Comparable`, `distance`, `comparable` with the
229 /// exact bounds T23's `distance(a, b)` will depend on.
230 fn _accepts_strategy<A, B, S>()
231 where
232 A: Geometry,
233 B: Geometry,
234 S: DistanceStrategy<A, B>,
235 S::Out: CoordinateScalar,
236 S::Comparable: DistanceStrategy<A, B, Out = S::Out>,
237 {
238 }
239
240 /// Witness: `Reversed<S>` implements `DistanceStrategy<B, A>`
241 /// for any `S: DistanceStrategy<A, B>`. Exercises the blanket
242 /// impl above so the algorithm crate can rely on it without a
243 /// concrete strategy in scope.
244 fn _reversed_witness<A, B, S>()
245 where
246 A: Geometry,
247 B: Geometry,
248 S: DistanceStrategy<A, B>,
249 Reversed<S>: DistanceStrategy<B, A, Out = S::Out>,
250 {
251 }
252
253 /// Witness: a `Reversed<Reversed<S>>` is *not* spelled the same
254 /// as `S` but does compute the same thing — the blanket impl
255 /// composes. Useful because reverse-dispatch lookups from
256 /// asymmetric-pair algorithms may layer two `Reversed`s in
257 /// degenerate code paths.
258 fn _double_reversed_witness<A, B, S>()
259 where
260 A: Geometry,
261 B: Geometry,
262 S: DistanceStrategy<A, B>,
263 Reversed<Reversed<S>>: DistanceStrategy<A, B, Out = S::Out>,
264 {
265 }
266
267 /// Witness: `DefaultDistanceStrategy<A, B>` resolves to the
268 /// `Strategy` projection of `DefaultDistance` keyed on the two
269 /// geometries' CS families, and that strategy implements
270 /// `DistanceStrategy<A, B>`. This is the exact bound T23's
271 /// strategy-less `distance(a, b)` will carry.
272 fn _default_strategy_witness<A, B>()
273 where
274 A: Geometry,
275 B: Geometry,
276 <<A as Geometry>::Point as Point>::Cs: CoordinateSystem,
277 <<B as Geometry>::Point as Point>::Cs: CoordinateSystem,
278 <<<A as Geometry>::Point as Point>::Cs as CoordinateSystem>::Family:
279 DefaultDistance<<<<B as Geometry>::Point as Point>::Cs as CoordinateSystem>::Family>,
280 DefaultDistanceStrategy<A, B>: DistanceStrategy<A, B> + Default,
281 {
282 }
283
284 use crate::cartesian::distance_pythagoras::{ComparablePythagoras, Pythagoras};
285 use geometry_cs::Cartesian;
286 use geometry_model::Point2D;
287
288 type P = Point2D<f64, Cartesian>;
289
290 /// Instantiating each type-level witness with a concrete strategy
291 /// (`Pythagoras` over two cartesian points) proves the trait bounds
292 /// they encode actually resolve, and executes their bodies.
293 #[test]
294 #[allow(
295 clippy::used_underscore_items,
296 reason = "the test exists to run the compile-time witness's body"
297 )]
298 fn witnesses_resolve_for_pythagoras_over_cartesian_points() {
299 _accepts_strategy::<P, P, Pythagoras>();
300 _reversed_witness::<P, P, Pythagoras>();
301 _double_reversed_witness::<P, P, Pythagoras>();
302 _default_strategy_witness::<P, P>();
303 }
304
305 /// `Reversed<Pythagoras>` computes the same distance as `Pythagoras`
306 /// with the arguments swapped, and its `comparable()` returns the
307 /// wrapped comparable strategy (squared distance).
308 #[test]
309 #[allow(
310 clippy::float_cmp,
311 reason = "compared values are exact integer-valued literals"
312 )]
313 fn reversed_swaps_arguments_and_forwards_comparable() {
314 let a = P::new(0.0, 0.0);
315 let b = P::new(3.0, 4.0);
316
317 let forward = Pythagoras.distance(&a, &b);
318 let reversed = Reversed(Pythagoras).distance(&b, &a);
319 assert_eq!(forward, reversed);
320 assert_eq!(forward, 5.0);
321
322 // The comparable companion skips the sqrt: 3² + 4² = 25.
323 let rev: Reversed<Pythagoras> = Reversed(Pythagoras);
324 let cmp = DistanceStrategy::<P, P>::comparable(&rev);
325 let cmp_val = cmp.distance(&b, &a);
326 assert_eq!(cmp_val, ComparablePythagoras.distance(&a, &b));
327 assert_eq!(cmp_val, 25.0);
328 }
329}