1 unstable release
0.1.0  Apr 11, 2023 

#110 in Geospatial
73KB
1.5K
SLoC
geovaliditycheck
Expose a Valid
trait to check if rust geotypes geometries are valid.
Valid
trait has the following signature:
trait Valid {
fn is_valid(&self) > bool;
fn explain_invalidity(&self) > Option<ProblemReport>;
}
The result of the invalidity reason is provided in a ProblemReport
struct (it contains a Vec
of (Problem, ProblemPosition)
,
two enums that respectively represent the type of problem and the position of the problem in the tested geometry  having
this machinereadable information could be useful to try to fix the geometry).
This ProblemReport
result can also be formatted as a string as it implements the Display
trait.
Checks implemented

Coord
andPoint
use finite numbers 
MultiPoint
is made of valid points 
Rect
,Line
andTriangle
are made of valid coords 
Triangle
are not empty or degenerate (i.e. all points are different and not collinear) 
Line
is not of 0length (i.e. both points are not the same) 
LineString
is made of valid points 
LineString
is not empty 
LineString
has at least two different points 
MultiLineString
is made of valid linestrings 
Polygon
rings are made of valid points 
Polygon
rings have at least 4 points (including the closing point) 
Polygon
interior rings are contained in the exterior ring (but can touch it on a point) 
Polygon
interior rings don't cross each other (but can touch on a point) 
MultiPolygon
components don't cross each other (but can touch on a point) 
MultiPolygon
is made of valid polygons 
GeometryCollection
is made of valid geometries
Verification is done against GEOS (any geometry invalid according to GEOS should be invalid according to this crate  the inverse doesn't have to be true since we are doing some extra check).
Example
use geo_validity_check::Valid;
use geo_types::{Point, LineString, Polygon, MultiPolygon};
let line1 = LineString::from(vec![(0., 0.), (1., 1.)]);
let line2 = LineString::from(vec![(0., 0.), (0., 0.)]);
let line3 = LineString::from(vec![(0., 0.), (f64::NAN, f64::NAN), (1., 1.)]);
assert!(line1.is_valid());
assert!(!line2.is_valid());
assert!(!line3.is_valid());
println!("{}", line2.invalidity_reason().unwrap()); // "LineString has too few points at coordinate 0 of the LineString"
println!("{}", line3.invalidity_reason().unwrap()); // "Coordinate is not finite (NaN or infinite) at coordinate 1 of the LineString"
let polygon = Polygon::new(
LineString::from(vec![(0.5, 0.5), (3., 0.5), (3., 2.5), (0.5, 2.5), (0.5, 0.5)]),
vec![LineString::from(vec![(1., 1.), (1., 2.), (2.5, 2.), (3.5, 1.), (1., 1.)])],
);
assert!(!polygon.is_valid());
println!("{}", polygon.invalidity_reason().unwrap()); // "The interior ring of a Polygon is not contained in the exterior ring on the interior ring n°0"
let multipolygon = MultiPolygon(vec![
Polygon::new(
LineString::from(vec![(0.5, 0.5), (3., 0.5), (3., 2.5), (0.5, 2.5), (0.5, 0.5)]),
vec![LineString::from(vec![(1., 1.), (1., 2.), (2.5, 2.), (3.5, 1.), (1., 1.)])],
),
Polygon::new(
LineString::from(vec![(0.5, 0.5), (3., 0.5), (3., 2.5), (0.5, 2.5), (0.5, 0.5)]),
vec![LineString::from(vec![(1., 1.), (1., 2.), (2.5, 2.), (3.5, 1.), (1., 1.)])],
),
]);
assert!(!multipolygon.is_valid());
println!("{}", multipolygon.invalidity_reason().unwrap());
// "The interior ring of a Polygon is not contained in the exterior ring on the interior ring n°0 of the Polygon n°0 of the MultiPolygon
// Two Polygons of MultiPolygons are identical on the exterior ring of the Polygon n°0 of the MultiPolygon
// The interior ring of a Polygon is not contained in the exterior ring on the interior ring n°0 of the Polygon n°1 of the MultiPolygon
// Two Polygons of MultiPolygons are identical on the exterior ring of the Polygon n°1 of the MultiPolygon"
TODO / Ideas

Improve the description of the invalidity reason (e.g. "Interior ring 0 intersects the exterior ring" could be "Interior ring 0 intersects the exterior ring at point (1.5, 1.5)")

Add a
make_valid
orfix_invalidity
method to try to fix the geometry (e.g. by removing the invalid points ?) 
Return the first invalidity reason found (instead of all of them) in
invalidity_reason
method ? (because some other checks could fail because of the first invalidity reason) 
Implement a rule that states that a
Polygon
is valid if the polygon interior is simply connected (i.e. the rings must not touch in a way that splits the polygon into more than one part) ?
License
Licensed under either of
 Apache License, Version 2.0, (LICENSEAPACHE or http://www.apache.org/licenses/LICENSE2.0)
 MIT license (LICENSEMIT or http://opensource.org/licenses/MIT)
at your option.
Dependencies
~3.5MB
~63K SLoC