use geometry_coords::CoordinateScalar;
use geometry_cs::{CartesianFamily, CoordinateSystem, GeographicFamily, SphericalFamily};
use geometry_tag::SameAs;
use geometry_trait::{Closure, Geometry, Linestring, Point, Ring};
use crate::cartesian::Pythagoras;
use crate::distance::DistanceStrategy;
pub trait LengthStrategy<G: Geometry> {
type Out: CoordinateScalar;
fn length(&self, g: &G) -> Self::Out;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct CartesianLength;
#[derive(Debug, Default, Clone, Copy)]
pub struct CartesianPerimeter;
impl<L> LengthStrategy<L> for CartesianLength
where
L: Linestring,
<L::Point as Point>::Cs: CoordinateSystem,
<<L::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
type Out = <L::Point as Point>::Scalar;
#[inline]
fn length(&self, g: &L) -> Self::Out {
sum_pairwise::<L::Point, _>(g.points())
}
}
impl<R> LengthStrategy<R> for CartesianPerimeter
where
R: Ring,
<R::Point as Point>::Cs: CoordinateSystem,
<<R::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
type Out = <R::Point as Point>::Scalar;
#[inline]
fn length(&self, g: &R) -> Self::Out {
let mut total = sum_pairwise::<R::Point, _>(g.points());
if matches!(g.closure(), Closure::Open) {
let mut points = g.points();
if let Some(first) = points.next() {
let last = points.last().unwrap_or(first);
total = total + Pythagoras.distance(last, first);
}
}
total
}
}
#[inline]
fn sum_pairwise<'a, P, I>(it: I) -> P::Scalar
where
P: Point + 'a,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
I: IntoIterator<Item = &'a P>,
I::IntoIter: Clone,
{
let it = it.into_iter();
let next = it.clone().skip(1);
it.zip(next).fold(P::Scalar::ZERO, |acc, (a, b)| {
acc + Pythagoras.distance(a, b)
})
}
pub trait DefaultLength<Family> {
type Strategy: Default;
}
pub trait DefaultPerimeter<Family> {
type Strategy: Default;
}
impl DefaultLength<CartesianFamily> for CartesianFamily {
type Strategy = CartesianLength;
}
impl DefaultLength<SphericalFamily> for SphericalFamily {
type Strategy = crate::spherical::SphericalLength;
}
impl DefaultLength<GeographicFamily> for GeographicFamily {
type Strategy = crate::geographic::GeographicLength;
}
impl DefaultPerimeter<CartesianFamily> for CartesianFamily {
type Strategy = CartesianPerimeter;
}
impl DefaultPerimeter<SphericalFamily> for SphericalFamily {
type Strategy = crate::spherical::SphericalPerimeter;
}
impl DefaultPerimeter<GeographicFamily> for GeographicFamily {
type Strategy = crate::geographic::GeographicPerimeter;
}
pub type DefaultLengthStrategy<G> =
<<<<G as Geometry>::Point as Point>::Cs as CoordinateSystem>::Family as DefaultLength<
<<<G as Geometry>::Point as Point>::Cs as CoordinateSystem>::Family,
>>::Strategy;
pub type DefaultPerimeterStrategy<G> =
<<<<G as Geometry>::Point as Point>::Cs as CoordinateSystem>::Family as DefaultPerimeter<
<<<G as Geometry>::Point as Point>::Cs as CoordinateSystem>::Family,
>>::Strategy;
#[cfg(test)]
mod tests {
use super::{CartesianLength, CartesianPerimeter, LengthStrategy};
use geometry_cs::Cartesian;
use geometry_model::{Linestring, Point2D, Ring, linestring};
#[test]
fn linestring_3_4_5() {
let ls: Linestring<Point2D<f64, Cartesian>> = linestring![(0.0, 0.0), (3.0, 4.0)];
let got = CartesianLength.length(&ls);
assert!((got - 5.0).abs() < 1e-12);
}
#[test]
fn linestring_5_plus_sqrt2() {
let ls: Linestring<Point2D<f64, Cartesian>> =
linestring![(0.0, 0.0), (3.0, 4.0), (4.0, 3.0)];
let got = CartesianLength.length(&ls);
let expected = 5.0 + 2.0_f64.sqrt();
assert!((got - expected).abs() < 1e-12);
}
#[test]
fn closed_ring_4_3_rectangle() {
let mut r = Ring::<Point2D<f64, Cartesian>>::new();
r.push(Point2D::new(0.0, 0.0));
r.push(Point2D::new(4.0, 0.0));
r.push(Point2D::new(4.0, 3.0));
r.push(Point2D::new(0.0, 3.0));
r.push(Point2D::new(0.0, 0.0));
let got = CartesianPerimeter.length(&r);
assert!((got - 14.0).abs() < 1e-12);
}
#[test]
fn open_ring_4_3_rectangle() {
let mut r = Ring::<Point2D<f64, Cartesian>, true, false>::new();
r.push(Point2D::new(0.0, 0.0));
r.push(Point2D::new(4.0, 0.0));
r.push(Point2D::new(4.0, 3.0));
r.push(Point2D::new(0.0, 3.0));
let got = CartesianPerimeter.length(&r);
assert!((got - 14.0).abs() < 1e-12);
}
fn _accepts_readonly_point<G, S>(s: &S, g: &G) -> S::Out
where
G: geometry_trait::Geometry,
<G as geometry_trait::Geometry>::Point: geometry_trait::Point,
S: LengthStrategy<G>,
{
s.length(g)
}
#[test]
#[allow(
clippy::used_underscore_items,
reason = "the test exists to run the compile-time witness's body"
)]
fn readonly_point_witness_computes_length() {
let ls: Linestring<Point2D<f64, Cartesian>> = linestring![(0.0, 0.0), (3.0, 4.0)];
let got = _accepts_readonly_point(&CartesianLength, &ls);
assert!((got - 5.0).abs() < 1e-12);
}
}