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

Add Concave Hull algorithm #480

Merged
merged 10 commits into from
Oct 7, 2020
Merged

Add Concave Hull algorithm #480

merged 10 commits into from
Oct 7, 2020

Conversation

JavedNissar
Copy link
Contributor

@JavedNissar JavedNissar commented Jul 20, 2020

As part of a-b-street/abstreet#198, I've been working on adding an implementation of the Concave Hull algorithm primarily inspired by https://github.com/mapbox/concaveman. This implementation doesn't completely copy concaveman but it does ape the notion of using R trees in order to avoid a linear search through the points although my use of R trees relies on a circular envelope rather than the rectangular envelope that concaveman seems to use.

@urschrei
Copy link
Member

Thanks for the addition, we'd be delighted to have it - let us know when you're ready for a review, or if you have any other questions in the mean time!

@JavedNissar JavedNissar force-pushed the concave-hull branch 2 times, most recently from 172187a to f079984 Compare August 9, 2020 01:07
@JavedNissar
Copy link
Contributor Author

@urschrei I think I'm close. Unfortunately, I'm struggling with setting up a search function for the R tree. I have the following:

impl<T, W> SelectionFunction<T> for SelectWithinDistanceOfGeomFunction<W>
where
    T: RTreeObject + EuclideanDistance<W, Line<W>>,
    W: Float + RTreeNum {
    fn should_unpack_parent(&self, envelope: &T::Envelope) -> bool {
        envelope.euclidean_distance(&self.geometry) < self.max_dist
    }

    fn should_unpack_leaf(&self, leaf: &T) -> bool {
        leaf.euclidean_distance(&self.geometry) < self.max_dist
    }
}

This results in the following compile error:

error[E0599]: no method named `euclidean_distance` found for reference `&<T as rstar::RTreeObject>::Envelope` in the current scope
   --> geo/src/algorithm/concavehull.rs:153:18
    |
153 |         envelope.euclidean_distance(&self.geometry) < self.max_dist
    |                  ^^^^^^^^^^^^^^^^^^ method not found in `&<T as rstar::RTreeObject>::Envelope`
    |
    = help: items from traits can only be used if the trait is implemented and in scope
note: `algorithm::euclidean_distance::EuclideanDistance` defines an item `euclidean_distance`, perhaps you need to implement it
   --> geo/src/algorithm/euclidean_distance.rs:17:1
    |
17  | pub trait EuclideanDistance<T, Rhs = Self> {

As you can imagine, EuclideanDistance isn't implemented on the envelope but it is implemented on the generic T. Is it possible to convert the envelope into T? If not, do you have an alternative formulation of the types and the implementation that would be better than this one? Any help would be appreciated.

@urschrei
Copy link
Member

@restitutororbis Just a brief comment as I'm pushed for time: not that I know of, but I'm not much of an authority on types – one possibility may be to use the Envelope's points (assuming it's an AABB) to construct a geo-types Rect instance, and use that?

@JavedNissar JavedNissar force-pushed the concave-hull branch 2 times, most recently from 40c57a3 to 3c7b3ba Compare August 19, 2020 04:09
@michaelkirk
Copy link
Member

@restitutororbis - were you able to work through the envelope type errors?

LMK if not, and I can take a look to see if I have any ideas.

@JavedNissar
Copy link
Contributor Author

JavedNissar commented Aug 21, 2020

@michaelkirk Unfortunately not, I instead took an alternative route where I use the distance from the center of the line which doesn't exactly match up with Concaveman but it's similar enough. If you want though, feel free to try to push forward on the custom search function.

@michaelkirk
Copy link
Member

Sure - which commit would best exemplify the currently faced issue?

@JavedNissar
Copy link
Contributor Author

@michaelkirk I'm confused. If you're talking about the custom search function that got overwritten when I adopted my new approach. Looking back on it, I probably should have just kept committing bit it's a bit late for that. Regardless, I can try to re-implement my approach over the weekend but that seems like a fool's errand to me since that didn't compile.

