Skip to content

Commit 3aae4e0

Browse files
author
cookiedan42
committed
add tests for empy geometry
comments explaining validity of derived covers functions
1 parent 87568b4 commit 3aae4e0

File tree

12 files changed

+371
-6
lines changed

12 files changed

+371
-6
lines changed

geo-benches/src/covers.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use criterion::{Criterion, criterion_group, criterion_main};
1+
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
22
use geo::algorithm::{Contains, Covers};
33
use geo::{Convert, wkt};
44
use geo::{coord, geometry::*};
@@ -39,5 +39,51 @@ fn linestring_covers_point(c: &mut Criterion) {
3939
});
4040
}
4141

42-
criterion_group!(benches, rect_covers, linestring_covers_point,);
42+
// bench a method derived from intersects
43+
fn rect_linestring_scaling(c: &mut Criterion) {
44+
fn make_outer_rect(n: i32) -> Rect<f64> {
45+
Rect::new(coord! {x:0,y:0}, coord! {x:n,y:3}).convert()
46+
}
47+
fn make_inner_ls(n: i32) -> LineString<f64> {
48+
LineString::new((1..n).map(|i: i32| coord! {x:i,y:(1+i%2)}).collect()).convert()
49+
}
50+
51+
{
52+
// create two polygons, both of of n+2 sides and no holes
53+
let mut group = c.benchmark_group("covers rect linestring scaling");
54+
55+
// trait is faster for small polygons, but relate overtakes from around 700*700 boundary segment checks
56+
for i in [10, 1_000, 100_000] {
57+
group.throughput(Throughput::Elements(i as u64));
58+
59+
let inner_poly = make_inner_ls(i);
60+
let outer_poly = make_outer_rect(i);
61+
62+
group.bench_with_input(
63+
BenchmarkId::new("covers trait", i),
64+
&(&outer_poly, &inner_poly),
65+
|bencher, &(a, b)| {
66+
bencher.iter(|| assert!(a.covers(b)));
67+
},
68+
);
69+
70+
// contains delegates this to relate
71+
group.bench_with_input(
72+
BenchmarkId::new("contains trait", i),
73+
&(&outer_poly, &inner_poly),
74+
|bencher, &(a, b)| {
75+
bencher.iter(|| assert!(a.contains(b)));
76+
},
77+
);
78+
}
79+
group.finish();
80+
}
81+
}
82+
83+
criterion_group!(
84+
benches,
85+
rect_covers,
86+
linestring_covers_point,
87+
rect_linestring_scaling
88+
);
4389
criterion_main!(benches);

geo/CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
- Add `concave_hull_with_options` method which requires `ConcaveHullOptions` as a parameter with `concavity` and `length_threshold` options.
2121
- <https://github.com/georust/geo/pull/1442>
2222
- Add `Covers` trait to relate and as a standalone operation
23+
- custom implementations for checking Geometries covered by `Rect`, `Triangle`, `Line`, `Point`, `Coord`
24+
- custom implementations for checking Geometries covering `Point` and `MultiPoint`
2325

2426
## 0.31.0 - 2025-09-01
2527

geo/src/algorithm/covers/coord.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ where
1313
}
1414
}
1515

16+
// valid because self is convex geometry
17+
// all exterior pts of RHS intersecting self means self covers RHS
1618
impl_covers_from_intersects!(Coord<T>, [
1719
Point<T>, MultiPoint<T>,
1820
Line<T>,
@@ -21,3 +23,19 @@ Rect<T>, Triangle<T>,
2123
Polygon<T>, MultiPolygon<T>,
2224
Geometry<T>, GeometryCollection<T>
2325
]);
26+
27+
#[cfg(test)]
28+
mod tests {
29+
use super::*;
30+
31+
#[test]
32+
fn test_rhs_empty() {
33+
let s: Coord<f64> = Coord::zero();
34+
assert!(!s.covers(&LineString::empty()));
35+
assert!(!s.covers(&Polygon::empty()));
36+
assert!(!s.covers(&MultiPoint::empty()));
37+
assert!(!s.covers(&MultiLineString::empty()));
38+
assert!(!s.covers(&MultiPolygon::empty()));
39+
assert!(!s.covers(&GeometryCollection::empty()));
40+
}
41+
}

