geometry_strategy/geographic/
length.rs1use alloc::vec::Vec;
14
15use geometry_cs::{CoordinateSystem, GeographicFamily};
16use geometry_tag::SameAs;
17use geometry_trait::{Closure, Linestring, Point, Ring};
18
19use crate::distance::DistanceStrategy;
20use crate::geographic::Andoyer;
21use crate::length::LengthStrategy;
22
23#[derive(Debug, Default, Clone, Copy)]
34pub struct GeographicLength {
35 pub andoyer: Andoyer,
37}
38
39#[derive(Debug, Default, Clone, Copy)]
47pub struct GeographicPerimeter {
48 pub andoyer: Andoyer,
50}
51
52impl<L> LengthStrategy<L> for GeographicLength
55where
56 L: Linestring,
57 <L::Point as Point>::Cs: CoordinateSystem,
58 <<L::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<GeographicFamily>,
59 Andoyer: DistanceStrategy<L::Point, L::Point, Out = <L::Point as Point>::Scalar>,
60{
61 type Out = <L::Point as Point>::Scalar;
62
63 #[inline]
64 fn length(&self, g: &L) -> Self::Out {
65 let pts: Vec<&L::Point> = g.points().collect();
66 let mut acc = <Self::Out as geometry_coords::CoordinateScalar>::ZERO;
67 for w in pts.windows(2) {
68 acc = acc + self.andoyer.distance(w[0], w[1]);
69 }
70 acc
71 }
72}
73
74impl<R> LengthStrategy<R> for GeographicPerimeter
77where
78 R: Ring,
79 <R::Point as Point>::Cs: CoordinateSystem,
80 <<R::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<GeographicFamily>,
81 Andoyer: DistanceStrategy<R::Point, R::Point, Out = <R::Point as Point>::Scalar>,
82{
83 type Out = <R::Point as Point>::Scalar;
84
85 #[inline]
86 fn length(&self, g: &R) -> Self::Out {
87 let pts: Vec<&R::Point> = g.points().collect();
88 let mut acc = <Self::Out as geometry_coords::CoordinateScalar>::ZERO;
89 for w in pts.windows(2) {
90 acc = acc + self.andoyer.distance(w[0], w[1]);
91 }
92 match (g.closure(), pts.first(), pts.last()) {
93 (Closure::Open, Some(first), Some(last)) => acc + self.andoyer.distance(last, first),
94 _ => acc,
95 }
96 }
97}
98
99#[cfg(all(test, feature = "std"))]
100mod tests {
101 #![allow(
106 clippy::float_cmp,
107 reason = "lengths are compared with an explicit absolute tolerance, not `==`"
108 )]
109
110 use super::{GeographicLength, GeographicPerimeter};
111 use crate::distance::DistanceStrategy;
112 use crate::geographic::Andoyer;
113 use crate::length::LengthStrategy;
114 use geometry_adapt::{Adapt, WithCs};
115 use geometry_cs::{Degree, Geographic};
116 use geometry_model::{Linestring, Ring};
117
118 type Gg = WithCs<Adapt<[f64; 2]>, Geographic<Degree>>;
119
120 #[inline]
121 fn deg(lon: f64, lat: f64) -> Gg {
122 WithCs::new(Adapt([lon, lat]))
123 }
124
125 #[test]
129 fn ams_geodesic_length() {
130 let ls: Linestring<Gg> = Linestring(vec![deg(4.0, 52.0), deg(3.0, 40.0)]);
131 let got = GeographicLength::default().length(&ls);
132 let direct = Andoyer::WGS84.distance(°(4.0, 52.0), °(3.0, 40.0));
133 assert!((got - direct).abs() < 1e-6);
134 assert!((got / 1000.0 - 1_336.039_890).abs() < 0.01);
135 }
136
137 #[test]
139 fn three_point_geodesic() {
140 let ls: Linestring<Gg> = Linestring(vec![deg(0., 0.), deg(0., 10.), deg(0., 20.)]);
141 let got = GeographicLength::default().length(&ls);
142 let a = Andoyer::WGS84;
143 let expected =
144 a.distance(°(0., 0.), °(0., 10.)) + a.distance(°(0., 10.), °(0., 20.));
145 assert!((got - expected).abs() < 1e-6);
146 }
147
148 #[test]
151 fn open_ring_closes_itself() {
152 let mut r = Ring::<Gg, true, false>::new();
153 r.push(deg(0., 0.));
154 r.push(deg(10., 0.));
155 r.push(deg(10., 10.));
156 r.push(deg(0., 10.));
157 let got = GeographicPerimeter::default().length(&r);
158 let a = Andoyer::WGS84;
159 let expected = a.distance(°(0., 0.), °(10., 0.))
160 + a.distance(°(10., 0.), °(10., 10.))
161 + a.distance(°(10., 10.), °(0., 10.))
162 + a.distance(°(0., 10.), °(0., 0.));
163 assert!((got - expected).abs() < 1e-6);
164 }
165}