geo_validity_check/
lib.rs

1//! # geo-validity-check
2//!
3//! This crate provides a way to check the validity of [geo-types](https://docs.rs/geo-types) geometries by implementing the Valid trait for all
4//! the geometries in geo-types.
5//!
6//! The Valid trait provides two methods:
7//! - `is_valid()` which returns a boolean,
8//! - `explain_invalidity()` which returns a ProblemReport (a vector of problems, each one with its position in the geometry) that implements the Display trait.
9//!
10mod coord;
11mod geometry;
12mod geometrycollection;
13mod line;
14mod linestring;
15mod multilinestring;
16mod multipoint;
17mod multipolygon;
18mod point;
19mod polygon;
20mod rect;
21mod triangle;
22mod utils;
23
24use std::boxed::Box;
25use std::fmt::Display;
26
27#[derive(Debug, PartialEq)]
28/// The role of a ring in a polygon.
29pub enum RingRole {
30    Exterior,
31    Interior(usize),
32}
33
34impl std::fmt::Display for RingRole {
35    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
36        match self {
37            RingRole::Exterior => write!(f, "exterior ring"),
38            RingRole::Interior(i) => write!(f, "interior ring n°{}", i),
39        }
40    }
41}
42
43#[derive(Debug, PartialEq)]
44/// The position of the problem in a multi-geometry.
45pub struct GeometryPosition(usize);
46
47#[derive(Debug, PartialEq)]
48/// The coordinate position of the problem in the geometry.
49pub struct CoordinatePosition(isize);
50
51#[derive(Debug, PartialEq)]
52/// The position of the problem in the geometry.
53pub enum ProblemPosition {
54    Point,
55    Line(CoordinatePosition),
56    Triangle(CoordinatePosition),
57    Rect(CoordinatePosition),
58    MultiPoint(GeometryPosition),
59    LineString(CoordinatePosition),
60    MultiLineString(GeometryPosition, CoordinatePosition),
61    Polygon(RingRole, CoordinatePosition),
62    MultiPolygon(GeometryPosition, RingRole, CoordinatePosition),
63    GeometryCollection(GeometryPosition, Box<ProblemPosition>),
64}
65
66#[derive(Debug, PartialEq)]
67/// The type of problem encountered.
68pub enum Problem {
69    /// A coordinate is not finite (NaN or infinite)
70    NotFinite,
71    /// A LineString or a Polygon ring has too few points
72    TooFewPoints,
73    /// Identical coords
74    IdenticalCoords,
75    /// Collinear coords
76    CollinearCoords,
77    /// A ring has a self-intersection
78    SelfIntersection,
79    /// Two interior rings of a Polygon share a common line
80    IntersectingRingsOnALine,
81    /// Two interior rings of a Polygon share a common area
82    IntersectingRingsOnAnArea,
83    /// The interior ring of a Polygon is not contained in the exterior ring
84    InteriorRingNotContainedInExteriorRing,
85    /// Two Polygons of MultiPolygons overlap partially
86    ElementsOverlaps,
87    /// Two Polygons of MultiPolygons touch on a line
88    ElementsTouchOnALine,
89    /// Two Polygons of MultiPolygons are identical
90    ElementsAreIdentical,
91}
92
93#[derive(Debug, PartialEq)]
94/// A problem, at a given position, encountered when checking the validity of a geometry.
95pub struct ProblemAtPosition(pub Problem, pub ProblemPosition);
96
97impl Display for ProblemAtPosition {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        write!(f, "{:?} at {:?}", self.0, self.1)
100    }
101}
102
103/// All the problems encountered when checking the validity of a geometry.
104#[derive(Debug, PartialEq)]
105pub struct ProblemReport(pub Vec<ProblemAtPosition>);
106
107impl Display for ProblemPosition {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        let mut str_buffer: Vec<String> = Vec::new();
110        match self {
111            ProblemPosition::Point => str_buffer.push(String::new()),
112            ProblemPosition::LineString(coord) => {
113                if coord.0 == -1 {
114                    str_buffer.push(String::new())
115                } else {
116                    str_buffer.push(format!(" at coordinate {} of the LineString", coord.0))
117                }
118            }
119            ProblemPosition::Triangle(coord) => {
120                if coord.0 == -1 {
121                    str_buffer.push(String::new())
122                } else {
123                    str_buffer.push(format!(" at coordinate {} of the Triangle", coord.0))
124                }
125            }
126            ProblemPosition::Polygon(ring_role, coord) => {
127                if coord.0 == -1 {
128                    str_buffer.push(format!(" on the {}", ring_role))
129                } else {
130                    str_buffer.push(format!(" at coordinate {} of the {}", coord.0, ring_role))
131                }
132            }
133            ProblemPosition::MultiPolygon(geom_number, ring_role, coord) => {
134                if coord.0 == -1 {
135                    str_buffer.push(format!(
136                        " on the {} of the Polygon n°{} of the MultiPolygon",
137                        ring_role, geom_number.0
138                    ))
139                } else {
140                    str_buffer.push(format!(
141                        " at coordinate {} of the {} of the Polygon n°{} of the MultiPolygon",
142                        coord.0, ring_role, geom_number.0
143                    ))
144                }
145            }
146            ProblemPosition::MultiLineString(geom_number, coord) => {
147                if coord.0 == -1 {
148                    str_buffer.push(format!(
149                        " on the LineString n°{} of the MultiLineString",
150                        geom_number.0
151                    ))
152                } else {
153                    str_buffer.push(format!(
154                        " at coordinate {} of the LineString n°{} of the MultiLineString",
155                        coord.0, geom_number.0
156                    ))
157                }
158            }
159            ProblemPosition::MultiPoint(geom_number) => str_buffer.push(format!(
160                " on the Point n°{} of the MultiPoint",
161                geom_number.0
162            )),
163            ProblemPosition::GeometryCollection(geom_number, problem_position) => {
164                str_buffer.push(format!(
165                    "{} of the geometry n°{} of the GeometryCollection",
166                    *problem_position, geom_number.0
167                ));
168            }
169            ProblemPosition::Rect(coord) => {
170                if coord.0 == -1 {
171                    str_buffer.push(String::new())
172                } else {
173                    str_buffer.push(format!(" at coordinate {} of the Rect", coord.0))
174                }
175            }
176            ProblemPosition::Line(coord) => {
177                if coord.0 == -1 {
178                    str_buffer.push(String::new())
179                } else {
180                    str_buffer.push(format!(" at coordinate {} of the Line", coord.0))
181                }
182            }
183        }
184        write!(f, "{}", str_buffer.join(""))
185    }
186}
187
188impl Display for ProblemReport {
189    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190        let buffer =
191            self.0
192                .iter()
193                .map(|p| {
194                    let (problem, position) = (&p.0, &p.1);
195                    let mut str_buffer: Vec<String> = Vec::new();
196                    let is_polygon = matches!(
197                        position,
198                        ProblemPosition::Polygon(_, _) | ProblemPosition::MultiPolygon(_, _, _)
199                    );
200
201                    str_buffer.push(format!("{}", position));
202
203                    match *problem {
204                        Problem::NotFinite => str_buffer
205                            .push("Coordinate is not finite (NaN or infinite)".to_string()),
206                        Problem::TooFewPoints => {
207                            if is_polygon {
208                                str_buffer.push("Polygon ring has too few points".to_string())
209                            } else {
210                                str_buffer.push("LineString has too few points".to_string())
211                            }
212                        }
213                        Problem::IdenticalCoords => str_buffer.push("Identical coords".to_string()),
214                        Problem::CollinearCoords => str_buffer.push("Collinear coords".to_string()),
215                        Problem::SelfIntersection => {
216                            str_buffer.push("Ring has a self-intersection".to_string())
217                        }
218                        Problem::IntersectingRingsOnALine => str_buffer.push(
219                            "Two interior rings of a Polygon share a common line".to_string(),
220                        ),
221                        Problem::IntersectingRingsOnAnArea => str_buffer.push(
222                            "Two interior rings of a Polygon share a common area".to_string(),
223                        ),
224                        Problem::InteriorRingNotContainedInExteriorRing => str_buffer.push(
225                            "The interior ring of a Polygon is not contained in the exterior ring"
226                                .to_string(),
227                        ),
228                        Problem::ElementsOverlaps => str_buffer
229                            .push("Two Polygons of MultiPolygons overlap partially".to_string()),
230                        Problem::ElementsTouchOnALine => str_buffer
231                            .push("Two Polygons of MultiPolygons touch on a line".to_string()),
232                        Problem::ElementsAreIdentical => str_buffer
233                            .push("Two Polygons of MultiPolygons are identical".to_string()),
234                    };
235                    str_buffer.into_iter().rev().collect::<Vec<_>>().join("")
236                })
237                .collect::<Vec<String>>()
238                .join("\n");
239
240        write!(f, "{}", buffer)
241    }
242}
243
244/// A trait to check if a geometry is valid and report the reason(s) of invalidity.
245pub trait Valid {
246    /// Check if the geometry is valid.
247    fn is_valid(&self) -> bool;
248    /// Return the reason(s) of invalidity of the geometry, or None if valid.
249    fn explain_invalidity(&self) -> Option<ProblemReport>;
250}