use alloc::vec::Vec;
use geometry_cs::CoordinateSystem;
use geometry_strategy::distance::DefaultDistance;
use geometry_strategy::{DefaultDistanceStrategy, DistanceStrategy};
use geometry_trait::{Geometry, Linestring, Point};
type Family<P> = <<P as Point>::Cs as CoordinateSystem>::Family;
type DefaultDistOut<L1, L2> = <DefaultDistanceStrategy<
<L1 as Geometry>::Point,
<L2 as Geometry>::Point,
> as DistanceStrategy<<L1 as Geometry>::Point, <L2 as Geometry>::Point>>::Out;
#[inline]
#[must_use]
pub fn discrete_hausdorff_distance<L1, L2>(l1: &L1, l2: &L2) -> DefaultDistOut<L1, L2>
where
L1: Linestring,
L2: Linestring,
Family<L1::Point>: DefaultDistance<Family<L2::Point>>,
DefaultDistanceStrategy<L1::Point, L2::Point>: DistanceStrategy<L1::Point, L2::Point> + Default,
{
let s = <DefaultDistanceStrategy<L1::Point, L2::Point>>::default();
discrete_hausdorff_distance_with(l1, l2, s)
}
#[must_use]
#[allow(
clippy::needless_pass_by_value,
reason = "Distance strategies are zero-sized/Copy; taking by value matches `distance_with`."
)]
pub fn discrete_hausdorff_distance_with<L1, L2, S>(l1: &L1, l2: &L2, dist: S) -> S::Out
where
L1: Linestring,
L2: Linestring,
S: DistanceStrategy<L1::Point, L2::Point>,
S::Out: PartialOrd + Copy,
{
let seq1: Vec<&L1::Point> = l1.points().collect();
let seq2: Vec<&L2::Point> = l2.points().collect();
assert!(
!seq1.is_empty() && !seq2.is_empty(),
"empty linestring in discrete_hausdorff"
);
let sup_ab = directed_sup(&seq1, &seq2, |p, q| dist.distance(p, q));
let sup_ba = directed_sup(&seq2, &seq1, |p, q| dist.distance(q, p));
if sup_ab > sup_ba { sup_ab } else { sup_ba }
}
fn directed_sup<PA, PB, O, F>(a: &[&PA], b: &[&PB], mut f: F) -> O
where
O: PartialOrd + Copy,
F: FnMut(&PA, &PB) -> O,
{
let mut sup: Option<O> = None;
for p in a {
let mut inf: Option<O> = None;
for q in b {
let d = f(p, q);
inf = Some(match inf {
None => d,
Some(cur) => {
if d < cur {
d
} else {
cur
}
}
});
}
let inf = inf.expect("non-empty inner sequence");
sup = Some(match sup {
None => inf,
Some(cur) => {
if inf > cur {
inf
} else {
cur
}
}
});
}
sup.expect("non-empty outer sequence")
}
#[cfg(test)]
#[allow(
clippy::float_cmp,
reason = "Hausdorff reference values compared with an epsilon."
)]
mod tests {
use super::discrete_hausdorff_distance;
use geometry_cs::Cartesian;
use geometry_model::{Linestring, Point2D, linestring};
type Pt = Point2D<f64, Cartesian>;
#[test]
fn identical_linestrings_distance_zero() {
let ls: Linestring<Pt> = linestring![(0., 0.), (1., 0.), (2., 0.)];
assert!(discrete_hausdorff_distance(&ls, &ls) < 1e-12);
}
#[test]
fn subset_drops_last_vertex() {
let a: Linestring<Pt> = linestring![(0., 0.), (1., 0.), (2., 0.)];
let b: Linestring<Pt> = linestring![(0., 0.), (1., 0.)];
assert!((discrete_hausdorff_distance(&a, &b) - 1.0).abs() < 1e-9);
}
#[test]
fn parallel_lines() {
let a: Linestring<Pt> = linestring![(0., 0.), (5., 0.)];
let b: Linestring<Pt> = linestring![(0., 1.), (5., 1.)];
assert!((discrete_hausdorff_distance(&a, &b) - 1.0).abs() < 1e-9);
}
#[test]
fn symmetric() {
let a: Linestring<Pt> = linestring![(0., 0.), (1., 0.), (2., 0.)];
let b: Linestring<Pt> = linestring![(0., 0.), (1., 0.)];
assert_eq!(
discrete_hausdorff_distance(&a, &b).to_bits(),
discrete_hausdorff_distance(&b, &a).to_bits(),
);
}
}