use alloc::vec::Vec;
use geometry_cs::{CoordinateSystem, GeographicFamily};
use geometry_tag::SameAs;
use geometry_trait::{Closure, Linestring, Point, Ring};
use crate::distance::DistanceStrategy;
use crate::geographic::Andoyer;
use crate::length::LengthStrategy;
#[derive(Debug, Default, Clone, Copy)]
pub struct GeographicLength {
pub andoyer: Andoyer,
}
#[derive(Debug, Default, Clone, Copy)]
pub struct GeographicPerimeter {
pub andoyer: Andoyer,
}
impl<L> LengthStrategy<L> for GeographicLength
where
L: Linestring,
<L::Point as Point>::Cs: CoordinateSystem,
<<L::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<GeographicFamily>,
Andoyer: DistanceStrategy<L::Point, L::Point, Out = <L::Point as Point>::Scalar>,
{
type Out = <L::Point as Point>::Scalar;
#[inline]
fn length(&self, g: &L) -> Self::Out {
let pts: Vec<&L::Point> = g.points().collect();
let mut acc = <Self::Out as geometry_coords::CoordinateScalar>::ZERO;
for w in pts.windows(2) {
acc = acc + self.andoyer.distance(w[0], w[1]);
}
acc
}
}
impl<R> LengthStrategy<R> for GeographicPerimeter
where
R: Ring,
<R::Point as Point>::Cs: CoordinateSystem,
<<R::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<GeographicFamily>,
Andoyer: DistanceStrategy<R::Point, R::Point, Out = <R::Point as Point>::Scalar>,
{
type Out = <R::Point as Point>::Scalar;
#[inline]
fn length(&self, g: &R) -> Self::Out {
let pts: Vec<&R::Point> = g.points().collect();
let mut acc = <Self::Out as geometry_coords::CoordinateScalar>::ZERO;
for w in pts.windows(2) {
acc = acc + self.andoyer.distance(w[0], w[1]);
}
match (g.closure(), pts.first(), pts.last()) {
(Closure::Open, Some(first), Some(last)) => acc + self.andoyer.distance(last, first),
_ => acc,
}
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
#![allow(
clippy::float_cmp,
reason = "lengths are compared with an explicit absolute tolerance, not `==`"
)]
use super::{GeographicLength, GeographicPerimeter};
use crate::distance::DistanceStrategy;
use crate::geographic::Andoyer;
use crate::length::LengthStrategy;
use geometry_adapt::{Adapt, WithCs};
use geometry_cs::{Degree, Geographic};
use geometry_model::{Linestring, Ring};
type Gg = WithCs<Adapt<[f64; 2]>, Geographic<Degree>>;
#[inline]
fn deg(lon: f64, lat: f64) -> Gg {
WithCs::new(Adapt([lon, lat]))
}
#[test]
fn ams_geodesic_length() {
let ls: Linestring<Gg> = Linestring(vec![deg(4.0, 52.0), deg(3.0, 40.0)]);
let got = GeographicLength::default().length(&ls);
let direct = Andoyer::WGS84.distance(°(4.0, 52.0), °(3.0, 40.0));
assert!((got - direct).abs() < 1e-6);
assert!((got / 1000.0 - 1_336.039_890).abs() < 0.01);
}
#[test]
fn three_point_geodesic() {
let ls: Linestring<Gg> = Linestring(vec![deg(0., 0.), deg(0., 10.), deg(0., 20.)]);
let got = GeographicLength::default().length(&ls);
let a = Andoyer::WGS84;
let expected =
a.distance(°(0., 0.), °(0., 10.)) + a.distance(°(0., 10.), °(0., 20.));
assert!((got - expected).abs() < 1e-6);
}
#[test]
fn open_ring_closes_itself() {
let mut r = Ring::<Gg, true, false>::new();
r.push(deg(0., 0.));
r.push(deg(10., 0.));
r.push(deg(10., 10.));
r.push(deg(0., 10.));
let got = GeographicPerimeter::default().length(&r);
let a = Andoyer::WGS84;
let expected = a.distance(°(0., 0.), °(10., 0.))
+ a.distance(°(10., 0.), °(10., 10.))
+ a.distance(°(10., 10.), °(0., 10.))
+ a.distance(°(0., 10.), °(0., 0.));
assert!((got - expected).abs() < 1e-6);
}
}