geo/algorithm/
area.rs

1use crate::geometry::*;
2use crate::{CoordFloat, CoordNum};
3
4pub(crate) fn twice_signed_ring_area<T>(linestring: &LineString<T>) -> T
5where
6    T: CoordNum,
7{
8    // LineString with less than 3 points is empty, or a
9    // single point, or is not closed.
10    if linestring.0.len() < 3 {
11        return T::zero();
12    }
13
14    // Above test ensures the vector has at least 2 elements.
15    // We check if linestring is closed, and return 0 otherwise.
16    if linestring.0.first().unwrap() != linestring.0.last().unwrap() {
17        return T::zero();
18    }
19
20    // Use a reasonable shift for the line-string coords
21    // to avoid numerical-errors when summing the
22    // determinants.
23    //
24    // Note: we can't use the `Centroid` trait as it
25    // requires `T: Float` and in fact computes area in the
26    // implementation. Another option is to use the average
27    // of the coordinates, but it is not fool-proof to
28    // divide by the length of the linestring (eg. a long
29    // line-string with T = u8)
30    let shift = linestring.0[0];
31
32    let mut tmp = T::zero();
33    for line in linestring.lines() {
34        use crate::MapCoords;
35        let line = line.map_coords(|c| c - shift);
36        tmp = tmp + line.determinant();
37    }
38
39    tmp
40}
41
42/// Signed and unsigned planar area of a geometry.
43///
44/// # Examples
45///
46/// ```
47/// use geo::polygon;
48/// use geo::Area;
49///
50/// let mut polygon = polygon![
51///     (x: 0., y: 0.),
52///     (x: 5., y: 0.),
53///     (x: 5., y: 6.),
54///     (x: 0., y: 6.),
55///     (x: 0., y: 0.),
56/// ];
57///
58/// assert_eq!(polygon.signed_area(), 30.);
59/// assert_eq!(polygon.unsigned_area(), 30.);
60///
61/// polygon.exterior_mut(|line_string| {
62///     line_string.0.reverse();
63/// });
64///
65/// assert_eq!(polygon.signed_area(), -30.);
66/// assert_eq!(polygon.unsigned_area(), 30.);
67/// ```
68pub trait Area<T>
69where
70    T: CoordNum,
71{
72    fn signed_area(&self) -> T;
73
74    fn unsigned_area(&self) -> T;
75}
76
77// Calculation of simple (no interior holes) Polygon area
78pub(crate) fn get_linestring_area<T>(linestring: &LineString<T>) -> T
79where
80    T: CoordFloat,
81{
82    twice_signed_ring_area(linestring) / (T::one() + T::one())
83}
84
85impl<T> Area<T> for Point<T>
86where
87    T: CoordNum,
88{
89    fn signed_area(&self) -> T {
90        T::zero()
91    }
92
93    fn unsigned_area(&self) -> T {
94        T::zero()
95    }
96}
97
98impl<T> Area<T> for LineString<T>
99where
100    T: CoordNum,
101{
102    fn signed_area(&self) -> T {
103        T::zero()
104    }
105
106    fn unsigned_area(&self) -> T {
107        T::zero()
108    }
109}
110
111impl<T> Area<T> for Line<T>
112where
113    T: CoordNum,
114{
115    fn signed_area(&self) -> T {
116        T::zero()
117    }
118
119    fn unsigned_area(&self) -> T {
120        T::zero()
121    }
122}
123
124/// **Note.** The implementation handles polygons whose
125/// holes do not all have the same orientation. The sign of
126/// the output is the same as that of the exterior shell.
127impl<T> Area<T> for Polygon<T>
128where
129    T: CoordFloat,
130{
131    fn signed_area(&self) -> T {
132        let area = get_linestring_area(self.exterior());
133
134        // We could use winding order here, but that would
135        // result in computing the shoelace formula twice.
136        let is_negative = area < T::zero();
137
138        let area = self.interiors().iter().fold(area.abs(), |total, next| {
139            total - get_linestring_area(next).abs()
140        });
141
142        if is_negative {
143            -area
144        } else {
145            area
146        }
147    }
148
149    fn unsigned_area(&self) -> T {
150        self.signed_area().abs()
151    }
152}
153
154impl<T> Area<T> for MultiPoint<T>
155where
156    T: CoordNum,
157{
158    fn signed_area(&self) -> T {
159        T::zero()
160    }
161
162    fn unsigned_area(&self) -> T {
163        T::zero()
164    }
165}
166
167impl<T> Area<T> for MultiLineString<T>
168where
169    T: CoordNum,
170{
171    fn signed_area(&self) -> T {
172        T::zero()
173    }
174
175    fn unsigned_area(&self) -> T {
176        T::zero()
177    }
178}
179
180/// **Note.** The implementation is a straight-forward
181/// summation of the signed areas of the individual
182/// polygons. In particular, `unsigned_area` is not
183/// necessarily the sum of the `unsigned_area` of the
184/// constituent polygons unless they are all oriented the
185/// same.
186impl<T> Area<T> for MultiPolygon<T>
187where
188    T: CoordFloat,
189{
190    fn signed_area(&self) -> T {
191        self.0
192            .iter()
193            .fold(T::zero(), |total, next| total + next.signed_area())
194    }
195
196    fn unsigned_area(&self) -> T {
197        self.0
198            .iter()
199            .fold(T::zero(), |total, next| total + next.signed_area().abs())
200    }
201}
202
203/// Because a `Rect` has no winding order, the area will always be positive.
204impl<T> Area<T> for Rect<T>
205where
206    T: CoordNum,
207{
208    fn signed_area(&self) -> T {
209        self.width() * self.height()
210    }
211
212    fn unsigned_area(&self) -> T {
213        self.width() * self.height()
214    }
215}
216
217impl<T> Area<T> for Triangle<T>
218where
219    T: CoordFloat,
220{
221    fn signed_area(&self) -> T {
222        self.to_lines()
223            .iter()
224            .fold(T::zero(), |total, line| total + line.determinant())
225            / (T::one() + T::one())
226    }
227
228    fn unsigned_area(&self) -> T {
229        self.signed_area().abs()
230    }
231}
232
233impl<T> Area<T> for Geometry<T>
234where
235    T: CoordFloat,
236{
237    crate::geometry_delegate_impl! {
238        fn signed_area(&self) -> T;
239        fn unsigned_area(&self) -> T;
240    }
241}
242
243impl<T> Area<T> for GeometryCollection<T>
244where
245    T: CoordFloat,
246{
247    fn signed_area(&self) -> T {
248        self.0
249            .iter()
250            .map(|g| g.signed_area())
251            .fold(T::zero(), |acc, next| acc + next)
252    }
253
254    fn unsigned_area(&self) -> T {
255        self.0
256            .iter()
257            .map(|g| g.unsigned_area())
258            .fold(T::zero(), |acc, next| acc + next)
259    }
260}
261
262#[cfg(test)]
263mod test {
264    use crate::Area;
265    use crate::{coord, polygon, Line, MultiPolygon, Polygon, Rect, Triangle};
266
267    // Area of the polygon
268    #[test]
269    fn area_empty_polygon_test() {
270        let poly: Polygon<f32> = polygon![];
271        assert_relative_eq!(poly.signed_area(), 0.);
272    }
273
274    #[test]
275    fn area_one_point_polygon_test() {
276        let poly = polygon![(x: 1., y: 0.)];
277        assert_relative_eq!(poly.signed_area(), 0.);
278    }
279    #[test]
280    fn area_polygon_test() {
281        let polygon = polygon![
282            (x: 0., y: 0.),
283            (x: 5., y: 0.),
284            (x: 5., y: 6.),
285            (x: 0., y: 6.),
286            (x: 0., y: 0.)
287        ];
288        assert_relative_eq!(polygon.signed_area(), 30.);
289    }
290    #[test]
291    fn area_polygon_numerical_stability() {
292        let polygon = {
293            use std::f64::consts::PI;
294            const NUM_VERTICES: usize = 10;
295            const ANGLE_INC: f64 = 2. * PI / NUM_VERTICES as f64;
296
297            Polygon::new(
298                (0..NUM_VERTICES)
299                    .map(|i| {
300                        let angle = i as f64 * ANGLE_INC;
301                        coord! {
302                            x: angle.cos(),
303                            y: angle.sin(),
304                        }
305                    })
306                    .collect::<Vec<_>>()
307                    .into(),
308                vec![],
309            )
310        };
311
312        let area = polygon.signed_area();
313
314        let shift = coord! { x: 1.5e8, y: 1.5e8 };
315
316        use crate::map_coords::MapCoords;
317        let polygon = polygon.map_coords(|c| c + shift);
318
319        let new_area = polygon.signed_area();
320        let err = (area - new_area).abs() / area;
321
322        assert!(err < 1e-2);
323    }
324    #[test]
325    fn rectangle_test() {
326        let rect1: Rect<f32> = Rect::new(coord! { x: 10., y: 30. }, coord! { x: 20., y: 40. });
327        assert_relative_eq!(rect1.signed_area(), 100.);
328
329        let rect2: Rect<i32> = Rect::new(coord! { x: 10, y: 30 }, coord! { x: 20, y: 40 });
330        assert_eq!(rect2.signed_area(), 100);
331    }
332    #[test]
333    fn area_polygon_inner_test() {
334        let poly = polygon![
335            exterior: [
336                (x: 0., y: 0.),
337                (x: 10., y: 0.),
338                (x: 10., y: 10.),
339                (x: 0., y: 10.),
340                (x: 0., y: 0.)
341            ],
342            interiors: [
343                [
344                    (x: 1., y: 1.),
345                    (x: 2., y: 1.),
346                    (x: 2., y: 2.),
347                    (x: 1., y: 2.),
348                    (x: 1., y: 1.),
349                ],
350                [
351                    (x: 5., y: 5.),
352                    (x: 6., y: 5.),
353                    (x: 6., y: 6.),
354                    (x: 5., y: 6.),
355                    (x: 5., y: 5.)
356                ],
357            ],
358        ];
359        assert_relative_eq!(poly.signed_area(), 98.);
360    }
361    #[test]
362    fn area_multipolygon_test() {
363        let poly0 = polygon![
364            (x: 0., y: 0.),
365            (x: 10., y: 0.),
366            (x: 10., y: 10.),
367            (x: 0., y: 10.),
368            (x: 0., y: 0.)
369        ];
370        let poly1 = polygon![
371            (x: 1., y: 1.),
372            (x: 2., y: 1.),
373            (x: 2., y: 2.),
374            (x: 1., y: 2.),
375            (x: 1., y: 1.)
376        ];
377        let poly2 = polygon![
378            (x: 5., y: 5.),
379            (x: 6., y: 5.),
380            (x: 6., y: 6.),
381            (x: 5., y: 6.),
382            (x: 5., y: 5.)
383        ];
384        let mpoly = MultiPolygon::new(vec![poly0, poly1, poly2]);
385        assert_relative_eq!(mpoly.signed_area(), 102.);
386        assert_relative_eq!(mpoly.signed_area(), 102.);
387    }
388    #[test]
389    fn area_line_test() {
390        let line1 = Line::new(coord! { x: 0.0, y: 0.0 }, coord! { x: 1.0, y: 1.0 });
391        assert_relative_eq!(line1.signed_area(), 0.);
392    }
393
394    #[test]
395    fn area_triangle_test() {
396        let triangle = Triangle::new(
397            coord! { x: 0.0, y: 0.0 },
398            coord! { x: 1.0, y: 0.0 },
399            coord! { x: 0.0, y: 1.0 },
400        );
401        assert_relative_eq!(triangle.signed_area(), 0.5);
402
403        let triangle = Triangle::new(
404            coord! { x: 0.0, y: 0.0 },
405            coord! { x: 0.0, y: 1.0 },
406            coord! { x: 1.0, y: 0.0 },
407        );
408        assert_relative_eq!(triangle.signed_area(), -0.5);
409    }
410
411    #[test]
412    fn area_multi_polygon_area_reversed() {
413        let polygon_cw: Polygon<f32> = polygon![
414            coord! { x: 0.0, y: 0.0 },
415            coord! { x: 0.0, y: 1.0 },
416            coord! { x: 1.0, y: 1.0 },
417            coord! { x: 1.0, y: 0.0 },
418            coord! { x: 0.0, y: 0.0 },
419        ];
420        let polygon_ccw: Polygon<f32> = polygon![
421            coord! { x: 0.0, y: 0.0 },
422            coord! { x: 1.0, y: 0.0 },
423            coord! { x: 1.0, y: 1.0 },
424            coord! { x: 0.0, y: 1.0 },
425            coord! { x: 0.0, y: 0.0 },
426        ];
427        let polygon_area = polygon_cw.unsigned_area();
428
429        let multi_polygon = MultiPolygon::new(vec![polygon_cw, polygon_ccw]);
430
431        assert_eq!(polygon_area * 2., multi_polygon.unsigned_area());
432    }
433
434    #[test]
435    fn area_north_america_cutout() {
436        let poly = polygon![
437            exterior: [
438                (x: -102.902861858977, y: 31.6943450891131),
439                (x: -102.917375513247, y: 31.6990175356827),
440                (x: -102.917887344527, y: 31.7044889522597),
441                (x: -102.938892711173, y: 31.7032871894594),
442                (x: -102.939919687305, y: 31.7142296141915),
443                (x: -102.946922353444, y: 31.713828170995),
444                (x: -102.954642979004, y: 31.7210594956594),
445                (x: -102.960927457803, y: 31.7130240707676),
446                (x: -102.967929895872, y: 31.7126214137469),
447                (x: -102.966383373178, y: 31.6962079209847),
448                (x: -102.973384192133, y: 31.6958049292994),
449                (x: -102.97390013779, y: 31.701276160078),
450                (x: -102.980901394769, y: 31.7008727405409),
451                (x: -102.987902575456, y: 31.7004689164622),
452                (x: -102.986878877087, y: 31.7127206248263),
453                (x: -102.976474089689, y: 31.7054378797983),
454                (x: -102.975448432121, y: 31.7176893134691),
455                (x: -102.96619351228, y: 31.7237224912303),
456                (x: -102.976481009643, y: 31.7286309669534),
457                (x: -102.976997412845, y: 31.7341016591658),
458                (x: -102.978030448215, y: 31.7450427747035),
459                (x: -102.985035821671, y: 31.7446391683265),
460                (x: -102.985552968771, y: 31.7501095683386),
461                (x: -102.992558780682, y: 31.7497055338313),
462                (x: -102.993594334215, y: 31.7606460184322),
463                (x: -102.973746840657, y: 31.7546100958509),
464                (x: -102.966082339116, y: 31.767730116605),
465                (x: -102.959074676589, y: 31.768132602064),
466                (x: -102.95206693787, y: 31.7685346826851),
467                (x: -102.953096767614, y: 31.7794749110023),
468                (x: -102.953611796704, y: 31.7849448911322),
469                (x: -102.952629078076, y: 31.7996518517642),
470                (x: -102.948661251495, y: 31.8072257578725),
471                (x: -102.934638176282, y: 31.8080282207231),
472                (x: -102.927626524626, y: 31.8084288446215),
473                (x: -102.927113253813, y: 31.8029591283411),
474                (x: -102.920102042027, y: 31.8033593239799),
475                (x: -102.919076759513, y: 31.792419577395),
476                (x: -102.912066503301, y: 31.7928193216213),
477                (x: -102.911554491357, y: 31.7873492912889),
478                (x: -102.904544675025, y: 31.7877486073783),
479                (x: -102.904033254331, y: 31.7822784646103),
480                (x: -102.903521909259, y: 31.7768082325431),
481                (x: -102.895800463718, y: 31.7695748336589),
482                (x: -102.889504111843, y: 31.7776055573633),
483                (x: -102.882495099915, y: 31.7780036124077),
484                (x: -102.868476849997, y: 31.7787985077398),
485                (x: -102.866950998738, y: 31.7623869292283),
486                (x: -102.873958615171, y: 31.7619897531194),
487                (x: -102.87888647278, y: 31.7688910039026),
488                (x: -102.879947237315, y: 31.750650764952),
489                (x: -102.886953672823, y: 31.750252825268),
490                (x: -102.89396003296, y: 31.7498544807869),
491                (x: -102.892939355062, y: 31.7389128078806),
492                (x: -102.913954892669, y: 31.7377154844276),
493                (x: -102.913443122277, y: 31.7322445829725),
494                (x: -102.912931427507, y: 31.7267735918962),
495                (x: -102.911908264767, y: 31.7158313407426),
496                (x: -102.904905220014, y: 31.7162307607961),
497                (x: -102.904394266551, y: 31.7107594775392),
498                (x: -102.903372586049, y: 31.6998166417321),
499                (x: -102.902861858977, y: 31.6943450891131),
500            ],
501            interiors: [
502                [
503                    (x: -102.916514879554, y: 31.7650686485918),
504                    (x: -102.921022256876, y: 31.7770831833398),
505                    (x: -102.93367363719, y: 31.771184865332),
506                    (x: -102.916514879554, y: 31.7650686485918),
507                ],
508                [
509                    (x: -102.935483140202, y: 31.7419852607081),
510                    (x: -102.932452314332, y: 31.7328567234689),
511                    (x: -102.918345099146, y: 31.7326099897391),
512                    (x: -102.925566322952, y: 31.7552505533503),
513                    (x: -102.928990700436, y: 31.747856686604),
514                    (x: -102.935996606762, y: 31.7474559134477),
515                    (x: -102.939021176592, y: 31.7539885279379),
516                    (x: -102.944714388971, y: 31.7488395547293),
517                    (x: -102.935996606762, y: 31.7474559134477),
518                    (x: -102.935483140202, y: 31.7419852607081),
519                ],
520                [
521                    (x: -102.956498858767, y: 31.7407805824758),
522                    (x: -102.960959476367, y: 31.7475080456347),
523                    (x: -102.972817445204, y: 31.742072061889),
524                    (x: -102.956498858767, y: 31.7407805824758),
525                ]
526            ],
527        ];
528        // Value from shapely
529        assert_relative_eq!(poly.unsigned_area(), 0.006547948219252177, max_relative = 0.0001);
530    }
531}