Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Point in triangle and rect performance improvements #1057

Merged
merged 14 commits into from
Feb 23, 2024
4 changes: 2 additions & 2 deletions geo-types/src/geometry/line_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,15 +257,15 @@ impl<T: CoordNum> LineString<T> {
/// );
/// assert!(lines.next().is_none());
/// ```
pub fn lines(&'_ self) -> impl ExactSizeIterator + Iterator<Item = Line<T>> + '_ {
pub fn lines(&'_ self) -> impl ExactSizeIterator<Item = Line<T>> + '_ {
self.0.windows(2).map(|w| {
// slice::windows(N) is guaranteed to yield a slice with exactly N elements
unsafe { Line::new(*w.get_unchecked(0), *w.get_unchecked(1)) }
})
}

/// An iterator which yields the coordinates of a [`LineString`] as [Triangle]s
pub fn triangles(&'_ self) -> impl ExactSizeIterator + Iterator<Item = Triangle<T>> + '_ {
pub fn triangles(&'_ self) -> impl ExactSizeIterator<Item = Triangle<T>> + '_ {
self.0.windows(3).map(|w| {
// slice::windows(N) is guaranteed to yield a slice with exactly N elements
unsafe {
Expand Down
2 changes: 2 additions & 0 deletions geo/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
implemented for `CoordsIter` without re-allocating (e.g., creating a `MultiPoint`).
* Add `compose_many` method to `AffineOps`
* <https://github.com/georust/geo/pull/1148>
* Point in `Triangle` and `Rect` performance improvemnets
* <https://github.com/georust/geo/pull/1057>

## 0.27.0

Expand Down
4 changes: 4 additions & 0 deletions geo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ wkt = "0.10.1"
name = "area"
harness = false

[[bench]]
name = "coordinate_position"
harness = false

[[bench]]
name = "contains"
harness = false
Expand Down
20 changes: 19 additions & 1 deletion geo/benches/contains.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use criterion::{criterion_group, criterion_main, Criterion};
use geo::contains::Contains;
use geo::{point, polygon, Line, Point, Polygon};
use geo::{point, polygon, Line, Point, Polygon, Triangle};

fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("point in simple polygon", |bencher| {
Expand Down Expand Up @@ -110,6 +110,24 @@ fn criterion_benchmark(c: &mut Criterion) {
);
});
});

c.bench_function("Triangle contains point", |bencher| {
let triangle = Triangle::from([(0., 0.), (10., 0.), (5., 10.)]);
let point = Point::new(5., 5.);

bencher.iter(|| {
assert!(criterion::black_box(&triangle).contains(criterion::black_box(&point)));
});
});

c.bench_function("Triangle contains point on edge", |bencher| {
let triangle = Triangle::from([(0., 0.), (10., 0.), (6., 10.)]);
let point = Point::new(3., 5.);

bencher.iter(|| {
assert!(!criterion::black_box(&triangle).contains(criterion::black_box(&point)));
});
});
}

criterion_group!(benches, criterion_benchmark);
Expand Down
69 changes: 69 additions & 0 deletions geo/benches/coordinate_position.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#[macro_use]
extern crate criterion;
extern crate geo;

use geo::{
coordinate_position::CoordPos, BoundingRect, Centroid, CoordinatePosition, Point, Rect,
Triangle,
};

use criterion::Criterion;

fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("Point position to rect", |bencher| {
let plot_centroids: Vec<Point> = geo_test_fixtures::nl_plots()
.iter()
.map(|plot| plot.centroid().unwrap())
.collect();
let zone_bbox: Vec<Rect> = geo_test_fixtures::nl_zones()
.iter()
.map(|plot| plot.bounding_rect().unwrap())
.collect();
bencher.iter(|| {
let mut inside = 0;
let mut outsied = 0;
let mut boundary = 0;

for a in &plot_centroids {
for b in &zone_bbox {
match criterion::black_box(b).coordinate_position(criterion::black_box(&a.0)) {
CoordPos::OnBoundary => boundary += 1,
CoordPos::Inside => inside += 1,
CoordPos::Outside => outsied += 1,
}
}
}

assert_eq!(inside, 2246);
assert_eq!(outsied, 26510);
assert_eq!(boundary, 0);
});
});

c.bench_function("Point in triangle", |bencher| {
let triangle = Triangle::from([(0., 0.), (10., 0.), (5., 10.)]);
let point = Point::new(5., 5.);

bencher.iter(|| {
assert!(
criterion::black_box(&triangle).coordinate_position(criterion::black_box(&point.0))
!= CoordPos::Outside
);
});
});

c.bench_function("Point on triangle boundary", |bencher| {
let triangle = Triangle::from([(0., 0.), (10., 0.), (6., 10.)]);
let point = Point::new(3., 5.);

bencher.iter(|| {
assert!(
criterion::black_box(&triangle).coordinate_position(criterion::black_box(&point.0))
== CoordPos::OnBoundary
);
});
});
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
101 changes: 100 additions & 1 deletion geo/benches/intersection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,110 @@ fn rect_intersection(c: &mut Criterion) {
});
}

fn point_rect_intersection(c: &mut Criterion) {
use geo::algorithm::{BoundingRect, Centroid};
use geo::geometry::{Point, Rect};
let plot_centroids: Vec<Point> = geo_test_fixtures::nl_plots()
.iter()
.map(|plot| plot.centroid().unwrap())
.collect();
let zone_bbox: Vec<Rect> = geo_test_fixtures::nl_zones()
.iter()
.map(|plot| plot.bounding_rect().unwrap())
.collect();

c.bench_function("Point intersects rect", |bencher| {
bencher.iter(|| {
let mut intersects = 0;
let mut non_intersects = 0;

for a in &plot_centroids {
for b in &zone_bbox {
if criterion::black_box(a.intersects(b)) {
intersects += 1;
} else {
non_intersects += 1;
}
}
}

assert_eq!(intersects, 2246);
assert_eq!(non_intersects, 26510);
});
});
}

fn point_triangle_intersection(c: &mut Criterion) {
use geo::{Centroid, TriangulateEarcut};
use geo_types::{Point, Triangle};
let plot_centroids: Vec<Point> = geo_test_fixtures::nl_plots()
.iter()
.map(|plot| plot.centroid().unwrap())
.collect();
let zone_triangles: Vec<Triangle> = geo_test_fixtures::nl_zones()
.iter()
.flat_map(|plot| plot.earcut_triangles_iter())
.collect();

c.bench_function("Point intersects triangle", |bencher| {
bencher.iter(|| {
let mut intersects = 0;
let mut non_intersects = 0;

for a in &plot_centroids {
for b in &zone_triangles {
if criterion::black_box(a.intersects(b)) {
intersects += 1;
} else {
non_intersects += 1;
}
}
}

assert_eq!(intersects, 533);
assert_eq!(non_intersects, 5450151);
});
});

c.bench_function("Triangle intersects point", |bencher| {
let triangle = Triangle::from([(0., 0.), (10., 0.), (5., 10.)]);
let point = Point::new(5., 5.);

bencher.iter(|| {
assert!(criterion::black_box(&triangle).intersects(criterion::black_box(&point)));
});
});

c.bench_function("Triangle intersects point on edge", |bencher| {
let triangle = Triangle::from([(0., 0.), (10., 0.), (6., 10.)]);
let point = Point::new(3., 5.);

bencher.iter(|| {
assert!(criterion::black_box(&triangle).intersects(criterion::black_box(&point)));
});
});
}

criterion_group! {
name = bench_multi_polygons;
config = Criterion::default().sample_size(10);
targets = multi_polygon_intersection
}
criterion_group!(bench_rects, rect_intersection);
criterion_group! {
name = bench_point_rect;
config = Criterion::default().sample_size(50);
targets = point_rect_intersection
}
criterion_group! {
name = bench_point_triangle;
config = Criterion::default().sample_size(50);
targets = point_triangle_intersection
}

criterion_main!(bench_multi_polygons, bench_rects);
criterion_main!(
bench_multi_polygons,
bench_rects,
bench_point_rect,
bench_point_triangle
);
30 changes: 26 additions & 4 deletions geo/src/algorithm/contains/triangle.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{impl_contains_from_relate, impl_contains_geometry_for, Contains};
use crate::geometry::*;
use crate::{GeoFloat, GeoNum};
use crate::{kernels::Kernel, GeoFloat, GeoNum, Orientation};

// ┌──────────────────────────────┐
// │ Implementations for Triangle │
Expand All @@ -11,9 +11,31 @@ where
T: GeoNum,
{
fn contains(&self, coord: &Coord<T>) -> bool {
let ls = LineString::new(vec![self.0, self.1, self.2, self.0]);
use crate::utils::{coord_pos_relative_to_ring, CoordPos};
coord_pos_relative_to_ring(*coord, &ls) == CoordPos::Inside
// leverageing robust predicates
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go with this impl. if it's correct, rather than the non-robust one.

For quick intro. to robust, you may want to refer this example that explains why it's important.

self.to_lines()
.map(|l| T::Ker::orient2d(l.start, l.end, *coord))
.windows(2)
.all(|win| win[0] == win[1] && win[0] != Orientation::Collinear)

// // neglecting robust prdicates, hence faster
// let p0x = self.0.x.to_f64().unwrap();
// let p0y = self.0.y.to_f64().unwrap();
// let p1x = self.1.x.to_f64().unwrap();
// let p1y = self.1.y.to_f64().unwrap();
// let p2x = self.2.x.to_f64().unwrap();
// let p2y = self.2.y.to_f64().unwrap();

// let px = coord.x.to_f64().unwrap();
// let py = coord.y.to_f64().unwrap();

// let a = 0.5 * (-p1y * p2x + p0y * (-p1x + p2x) + p0x * (p1y - p2y) + p1x * p2y);

// let sign = a.signum();

// let s = (p0y * p2x - p0x * p2y + (p2y - p0y) * px + (p0x - p2x) * py) * sign;
// let t = (p0x * p1y - p0y * p1x + (p0y - p1y) * px + (p1x - p0x) * py) * sign;

// s > 0. && t > 0. && (s + t) < 2. * a * sign
}
}

Expand Down
57 changes: 50 additions & 7 deletions geo/src/algorithm/coordinate_position.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::cmp::Ordering;

use crate::geometry::*;
use crate::intersects::value_in_between;
use crate::intersects::{point_in_rect, value_in_between};
use crate::kernels::*;
use crate::{BoundingRect, HasDimensions, Intersects};
use crate::{GeoNum, GeometryCow};
Expand Down Expand Up @@ -184,9 +186,20 @@ where
is_inside: &mut bool,
boundary_count: &mut usize,
) {
// PERF TODO: I'm sure there's a better way to calculate than converting to a polygon
self.to_polygon()
.calculate_coordinate_position(coord, is_inside, boundary_count);
*is_inside = self
.to_lines()
.map(|l| {
let orientation = T::Ker::orient2d(l.start, l.end, *coord);
if orientation == Orientation::Collinear
&& point_in_rect(*coord, l.start, l.end)
&& coord.x != l.end.x
{
*boundary_count += 1;
}
orientation
})
.windows(2)
.all(|win| win[0] == win[1] && win[0] != Orientation::Collinear);
}
}

Expand All @@ -201,9 +214,39 @@ where
is_inside: &mut bool,
boundary_count: &mut usize,
) {
// PERF TODO: I'm sure there's a better way to calculate than converting to a polygon
self.to_polygon()
.calculate_coordinate_position(coord, is_inside, boundary_count);
let mut boundary = false;

let min = self.min();

match coord.x.partial_cmp(&min.x).unwrap() {
Ordering::Less => return,
Ordering::Equal => boundary = true,
Ordering::Greater => {}
}
match coord.y.partial_cmp(&min.y).unwrap() {
Ordering::Less => return,
Ordering::Equal => boundary = true,
Ordering::Greater => {}
}

let max = self.max();

match max.x.partial_cmp(&coord.x).unwrap() {
Ordering::Less => return,
Ordering::Equal => boundary = true,
Ordering::Greater => {}
}
match max.y.partial_cmp(&coord.y).unwrap() {
Ordering::Less => return,
Ordering::Equal => boundary = true,
Ordering::Greater => {}
}

if boundary {
*boundary_count += 1;
} else {
*is_inside = true;
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions geo/src/algorithm/intersects/collections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ where
symmetric_intersects_impl!(Coord<T>, Geometry<T>);
symmetric_intersects_impl!(Line<T>, Geometry<T>);
symmetric_intersects_impl!(Rect<T>, Geometry<T>);
symmetric_intersects_impl!(Triangle<T>, Geometry<T>);
symmetric_intersects_impl!(Polygon<T>, Geometry<T>);

impl<T, G> Intersects<G> for GeometryCollection<T>
Expand All @@ -43,4 +44,5 @@ where
symmetric_intersects_impl!(Coord<T>, GeometryCollection<T>);
symmetric_intersects_impl!(Line<T>, GeometryCollection<T>);
symmetric_intersects_impl!(Rect<T>, GeometryCollection<T>);
symmetric_intersects_impl!(Triangle<T>, GeometryCollection<T>);
symmetric_intersects_impl!(Polygon<T>, GeometryCollection<T>);
10 changes: 10 additions & 0 deletions geo/src/algorithm/intersects/line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,13 @@ where
}
}
}

impl<T> Intersects<Triangle<T>> for Line<T>
where
T: GeoNum,
{
fn intersects(&self, rhs: &Triangle<T>) -> bool {
self.intersects(&rhs.to_polygon())
}
}
symmetric_intersects_impl!(Triangle<T>, Line<T>);
Loading
Loading