If you're talking about the current action points. All that's left is tests. The current implementation should work so it's just a matter of testing various usecases. I got some ideas for tests that I'll hopefully push up tonight.

@michaelkirk
Copy link
Member

I think I misunderstood. I was offering to help look into a compiler error, and wanted to know which sha to build from to see said error.

If you're no longer blocked by anything, I don't need to step on your toes. =)

@JavedNissar
Copy link
Contributor Author

@michaelkirk Oh, no worries. I think that was a misunderstanding more on my part than yours anyways. I'm not blocked on anything atm.

@JavedNissar JavedNissar force-pushed the concave-hull branch 4 times, most recently from da4184c to ae6480e Compare August 24, 2020 04:26
@JavedNissar JavedNissar marked this pull request as ready for review August 24, 2020 04:27
@JavedNissar
Copy link
Contributor Author

@urschrei Do you happen to have the time to review this any time soon? No worries if not.

@urschrei
Copy link
Member

If by soon you mean "this week", then yes!

Copy link
Member

@michaelkirk michaelkirk left a comment

Choose a reason for hiding this comment

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

Thanks for working on this @restitutororbis!

Some other folks might want to take a look, but I've left a first-round of feedback (some of it labeled as optional).

@@ -0,0 +1,150 @@
vec![
[5.227792433014884,59.468030338041025],
Copy link
Member

Choose a reason for hiding this comment

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

Did this fixture come from somewhere else or is this more so intended as a regression check?

I wonder if we should add a json parser to our devDependencies to make fixture wrangling easier/less error prone...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a regression check. I ran the algorithm against the Norway data and exported the output to determine if the algorithm has changed in the future.

@@ -0,0 +1,85 @@
use geo::algorithm::concavehull::ConcaveHull;
Copy link
Member

Choose a reason for hiding this comment

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

thanks for including an example!

}
}

pub trait ConcaveHull<T> {
Copy link
Member

@michaelkirk michaelkirk Aug 25, 2020

Choose a reason for hiding this comment

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

Could you please add a doc comment, including an example invocation, to this trait declaration.

Also, minor code organization nit, typically we order things like:

  1. trait declaration
  2. trait impls
  3. private helper functions
  4. test module

I think the idea is that often people's preferred documentation is the source code itself, so we should order it in a way that puts the more relevant usage info towards the type top.

So, could you move the private methods defined above this trait declaration to be after the trait's impls (just before mod test?

}
}

fn concave_hull<T>(points: Vec<Point<T>>, concavity: T) -> Vec<Point<T>>
Copy link
Member

Choose a reason for hiding this comment

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

Could you include a comment with a link to the version/line of the source inspiration for the implementation.

