geo/algorithm/validation/
multi_polygon.rs

1use super::{GeometryIndex, InvalidPolygon, Validation};
2use crate::coordinate_position::CoordPos;
3use crate::dimensions::Dimensions;
4use crate::{GeoFloat, MultiPolygon, Relate};
5
6use std::fmt;
7
8/// A [`MultiPolygon`] is valid if:
9/// - [x] all its polygons are valid,
10/// - [x] elements do not overlaps (i.e. their interiors must not intersect)
11/// - [x] elements touch only at points
12#[derive(Debug, Clone, PartialEq)]
13pub enum InvalidMultiPolygon {
14    /// For a [`MultiPolygon`] to be valid, each member [`Polygon`](crate::Polygon) must be valid.
15    InvalidPolygon(GeometryIndex, InvalidPolygon),
16    /// No [`Polygon`](crate::Polygon) in a valid [`MultiPolygon`] may overlap (2-dimensional intersection)
17    ElementsOverlaps(GeometryIndex, GeometryIndex),
18    /// No [`Polygon`](crate::Polygon) in a valid [`MultiPolygon`] may touch on a line (1-dimensional intersection)
19    ElementsTouchOnALine(GeometryIndex, GeometryIndex),
20}
21
22impl fmt::Display for InvalidMultiPolygon {
23    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
24        match self {
25            InvalidMultiPolygon::InvalidPolygon(idx, err) => {
26                write!(f, "polygon at index {} is invalid: {}", idx.0, err)
27            }
28            InvalidMultiPolygon::ElementsOverlaps(idx1, idx2) => {
29                write!(f, "polygons at indices {} and {} overlap", idx1.0, idx2.0)
30            }
31            InvalidMultiPolygon::ElementsTouchOnALine(idx1, idx2) => {
32                write!(
33                    f,
34                    "polygons at indices {} and {} touch on a line",
35                    idx1.0, idx2.0
36                )
37            }
38        }
39    }
40}
41
42impl std::error::Error for InvalidMultiPolygon {}
43
44impl<F: GeoFloat> Validation for MultiPolygon<F> {
45    type Error = InvalidMultiPolygon;
46
47    fn visit_validation<T>(
48        &self,
49        mut handle_validation_error: Box<dyn FnMut(Self::Error) -> Result<(), T> + '_>,
50    ) -> Result<(), T> {
51        for (i, polygon) in self.0.iter().enumerate() {
52            polygon.visit_validation(Box::new(&mut |invalid_polygon| {
53                handle_validation_error(InvalidMultiPolygon::InvalidPolygon(
54                    GeometryIndex(i),
55                    invalid_polygon,
56                ))
57            }))?;
58
59            // Special case for MultiPolygon: elements must not overlap and must touch only at points
60            for (j, pol2) in self.0.iter().enumerate().skip(i + 1) {
61                let im = polygon.relate(pol2);
62                if im.get(CoordPos::Inside, CoordPos::Inside) == Dimensions::TwoDimensional {
63                    let err =
64                        InvalidMultiPolygon::ElementsOverlaps(GeometryIndex(i), GeometryIndex(j));
65                    handle_validation_error(err)?;
66                }
67                if im.get(CoordPos::OnBoundary, CoordPos::OnBoundary) == Dimensions::OneDimensional
68                {
69                    let err = InvalidMultiPolygon::ElementsTouchOnALine(
70                        GeometryIndex(i),
71                        GeometryIndex(j),
72                    );
73                    handle_validation_error(err)?;
74                }
75            }
76        }
77        Ok(())
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::super::assert_validation_errors;
84    use super::*;
85    use crate::algorithm::validation::RingRole;
86    use crate::wkt;
87
88    #[test]
89    fn test_multipolygon_invalid() {
90        // The following multipolygon contains two invalid polygon
91        // and it is invalid itself because the two polygons of the multipolygon are not disjoint
92        // (here they are identical)
93        let multi_polygon = wkt!(
94            MULTIPOLYGON (
95                (
96                    (0.5 0.5, 3.0 0.5, 3.0 2.5, 0.5 2.5, 0.5 0.5),
97                    (1.0 1.0, 1.0 2.0, 2.5 2.0, 3.5 1.0, 1.0 1.0)
98                ),
99                (
100                    (0.5 0.5, 3.0 0.5, 3.0 2.5, 0.5 2.5, 0.5 0.5),
101                    (1.0 1.0, 1.0 2.0, 2.5 2.0, 3.5 1.0, 1.0 1.0)
102                )
103            )
104        );
105        assert_validation_errors!(
106            &multi_polygon,
107            vec![
108                InvalidMultiPolygon::InvalidPolygon(
109                    GeometryIndex(0),
110                    InvalidPolygon::InteriorRingNotContainedInExteriorRing(RingRole::Interior(0))
111                ),
112                InvalidMultiPolygon::ElementsOverlaps(GeometryIndex(0), GeometryIndex(1)),
113                InvalidMultiPolygon::ElementsTouchOnALine(GeometryIndex(0), GeometryIndex(1)),
114                InvalidMultiPolygon::InvalidPolygon(
115                    GeometryIndex(1),
116                    InvalidPolygon::InteriorRingNotContainedInExteriorRing(RingRole::Interior(0))
117                ),
118            ]
119        );
120    }
121}