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
9pub trait Centroid {
37 type Output;
38
39 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 => {}
611 ZeroDimensional => self.add_coord(ring[0]),
613 _ => self.add_line_string(ring),
615 }
616 return;
617 }
618
619 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
651struct WeightedCentroid<T: GeoFloat> {
653 weight: T,
654 accumulated: Coord<T>,
655 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 fn c<T: GeoFloat>(x: T, y: T) -> Coord<T> {
699 coord! { x: x, y: y }
700 }
701
702 fn p<T: GeoFloat>(x: T, y: T) -> Point<T> {
704 point! { x: x, y: y }
705 }
706
707 #[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 #[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 #[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 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 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 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 #[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 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 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 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 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 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 assert_eq!(
1072 Rect::new(c(0., 0.), c(4., 4.)).centroid(),
1073 point!(x: 2.0, y: 2.0)
1074 );
1075
1076 assert_eq!(
1078 Rect::new(c(0., 0.), c(4., 0.)).centroid(),
1079 point!(x: 2.0, y: 0.0)
1080 );
1081
1082 assert_eq!(
1084 Rect::new(c(4., 4.), c(4., 4.)).centroid(),
1085 point!(x: 4., y: 4.)
1086 );
1087
1088 let mut collection = GeometryCollection::new_from(vec![
1090 p(0., 0.).into(),
1091 p(6., 0.).into(),
1092 p(6., 6.).into(),
1093 ]);
1094 assert_eq!(collection.centroid().unwrap(), point!(x: 4., y: 2.));
1096
1097 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 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 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}