use crate::Real;
use crate::distance::{GeoCoord, PreparedGeoCoord, haversine_distance_prepared, prepare_geo_coord};
use crate::projected::{Anisotropy2D, ProjectedCoord};
pub trait SpatialMetric: Copy + Send + Sync + std::fmt::Debug {
type Coord: Copy + Send + Sync + std::fmt::Debug + PartialEq;
type Prepared: Copy + Send + Sync + std::fmt::Debug;
fn prepare(&self, coord: Self::Coord) -> Self::Prepared;
fn distance(&self, a: Self::Prepared, b: Self::Prepared) -> Real;
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct GeoMetric;
impl SpatialMetric for GeoMetric {
type Coord = GeoCoord;
type Prepared = PreparedGeoCoord;
#[inline]
fn prepare(&self, coord: Self::Coord) -> Self::Prepared {
prepare_geo_coord(coord)
}
#[inline]
fn distance(&self, a: Self::Prepared, b: Self::Prepared) -> Real {
haversine_distance_prepared(a, b)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ProjectedMetric {
pub anisotropy: Anisotropy2D,
}
impl ProjectedMetric {
pub fn isotropic() -> Self {
Self {
anisotropy: Anisotropy2D::isotropic(),
}
}
pub fn with_anisotropy(anisotropy: Anisotropy2D) -> Self {
Self { anisotropy }
}
}
impl Default for ProjectedMetric {
fn default() -> Self {
Self::isotropic()
}
}
impl SpatialMetric for ProjectedMetric {
type Coord = ProjectedCoord;
type Prepared = ProjectedCoord;
#[inline]
fn prepare(&self, coord: Self::Coord) -> Self::Prepared {
coord
}
#[inline]
fn distance(&self, a: Self::Prepared, b: Self::Prepared) -> Real {
self.anisotropy.distance(a, b)
}
}
pub trait SpatialBasis: SpatialMetric {
fn spatial_components(&self, coord: Self::Coord) -> (Real, Real);
}
impl SpatialBasis for GeoMetric {
#[inline]
fn spatial_components(&self, coord: Self::Coord) -> (Real, Real) {
(coord.lat(), coord.lon())
}
}
impl SpatialBasis for ProjectedMetric {
#[inline]
fn spatial_components(&self, coord: Self::Coord) -> (Real, Real) {
(coord.x, coord.y)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::projected::ProjectedCoord;
use approx::assert_relative_eq;
#[test]
fn geo_metric_matches_haversine() {
let m = GeoMetric;
let a = m.prepare(GeoCoord::try_new(0.0, 0.0).unwrap());
let b = m.prepare(GeoCoord::try_new(0.0, 1.0).unwrap());
assert_relative_eq!(m.distance(a, b), 111.1949, epsilon = 0.05);
}
#[test]
fn projected_isotropic_matches_pythagoras() {
let m = ProjectedMetric::isotropic();
let a = m.prepare(ProjectedCoord::new(0.0, 0.0));
let b = m.prepare(ProjectedCoord::new(3.0, 4.0));
assert_relative_eq!(m.distance(a, b), 5.0, epsilon = 1e-6);
}
#[test]
fn projected_anisotropy_shrinks_along_minor_axis() {
let aniso = Anisotropy2D::new(0.0, 0.5).unwrap();
let m = ProjectedMetric::with_anisotropy(aniso);
let along_major = m.distance(ProjectedCoord::new(0.0, 0.0), ProjectedCoord::new(1.0, 0.0));
let along_minor = m.distance(ProjectedCoord::new(0.0, 0.0), ProjectedCoord::new(0.0, 1.0));
assert_relative_eq!(along_major, 1.0, epsilon = 1e-6);
assert_relative_eq!(along_minor, 2.0, epsilon = 1e-6);
}
}