use geometry_cs::CoordinateSystem;
use geometry_strategy::distance::{DefaultDistance, DefaultDistanceStrategy, DistanceStrategy};
use geometry_trait::{Geometry, Point};
type Family<G> = <<<G as Geometry>::Point as Point>::Cs as CoordinateSystem>::Family;
#[inline]
pub fn distance<A, B>(
a: &A,
b: &B,
) -> <DefaultDistanceStrategy<A, B> as DistanceStrategy<A, B>>::Out
where
A: Geometry,
B: Geometry,
Family<A>: DefaultDistance<Family<B>>,
DefaultDistanceStrategy<A, B>: DistanceStrategy<A, B> + Default,
{
DefaultDistanceStrategy::<A, B>::default().distance(a, b)
}
#[inline]
#[allow(
clippy::needless_pass_by_value,
reason = "Strategies are ZST/small Copy configuration objects; by-value matches the user-facing call shape."
)]
pub fn distance_with<A, B, S>(a: &A, b: &B, strategy: S) -> S::Out
where
A: Geometry,
B: Geometry,
S: DistanceStrategy<A, B>,
{
strategy.distance(a, b)
}
#[inline]
#[must_use]
pub fn comparable_distance<A, B>(
a: &A,
b: &B,
) -> <<DefaultDistanceStrategy<A, B> as DistanceStrategy<A, B>>::Comparable as DistanceStrategy<
A,
B,
>>::Out
where
A: Geometry,
B: Geometry,
Family<A>: DefaultDistance<Family<B>>,
DefaultDistanceStrategy<A, B>: DistanceStrategy<A, B> + Default,
{
DefaultDistanceStrategy::<A, B>::default()
.comparable()
.distance(a, b)
}
#[inline]
#[must_use]
#[allow(
clippy::needless_pass_by_value,
reason = "distance strategies are zero-sized or small Copy values and match distance_with's public call shape"
)]
pub fn comparable_distance_with<A, B, S>(a: &A, b: &B, strategy: S) -> S::Out
where
A: Geometry,
B: Geometry,
S: DistanceStrategy<A, B>,
{
strategy.comparable().distance(a, b)
}
#[cfg(test)]
mod tests {
use super::{comparable_distance, distance, distance_with};
use geometry_cs::Cartesian;
use geometry_model::Point2D;
use geometry_strategy::cartesian::Pythagoras;
#[allow(clippy::float_cmp, reason = "3-4-5 is exact in IEEE-754 f64.")]
#[test]
fn cartesian_quickstart_3_4_5() {
let a = Point2D::<f64, Cartesian>::new(0.0, 0.0);
let b = Point2D::<f64, Cartesian>::new(3.0, 4.0);
assert_eq!(distance(&a, &b), 5.0);
assert_eq!(distance_with(&a, &b, Pythagoras), 5.0);
assert_eq!(comparable_distance(&a, &b), 25.0);
}
#[test]
fn one_dimensional_distance() {
type P1 = geometry_model::Point<f64, 1, Cartesian>;
let a = P1::new(2.0);
let b = P1::new(7.0);
assert!((distance(&a, &b) - 5.0).abs() < 1e-12);
assert!((comparable_distance(&a, &b) - 25.0).abs() < 1e-12);
}
#[test]
fn four_dimensional_distance() {
use geometry_trait::PointMut as _;
type P4 = geometry_model::Point<f64, 4, Cartesian>;
let o = P4::default();
let mut p = P4::default();
p.set::<0>(1.0);
p.set::<1>(1.0);
p.set::<2>(1.0);
p.set::<3>(1.0);
assert!((comparable_distance(&o, &p) - 4.0).abs() < 1e-12);
assert!((distance(&o, &p) - 2.0).abs() < 1e-12);
}
#[test]
fn three_dimensional_point_to_segment() {
use geometry_model::{Point3D, Segment};
use geometry_strategy::PointToSegment;
type P3 = Point3D<f64, Cartesian>;
let p = P3::new(3.0, 4.0, 0.0);
let s = Segment::new(P3::new(0.0, 0.0, 0.0), P3::new(0.0, 0.0, 10.0));
let d = distance_with(&p, &s, PointToSegment::<Pythagoras>::default());
assert!((d - 5.0).abs() < 1e-12, "got {d}");
}
#[cfg(feature = "std")]
#[test]
fn geodesic_coincident_poles_at_different_longitudes_are_zero() {
use geometry_adapt::{Adapt, WithCs};
use geometry_cs::{Degree, Geographic};
use geometry_strategy::geographic::{Andoyer, Thomas, Vincenty};
type Gg = WithCs<Adapt<[f64; 2]>, Geographic<Degree>>;
let deg = |lon: f64, lat: f64| -> Gg { WithCs::new(Adapt([lon, lat])) };
let a = deg(0.0, 90.0);
let b = deg(180.0, 90.0);
assert!(distance_with(&a, &b, Andoyer::WGS84).abs() < 1e-3);
assert!(distance_with(&a, &b, Thomas::WGS84).abs() < 1e-6);
assert!(distance_with(&a, &b, Vincenty::WGS84).abs() < 1e-3);
}
#[cfg(feature = "std")]
#[test]
fn thomas_second_endpoint_at_pole() {
use geometry_adapt::{Adapt, WithCs};
use geometry_cs::{Degree, Geographic};
use geometry_strategy::geographic::Thomas;
type Gg = WithCs<Adapt<[f64; 2]>, Geographic<Degree>>;
let deg = |lon: f64, lat: f64| -> Gg { WithCs::new(Adapt([lon, lat])) };
let d = distance_with(°(1.0, 80.0), °(0.0, 90.0), Thomas::WGS84);
assert!((d / 1000.0 - 1_116.825_795).abs() < 0.012);
}
#[cfg(feature = "std")]
#[test]
fn vincenty_antimeridian_longitude_is_normalised_both_ways() {
use geometry_adapt::{Adapt, WithCs};
use geometry_cs::{Degree, Geographic};
use geometry_strategy::geographic::Vincenty;
type Gg = WithCs<Adapt<[f64; 2]>, Geographic<Degree>>;
let deg = |lon: f64, lat: f64| -> Gg { WithCs::new(Adapt([lon, lat])) };
let east = distance_with(°(170.0, 0.0), °(-170.0, 0.0), Vincenty::WGS84);
let west = distance_with(°(-170.0, 0.0), °(170.0, 0.0), Vincenty::WGS84);
assert!((east - west).abs() < 1e-6, "{east} vs {west}");
assert!((east / 1000.0 - 2_226.0).abs() < 5.0);
}
}