geo/algorithm/
centroid.rs

1use std::cmp::Ordering;
2
3use crate::GeoFloat;
4use crate::area::{Area, get_linestring_area};
5use crate::dimensions::{Dimensions, Dimensions::*, HasDimensions};
6use crate::geometry::*;
7use crate::line_measures::{Euclidean, Length};
8
9/// Calculation of the centroid.
10/// The centroid is the arithmetic mean position of all points in the shape.
11/// Informally, it is the point at which a cutout of the shape could be perfectly
12/// balanced on the tip of a pin.
13/// The geometric centroid of a convex object always lies in the object.
14/// A non-convex object might have a centroid that _is outside the object itself_.
15///
16/// # Examples
17///
18/// ```
19/// use geo::Centroid;
20/// use geo::{point, polygon};
21///
22/// // rhombus shaped polygon
23/// let polygon = polygon![
24///     (x: -2., y: 1.),
25///     (x: 1., y: 3.),
26///     (x: 4., y: 1.),
27///     (x: 1., y: -1.),
28///     (x: -2., y: 1.),
29/// ];
30///
31/// assert_eq!(
32///     Some(point!(x: 1., y: 1.)),
33///     polygon.centroid(),
34/// );
35/// ```
36pub trait Centroid {
37    type Output;
38
39    /// See: <https://en.wikipedia.org/wiki/Centroid>
40    ///
41    /// # Examples
42    ///
43    /// ```
44    /// use geo::Centroid;
45    /// use geo::{line_string, point};
46    ///
47    /// let line_string = line_string![
48    ///     (x: 40.02f64, y: 116.34),
49    ///     (x: 40.02f64, y: 118.23),
50    /// ];
51    ///
52    /// assert_eq!(
53    ///     Some(point!(x: 40.02, y: 117.285)),
54    ///     line_string.centroid(),
55    /// );
56    /// ```
57    fn centroid(&self) -> Self::Output;
58}
59
60impl<T> Centroid for Line<T>
61where
62    T: GeoFloat,
63{
64    type Output = Point<T>;
65
66    /// The Centroid of a [`Line`] is its middle point
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// use geo::Centroid;
72    /// use geo::{Line, point};
73    ///
74    /// let line = Line::new(
75    ///     point!(x: 1.0f64, y: 3.0),
76    ///     point!(x: 2.0f64, y: 4.0),
77    /// );
78    ///
79    /// assert_eq!(
80    ///     point!(x: 1.5, y: 3.5),
81    ///     line.centroid(),
82    /// );
83    /// ```
84    fn centroid(&self) -> Self::Output {
85        let two = T::one() + T::one();
86        (self.start_point() + self.end_point()) / two
87    }
88}
89
90impl<T> Centroid for LineString<T>
91where
92    T: GeoFloat,
93{
94    type Output = Option<Point<T>>;
95
96    // The Centroid of a [`LineString`] is the mean of the middle of the segment
97    // weighted by the length of the segments.
98    ///
99    /// # Examples
100    ///
101    /// ```
102    /// use geo::Centroid;
103    /// use geo::{line_string, point};
104    ///
105    /// let line_string = line_string![
106    ///   (x: 1.0f32, y: 1.0),
107    ///   (x: 2.0, y: 2.0),
108    ///   (x: 4.0, y: 4.0)
109    ///   ];
110    ///
111    /// assert_eq!(
112    ///     // (1.0 * (1.5, 1.5) + 2.0 * (3.0, 3.0)) / 3.0
113    ///     Some(point!(x: 2.5, y: 2.5)),
114    ///     line_string.centroid(),
115    /// );
116    /// ```
117    fn centroid(&self) -> Self::Output {
118        let mut operation = CentroidOperation::new();
119        operation.add_line_string(self);
120        operation.centroid()
121    }
122}
123
124impl<T> Centroid for MultiLineString<T>
125where
126    T: GeoFloat,
127{
128    type Output = Option<Point<T>>;
129
130    /// The Centroid of a [`MultiLineString`] is the mean of the centroids of all the constituent linestrings,
131    /// weighted by the length of each linestring
132    ///
133    /// # Examples
134    ///
135    /// ```
136    /// use geo::Centroid;
137    /// use geo::{MultiLineString, line_string, point};
138    ///
139    /// let multi_line_string = MultiLineString::new(vec![
140    ///     // centroid: (2.5, 2.5)
141    ///     line_string![(x: 1.0f32, y: 1.0), (x: 2.0, y: 2.0), (x: 4.0, y: 4.0)],
142    ///     // centroid: (4.0, 4.0)
143    ///     line_string![(x: 1.0, y: 1.0), (x: 3.0, y: 3.0), (x: 7.0, y: 7.0)],
144    /// ]);
145    ///
146    /// assert_eq!(
147    ///     // ( 3.0 * (2.5, 2.5) + 6.0 * (4.0, 4.0) ) / 9.0
148    ///     Some(point!(x: 3.5, y: 3.5)),
149    ///     multi_line_string.centroid(),
150    /// );
151    /// ```
152    fn centroid(&self) -> Self::Output {
153        let mut operation = CentroidOperation::new();
154        operation.add_multi_line_string(self);
155        operation.centroid()
156    }
157}
158
159impl<T> Centroid for Polygon<T>
160where
161    T: GeoFloat,
162{
163    type Output = Option<Point<T>>;
164
165    /// The Centroid of a [`Polygon`] is the mean of its points
166    ///
167    /// # Examples
168    ///
169    /// ```
170    /// use geo::Centroid;
171    /// use geo::{polygon, point};
172    ///
173    /// let polygon = polygon![
174    ///     (x: 0.0f32, y: 0.0),
175    ///     (x: 2.0, y: 0.0),
176    ///     (x: 2.0, y: 1.0),
177    ///     (x: 0.0, y: 1.0),
178    /// ];
179    ///
180    /// assert_eq!(
181    ///     Some(point!(x: 1.0, y: 0.5)),
182    ///     polygon.centroid(),
183    /// );
184    /// ```
185    fn centroid(&self) -> Self::Output {
186        let mut operation = CentroidOperation::new();
187        operation.add_polygon(self);
188        operation.centroid()
189    }
190}
191
192impl<T> Centroid for MultiPolygon<T>
193where
194    T: GeoFloat,
195{
196    type Output = Option<Point<T>>;
197
198    /// The Centroid of a [`MultiPolygon`] is the mean of the centroids of its polygons, weighted
199    /// by the area of the polygons
200    ///
201    /// # Examples
202    ///
203    /// ```
204    /// use geo::Centroid;
205    /// use geo::{MultiPolygon, polygon, point};
206    ///
207    /// let multi_polygon = MultiPolygon::new(vec![
208    ///   // centroid (1.0, 0.5)
209    ///   polygon![
210    ///     (x: 0.0f32, y: 0.0),
211    ///     (x: 2.0, y: 0.0),
212    ///     (x: 2.0, y: 1.0),
213    ///     (x: 0.0, y: 1.0),
214    ///   ],
215    ///   // centroid (-0.5, 0.0)
216    ///   polygon![
217    ///     (x: 1.0, y: 1.0),
218    ///     (x: -2.0, y: 1.0),
219    ///     (x: -2.0, y: -1.0),
220    ///     (x: 1.0, y: -1.0),
221    ///   ]
222    /// ]);
223    ///
224    /// assert_eq!(
225    ///     // ( 2.0 * (1.0, 0.5) + 6.0 * (-0.5, 0.0) ) / 8.0
226    ///     Some(point!(x: -0.125, y: 0.125)),
227    ///     multi_polygon.centroid(),
228    /// );
229    /// ```
230    fn centroid(&self) -> Self::Output {
231        let mut operation = CentroidOperation::new();
232        operation.add_multi_polygon(self);
233        operation.centroid()
234    }
235}
236
237impl<T> Centroid for Rect<T>
238where
239    T: GeoFloat,
240{
241    type Output = Point<T>;
242
243    /// The Centroid of a [`Rect`] is the mean of its [`Point`]s
244    ///
245    /// # Examples
246    ///
247    /// ```
248    /// use geo::Centroid;
249    /// use geo::{Rect, point};
250    ///
251    /// let rect = Rect::new(
252    ///   point!(x: 0.0f32, y: 0.0),
253    ///   point!(x: 1.0, y: 1.0),
254    /// );
255    ///
256    /// assert_eq!(
257    ///     point!(x: 0.5, y: 0.5),
258    ///     rect.centroid(),
259    /// );
260    /// ```
261    fn centroid(&self) -> Self::Output {
262        self.center().into()
263    }
264}
265
266impl<T> Centroid for Triangle<T>
267where
268    T: GeoFloat,
269{
270    type Output = Point<T>;
271
272    /// The Centroid of a [`Triangle`] is the mean of its [`Point`]s
273    ///
274    /// # Examples
275    ///
276    /// ```
277    /// use geo::Centroid;
278    /// use geo::{Triangle, coord, point};
279    ///
280    /// let triangle = Triangle::new(
281    ///   coord!(x: 0.0f32, y: -1.0),
282    ///   coord!(x: 3.0, y: 0.0),
283    ///   coord!(x: 0.0, y: 1.0),
284    /// );
285    ///
286    /// assert_eq!(
287    ///     point!(x: 1.0, y: 0.0),
288    ///     triangle.centroid(),
289    /// );
290    /// ```
291    fn centroid(&self) -> Self::Output {
292        let mut operation = CentroidOperation::new();
293        operation.add_triangle(self);
294        operation
295            .centroid()
296            .expect("triangle cannot have an empty centroid")
297    }
298}
299
300impl<T> Centroid for Point<T>
301where
302    T: GeoFloat,
303{
304    type Output = Point<T>;
305
306    /// The Centroid of a [`Point`] is the point itself
307    ///
308    /// # Examples
309    ///
310    /// ```
311    /// use geo::Centroid;
312    /// use geo::point;
313    ///
314    /// let point = point!(x: 1.0f32, y: 2.0);
315    ///
316    /// assert_eq!(
317    ///     point!(x: 1.0f32, y: 2.0),
318    ///     point.centroid(),
319    /// );
320    /// ```
321    fn centroid(&self) -> Self::Output {
322        *self
323    }
324}
325
326impl<T> Centroid for MultiPoint<T>
327where
328    T: GeoFloat,
329{
330    type Output = Option<Point<T>>;
331
332    /// The Centroid of a [`MultiPoint`] is the mean of all [`Point`]s
333    ///
334    /// # Example
335    ///
336    /// ```
337    /// use geo::Centroid;
338    /// use geo::{MultiPoint, Point};
339    ///
340    /// let empty: Vec<Point> = Vec::new();
341    /// let empty_multi_points: MultiPoint<_> = empty.into();
342    /// assert_eq!(empty_multi_points.centroid(), None);
343    ///
344    /// let points: MultiPoint<_> = vec![(5., 1.), (1., 3.), (3., 2.)].into();
345    /// assert_eq!(points.centroid(), Some(Point::new(3., 2.)));
346    /// ```
347    fn centroid(&self) -> Self::Output {
348        let mut operation = CentroidOperation::new();
349        operation.add_multi_point(self);
350        operation.centroid()
351    }
352}
353
354impl<T> Centroid for Geometry<T>
355where
356    T: GeoFloat,
357{
358    type Output = Option<Point<T>>;
359
360    crate::geometry_delegate_impl! {
361        /// The Centroid of a [`Geometry`] is the centroid of its enum variant
362        ///
363        /// # Examples
364        ///
365        /// ```
366        /// use geo::Centroid;
367        /// use geo::{Geometry, Rect, point};
368        ///
369        /// let rect = Rect::new(
370        ///   point!(x: 0.0f32, y: 0.0),
371        ///   point!(x: 1.0, y: 1.0),
372        /// );
373        /// let geometry = Geometry::from(rect.clone());
374        ///
375        /// assert_eq!(
376        ///     Some(rect.centroid()),
377        ///     geometry.centroid(),
378        /// );
379        ///
380        /// assert_eq!(
381        ///     Some(point!(x: 0.5, y: 0.5)),
382        ///     geometry.centroid(),
383        /// );
384        /// ```
385        fn centroid(&self) -> Self::Output;
386    }
387}
388
389impl<T> Centroid for GeometryCollection<T>
390where
391    T: GeoFloat,
392{
393    type Output = Option<Point<T>>;
394
395    /// The Centroid of a [`GeometryCollection`] is the mean of the centroids of elements, weighted
396    /// by the area of its elements.
397    ///
398    /// Note that this means, that elements which have no area are not considered when calculating
399    /// the centroid.
400    ///
401    /// # Examples
402    ///
403    /// ```
404    /// use geo::Centroid;
405    /// use geo::{Geometry, GeometryCollection, Rect, Triangle, point, coord};
406    ///
407    /// let rect_geometry = Geometry::from(Rect::new(
408    ///   point!(x: 0.0f32, y: 0.0),
409    ///   point!(x: 1.0, y: 1.0),
410    /// ));
411    ///
412    /// let triangle_geometry = Geometry::from(Triangle::new(
413    ///     coord!(x: 0.0f32, y: -1.0),
414    ///     coord!(x: 3.0, y: 0.0),
415    ///     coord!(x: 0.0, y: 1.0),
416    /// ));
417    ///
418    /// let point_geometry = Geometry::from(
419    ///   point!(x: 12351.0, y: 129815.0)
420    /// );
421    ///
422    /// let geometry_collection = GeometryCollection::new_from(
423    ///   vec![
424    ///     rect_geometry,
425    ///     triangle_geometry,
426    ///     point_geometry
427    ///   ]
428    /// );
429    ///
430    /// assert_eq!(
431    ///     Some(point!(x: 0.875, y: 0.125)),
432    ///     geometry_collection.centroid(),
433    /// );
434    /// ```
435    fn centroid(&self) -> Self::Output {
436        let mut operation = CentroidOperation::new();
437        operation.add_geometry_collection(self);
438        operation.centroid()
439    }
440}
441
442struct CentroidOperation<T: GeoFloat>(Option<WeightedCentroid<T>>);
443impl<T: GeoFloat> CentroidOperation<T> {
444    fn new() -> Self {
445        CentroidOperation(None)
446    }
447
448    fn centroid(&self) -> Option<Point<T>> {
449        self.0.as_ref().map(|weighted_centroid| {
450            Point::from(weighted_centroid.accumulated / weighted_centroid.weight)
451        })
452    }
453
454    fn centroid_dimensions(&self) -> Dimensions {
455        self.0
456            .as_ref()
457            .map(|weighted_centroid| weighted_centroid.dimensions)
458            .unwrap_or(Empty)
459    }
460
461    fn add_coord(&mut self, coord: Coord<T>) {
462        self.add_centroid(ZeroDimensional, coord, T::one());
463    }
464
465    fn add_line(&mut self, line: &Line<T>) {
466        match line.dimensions() {
467            ZeroDimensional => self.add_coord(line.start),
468            OneDimensional => {
469                self.add_centroid(OneDimensional, line.centroid().0, Euclidean.length(line))
470            }
471            _ => unreachable!("Line must be zero or one dimensional"),
472        }
473    }
474
475    fn add_line_string(&mut self, line_string: &LineString<T>) {
476        if self.centroid_dimensions() > OneDimensional {
477            return;
478        }
479
480        if line_string.0.len() == 1 {
481            self.add_coord(line_string.0[0]);
482            return;
483        }
484
485        for line in line_string.lines() {
486            self.add_line(&line);
487        }
488    }
489
490    fn add_multi_line_string(&mut self, multi_line_string: &MultiLineString<T>) {
491        if self.centroid_dimensions() > OneDimensional {
492            return;
493        }
494
495        for element in &multi_line_string.0 {
496            self.add_line_string(element);
497        }
498    }
499
500    fn add_polygon(&mut self, polygon: &Polygon<T>) {
501        // Polygons which are completely covered by their interior rings have zero area, and
502        // represent a unique degeneracy into a line_string which cannot be handled by accumulating
503        // directly into `self`. Instead, we perform a sub-operation, inspect the result, and only
504        // then incorporate the result into `self.
505
506        let mut exterior_operation = CentroidOperation::new();
507        exterior_operation.add_ring(polygon.exterior());
508
509        let mut interior_operation = CentroidOperation::new();
510        for interior in polygon.interiors() {
511            interior_operation.add_ring(interior);
512        }
513
514        if let Some(exterior_weighted_centroid) = exterior_operation.0 {
515            let mut poly_weighted_centroid = exterior_weighted_centroid;
516            if let Some(interior_weighted_centroid) = interior_operation.0 {
517                poly_weighted_centroid.sub_assign(interior_weighted_centroid);
518                if poly_weighted_centroid.weight.is_zero() {
519                    // A polygon with no area `interiors` completely covers `exterior`, degenerating to a linestring
520                    self.add_line_string(polygon.exterior());
521                    return;
522                }
523            }
524            self.add_weighted_centroid(poly_weighted_centroid);
525        }
526    }
527
528    fn add_multi_point(&mut self, multi_point: &MultiPoint<T>) {
529        if self.centroid_dimensions() > ZeroDimensional {
530            return;
531        }
532
533        for element in &multi_point.0 {
534            self.add_coord(element.0);
535        }
536    }
537
538    fn add_multi_polygon(&mut self, multi_polygon: &MultiPolygon<T>) {
539        for element in &multi_polygon.0 {
540            self.add_polygon(element);
541        }
542    }
543
544    fn add_geometry_collection(&mut self, geometry_collection: &GeometryCollection<T>) {
545        for element in &geometry_collection.0 {
546            self.add_geometry(element);
547        }
548    }
549
550    fn add_rect(&mut self, rect: &Rect<T>) {
551        match rect.dimensions() {
552            ZeroDimensional => self.add_coord(rect.min()),
553            OneDimensional => {
554                // Degenerate rect is a line, treat it the same way we treat flat polygons
555                self.add_line(&Line::new(rect.min(), rect.min()));
556                self.add_line(&Line::new(rect.min(), rect.max()));
557                self.add_line(&Line::new(rect.max(), rect.max()));
558                self.add_line(&Line::new(rect.max(), rect.min()));
559            }
560            TwoDimensional => {
561                self.add_centroid(TwoDimensional, rect.centroid().0, rect.unsigned_area())
562            }
563            Empty => unreachable!("Rect dimensions cannot be empty"),
564        }
565    }
566
567    fn add_triangle(&mut self, triangle: &Triangle<T>) {
568        match triangle.dimensions() {
569            ZeroDimensional => self.add_coord(triangle.0),
570            OneDimensional => {
571                // Degenerate triangle is a line, treat it the same way we treat flat
572                // polygons
573                let l0_1 = Line::new(triangle.0, triangle.1);
574                let l1_2 = Line::new(triangle.1, triangle.2);
575                let l2_0 = Line::new(triangle.2, triangle.0);
576                self.add_line(&l0_1);
577                self.add_line(&l1_2);
578                self.add_line(&l2_0);
579            }
580            TwoDimensional => {
581                let centroid = (triangle.0 + triangle.1 + triangle.2) / T::from(3).unwrap();
582                self.add_centroid(TwoDimensional, centroid, triangle.unsigned_area());
583            }
584            Empty => unreachable!("Rect dimensions cannot be empty"),
585        }
586    }
587
588    fn add_geometry(&mut self, geometry: &Geometry<T>) {
589        match geometry {
590            Geometry::Point(g) => self.add_coord(g.0),
591            Geometry::Line(g) => self.add_line(g),
592            Geometry::LineString(g) => self.add_line_string(g),
593            Geometry::Polygon(g) => self.add_polygon(g),
594            Geometry::MultiPoint(g) => self.add_multi_point(g),
595            Geometry::MultiLineString(g) => self.add_multi_line_string(g),
596            Geometry::MultiPolygon(g) => self.add_multi_polygon(g),
597            Geometry::GeometryCollection(g) => self.add_geometry_collection(g),
598            Geometry::Rect(g) => self.add_rect(g),
599            Geometry::Triangle(g) => self.add_triangle(g),
600        }
601    }
602
603    fn add_ring(&mut self, ring: &LineString<T>) {
604        debug_assert!(ring.is_closed());
605
606        let area = get_linestring_area(ring);
607        if area == T::zero() {
608            match ring.dimensions() {
609                // empty ring doesn't contribute to centroid
610                Empty => {}
611                // degenerate ring is a point
612                ZeroDimensional => self.add_coord(ring[0]),
613                // zero-area ring is a line string
614                _ => self.add_line_string(ring),
615            }
616            return;
617        }
618
619        // Since area is non-zero, we know the ring has at least one point
620        let shift = ring.0[0];
621
622        let accumulated_coord = ring.lines().fold(Coord::zero(), |accum, line| {
623            use crate::MapCoords;
624            let line = line.map_coords(|c| c - shift);
625            let tmp = line.determinant();
626            accum + (line.end + line.start) * tmp
627        });
628        let six = T::from(6).unwrap();
629        let centroid = accumulated_coord / (six * area) + shift;
630        let weight = area.abs();
631        self.add_centroid(TwoDimensional, centroid, weight);
632    }
633
634    fn add_centroid(&mut self, dimensions: Dimensions, centroid: Coord<T>, weight: T) {
635        let weighted_centroid = WeightedCentroid {
636            dimensions,
637            weight,
638            accumulated: centroid * weight,
639        };
640        self.add_weighted_centroid(weighted_centroid);
641    }
642
643    fn add_weighted_centroid(&mut self, other: WeightedCentroid<T>) {
644        match self.0.as_mut() {
645            Some(centroid) => centroid.add_assign(other),
646            None => self.0 = Some(other),
647        }
648    }
649}
650
651// Aggregated state for accumulating the centroid of a geometry or collection of geometries.
652struct WeightedCentroid<T: GeoFloat> {
653    weight: T,
654    accumulated: Coord<T>,
655    /// Collections of Geometries can have different dimensionality. Centroids must be considered
656    /// separately by dimensionality.
657    ///
658    /// e.g. If I have several Points, adding a new `Point` will affect their centroid.
659    ///
660    /// However, because a Point is zero dimensional, it is infinitely small when compared to
661    /// any 2-D Polygon. Thus a Point will not affect the centroid of any GeometryCollection
662    /// containing a 2-D Polygon.
663    ///
664    /// So, when accumulating a centroid, we must track the dimensionality of the centroid
665    dimensions: Dimensions,
666}
667
668impl<T: GeoFloat> WeightedCentroid<T> {
669    fn add_assign(&mut self, b: WeightedCentroid<T>) {
670        match self.dimensions.cmp(&b.dimensions) {
671            Ordering::Less => *self = b,
672            Ordering::Greater => {}
673            Ordering::Equal => {
674                self.accumulated = self.accumulated + b.accumulated;
675                self.weight = self.weight + b.weight;
676            }
677        }
678    }
679
680    fn sub_assign(&mut self, b: WeightedCentroid<T>) {
681        match self.dimensions.cmp(&b.dimensions) {
682            Ordering::Less => *self = b,
683            Ordering::Greater => {}
684            Ordering::Equal => {
685                self.accumulated = self.accumulated - b.accumulated;
686                self.weight = self.weight - b.weight;
687            }
688        }
689    }
690}
691
692#[cfg(test)]
693mod test {
694    use super::*;
695    use crate::{coord, line_string, point, polygon, wkt};
696
697    /// small helper to create a coordinate
698    fn c<T: GeoFloat>(x: T, y: T) -> Coord<T> {
699        coord! { x: x, y: y }
700    }
701
702    /// small helper to create a point
703    fn p<T: GeoFloat>(x: T, y: T) -> Point<T> {
704        point! { x: x, y: y }
705    }
706
707    // Tests: Centroid of LineString
708    #[test]
709    fn empty_linestring_test() {
710        let linestring: LineString<f32> = line_string![];
711        let centroid = linestring.centroid();
712        assert!(centroid.is_none());
713    }
714    #[test]
715    fn linestring_one_point_test() {
716        let coord = coord! {
717            x: 40.02f64,
718            y: 116.34,
719        };
720        let linestring = line_string![coord];
721        let centroid = linestring.centroid();
722        assert_eq!(centroid, Some(Point::from(coord)));
723    }
724    #[test]
725    fn linestring_test() {
726        let linestring = line_string![
727            (x: 1., y: 1.),
728            (x: 7., y: 1.),
729            (x: 8., y: 1.),
730            (x: 9., y: 1.),
731            (x: 10., y: 1.),
732            (x: 11., y: 1.)
733        ];
734        assert_eq!(linestring.centroid(), Some(point!(x: 6., y: 1. )));
735    }
736    #[test]
737    fn linestring_with_repeated_point_test() {
738        let l1 = LineString::from(vec![p(1., 1.), p(1., 1.), p(1., 1.)]);
739        assert_eq!(l1.centroid(), Some(p(1., 1.)));
740
741        let l2 = LineString::from(vec![p(2., 2.), p(2., 2.), p(2., 2.)]);
742        let mls = MultiLineString::new(vec![l1, l2]);
743        assert_eq!(mls.centroid(), Some(p(1.5, 1.5)));
744    }
745    // Tests: Centroid of MultiLineString
746    #[test]
747    fn empty_multilinestring_test() {
748        let mls: MultiLineString = MultiLineString::empty();
749        let centroid = mls.centroid();
750        assert!(centroid.is_none());
751    }
752    #[test]
753    fn multilinestring_with_empty_line_test() {
754        let mls: MultiLineString = MultiLineString::new(vec![line_string![]]);
755        let centroid = mls.centroid();
756        assert!(centroid.is_none());
757    }
758    #[test]
759    fn multilinestring_length_0_test() {
760        let coord = coord! {
761            x: 40.02f64,
762            y: 116.34,
763        };
764        let mls: MultiLineString = MultiLineString::new(vec![
765            line_string![coord],
766            line_string![coord],
767            line_string![coord],
768        ]);
769        assert_relative_eq!(mls.centroid().unwrap(), Point::from(coord));
770    }
771    #[test]
772    fn multilinestring_one_line_test() {
773        let linestring = line_string![
774            (x: 1., y: 1.),
775            (x: 7., y: 1.),
776            (x: 8., y: 1.),
777            (x: 9., y: 1.),
778            (x: 10., y: 1.),
779            (x: 11., y: 1.)
780        ];
781        let mls: MultiLineString = MultiLineString::new(vec![linestring]);
782        assert_relative_eq!(mls.centroid().unwrap(), point! { x: 6., y: 1. });
783    }
784    #[test]
785    fn multilinestring_test() {
786        let mls = wkt! {
787            MULTILINESTRING(
788                (0.0 0.0,1.0 10.0),
789                (1.0 10.0,2.0 0.0,3.0 1.0),
790                (-12.0 -100.0,7.0 8.0)
791            )
792        };
793        assert_relative_eq!(
794            mls.centroid().unwrap(),
795            point![x: -1.9097834383655845, y: -37.683866439745714]
796        );
797    }
798    // Tests: Centroid of Polygon
799    #[test]
800    fn empty_polygon_test() {
801        let poly: Polygon<f32> = polygon![];
802        assert!(poly.centroid().is_none());
803    }
804    #[test]
805    fn polygon_one_point_test() {
806        let p = point![ x: 2., y: 1. ];
807        let poly = polygon![p.0];
808        assert_relative_eq!(poly.centroid().unwrap(), p);
809    }
810
811    #[test]
812    fn centroid_polygon_numerical_stability() {
813        let polygon = {
814            use std::f64::consts::PI;
815            const NUM_VERTICES: usize = 10;
816            const ANGLE_INC: f64 = 2. * PI / NUM_VERTICES as f64;
817
818            Polygon::new(
819                (0..NUM_VERTICES)
820                    .map(|i| {
821                        let angle = i as f64 * ANGLE_INC;
822                        coord! {
823                            x: angle.cos(),
824                            y: angle.sin(),
825                        }
826                    })
827                    .collect::<Vec<_>>()
828                    .into(),
829                vec![],
830            )
831        };
832
833        let centroid = polygon.centroid().unwrap();
834
835        let shift = coord! { x: 1.5e8, y: 1.5e8 };
836
837        use crate::map_coords::MapCoords;
838        let polygon = polygon.map_coords(|c| c + shift);
839
840        let new_centroid = polygon.centroid().unwrap().map_coords(|c| c - shift);
841        debug!("centroid {:?}", centroid.0);
842        debug!("new_centroid {:?}", new_centroid.0);
843        assert_relative_eq!(centroid.0.x, new_centroid.0.x, max_relative = 0.0001);
844        assert_relative_eq!(centroid.0.y, new_centroid.0.y, max_relative = 0.0001);
845    }
846
847    #[test]
848    fn polygon_test() {
849        let poly = polygon![
850            (x: 0., y: 0.),
851            (x: 2., y: 0.),
852            (x: 2., y: 2.),
853            (x: 0., y: 2.),
854            (x: 0., y: 0.)
855        ];
856        assert_relative_eq!(poly.centroid().unwrap(), point![x:1., y:1.]);
857    }
858    #[test]
859    fn polygon_hole_test() {
860        // hexagon
861        let p1 = wkt! { POLYGON(
862            (5.0 1.0,4.0 2.0,4.0 3.0,5.0 4.0,6.0 4.0,7.0 3.0,7.0 2.0,6.0 1.0,5.0 1.0),
863            (5.0 1.3,5.5 2.0,6.0 1.3,5.0 1.3),
864            (5.0 2.3,5.5 3.0,6.0 2.3,5.0 2.3)
865        ) };
866        let centroid = p1.centroid().unwrap();
867        assert_relative_eq!(centroid, point!(x: 5.5, y: 2.5518518518518523));
868    }
869    #[test]
870    fn flat_polygon_test() {
871        let poly = wkt! { POLYGON((0. 1.,1. 1.,0. 1.)) };
872        assert_eq!(poly.centroid(), Some(p(0.5, 1.)));
873    }
874    #[test]
875    fn multi_poly_with_flat_polygon_test() {
876        let multipoly = wkt! { MULTIPOLYGON(((0. 0.,1. 0.,0. 0.))) };
877        assert_eq!(multipoly.centroid(), Some(p(0.5, 0.)));
878    }
879    #[test]
880    fn multi_poly_with_multiple_flat_polygon_test() {
881        let multipoly = wkt! { MULTIPOLYGON(
882            ((1. 1.,1. 3.,1. 1.)),
883            ((2. 2.,6. 2.,2. 2.))
884        )};
885
886        assert_eq!(multipoly.centroid(), Some(p(3., 2.)));
887    }
888    #[test]
889    fn multi_poly_with_only_points_test() {
890        let p1 = wkt! { POLYGON((1. 1.,1. 1.,1. 1.)) };
891        assert_eq!(p1.centroid(), Some(p(1., 1.)));
892
893        let multipoly = wkt! { MULTIPOLYGON(
894            ((1. 1.,1. 1.,1. 1.)),
895            ((2. 2., 2. 2.,2. 2.))
896        ) };
897        assert_eq!(multipoly.centroid(), Some(p(1.5, 1.5)));
898    }
899    #[test]
900    fn multi_poly_with_one_ring_and_one_real_poly() {
901        // if the multipolygon is composed of a 'normal' polygon (with an area not null)
902        // and a ring (a polygon with a null area)
903        // the centroid of the multipolygon is the centroid of the 'normal' polygon
904        let normal = Polygon::new(
905            LineString::from(vec![p(1., 1.), p(1., 3.), p(3., 1.), p(1., 1.)]),
906            vec![],
907        );
908        let flat = Polygon::new(
909            LineString::from(vec![p(2., 2.), p(6., 2.), p(2., 2.)]),
910            vec![],
911        );
912        let multipoly = MultiPolygon::new(vec![normal.clone(), flat]);
913        assert_eq!(multipoly.centroid(), normal.centroid());
914    }
915    #[test]
916    fn polygon_flat_interior_test() {
917        let poly = Polygon::new(
918            LineString::from(vec![p(0., 0.), p(0., 1.), p(1., 1.), p(1., 0.), p(0., 0.)]),
919            vec![LineString::from(vec![p(0., 0.), p(0., 1.), p(0., 0.)])],
920        );
921        assert_eq!(poly.centroid(), Some(p(0.5, 0.5)));
922    }
923    #[test]
924    fn empty_interior_polygon_test() {
925        let poly = Polygon::new(
926            LineString::from(vec![p(0., 0.), p(0., 1.), p(1., 1.), p(1., 0.), p(0., 0.)]),
927            vec![LineString::empty()],
928        );
929        assert_eq!(poly.centroid(), Some(p(0.5, 0.5)));
930    }
931    #[test]
932    fn polygon_ring_test() {
933        let square = LineString::from(vec![p(0., 0.), p(0., 1.), p(1., 1.), p(1., 0.), p(0., 0.)]);
934        let poly = Polygon::new(square.clone(), vec![square]);
935        assert_eq!(poly.centroid(), Some(p(0.5, 0.5)));
936    }
937    #[test]
938    fn polygon_cell_test() {
939        // test the centroid of polygon with a null area
940        // this one a polygon with 2 interior polygon that makes a partition of the exterior
941        let square = LineString::from(vec![p(0., 0.), p(0., 2.), p(2., 2.), p(2., 0.), p(0., 0.)]);
942        let bottom = LineString::from(vec![p(0., 0.), p(2., 0.), p(2., 1.), p(0., 1.), p(0., 0.)]);
943        let top = LineString::from(vec![p(0., 1.), p(2., 1.), p(2., 2.), p(0., 2.), p(0., 1.)]);
944        let poly = Polygon::new(square, vec![top, bottom]);
945        assert_eq!(poly.centroid(), Some(p(1., 1.)));
946    }
947    // Tests: Centroid of MultiPolygon
948    #[test]
949    fn empty_multipolygon_polygon_test() {
950        assert!(MultiPolygon::<f64>::empty().centroid().is_none());
951    }
952
953    #[test]
954    fn multipolygon_one_polygon_test() {
955        let linestring =
956            LineString::from(vec![p(0., 0.), p(2., 0.), p(2., 2.), p(0., 2.), p(0., 0.)]);
957        let poly = Polygon::new(linestring, Vec::new());
958        assert_eq!(MultiPolygon::new(vec![poly]).centroid(), Some(p(1., 1.)));
959    }
960    #[test]
961    fn multipolygon_two_polygons_test() {
962        let linestring =
963            LineString::from(vec![p(2., 1.), p(5., 1.), p(5., 3.), p(2., 3.), p(2., 1.)]);
964        let poly1 = Polygon::new(linestring, Vec::new());
965        let linestring =
966            LineString::from(vec![p(7., 1.), p(8., 1.), p(8., 2.), p(7., 2.), p(7., 1.)]);
967        let poly2 = Polygon::new(linestring, Vec::new());
968        let centroid = MultiPolygon::new(vec![poly1, poly2]).centroid().unwrap();
969        assert_relative_eq!(
970            centroid,
971            point![x: 4.071428571428571, y: 1.9285714285714286]
972        );
973    }
974    #[test]
975    fn multipolygon_two_polygons_of_opposite_clockwise_test() {
976        let linestring = LineString::from(vec![(0., 0.), (2., 0.), (2., 2.), (0., 2.), (0., 0.)]);
977        let poly1 = Polygon::new(linestring, Vec::new());
978        let linestring = LineString::from(vec![(0., 0.), (-2., 0.), (-2., 2.), (0., 2.), (0., 0.)]);
979        let poly2 = Polygon::new(linestring, Vec::new());
980        assert_relative_eq!(
981            MultiPolygon::new(vec![poly1, poly2]).centroid().unwrap(),
982            point![x: 0., y: 1.]
983        );
984    }
985    #[test]
986    fn bounding_rect_test() {
987        let bounding_rect = Rect::new(coord! { x: 0., y: 50. }, coord! { x: 4., y: 100. });
988        let point = point![x: 2., y: 75.];
989        assert_eq!(point, bounding_rect.centroid());
990    }
991    #[test]
992    fn line_test() {
993        let line1 = Line::new(c(0., 1.), c(1., 3.));
994        assert_eq!(line1.centroid(), point![x: 0.5, y: 2.]);
995    }
996    #[test]
997    fn collection_weighting() {
998        let p0 = point!(x: 0.0, y: 0.0);
999        let p1 = point!(x: 2.0, y: 0.0);
1000        let p2 = point!(x: 2.0, y: 2.0);
1001        let p3 = point!(x: 0.0, y: 2.0);
1002
1003        let multi_point = MultiPoint::new(vec![p0, p1, p2, p3]);
1004        assert_eq!(multi_point.centroid().unwrap(), point!(x: 1.0, y: 1.0));
1005
1006        let collection =
1007            GeometryCollection::new_from(vec![MultiPoint::new(vec![p1, p2, p3]).into(), p0.into()]);
1008
1009        assert_eq!(collection.centroid().unwrap(), point!(x: 1.0, y: 1.0));
1010    }
1011    #[test]
1012    fn triangles() {
1013        // boring triangle
1014        assert_eq!(
1015            Triangle::new(c(0., 0.), c(3., 0.), c(1.5, 3.)).centroid(),
1016            point!(x: 1.5, y: 1.0)
1017        );
1018
1019        // flat triangle
1020        assert_eq!(
1021            Triangle::new(c(0., 0.), c(3., 0.), c(1., 0.)).centroid(),
1022            point!(x: 1.5, y: 0.0)
1023        );
1024
1025        // flat triangle that's not axis-aligned
1026        assert_eq!(
1027            Triangle::new(c(0., 0.), c(3., 3.), c(1., 1.)).centroid(),
1028            point!(x: 1.5, y: 1.5)
1029        );
1030
1031        // triangle with some repeated points
1032        assert_eq!(
1033            Triangle::new(c(0., 0.), c(0., 0.), c(1., 0.)).centroid(),
1034            point!(x: 0.5, y: 0.0)
1035        );
1036
1037        // triangle with all repeated points
1038        assert_eq!(
1039            Triangle::new(c(0., 0.5), c(0., 0.5), c(0., 0.5)).centroid(),
1040            point!(x: 0., y: 0.5)
1041        )
1042    }
1043
1044    #[test]
1045    fn degenerate_triangle_like_ring() {
1046        let triangle = Triangle::new(c(0., 0.), c(1., 1.), c(2., 2.));
1047        let poly: Polygon<_> = triangle.into();
1048
1049        let line = Line::new(c(0., 1.), c(1., 3.));
1050
1051        let g1 = GeometryCollection::new_from(vec![triangle.into(), line.into()]);
1052        let g2 = GeometryCollection::new_from(vec![poly.into(), line.into()]);
1053        assert_eq!(g1.centroid(), g2.centroid());
1054    }
1055
1056    #[test]
1057    fn degenerate_rect_like_ring() {
1058        let rect = Rect::new(c(0., 0.), c(0., 4.));
1059        let poly: Polygon<_> = rect.into();
1060
1061        let line = Line::new(c(0., 1.), c(1., 3.));
1062
1063        let g1 = GeometryCollection::new_from(vec![rect.into(), line.into()]);
1064        let g2 = GeometryCollection::new_from(vec![poly.into(), line.into()]);
1065        assert_eq!(g1.centroid(), g2.centroid());
1066    }
1067
1068    #[test]
1069    fn rectangles() {
1070        // boring rect
1071        assert_eq!(
1072            Rect::new(c(0., 0.), c(4., 4.)).centroid(),
1073            point!(x: 2.0, y: 2.0)
1074        );
1075
1076        // flat rect
1077        assert_eq!(
1078            Rect::new(c(0., 0.), c(4., 0.)).centroid(),
1079            point!(x: 2.0, y: 0.0)
1080        );
1081
1082        // rect with all repeated points
1083        assert_eq!(
1084            Rect::new(c(4., 4.), c(4., 4.)).centroid(),
1085            point!(x: 4., y: 4.)
1086        );
1087
1088        // collection with rect
1089        let mut collection = GeometryCollection::new_from(vec![
1090            p(0., 0.).into(),
1091            p(6., 0.).into(),
1092            p(6., 6.).into(),
1093        ]);
1094        // sanity check
1095        assert_eq!(collection.centroid().unwrap(), point!(x: 4., y: 2.));
1096
1097        // 0-d rect treated like point
1098        collection.0.push(Rect::new(c(0., 6.), c(0., 6.)).into());
1099        assert_eq!(collection.centroid().unwrap(), point!(x: 3., y: 3.));
1100
1101        // 1-d rect treated like line. Since a line has higher dimensions than the rest of the
1102        // collection, its centroid clobbers everything else in the collection.
1103        collection.0.push(Rect::new(c(0., 0.), c(0., 2.)).into());
1104        assert_eq!(collection.centroid().unwrap(), point!(x: 0., y: 1.));
1105
1106        // 2-d has higher dimensions than the rest of the collection, so its centroid clobbers
1107        // everything else in the collection.
1108        collection
1109            .0
1110            .push(Rect::new(c(10., 10.), c(11., 11.)).into());
1111        assert_eq!(collection.centroid().unwrap(), point!(x: 10.5, y: 10.5));
1112    }
1113}