geo/src/algorithm/covers/geometry_collection.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,41 @@ Polygon<T>, MultiPolygon<T>,
1212
GeometryCollection<T>
1313
]);
1414
impl_covers_geometry_for!(GeometryCollection<T>);
15+
16+
#[cfg(test)]
17+
mod tests {
18+
use super::*;
19+
use crate::algorithm::convert::Convert;
20+
use crate::wkt;
21+
22+
#[test]
23+
fn test_rhs_empty() {
24+
let s: GeometryCollection<f64> = wkt!(GEOMETRYCOLLECTION(POINT(0 0))).convert();
25+
assert!(!s.covers(&LineString::empty()));
26+
assert!(!s.covers(&Polygon::empty()));
27+
assert!(!s.covers(&MultiPoint::empty()));
28+
assert!(!s.covers(&MultiLineString::empty()));
29+
assert!(!s.covers(&MultiPolygon::empty()));
30+
assert!(!s.covers(&GeometryCollection::empty()));
31+
}
32+
33+
#[test]
34+
fn test_lhs_empty() {
35+
let s: GeometryCollection<f64> = GeometryCollection::empty();
36+
37+
assert!(!s.covers(&wkt!(POINT(0 0)).convert()));
38+
assert!(!s.covers(&wkt!(MULTIPOINT(0 0,1 1)).convert()));
39+
40+
assert!(!s.covers(&wkt!(LINE(0 0,1 1)).convert()));
41+
assert!(!s.covers(&wkt!(LINESTRING(0 0,1 1)).convert()));
42+
assert!(!s.covers(&wkt!(MULTILINESTRING((0 0,1 1),(2 2,3 3))).convert()));
43+
44+
assert!(!s.covers(&wkt!(POLYGON((0 0,1 1,1 0,0 0))).convert()));
45+
assert!(!s.covers(&wkt!(MULTIPOLYGON(((0 0,1 0, 1 1,0 1, 0 0)))).convert()));
46+
assert!(!s.covers(&wkt!(RECT(0 0, 1 1)).convert()));
47+
assert!(!s.covers(&wkt!(TRIANGLE(0 0, 0 1, 1 1)).convert()));
48+
49+
assert!(!s.covers(&Into::<Geometry>::into(wkt!(POINT(0 0)).convert())));
50+
assert!(!s.covers(&wkt!(GEOMETRYCOLLECTION(POINT(0 0))).convert()));
51+
}
52+
}

geo/src/algorithm/covers/line.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use super::{Covers, impl_covers_from_intersects};
22
use crate::GeoNum;
33
use crate::geometry::*;
44

5+
// valid because self is convex geometry
6+
// all exterior pts of RHS intersecting self means self covers RHS
57
impl_covers_from_intersects!(Line<T>, [
68
Point<T>, MultiPoint<T>,
79
Line<T>,
@@ -10,3 +12,21 @@ Rect<T>, Triangle<T>,
1012
Polygon<T>, MultiPolygon<T> ,
1113
Geometry<T>, GeometryCollection<T>
1214
]);
15+
16+
#[cfg(test)]
17+
mod tests {
18+
use super::*;
19+
use crate::algorithm::convert::Convert;
20+
use crate::wkt;
21+
22+
#[test]
23+
fn test_rhs_empty() {
24+
let s: Line<f64> = wkt!(LINE(0 0,1 1)).convert();
25+
assert!(!s.covers(&LineString::empty()));
26+
assert!(!s.covers(&Polygon::empty()));
27+
assert!(!s.covers(&MultiPoint::empty()));
28+
assert!(!s.covers(&MultiLineString::empty()));
29+
assert!(!s.covers(&MultiPolygon::empty()));
30+
assert!(!s.covers(&GeometryCollection::empty()));
31+
}
32+
}

