-
Notifications
You must be signed in to change notification settings - Fork 200
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
Use QuickHull implementation for convex hull #89
Conversation
Implementation based on: Barber, C. Bradford; Dobkin, David P.; Huhdanpaa, Hannu(1 December 1996) https://dx.doi.org/10.1145%2F235815.235821) Original paper here: http://www.cs.princeton.edu/~dpd/Papers/BarberDobkinHuhdanpaa.pdf
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work!
src/algorithm/convexhull.rs
Outdated
// this is a bit hairy | ||
for point in &to_retain.difference(&to_remove) | ||
.map(|&idx| points[idx]) | ||
.collect::<Vec<Point<T>>>() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let iter = points.iter()
.enumerate()
.filter(|(idx, _)| ![mix_x_idx, max_x_idx].contains(idx))
.map(|(_, p)| *p);
for point in iter {
And you don't need to_retain and to_remove (and much less allocations)
src/algorithm/convexhull.rs
Outdated
hull.push(p_a); | ||
hull.push(p_b); | ||
let mut left_set: Vec<Point<T>> = vec![]; | ||
let mut right_set: Vec<Point<T>> = vec![]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These type annotations should not be needed.
src/algorithm/convexhull.rs
Outdated
/// let res = poly.convex_hull(); | ||
/// assert_eq!(res.exterior, correct_hull); | ||
/// ``` | ||
fn convex_hull(&self) -> Self where T: Float; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
convex_hull must return a Polygon.
src/algorithm/convexhull.rs
Outdated
fn convex_hull(&self) -> Polygon<T> { | ||
Polygon::new(LineString(quick_hull(&self.exterior.0)), vec![]) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be great if more geometries are supported (I think at MultiPolygon, LineString and MultiLineString in particular).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like those can happen in follow up PRs, but would be good to have support for those other types
src/algorithm/convexhull.rs
Outdated
if points.len() < 4 { | ||
return points.to_vec(); | ||
} | ||
let mut hull: Vec<Point<T>> = vec![]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useless type annotation
src/algorithm/convexhull.rs
Outdated
} | ||
let mut hull: Vec<Point<T>> = vec![]; | ||
let mut min_x_idx = <usize>::min_value(); | ||
let mut max_x_idx = <usize>::min_value(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
simply use
let mut min_x_idx = 0;
let mut max_x_idx = 0;
src/algorithm/convexhull.rs
Outdated
let mut max_x_idx = <usize>::min_value(); | ||
let mut min_x = Float::max_value(); | ||
let mut max_x = Float::min_value(); | ||
let to_retain: BTreeSet<usize> = (0..points.len() - 1).collect(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can simply write
let to_retain: BTreeSet<_> = (0..points.len() - 1).collect();'
src/algorithm/convexhull.rs
Outdated
let mut min_x = Float::max_value(); | ||
let mut max_x = Float::min_value(); | ||
let to_retain: BTreeSet<usize> = (0..points.len() - 1).collect(); | ||
let mut to_remove: BTreeSet<usize> = BTreeSet::new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useless type annotation
src/algorithm/convexhull.rs
Outdated
} | ||
// recur | ||
hull_set(p_a, &furthest_point, &mut left_ap, hull); | ||
hull_set(&furthest_point, p_b, &mut left_pb, hull); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the hull.insert calls can have a squared effect because you push at the middle of a Vec (you then must shift n/2 elements). You can rewrite it just using hull.push
:
// doing the first part
let mut new_set: Vec<_> = set.iter().filter(|point| point_location(p_a, &furthest_point, point)).collect();
hull_set(p_a, &furthest_point, &mut new_set, hull);
// inserting the current point
hull.push(set[furthest_idx]);
// doing the second part, reusing the memory allocated for the first part
new_set.clear();
new_set.extends(set.iter().filter(|point| point_location(&furthest_point, p_b, point));
hull_set(&furthest_point, p_b, &mut new_set, hull);
and correct the beginning of the function and the insertion in quick_hull accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let mut new_set: Vec<_> = set.iter().filter(|point| point_location(p_a, &furthest_point, point)).collect();
gives me a reference, but I need the Point…
= note: expected type `&mut std::vec::Vec<types::Point<T>>`
= note: found type `&mut std::vec::Vec<&types::Point<T>>`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add .cloned()
after .iter()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to adapt this and #89 (comment), but it's failing to compile for me in about three different ways – can you send me a PR against https://github.com/urschrei/rust-geo/tree/convex_hull_quick?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/algorithm/convexhull.rs
Outdated
// this is a bit hairy | ||
for point in &to_retain.difference(&to_remove) | ||
.map(|&idx| points[idx]) | ||
.collect::<Vec<Point<T>>>() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this collect
call be removed? Seems like we could just iterate over the Point
s in the Iterator
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing the collect()
gives me a trait bound error:
the trait std::iter::Iterator is not implemented for &std::iter::Map<std::collections::btree_set::Difference<'_, usize>, [closure@convexhull.rs:73:14: 73:32 points:_]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you have to remove the &
after the for point in
src/algorithm/convexhull.rs
Outdated
} | ||
// recur | ||
hull_set(p_a, &furthest_point, &mut left_ap, hull); | ||
hull_set(&furthest_point, p_b, &mut left_pb, hull); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like these calls could be done concurrently? If so, something we can do later in a future PR. Though we don't really depend on any parallelism libraries like rayon yet, hmm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I've been messing about with Rayon to try to parallelise this. It'll be in a follow-up, if only for discussion, because we're not doing it anywhere yet…
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you must have a very big polygon for parallelism being interesting.
src/algorithm/convexhull.rs
Outdated
fn convex_hull(&self) -> Polygon<T> { | ||
Polygon::new(LineString(quick_hull(&self.exterior.0)), vec![]) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like those can happen in follow up PRs, but would be good to have support for those other types
I've covered the nits, but:
|
Amazing work, thanks @TeXitoi! |
For multipolygon, just aggregate all the exterior points of the outer polygons in a Vec. You need to give a mutable slice, so creating the Vec is needed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't really approve as I'm now a contributor, but that's to remove my request changes.
TODO: add some tests for them
- MultiPoint - LineString - MultiLineString - MultiPolygon
src/algorithm/convexhull.rs
Outdated
{ | ||
fn convex_hull(&self) -> Polygon<T> { | ||
let mut aggregated: Vec<Point<T>> = self.0.iter() | ||
.flat_map(|elem| elem.exterior.0.clone()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.flat_map(|elem| elem.exterior.0.iter().cloned()
src/algorithm/convexhull.rs
Outdated
{ | ||
fn convex_hull(&self) -> Polygon<T> { | ||
let mut aggregated: Vec<Point<T>> = self.0.iter() | ||
.flat_map(|elem| elem.0.clone()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great, nice job :)
@urschrei Anything else here or is this good to merge? |
Good to go here. Thanks! |
bors r+ |
Build succeeded |
This supersedes #87, for a number of reasons:
Ord
andEq
traitsThere's also a more robust test provided.