1use crate::geometry::*;
2use crate::{CoordFloat, EQUATORIAL_EARTH_RADIUS};
3
4pub 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 (coords_len - 2, coords_len - 1, 0)
85 } else if i == coords_len - 1 {
86 (coords_len - 1, 0, 1)
88 } else {
89 (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
106macro_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
124macro_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
143macro_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}