geo/src/algorithm/covers/line_string.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use super::{Covers, impl_covers_from_intersects, impl_covers_from_relate};
22
use crate::{Contains, HasDimensions, geometry::*};
33
use crate::{GeoFloat, GeoNum};
44

5+
// valid because RHS is point or multipoint
6+
// all exterior pts of RHS intersecting self means self covers RHS
57
impl_covers_from_intersects!(LineString<T>, [Point<T>, MultiPoint<T>]);
68

79
impl<T> Covers<Line<T>> for LineString<T>
@@ -10,8 +12,10 @@ where
1012
{
1113
fn covers(&self, rhs: &Line<T>) -> bool {
1214
if rhs.start == rhs.end {
15+
// handle edge case where line might sit within a linestring's boundary
1316
self.covers(&rhs.start)
1417
} else {
18+
// remaining scenarios are eqivalent to contains
1519
self.contains(rhs)
1620
}
1721
}
@@ -36,11 +40,99 @@ Polygon<T>, MultiPolygon<T>,
3640
Geometry<T>, GeometryCollection<T>
3741
]);
3842

43+
// valid because RHS is point or multipoint
44+
// all exterior pts of RHS intersecting self means self covers RHS
3945
impl_covers_from_intersects!(MultiLineString<T>, [Point<T>, MultiPoint<T>]);
46+
4047
impl_covers_from_relate!(MultiLineString<T>, [
4148
Line<T>,
4249
LineString<T>, MultiLineString<T>,
4350
Rect<T>, Triangle<T>,
4451
Polygon<T>, MultiPolygon<T>,
4552
Geometry<T>, GeometryCollection<T>
4653
]);
54+
55+
#[cfg(test)]
56+
mod tests {
57+
use super::*;
58+
use crate::algorithm::convert::Convert;
59+
use crate::wkt;
60+
61+
#[test]
62+
fn test_rhs_empty() {
63+
let s: LineString<f64> = wkt!(LINESTRING(0 0,1 1)).convert();
64+
assert!(!s.covers(&LineString::empty()));
65+
assert!(!s.covers(&Polygon::empty()));
66+
assert!(!s.covers(&MultiPoint::empty()));
67+
assert!(!s.covers(&MultiLineString::empty()));
68+
assert!(!s.covers(&MultiPolygon::empty()));
69+
assert!(!s.covers(&GeometryCollection::empty()));
70+
71+
let s: MultiLineString<f64> = wkt!(MULTILINESTRING((0 0,1 1))).convert();
72+
assert!(!s.covers(&LineString::empty()));
73+
assert!(!s.covers(&Polygon::empty()));
74+
assert!(!s.covers(&MultiPoint::empty()));
75+
assert!(!s.covers(&MultiLineString::empty()));
76+
assert!(!s.covers(&MultiPolygon::empty()));
77+
assert!(!s.covers(&GeometryCollection::empty()));
78+
}
79+
80+
#[test]
81+
fn test_lhs_empty() {
82+
let s: LineString<f64> = LineString::empty();
83+
assert!(!s.covers(&wkt!(POINT(0 0)).convert()));
84+
assert!(!s.covers(&wkt!(MULTIPOINT(0 0,1 1)).convert()));
85+
assert!(!s.covers(&wkt!(LINE(0 0,1 1)).convert()));
86+
assert!(!s.covers(&wkt!(LINESTRING(0 0,1 1)).convert()));
87+
assert!(!s.covers(&wkt!(MULTILINESTRING((0 0,1 1),(2 2,3 3))).convert()));
88+
assert!(!s.covers(&wkt!(POLYGON((0 0,1 1,1 0,0 0))).convert()));
89+
assert!(!s.covers(&wkt!(MULTIPOLYGON(((0 0,1 0, 1 1,0 1, 0 0)))).convert()));
90+
assert!(!s.covers(&wkt!(RECT(0 0, 1 1)).convert()));
91+
assert!(!s.covers(&wkt!(TRIANGLE(0 0, 0 1, 1 1)).convert()));
92+
assert!(!s.covers(&Into::<Geometry>::into(wkt!(POINT(0 0)).convert())));
93+
assert!(!s.covers(&wkt!(GEOMETRYCOLLECTION(POINT(0 0))).convert()));
94+
95+
let s: MultiLineString<f64> = MultiLineString::empty();
96+
assert!(!s.covers(&wkt!(POINT(0 0)).convert()));
97+
assert!(!s.covers(&wkt!(MULTIPOINT(0 0,1 1)).convert()));
98+
assert!(!s.covers(&wkt!(LINE(0 0,1 1)).convert()));
99+
assert!(!s.covers(&wkt!(LINESTRING(0 0,1 1)).convert()));
100+
assert!(!s.covers(&wkt!(MULTILINESTRING((0 0,1 1),(2 2,3 3))).convert()));
101+
assert!(!s.covers(&wkt!(POLYGON((0 0,1 1,1 0,0 0))).convert()));
102+
assert!(!s.covers(&wkt!(MULTIPOLYGON(((0 0,1 0, 1 1,0 1, 0 0)))).convert()));
103+
assert!(!s.covers(&wkt!(RECT(0 0, 1 1)).convert()));
104+
assert!(!s.covers(&wkt!(TRIANGLE(0 0, 0 1, 1 1)).convert()));
105+
assert!(!s.covers(&Into::<Geometry>::into(wkt!(POINT(0 0)).convert())));
106+
assert!(!s.covers(&wkt!(GEOMETRYCOLLECTION(POINT(0 0))).convert()));
107+
}
108+
109+
#[test]
110+
fn differ_from_contains() {
111+
// test that the edge case is covered by custom implementation
112+
113+
let ls: LineString<f64> = wkt!(LINESTRING(0 0, 1 1)).convert();
114+
let ls_s: LineString<f64> = wkt!(LINESTRING(0 0, 0 0)).convert();
115+
let ls_e: LineString<f64> = wkt!(LINESTRING(1 1, 1 1)).convert();
116+
let ln_s: Line<f64> = wkt!(LINE(0 0, 0 0)).convert();
117+
let ln_e: Line<f64> = wkt!(LINE(1 1, 1 1)).convert();
118+
let pt_s: Point<f64> = wkt!(POINT(0 0)).convert();
119+
let pt_e: Point<f64> = wkt!(POINT(1 1)).convert();
120+
121+
assert!(ls.covers(&ls_s));
122+
assert!(ls.covers(&ls_e));
123+
assert!(ls.covers(&ln_s));
124+
assert!(ls.covers(&ln_e));
125+
assert!(ls.covers(&pt_s));
126+
assert!(ls.covers(&pt_e));
127+
128+
assert!(!ls.contains(&ls_s));
129+
assert!(!ls.contains(&ls_e));
130+
assert!(!ls.contains(&ln_s));
131+
assert!(!ls.contains(&ln_e));
132+
assert!(!ls.contains(&pt_s));
133+
assert!(!ls.contains(&pt_e));
134+
135+
}
136+
137+
138+
}

