Skip to content

Commit b1e550a

Browse files
authored
Merge pull request #1057 from georust/triangle
Point in triangle and rect performance improvements
2 parents a46eb87 + 654af8b commit b1e550a

File tree

15 files changed

+361
-36
lines changed

15 files changed

+361
-36
lines changed

geo-types/src/geometry/line_string.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,15 +257,15 @@ impl<T: CoordNum> LineString<T> {
257257
/// );
258258
/// assert!(lines.next().is_none());
259259
/// ```
260-
pub fn lines(&'_ self) -> impl ExactSizeIterator + Iterator<Item = Line<T>> + '_ {
260+
pub fn lines(&'_ self) -> impl ExactSizeIterator<Item = Line<T>> + '_ {
261261
self.0.windows(2).map(|w| {
262262
// slice::windows(N) is guaranteed to yield a slice with exactly N elements
263263
unsafe { Line::new(*w.get_unchecked(0), *w.get_unchecked(1)) }
264264
})
265265
}
266266

267267
/// An iterator which yields the coordinates of a [`LineString`] as [Triangle]s
268-
pub fn triangles(&'_ self) -> impl ExactSizeIterator + Iterator<Item = Triangle<T>> + '_ {
268+
pub fn triangles(&'_ self) -> impl ExactSizeIterator<Item = Triangle<T>> + '_ {
269269
self.0.windows(3).map(|w| {
270270
// slice::windows(N) is guaranteed to yield a slice with exactly N elements
271271
unsafe {

geo/CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
implemented for `CoordsIter` without re-allocating (e.g., creating a `MultiPoint`).
4242
* Add `compose_many` method to `AffineOps`
4343
* <https://github.com/georust/geo/pull/1148>
44+
* Point in `Triangle` and `Rect` performance improvemnets
45+
* <https://github.com/georust/geo/pull/1057>
4446

4547
## 0.27.0
4648

geo/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ wkt = "0.10.1"
4747
name = "area"
4848
harness = false
4949

50+
[[bench]]
51+
name = "coordinate_position"
52+
harness = false
53+
5054
[[bench]]
5155
name = "contains"
5256
harness = false

geo/benches/contains.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use criterion::{criterion_group, criterion_main, Criterion};
22
use geo::contains::Contains;
3-
use geo::{point, polygon, Line, Point, Polygon};
3+
use geo::{point, polygon, Line, Point, Polygon, Triangle};
44

55
fn criterion_benchmark(c: &mut Criterion) {
66
c.bench_function("point in simple polygon", |bencher| {
@@ -110,6 +110,24 @@ fn criterion_benchmark(c: &mut Criterion) {
110110
);
111111
});
112112
});
113+
114+
c.bench_function("Triangle contains point", |bencher| {
115+
let triangle = Triangle::from([(0., 0.), (10., 0.), (5., 10.)]);
116+
let point = Point::new(5., 5.);
117+
118+
bencher.iter(|| {
119+
assert!(criterion::black_box(&triangle).contains(criterion::black_box(&point)));
120+
});
121+
});
122+
123+
c.bench_function("Triangle contains point on edge", |bencher| {
124+
let triangle = Triangle::from([(0., 0.), (10., 0.), (6., 10.)]);
125+
let point = Point::new(3., 5.);
126+
127+
bencher.iter(|| {
128+
assert!(!criterion::black_box(&triangle).contains(criterion::black_box(&point)));
129+
});
130+
});
113131
}
114132

115133
criterion_group!(benches, criterion_benchmark);

geo/benches/coordinate_position.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#[macro_use]
2+
extern crate criterion;
3+
extern crate geo;
4+
5+
use geo::{
6+
coordinate_position::CoordPos, BoundingRect, Centroid, CoordinatePosition, Point, Rect,
7+
Triangle,
8+
};
9+
10+
use criterion::Criterion;
11+
12+
fn criterion_benchmark(c: &mut Criterion) {
13+
c.bench_function("Point position to rect", |bencher| {
14+
let plot_centroids: Vec<Point> = geo_test_fixtures::nl_plots()
15+
.iter()
16+
.map(|plot| plot.centroid().unwrap())
17+
.collect();
18+
let zone_bbox: Vec<Rect> = geo_test_fixtures::nl_zones()
19+
.iter()
20+
.map(|plot| plot.bounding_rect().unwrap())
21+
.collect();
22+
bencher.iter(|| {
23+
let mut inside = 0;
24+
let mut outsied = 0;
25+
let mut boundary = 0;
26+
27+
for a in &plot_centroids {
28+
for b in &zone_bbox {
29+
match criterion::black_box(b).coordinate_position(criterion::black_box(&a.0)) {
30+
CoordPos::OnBoundary => boundary += 1,
31+
CoordPos::Inside => inside += 1,
32+
CoordPos::Outside => outsied += 1,
33+
}
34+
}
35+
}
36+
37+
assert_eq!(inside, 2246);
38+
assert_eq!(outsied, 26510);
39+
assert_eq!(boundary, 0);
40+
});
41+
});
42+
43+
c.bench_function("Point in triangle", |bencher| {
44+
let triangle = Triangle::from([(0., 0.), (10., 0.), (5., 10.)]);
45+
let point = Point::new(5., 5.);
46+
47+
bencher.iter(|| {
48+
assert!(
49+
criterion::black_box(&triangle).coordinate_position(criterion::black_box(&point.0))
50+
!= CoordPos::Outside
51+
);
52+
});
53+
});
54+
55+
c.bench_function("Point on triangle boundary", |bencher| {
56+
let triangle = Triangle::from([(0., 0.), (10., 0.), (6., 10.)]);
57+
let point = Point::new(3., 5.);
58+
59+
bencher.iter(|| {
60+
assert!(
61+
criterion::black_box(&triangle).coordinate_position(criterion::black_box(&point.0))
62+
== CoordPos::OnBoundary
63+
);
64+
});
65+
});
66+
}
67+
68+
criterion_group!(benches, criterion_benchmark);
69+
criterion_main!(benches);

geo/benches/intersection.rs

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,110 @@ fn rect_intersection(c: &mut Criterion) {
6060
});
6161
}
6262

63+
fn point_rect_intersection(c: &mut Criterion) {
64+
use geo::algorithm::{BoundingRect, Centroid};
65+
use geo::geometry::{Point, Rect};
66+
let plot_centroids: Vec<Point> = geo_test_fixtures::nl_plots()
67+
.iter()
68+
.map(|plot| plot.centroid().unwrap())
69+
.collect();
70+
let zone_bbox: Vec<Rect> = geo_test_fixtures::nl_zones()
71+
.iter()
72+
.map(|plot| plot.bounding_rect().unwrap())
73+
.collect();
74+
75+
c.bench_function("Point intersects rect", |bencher| {
76+
bencher.iter(|| {
77+
let mut intersects = 0;
78+
let mut non_intersects = 0;
79+
80+
for a in &plot_centroids {
81+
for b in &zone_bbox {
82+
if criterion::black_box(a.intersects(b)) {
83+
intersects += 1;
84+
} else {
85+
non_intersects += 1;
86+
}
87+
}
88+
}
89+
90+
assert_eq!(intersects, 2246);
91+
assert_eq!(non_intersects, 26510);
92+
});
93+
});
94+
}
95+
96+
fn point_triangle_intersection(c: &mut Criterion) {
97+
use geo::{Centroid, TriangulateEarcut};
98+
use geo_types::{Point, Triangle};
99+
let plot_centroids: Vec<Point> = geo_test_fixtures::nl_plots()
100+
.iter()
101+
.map(|plot| plot.centroid().unwrap())
102+
.collect();
103+
let zone_triangles: Vec<Triangle> = geo_test_fixtures::nl_zones()
104+
.iter()
105+
.flat_map(|plot| plot.earcut_triangles_iter())
106+
.collect();
107+
108+
c.bench_function("Point intersects triangle", |bencher| {
109+
bencher.iter(|| {
110+
let mut intersects = 0;
111+
let mut non_intersects = 0;
112+
113+
for a in &plot_centroids {
114+
for b in &zone_triangles {
115+
if criterion::black_box(a.intersects(b)) {
116+
intersects += 1;
117+
} else {
118+
non_intersects += 1;
119+
}
120+
}
121+
}
122+
123+
assert_eq!(intersects, 533);
124+
assert_eq!(non_intersects, 5450151);
125+
});
126+
});
127+
128+
c.bench_function("Triangle intersects point", |bencher| {
129+
let triangle = Triangle::from([(0., 0.), (10., 0.), (5., 10.)]);
130+
let point = Point::new(5., 5.);
131+
132+
bencher.iter(|| {
133+
assert!(criterion::black_box(&triangle).intersects(criterion::black_box(&point)));
134+
});
135+
});
136+
137+
c.bench_function("Triangle intersects point on edge", |bencher| {
138+
let triangle = Triangle::from([(0., 0.), (10., 0.), (6., 10.)]);
139+
let point = Point::new(3., 5.);
140+
141+
bencher.iter(|| {
142+
assert!(criterion::black_box(&triangle).intersects(criterion::black_box(&point)));
143+
});
144+
});
145+
}
146+
63147
criterion_group! {
64148
name = bench_multi_polygons;
65149
config = Criterion::default().sample_size(10);
66150
targets = multi_polygon_intersection
67151
}
68152
criterion_group!(bench_rects, rect_intersection);
153+
criterion_group! {
154+
name = bench_point_rect;
155+
config = Criterion::default().sample_size(50);
156+
targets = point_rect_intersection
157+
}
158+
criterion_group! {
159+
name = bench_point_triangle;
160+
config = Criterion::default().sample_size(50);
161+
targets = point_triangle_intersection
162+
}
69163

70-
criterion_main!(bench_multi_polygons, bench_rects);
164+
criterion_main!(
165+
bench_multi_polygons,
166+
bench_rects,
167+
bench_point_rect,
168+
bench_point_triangle
169+
);

geo/src/algorithm/contains/triangle.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::{impl_contains_from_relate, impl_contains_geometry_for, Contains};
22
use crate::geometry::*;
3-
use crate::{GeoFloat, GeoNum};
3+
use crate::{kernels::Kernel, GeoFloat, GeoNum, Orientation};
44

55
// ┌──────────────────────────────┐
66
// │ Implementations for Triangle │
@@ -11,9 +11,31 @@ where
1111
T: GeoNum,
1212
{
1313
fn contains(&self, coord: &Coord<T>) -> bool {
14-
let ls = LineString::new(vec![self.0, self.1, self.2, self.0]);
15-
use crate::utils::{coord_pos_relative_to_ring, CoordPos};
16-
coord_pos_relative_to_ring(*coord, &ls) == CoordPos::Inside
14+
// leverageing robust predicates
15+
self.to_lines()
16+
.map(|l| T::Ker::orient2d(l.start, l.end, *coord))
17+
.windows(2)
18+
.all(|win| win[0] == win[1] && win[0] != Orientation::Collinear)
19+
20+
// // neglecting robust prdicates, hence faster
21+
// let p0x = self.0.x.to_f64().unwrap();
22+
// let p0y = self.0.y.to_f64().unwrap();
23+
// let p1x = self.1.x.to_f64().unwrap();
24+
// let p1y = self.1.y.to_f64().unwrap();
25+
// let p2x = self.2.x.to_f64().unwrap();
26+
// let p2y = self.2.y.to_f64().unwrap();
27+
28+
// let px = coord.x.to_f64().unwrap();
29+
// let py = coord.y.to_f64().unwrap();
30+
31+
// let a = 0.5 * (-p1y * p2x + p0y * (-p1x + p2x) + p0x * (p1y - p2y) + p1x * p2y);
32+
33+
// let sign = a.signum();
34+
35+
// let s = (p0y * p2x - p0x * p2y + (p2y - p0y) * px + (p0x - p2x) * py) * sign;
36+
// let t = (p0x * p1y - p0y * p1x + (p0y - p1y) * px + (p1x - p0x) * py) * sign;
37+
38+
// s > 0. && t > 0. && (s + t) < 2. * a * sign
1739
}
1840
}
1941

geo/src/algorithm/coordinate_position.rs

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
use std::cmp::Ordering;
2+
13
use crate::geometry::*;
2-
use crate::intersects::value_in_between;
4+
use crate::intersects::{point_in_rect, value_in_between};
35
use crate::kernels::*;
46
use crate::{BoundingRect, HasDimensions, Intersects};
57
use crate::{GeoNum, GeometryCow};
@@ -184,9 +186,20 @@ where
184186
is_inside: &mut bool,
185187
boundary_count: &mut usize,
186188
) {
187-
// PERF TODO: I'm sure there's a better way to calculate than converting to a polygon
188-
self.to_polygon()
189-
.calculate_coordinate_position(coord, is_inside, boundary_count);
189+
*is_inside = self
190+
.to_lines()
191+
.map(|l| {
192+
let orientation = T::Ker::orient2d(l.start, l.end, *coord);
193+
if orientation == Orientation::Collinear
194+
&& point_in_rect(*coord, l.start, l.end)
195+
&& coord.x != l.end.x
196+
{
197+
*boundary_count += 1;
198+
}
199+
orientation
200+
})
201+
.windows(2)
202+
.all(|win| win[0] == win[1] && win[0] != Orientation::Collinear);
190203
}
191204
}
192205

@@ -201,9 +214,39 @@ where
201214
is_inside: &mut bool,
202215
boundary_count: &mut usize,
203216
) {
204-
// PERF TODO: I'm sure there's a better way to calculate than converting to a polygon
205-
self.to_polygon()
206-
.calculate_coordinate_position(coord, is_inside, boundary_count);
217+
let mut boundary = false;
218+
219+
let min = self.min();
220+
221+
match coord.x.partial_cmp(&min.x).unwrap() {
222+
Ordering::Less => return,
223+
Ordering::Equal => boundary = true,
224+
Ordering::Greater => {}
225+
}
226+
match coord.y.partial_cmp(&min.y).unwrap() {
227+
Ordering::Less => return,
228+
Ordering::Equal => boundary = true,
229+
Ordering::Greater => {}
230+
}
231+
232+
let max = self.max();
233+
234+
match max.x.partial_cmp(&coord.x).unwrap() {
235+
Ordering::Less => return,
236+
Ordering::Equal => boundary = true,
237+
Ordering::Greater => {}
238+
}
239+
match max.y.partial_cmp(&coord.y).unwrap() {
240+
Ordering::Less => return,
241+
Ordering::Equal => boundary = true,
242+
Ordering::Greater => {}
243+
}
244+
245+
if boundary {
246+
*boundary_count += 1;
247+
} else {
248+
*is_inside = true;
249+
}
207250
}
208251
}
209252

geo/src/algorithm/intersects/collections.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ where
2525
symmetric_intersects_impl!(Coord<T>, Geometry<T>);
2626
symmetric_intersects_impl!(Line<T>, Geometry<T>);
2727
symmetric_intersects_impl!(Rect<T>, Geometry<T>);
28+
symmetric_intersects_impl!(Triangle<T>, Geometry<T>);
2829
symmetric_intersects_impl!(Polygon<T>, Geometry<T>);
2930

3031
impl<T, G> Intersects<G> for GeometryCollection<T>
@@ -43,4 +44,5 @@ where
4344
symmetric_intersects_impl!(Coord<T>, GeometryCollection<T>);
4445
symmetric_intersects_impl!(Line<T>, GeometryCollection<T>);
4546
symmetric_intersects_impl!(Rect<T>, GeometryCollection<T>);
47+
symmetric_intersects_impl!(Triangle<T>, GeometryCollection<T>);
4648
symmetric_intersects_impl!(Polygon<T>, GeometryCollection<T>);

geo/src/algorithm/intersects/line.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,13 @@ where
6767
}
6868
}
6969
}
70+
71+
impl<T> Intersects<Triangle<T>> for Line<T>
72+
where
73+
T: GeoNum,
74+
{
75+
fn intersects(&self, rhs: &Triangle<T>) -> bool {
76+
self.intersects(&rhs.to_polygon())
77+
}
78+
}
79+
symmetric_intersects_impl!(Triangle<T>, Line<T>);

0 commit comments

Comments
 (0)