geo/algorithm/
chamberlain_duquette_area.rs

1use crate::geometry::*;
2use crate::{CoordFloat, EQUATORIAL_EARTH_RADIUS};
3
4/// Calculate the signed approximate geodesic area of a `Geometry`.
5///
6/// # Units
7///
8/// - return value: meters²
9///
10/// # References
11///
12/// * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for Polygons on a Sphere",
13///
14///   JPL Publication 07-03, Jet Propulsion Laboratory, Pasadena, CA, June 2007 <https://trs.jpl.nasa.gov/handle/2014/41271>
15///
16/// # Examples
17///
18/// ```
19/// use geo::{polygon, Polygon};
20/// use geo::chamberlain_duquette_area::ChamberlainDuquetteArea;
21///
22/// // The O2 in London
23/// let mut polygon: Polygon<f64> = polygon![
24///     (x: 0.00388383, y: 51.501574),
25///     (x: 0.00538587, y: 51.502278),
26///     (x: 0.00553607, y: 51.503299),
27///     (x: 0.00467777, y: 51.504181),
28///     (x: 0.00327229, y: 51.504435),
29///     (x: 0.00187754, y: 51.504168),
30///     (x: 0.00087976, y: 51.503380),
31///     (x: 0.00107288, y: 51.502324),
32///     (x: 0.00185608, y: 51.501770),
33///     (x: 0.00388383, y: 51.501574),
34/// ];
35///
36/// // 78,478 meters²
37/// assert_eq!(78_478., polygon.chamberlain_duquette_unsigned_area().round());
38/// assert_eq!(78_478., polygon.chamberlain_duquette_signed_area().round());
39///
40/// polygon.exterior_mut(|line_string| {
41///     line_string.0.reverse();
42/// });
43///
44/// assert_eq!(78_478., polygon.chamberlain_duquette_unsigned_area().round());
45/// assert_eq!(-78_478., polygon.chamberlain_duquette_signed_area().round());
46/// ```
47pub trait ChamberlainDuquetteArea<T>
48where
49    T: CoordFloat,
50{
51    fn chamberlain_duquette_signed_area(&self) -> T;
52
53    fn chamberlain_duquette_unsigned_area(&self) -> T;
54}
55
56impl<T> ChamberlainDuquetteArea<T> for Polygon<T>
57where
58    T: CoordFloat,
59{
60    fn chamberlain_duquette_signed_area(&self) -> T {
61        self.interiors()
62            .iter()
63            .fold(ring_area(self.exterior()), |total, next| {
64                total - ring_area(next)
65            })
66    }
67
68    fn chamberlain_duquette_unsigned_area(&self) -> T {
69        self.chamberlain_duquette_signed_area().abs()
70    }
71}
72
73fn ring_area<T>(coords: &LineString<T>) -> T
74where
75    T: CoordFloat,
76{
77    let mut total = T::zero();
78    let coords_len = coords.0.len();
79
80    if coords_len > 2 {
81        for i in 0..coords_len {
82            let (lower_index, middle_index, upper_index) = if i == coords_len - 2 {
83                // i = N-2
84                (coords_len - 2, coords_len - 1, 0)
85            } else if i == coords_len - 1 {
86                // i = N-1
87                (coords_len - 1, 0, 1)
88            } else {
89                // i = 0 to N-3
90                (i, i + 1, i + 2)
91            };
92            let p1 = coords[lower_index];
93            let p2 = coords[middle_index];
94            let p3 = coords[upper_index];
95            total = total + (p3.x.to_radians() - p1.x.to_radians()) * p2.y.to_radians().sin();
96        }
97
98        total = total
99            * T::from(EQUATORIAL_EARTH_RADIUS).unwrap()
100            * T::from(EQUATORIAL_EARTH_RADIUS).unwrap()
101            / T::from(-2).unwrap();
102    }
103    total
104}
105
106/// Generate a `ChamberlainDuquetteArea` implementation where the result is zero.
107macro_rules! zero_impl {
108    ($type:ident) => {
109        impl<T> ChamberlainDuquetteArea<T> for $type<T>
110        where
111            T: CoordFloat,
112        {
113            fn chamberlain_duquette_signed_area(&self) -> T {
114                T::zero()
115            }
116
117            fn chamberlain_duquette_unsigned_area(&self) -> T {
118                T::zero()
119            }
120        }
121    };
122}
123
124/// Generate a `ChamberlainDuquetteArea` implementation which delegates to the `Polygon`
125/// implementation.
126macro_rules! to_polygon_impl {
127    ($type:ident) => {
128        impl<T> ChamberlainDuquetteArea<T> for $type<T>
129        where
130            T: CoordFloat,
131        {
132            fn chamberlain_duquette_signed_area(&self) -> T {
133                self.to_polygon().chamberlain_duquette_signed_area()
134            }
135
136            fn chamberlain_duquette_unsigned_area(&self) -> T {
137                self.to_polygon().chamberlain_duquette_unsigned_area()
138            }
139        }
140    };
141}
142
143/// Generate a `ChamberlainDuquetteArea` implementation which calculates the area for each of its
144/// sub-components and sums them up.
145macro_rules! sum_impl {
146    ($type:ident) => {
147        impl<T> ChamberlainDuquetteArea<T> for $type<T>
148        where
149            T: CoordFloat,
150        {
151            fn chamberlain_duquette_signed_area(&self) -> T {
152                self.iter().fold(T::zero(), |total, next| {
153                    total + next.chamberlain_duquette_signed_area()
154                })
155            }
156
157            fn chamberlain_duquette_unsigned_area(&self) -> T {
158                self.iter().fold(T::zero(), |total, next| {
159                    total + next.chamberlain_duquette_unsigned_area()
160                })
161            }
162        }
163    };
164}
165
166zero_impl!(Point);
167zero_impl!(Line);
168zero_impl!(LineString);
169zero_impl!(MultiPoint);
170zero_impl!(MultiLineString);
171to_polygon_impl!(Rect);
172to_polygon_impl!(Triangle);
173sum_impl!(GeometryCollection);
174sum_impl!(MultiPolygon);
175
176impl<T> ChamberlainDuquetteArea<T> for Geometry<T>
177where
178    T: CoordFloat,
179{
180    crate::geometry_delegate_impl! {
181        fn chamberlain_duquette_signed_area(&self) -> T;
182        fn chamberlain_duquette_unsigned_area(&self) -> T;
183    }
184}
185
186#[cfg(test)]
187mod test {
188    use super::*;
189    use crate::polygon;
190
191    #[test]
192    fn test_negative() {
193        let polygon = polygon![
194            (x: 125., y: -15.),
195            (x: 144., y: -15.),
196            (x: 154., y: -27.),
197            (x: 148., y: -39.),
198            (x: 130., y: -33.),
199            (x: 117., y: -37.),
200            (x: 113., y: -22.),
201            (x: 125., y: -15.),
202        ];
203        assert_relative_eq!(
204            -7766240997209.013,
205            polygon.chamberlain_duquette_signed_area()
206        );
207    }
208
209    #[test]
210    fn test_positive() {
211        let polygon = polygon![
212            (x: 125., y: -15.),
213            (x: 113., y: -22.),
214            (x: 117., y: -37.),
215            (x: 130., y: -33.),
216            (x: 148., y: -39.),
217            (x: 154., y: -27.),
218            (x: 144., y: -15.),
219            (x: 125., y: -15.),
220        ];
221        assert_relative_eq!(
222            7766240997209.013,
223            polygon.chamberlain_duquette_signed_area()
224        );
225    }
226
227    #[test]
228    fn test_holes() {
229        let poly = polygon![
230            exterior: [
231                (x: 0., y: 0.),
232                (x: 10., y: 0.),
233                (x: 10., y: 10.),
234                (x: 0., y: 10.),
235                (x: 0., y: 0.)
236            ],
237            interiors: [
238                [
239                    (x: 1., y: 1.),
240                    (x: 2., y: 1.),
241                    (x: 2., y: 2.),
242                    (x: 1., y: 2.),
243                    (x: 1., y: 1.),
244                ],
245                [
246                    (x: 5., y: 5.),
247                    (x: 6., y: 5.),
248                    (x: 6., y: 6.),
249                    (x: 5., y: 6.),
250                    (x: 5., y: 5.)
251                ],
252            ],
253        ];
254        assert_relative_eq!(1208198651182.4727, poly.chamberlain_duquette_signed_area());
255    }
256}