geo/src/algorithm/covers/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@
3434
/// ```
3535
///
3636
/// # Performance Note
37-
/// Much of this trait is currently implemented by delegating to the Relate trait.
38-
/// Custom Contains implementations might be faster if you need speed
37+
///
38+
/// Much of this trait is currently implemented by delegating to the [`Relate`](crate::algorithm::Relate) trait - see
39+
/// [`IntersectionMatrix::is_covers`](crate::algorithm::relate::IntersectionMatrix::is_covers);
40+
/// However, [`Covers`] may be faster for checking if geometries are covered by `Point`, `Line`, `Rect` and `Triangle`
3941
///
4042
pub trait Covers<Rhs = Self> {
4143
fn covers(&self, rhs: &Rhs) -> bool;

geo/src/algorithm/covers/point.rs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use super::{Covers, impl_covers_from_intersects, impl_covers_from_relate};
22
use crate::{Contains, geometry::*};
33
use crate::{GeoFloat, GeoNum};
44

5+
// valid because self is convex geometry
6+
// all exterior pts of RHS intersecting self means self covers RHS
57
impl_covers_from_intersects!(Point<T>, [
68
Point<T>, MultiPoint<T>,
79
Line<T>,
@@ -11,14 +13,16 @@ Polygon<T>, MultiPolygon<T>,
1113
Geometry<T>, GeometryCollection<T>
1214
]);
1315

16+
// valid because RHS is point
1417
impl_covers_from_intersects!(MultiPoint<T>, [Point<T>]);
1518

19+
// use the sliding window comparison implementation of contains
20+
// multipoint has no boundary so they are equivalent
1621
impl<T> Covers<MultiPoint<T>> for MultiPoint<T>
1722
where
1823
T: GeoNum,
1924
{
2025
fn covers(&self, rhs: &MultiPoint<T>) -> bool {
21-
// use the sliding comparison implementation
2226
self.contains(rhs)
2327
}
2428
}
@@ -30,3 +34,45 @@ Rect<T>, Triangle<T>,
3034
Polygon<T>, MultiPolygon<T>,
3135
Geometry<T>, GeometryCollection<T>
3236
]);
37+
38+
#[cfg(test)]
39+
mod tests {
40+
use super::*;
41+
use crate::algorithm::convert::Convert;
42+
use crate::wkt;
43+
44+
#[test]
45+
fn test_rhs_empty() {
46+
let s: Point<f64> = wkt!(POINT(0 0)).convert();
47+
assert!(!s.covers(&LineString::empty()));
48+
assert!(!s.covers(&Polygon::empty()));
49+
assert!(!s.covers(&MultiPoint::empty()));
50+
assert!(!s.covers(&MultiLineString::empty()));
51+
assert!(!s.covers(&MultiPolygon::empty()));
52+
assert!(!s.covers(&GeometryCollection::empty()));
53+
54+
let s: MultiPoint<f64> = wkt!(MULTIPOINT(0 0, 1 1)).convert();
55+
assert!(!s.covers(&LineString::empty()));
56+
assert!(!s.covers(&Polygon::empty()));
57+
assert!(!s.covers(&MultiPoint::empty()));
58+
assert!(!s.covers(&MultiLineString::empty()));
59+
assert!(!s.covers(&MultiPolygon::empty()));
60+
assert!(!s.covers(&GeometryCollection::empty()));
61+
}
62+
63+
#[test]
64+
fn test_lhs_empty() {
65+
let s: MultiPoint<f64> = MultiPoint::empty();
66+
assert!(!s.covers(&wkt!(POINT(0 0)).convert()));
67+
assert!(!s.covers(&wkt!(MULTIPOINT(0 0,1 1)).convert()));
68+
assert!(!s.covers(&wkt!(LINE(0 0,1 1)).convert()));
69+
assert!(!s.covers(&wkt!(LINESTRING(0 0,1 1)).convert()));
70+
assert!(!s.covers(&wkt!(MULTILINESTRING((0 0,1 1),(2 2,3 3))).convert()));
71+
assert!(!s.covers(&wkt!(POLYGON((0 0,1 1,1 0,0 0))).convert()));
72+
assert!(!s.covers(&wkt!(MULTIPOLYGON(((0 0,1 0, 1 1,0 1, 0 0)))).convert()));
73+
assert!(!s.covers(&wkt!(RECT(0 0, 1 1)).convert()));
74+
assert!(!s.covers(&wkt!(TRIANGLE(0 0, 0 1, 1 1)).convert()));
75+
assert!(!s.covers(&Into::<Geometry>::into(wkt!(POINT(0 0)).convert())));
76+
assert!(!s.covers(&wkt!(GEOMETRYCOLLECTION(POINT(0 0))).convert()));
77+
}
78+
}

0 commit comments

Comments
 (0)