use geo_types::{CoordFloat, LineString, Point};
use crate::CoordsIter;
use super::Distance;
pub trait FrechetDistance<F: CoordFloat>: Distance<F, Point<F>, Point<F>> {
fn frechet_distance(&self, ls_1: &LineString<F>, ls_2: &LineString<F>) -> F;
}
impl<F, MetricSpace> FrechetDistance<F> for MetricSpace
where
F: CoordFloat,
MetricSpace: Distance<F, Point<F>, Point<F>>,
{
fn frechet_distance(&self, ls_1: &LineString<F>, ls_2: &LineString<F>) -> F {
let (n1, n2) = (ls_1.coords_count(), ls_2.coords_count());
if n1 == 0 || n2 == 0 {
return F::zero();
}
let (ls_short, ls_long) = if n1 <= n2 { (ls_1, ls_2) } else { (ls_2, ls_1) };
DiscreteFrechetCalculator::new(ls_short, ls_long).calculate(self)
}
}
struct DiscreteFrechetCalculator<'a, F: CoordFloat> {
cache: Vec<F>,
ls_short: &'a LineString<F>,
ls_long: &'a LineString<F>,
}
impl<'a, F: CoordFloat> DiscreteFrechetCalculator<'a, F> {
fn new(ls_short: &'a LineString<F>, ls_long: &'a LineString<F>) -> Self {
Self {
cache: vec![F::zero(); ls_short.coords_count() * 2],
ls_short,
ls_long,
}
}
fn calculate(&mut self, metric_space: &impl Distance<F, Point<F>, Point<F>>) -> F {
let row_length = self.ls_short.coords_count();
let (mut prev_row, mut cur_row) = self.cache.split_at_mut(row_length);
let p_long_0: Point<F> = self.ls_long[0].into();
let p_short_0: Point<F> = self.ls_short[0].into();
prev_row[0] = metric_space.distance(p_long_0, p_short_0);
for (j, p_short) in self.ls_short.points().enumerate().skip(1) {
let d = metric_space.distance(p_long_0, p_short);
prev_row[j] = prev_row[j - 1].max(d);
}
for p_long in self.ls_long.points().skip(1) {
let d = metric_space.distance(p_long, p_short_0);
cur_row[0] = prev_row[0].max(d);
for (j, p_short) in self.ls_short.points().enumerate().skip(1) {
let d = metric_space.distance(p_long, p_short);
cur_row[j] = {
let best_prev = prev_row[j].min(prev_row[j - 1]).min(cur_row[j - 1]);
d.max(best_prev)
};
}
std::mem::swap(&mut prev_row, &mut cur_row);
}
prev_row[row_length - 1]
}
}
#[cfg(test)]
mod test {
use crate::{Euclidean, HaversineMeasure};
use super::*;
#[test]
fn test_single_point_in_linestring_euclidean() {
let ls_a = LineString::from(vec![(1., 1.)]);
let ls_b = LineString::from(vec![(0., 2.)]);
assert_relative_eq!(
Euclidean.distance(Point::from(ls_a[0]), Point::from(ls_b[0])),
Euclidean.frechet_distance(&ls_a, &ls_b)
);
}
#[test]
fn test_identical_linestrings_euclidean() {
let ls_a = LineString::from(vec![(1., 1.), (2., 1.), (2., 2.)]);
let ls_b = LineString::from(vec![(1., 1.), (2., 1.), (2., 2.)]);
assert_relative_eq!(0., Euclidean.frechet_distance(&ls_a, &ls_b));
}
#[test]
fn different_dimensions_linestrings_euclidean() {
let ls_a = LineString::from(vec![(1., 1.)]);
let ls_b = LineString::from(vec![(2., 2.), (0., 1.)]);
assert_relative_eq!(2f64.sqrt(), Euclidean.frechet_distance(&ls_a, &ls_b));
}
#[test]
fn test_frechet_1_euclidean() {
let ls_a = LineString::from(vec![(1., 1.), (2., 1.)]);
let ls_b = LineString::from(vec![(2., 2.), (2., 3.)]);
assert_relative_eq!(2., Euclidean.frechet_distance(&ls_a, &ls_b));
}
#[test]
fn test_frechet_2_euclidean() {
let ls_a = LineString::from(vec![(1., 1.), (2., 1.), (2., 2.)]);
let ls_b = LineString::from(vec![(2., 2.), (0., 1.), (2., 4.)]);
assert_relative_eq!(2., Euclidean.frechet_distance(&ls_a, &ls_b));
}
#[test] fn test_frechet_long_linestrings_euclidean() {
let ls: LineString = {
let delta = 0.01;
let mut ls = vec![(0.0, 0.0); 1_000];
for i in 1..ls.len() {
let (lat, lon) = ls[i - 1];
ls[i] = (lat - delta, lon + delta);
}
ls.into()
};
assert_relative_eq!(Euclidean.frechet_distance(&ls, &ls), 0.0);
}
#[test]
fn test_single_point_in_linestring_haversine_custom() {
let mars_measure = HaversineMeasure::new(3389.5); let ls_a = LineString::from(vec![(1., 1.)]);
let ls_b = LineString::from(vec![(0., 2.)]);
assert_relative_eq!(
mars_measure.distance(Point::from(ls_a[0]), Point::from(ls_b[0])),
mars_measure.frechet_distance(&ls_a, &ls_b)
);
}
#[test]
fn test_identical_linestrings_haversine_custom() {
let mars_measure = HaversineMeasure::new(3389.5);
let ls_a = LineString::from(vec![(1., 1.), (2., 1.), (2., 2.)]);
let ls_b = LineString::from(vec![(1., 1.), (2., 1.), (2., 2.)]);
assert_relative_eq!(0., mars_measure.frechet_distance(&ls_a, &ls_b));
}
#[test]
fn different_dimensions_linestrings_haversine_custom() {
let mars_measure = HaversineMeasure::new(3389.5);
let ls_a = LineString::from(vec![(1., 1.)]);
let ls_b = LineString::from(vec![(2., 2.), (0., 1.)]);
let expected_distance = mars_measure.distance(Point::new(1., 1.), Point::new(2., 2.));
assert_relative_eq!(
expected_distance,
mars_measure.frechet_distance(&ls_a, &ls_b)
);
}
#[test]
fn test_frechet_long_linestrings_haversine_custom() {
let mars_measure = HaversineMeasure::new(3389.5);
let ls: LineString = {
let delta = 0.01;
let mut ls = vec![(0.0, 0.0); 1_000];
for i in 1..ls.len() {
let (lat, lon) = ls[i - 1];
ls[i] = (lat - delta, lon + delta);
}
ls.into()
};
assert_relative_eq!(mars_measure.frechet_distance(&ls, &ls), 0.0);
}
}