(I think maybe it's https://github.com/mapbox/concaveman/blob/54838e1/index.js#L11)?

This is helpful if we later find bugs or have questions about why things are a certain way.

Copy link
Member

Choose a reason for hiding this comment

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

seems like we could get rid of some more clones if this method would borrow points, I don't see a reason to move it.

}
while !line_queue.is_empty() {
let maybe_line = line_queue.pop_front();
if let Some(line) = maybe_line {
Copy link
Member

Choose a reason for hiding this comment

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

Alternatively:

while let Some(line) = line_queue.pop_front() {
    let edge_length = line.euclidean_length();
    ....

But take it or leave it.

let maybe_line = line_queue.pop_front();
if let Some(line) = maybe_line {
let edge_length = line.euclidean_length();
let dist = edge_length.clone() / concavity;
Copy link
Member

Choose a reason for hiding this comment

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

unnecessary clone

let edge_length = line.euclidean_length();
let dist = edge_length.clone() / concavity;
let possible_closest_point =
find_point_closest_to_line(interior_points_tree.clone(), line, dist);
Copy link
Member

@michaelkirk michaelkirk Aug 25, 2020

Choose a reason for hiding this comment

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

seems like we should borrow &interior_points_tree and remove the clone

T: Float + RTreeNum,
{
let w = line.euclidean_length() + max_dist.clone() + max_dist.clone();
let h = max_dist.clone() + max_dist.clone();
Copy link
Member

Choose a reason for hiding this comment

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

unnecessary clones in the preceding two lines

.collect();
let mut interior_points_tree: RTree<Point<T>> = RTree::bulk_load(interior_points.clone());

let mut concave_list: Vec<Point<T>> = Vec::default();
Copy link
Member

Choose a reason for hiding this comment

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

idiomatically, just vec![], like the line below.

let mut candidates = interior_points_tree
.locate_within_distance(line.centroid(), search_dist)
.peekable();
let peek = candidates.peek();
Copy link
Member

@michaelkirk michaelkirk Aug 25, 2020

Choose a reason for hiding this comment

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

would the rest of this method be cleaner implemented using candidates.min_by(...?

Though maybe it'd be less clean that way once you have to explicitly handle NaN... so take it or leave it.

@JavedNissar
Copy link
Contributor Author

@michaelkirk Thanks for the review! I'll address your feedback as soon as I can.

where
T: Float + RTreeNum,
{
let h = max_dist.add(max_dist.clone());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@michaelkirk Is there a way to avoid cloning here? When I take it out, I get the following error:

error[E0308]: mismatched types
   --> geo/src/algorithm/concavehull.rs:125:26
    |
117 | fn find_point_closest_to_line<T>(
    |                               - this type parameter
...
125 |     let h = max_dist.add(max_dist);
    |                          ^^^^^^^^
    |                          |
    |                          expected type parameter `T`, found `&T`
    |                          help: consider dereferencing the borrow: `*max_dist`
    |
    = note: expected type parameter `T`
                    found reference `&T`
    = help: type parameters must be constrained to match other types
    = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters

where
T: Float + RTreeNum,
{
let multipoint: MultiPoint<T> = points.clone().into();
Copy link
Contributor Author

@JavedNissar JavedNissar Aug 27, 2020

Choose a reason for hiding this comment

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

@michaelkirk Similarly, is there a way to avoid cloning here? Once again, the error is below:

error[E0277]: the trait bound `geo_types::Point<T>: std::convert::From<&std::vec::Vec<geo_types::Point<T>>>` is not satisfied
   --> geo/src/algorithm/concavehull.rs:151:44
    |
151 |     let multipoint: MultiPoint<T> = points.into();
    |                                            ^^^^ the trait `std::convert::From<&std::vec::Vec<geo_types::Point<T>>>` is not implemented for `geo_types::Point<T>`
    |
    = help: the following implementations were found:
              <geo_types::Point<T> as std::convert::From<(T, T)>>
              <geo_types::Point<T> as std::convert::From<[T; 2]>>
              <geo_types::Point<T> as std::convert::From<geo_types::Coordinate<T>>>
    = note: required because of the requirements on the impl of `std::convert::Into<geo_types::Point<T>>` for `&std::vec::Vec<geo_types::Point<T>>`
    = note: required because of the requirements on the impl of `std::convert::From<&std::vec::Vec<geo_types::Point<T>>>` for `geo_types::MultiPoint<T>`
    = note: required because of the requirements on the impl of `std::convert::Into<geo_types::MultiPoint<T>>` for `&std::vec::Vec<geo_types::Point<T>>`

Copy link
Member

Choose a reason for hiding this comment

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

I don't see an easy way to get rid of this one.

fn find_point_closest_to_line<T>(
interior_points_tree: &RTree<Point<T>>,
line: Line<T>,
max_dist: &T,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
max_dist: &T,
max_dist: T,

Copy link
Member

Choose a reason for hiding this comment

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

I've never used GH code "suggestion" feature... let's see if it works, I tried to suggest the way that I would write this bit. T is Float, so that means it's also Copy. I'd probably lean into that here and not borrow T at all.

where
T: Float + RTreeNum,
{
let h = max_dist.add(max_dist.clone());
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
let h = max_dist.add(max_dist.clone());
let h = max_dist + max_dist;

where
T: Float + RTreeNum,
{
let multipoint: MultiPoint<T> = points.clone().into();
Copy link
Member

Choose a reason for hiding this comment

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

I don't see an easy way to get rid of this one.

@@ -0,0 +1,418 @@
use crate::algorithm::convexhull::ConvexHull;
Copy link
Member

@michaelkirk michaelkirk Aug 27, 2020

Choose a reason for hiding this comment

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

one more nit: could you rename this file to concave_hull.rs to be in line with the rest of the crate?

use std::ops::Add;

pub trait ConcaveHull<T> {
/// Returns the concave hull of a Polygon.
Copy link
Member

@michaelkirk michaelkirk Aug 27, 2020

Choose a reason for hiding this comment

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

Thank you for adding some doc comments.

Since you've implemented this for more than just Polygons, maybe clearer to say something like:

Returns the concave hull of a Geometry.

Also bonus points if you can avoid using the term "concave hull" in your description. Maybe something like...

Returns a polygon which covers the geometry. Unlike convex hulls, which also cover their geometry, a concave hull does so while trying to further minimize its area by "shrink wrapping" around the geometry it covers.

Heh - do people know what "shrink wrapping" means? Anyway, good docs are tough, and somewhat subjective, so take it or leave it.

Copy link
Contributor Author

@JavedNissar JavedNissar Aug 28, 2020

Choose a reason for hiding this comment

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

What do you think about this?

/// Returns a polygon which covers a geometry. Unlike convex hulls, which also cover
/// their geometry, a concave hull does so while trying to further minimize its area by
/// constructing edges such that the exterior of the polygon incorporates points that would
/// be interior points in a convex hull.

Copy link
Member

Choose a reason for hiding this comment

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

👏 👏 👏

@JavedNissar JavedNissar force-pushed the concave-hull branch 2 times, most recently from c61c4b8 to fadc7a9 Compare August 28, 2020 04:09
@JavedNissar JavedNissar requested a review from rmanoka September 22, 2020 04:17
@JavedNissar
Copy link
Contributor Author

Hi @restitutororbis I've been using your branch and it works great. Many thanks for doing this!

I'm seeing errors at the moment in my project's CI. I don't think it's on my end, and not a big deal if you know about them already, but in case:

#31 118.9    Compiling geo v0.14.2 (https://github.com/RestitutorOrbis/geo.git?branch=concave-hull#6dabf3ab)
#31 119.8 error[E0308]: mismatched types
#31 119.8   --> /root/.cargo/git/checkouts/geo-afd961a39c52d1ab/6dabf3a/geo/src/algorithm/concave_hull.rs:75:22
#31 119.8    |
#31 119.8 75 |         concave_hull(&mut aggregated, concavity)
#31 119.8    |                      ^^^^^^^^^^^^^^^ expected slice, found struct `Vec`
#31 119.8    |
#31 119.8    = note: expected mutable reference `&mut [geo_types::Coordinate<T>]`
#31 119.8               found mutable reference `&mut Vec<geo_types::Point<T>>`

Full set of errors: https://gist.github.com/gak/e7b894726aa1a8ac70d3df85407bbde6

Please let me know if you need help with the PR.

Hi @gak! Thanks for reaching out, these flaws are definitely not your fault, I've been in the midst of a refactor and I pushed too soon. Try giving the latest revision a shot, it should pass your CI. Let me know if it doesn't.

@gak
Copy link

gak commented Sep 22, 2020

Thanks heaps for the fix, just had to rename the import and it worked again.

I realise a branch in a PR is a WIP and totally don't expect it to be always working. It was just a heads up. 👍

Copy link
Contributor

@rmanoka rmanoka left a comment

Choose a reason for hiding this comment

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

Had only one minor suggestion. Otherwise, lgtm.

Thanks for your contribution @restitutororbis . I'll give it a few days, to see if anyone else has suggestions, and otherwise merge the PR

}
})
.collect();
let mut interior_points_tree: RTree<Coordinate<T>> = RTree::bulk_load(interior_coords.clone());
Copy link
Contributor

Choose a reason for hiding this comment

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

interior_coords does not seem to be used after this; the clone can be removed.

Copy link
Member

@michaelkirk michaelkirk left a comment

Choose a reason for hiding this comment

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

I have some small feedback, but I think they should be quick changes, otherwise looks good!

_ => unreachable!(),
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Could you please run cargo fmt and push up any changes?

{
type Scalar = T;
fn concave_hull(&self, concavity: Self::Scalar) -> Polygon<Self::Scalar> {
let mut points: Vec<_> = self.exterior().points_iter().map(|point|{
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
let mut points: Vec<_> = self.exterior().points_iter().map(|point|{
let mut points: Vec<_> = self.exterior().0.clone();

Copy link
Member

Choose a reason for hiding this comment

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

(and delete the following 2 lines)

let mut aggregated: Vec<Coordinate<Self::Scalar>> = self
.0
.iter()
.flat_map(|elem| elem.exterior().0.iter().map(|c| *c))
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
.flat_map(|elem| elem.exterior().0.iter().map(|c| *c))
.flat_map(|elem| elem.exterior().0.clone()

{
type Scalar = T;
fn concave_hull(&self, concavity: Self::Scalar) -> Polygon<Self::Scalar> {
Polygon::new(concave_hull(&mut self.clone().0, concavity), vec![])
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Polygon::new(concave_hull(&mut self.clone().0, concavity), vec![])
Polygon::new(concave_hull(&mut self.0.clone(), concavity), vec![])

Pretty nitpicky - but since we only need the coords, we can clone just those, rather than cloning the entire Geometry.

let mut aggregated: Vec<Coordinate<T>> = self
.0
.iter()
.flat_map(|elem| elem.clone().0)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
.flat_map(|elem| elem.clone().0)
.flat_map(|elem| elem.0.clone())

@rmanoka
Copy link
Contributor

rmanoka commented Sep 24, 2020

There is one useful optimization that is possible in the beginning of this algo. The implementation of quick hull actually guarantees that unless there are at most three coords in the input, the coords slice is mutated so that the first n entries are the hull points, and the rest are the interior (were n is size of conv hull).

Since there's no concave-ness to add if we have < 4 points, we could handle that case and just return the conv hull. Then, the first part of figuring which are interior points becomes trivial (and we don't need the rtree for it).

The guarantee I've mentioned above of the qhull implementation is not documented, so I'm okay with not doing that optimization in this PR. Just noting this so we may get to it at a later point.

@JavedNissar
Copy link
Contributor Author

There is one useful optimization that is possible in the beginning of this algo. The implementation of quick hull actually guarantees that unless there are at most three coords in the input, the coords slice is mutated so that the first n entries are the hull points, and the rest are the interior (were n is size of conv hull).

Since there's no concave-ness to add if we have < 4 points, we could handle that case and just return the conv hull. Then, the first part of figuring which are interior points becomes trivial (and we don't need the rtree for it).

The guarantee I've mentioned above of the qhull implementation is not documented, so I'm okay with not doing that optimization in this PR. Just noting this so we may get to it at a later point.

I've implemented the early return on less than 4 coordinates but I didn't have any luck implementing the optimization with regard to using the earlier portion of coords as the hull. From looking at the one_flex test in particular with a debugger, I don't see the coords mutated that way.

@rmanoka
Copy link
Contributor

rmanoka commented Sep 28, 2020

@restitutororbis Sorry, I didn't consider some of the recursive calls; the observation doesn't hold in the current impl. Please ignore the suggestion.

@JavedNissar
Copy link
Contributor Author

JavedNissar commented Sep 28, 2020 via email

Copy link
Member

@michaelkirk michaelkirk left a comment

Choose a reason for hiding this comment

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

This LGTM - I'll wait a couple days before merging in case anyone else wants a final review.

Thanks for your contribution and your patience with the review process @restitutororbis!

@michaelkirk
Copy link
Member

bors r+

bors bot added a commit that referenced this pull request Sep 30, 2020
480: Add Concave Hull algorithm r=michaelkirk a=RestitutorOrbis

As part of a-b-street/abstreet#198, I've been working on adding an implementation of the Concave Hull algorithm primarily inspired by https://github.com/mapbox/concaveman. This implementation doesn't completely copy concaveman but it does ape the notion of using R trees in order to avoid a linear search through the points although my use of R trees relies on a circular envelope rather than the rectangular envelope that concaveman seems to use. 

Co-authored-by: Javed Nissar <javed.nissar@mail.utoronto.ca>
@bors
Copy link
Contributor

bors bot commented Sep 30, 2020

Build failed:

@michaelkirk
Copy link
Member

Do you know why this new test might be failing @restitutororbis?

from https://github.com/georust/geo/runs/1190445056

---- algorithm::concave_hull::test::concave_hull_norway_test stdout ----
thread 'algorithm::concave_hull::test::concave_hull_norway_test' panicked at 'assertion failed: `(left == right)`

@JavedNissar
Copy link
Contributor Author

JavedNissar commented Oct 6, 2020

Do you know why this new test might be failing @restitutororbis?

from https://github.com/georust/geo/runs/1190445056

---- algorithm::concave_hull::test::concave_hull_norway_test stdout ----
thread 'algorithm::concave_hull::test::concave_hull_norway_test' panicked at 'assertion failed: `(left == right)`

@michaelkirk It seems when I updated the impl, the Norway output changed as well. From looking at the SVG file, it doesn't seem visually distinct so I'm not entirely sure how concerned I should be about that. Do you think it would make sense to remove the Norway test? I was hoping it would be an effective regression test but it seems so brittle that any slight change to the algorithm perturbs it. For reference, from running git bisect, it seems to have been switching to coordinates from points that caused this breakage.

If you think it is fine as is, then the updated Norway test works, so you should be able to merge it in without issues.

Copy link
Member

@frewsxcv frewsxcv left a comment

Choose a reason for hiding this comment

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

thanks so much for adding this! none of my comments are blocking

/// and also uses ideas from the following paper:
/// www.iis.sinica.edu.tw/page/jise/2012/201205_10.pdf
///
/// # Example
Copy link
Member

Choose a reason for hiding this comment

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

let mut candidates = interior_coords_tree
.locate_within_distance(centroid_coord, search_dist)
.peekable();
let peek = candidates.peek();
Copy link
Member

Choose a reason for hiding this comment

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

if you add a ? here, you can avoid doing the big match below

Suggested change
let peek = candidates.peek();
let peek = candidates.peek()?;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I try to do this, I get borrow checker errors that I'll need to work through so I'm going to elect to not proceed with this one.

Comment on lines 204 to 210
.filter_map(|coord| {
if !hull_tree.contains(coord) {
Some(*coord)
} else {
None
}
})
Copy link
Member

Choose a reason for hiding this comment

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

i think this is the same?

Suggested change
.filter_map(|coord| {
if !hull_tree.contains(coord) {
Some(*coord)
} else {
None
}
})
.filter(|coord| !hull_tree.contains(coord))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for catching that! I've made a change to account for this.

@rmanoka
Copy link
Contributor

rmanoka commented Oct 7, 2020

@restitutororbis Thanks for this effort, and your patience! Let's leave the norway test in for now.

bors r+

@bors
Copy link
Contributor

bors bot commented Oct 7, 2020

Build succeeded:

@bors bors bot merged commit 580f4ec into georust:master Oct 7, 2020
@michaelkirk
Copy link
Member

Woohoo, thanks @restitutororbis!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants