geometry_strategy/spherical/
length.rs1use alloc::vec::Vec;
14
15use geometry_cs::{CoordinateSystem, SphericalFamily};
16use geometry_tag::SameAs;
17use geometry_trait::{Closure, Linestring, Point, Ring};
18
19use crate::distance::DistanceStrategy;
20use crate::length::LengthStrategy;
21use crate::spherical::Haversine;
22
23#[derive(Debug, Default, Clone, Copy)]
34pub struct SphericalLength {
35 pub haversine: Haversine,
37}
38
39#[derive(Debug, Default, Clone, Copy)]
49pub struct SphericalPerimeter {
50 pub haversine: Haversine,
52}
53
54impl<L> LengthStrategy<L> for SphericalLength
61where
62 L: Linestring,
63 <L::Point as Point>::Cs: CoordinateSystem,
64 <<L::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<SphericalFamily>,
65 Haversine: DistanceStrategy<L::Point, L::Point, Out = <L::Point as Point>::Scalar>,
66{
67 type Out = <L::Point as Point>::Scalar;
68
69 #[inline]
70 fn length(&self, g: &L) -> Self::Out {
71 let pts: Vec<&L::Point> = g.points().collect();
72 let mut acc = <Self::Out as geometry_coords::CoordinateScalar>::ZERO;
73 for w in pts.windows(2) {
74 acc = acc + self.haversine.distance(w[0], w[1]);
75 }
76 acc
77 }
78}
79
80impl<R> LengthStrategy<R> for SphericalPerimeter
88where
89 R: Ring,
90 <R::Point as Point>::Cs: CoordinateSystem,
91 <<R::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<SphericalFamily>,
92 Haversine: DistanceStrategy<R::Point, R::Point, Out = <R::Point as Point>::Scalar>,
93{
94 type Out = <R::Point as Point>::Scalar;
95
96 #[inline]
97 fn length(&self, g: &R) -> Self::Out {
98 let pts: Vec<&R::Point> = g.points().collect();
99 let mut acc = <Self::Out as geometry_coords::CoordinateScalar>::ZERO;
100 for w in pts.windows(2) {
101 acc = acc + self.haversine.distance(w[0], w[1]);
102 }
103 if matches!(g.closure(), Closure::Open) {
104 if let (Some(first), Some(last)) = (pts.first(), pts.last()) {
105 acc = acc + self.haversine.distance(last, first);
106 }
107 }
108 acc
109 }
110}
111
112#[cfg(all(test, feature = "std"))]
113mod tests {
114 #![allow(
119 clippy::float_cmp,
120 reason = "lengths are compared with an explicit absolute tolerance, not `==`"
121 )]
122
123 use super::{SphericalLength, SphericalPerimeter};
124 use crate::distance::DistanceStrategy;
125 use crate::length::LengthStrategy;
126 use crate::spherical::Haversine;
127 use geometry_adapt::{Adapt, WithCs};
128 use geometry_cs::{Degree, Spherical};
129 use geometry_model::{Linestring, Ring};
130
131 type Sp = WithCs<Adapt<[f64; 2]>, Spherical<Degree>>;
132
133 #[inline]
134 fn deg(lon: f64, lat: f64) -> Sp {
135 WithCs::new(Adapt([lon, lat]))
136 }
137
138 #[test]
141 fn ams_paris_haversine_length() {
142 let ls: Linestring<Sp> = Linestring(vec![deg(4.90, 52.37), deg(2.35, 48.86)]);
143 let got = SphericalLength::default().length(&ls);
144 let direct = Haversine::EARTH.distance(°(4.90, 52.37), °(2.35, 48.86));
145 assert!((got - direct).abs() < 1e-6);
146 }
147
148 #[test]
150 fn three_point_great_circle() {
151 let ls: Linestring<Sp> = Linestring(vec![deg(0., 0.), deg(0., 10.), deg(0., 20.)]);
152 let got = SphericalLength::default().length(&ls);
153 let segment = Haversine::EARTH.distance(°(0., 0.), °(0., 10.));
154 assert!((got - segment * 2.0).abs() < 1e-6);
155 }
156
157 #[test]
160 fn open_ring_closes_itself() {
161 let mut r = Ring::<Sp, true, false>::new();
162 r.push(deg(0., 0.));
163 r.push(deg(10., 0.));
164 r.push(deg(10., 10.));
165 r.push(deg(0., 10.));
166 let got = SphericalPerimeter::default().length(&r);
167 let h = Haversine::EARTH;
168 let expected = h.distance(°(0., 0.), °(10., 0.))
169 + h.distance(°(10., 0.), °(10., 10.))
170 + h.distance(°(10., 10.), °(0., 10.))
171 + h.distance(°(0., 10.), °(0., 0.));
172 assert!((got - expected).abs() < 1e-6);
173 }
174}