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

Robust ExtremePoint trait #505

Merged
merged 7 commits into from
Sep 8, 2020
Merged

Conversation

rmanoka
Copy link
Contributor

@rmanoka rmanoka commented Sep 4, 2020

This PR introduces IsConvex trait, and partially revamps the ExtremePoint trait.

  1. add IsConvex trait and documentation of predicates.

  2. add vector space ops in Coordinate. These were earlier
    available in Point, but it makes more sense to be
    available for Coordinate too.

  3. many minor doc improvements: explain corner cases,
    better references, fix spellings (collinear).

  4. make ExtremePoint robust

  5. Remove unneeded trait generic parameter T, and requirement T: Float

Things to revamp / discuss in ExtremePoint

  1. FIgure out what the trait is about. Should we really compute convex_hull in the impl?
  2. Replace Result<Extremes, ()> with Option

@rmanoka
Copy link
Contributor Author

rmanoka commented Sep 4, 2020

@urschrei @frewsxcv The Polygon type has an is_convex method that is somewhat buggy: is not robust, and handles collinear points in an unexpected manner (it allows collinear pts if oriented cw, but not otherwise). Also, the usual math. defn. of convex also requires that the polygon has no interior holes. Further, we can not directly make it robust as the kernels are part of geo crate, and not geo-types.

Should we remove that functionality from geo-types in favor of introducing it as a trait in geo? This way, we can add a more exhaustive list of (robust) predicates: is_strongly_ccw_convex is_strongly_cw_convex is_strongly_convex is_ccw_convex is_cw_convex is_convex.

It is only used in a couple of geo traits, and we can fix that. Is a breaking change though as it is a pub fn.

@michaelkirk
Copy link
Member

It is only used in a couple of geo traits, and we can fix that. Is a breaking change though as it is a pub fn.

This makes sense to me. It would be kinder to deprecate it, change the internal impls to use the robust versions, and remove it after some time has passed.

@rmanoka rmanoka mentioned this pull request Sep 7, 2020

/// Construct a point at the origin, that is, with both
/// `x` and `y` coordinates as zero.
pub fn zero() -> Self {
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 wanted this little method many times.

1. add `IsConvex` trait and documentation of predicates.

1. add vector space ops in `Coordinate`. These were earlier
available in `Point`, but it makes more sense to be
available for `Coordinate` too.

1. many minor doc improvements:  explain corner cases,
better references, fix spellings (collinear).

Oh, btw, please skip ci !
@rmanoka rmanoka force-pushed the fix/robust-extremes branch from 9922800 to 9ea0c90 Compare September 7, 2020 20:31
@rmanoka
Copy link
Contributor Author

rmanoka commented Sep 7, 2020

@michaelkirk @urschrei I'm quite confused about the ExtremePoint trait. The implementation seems to simply compute the coordinates with min-x, min-y, max-x, and max-y. However, for doing this, it seems to be running the convex hull algorithm! Note that convex hull is O(n logn) while just direct search would just be O(n), so I have no idea what is happening.

For now, I'm going to leave this trait as is, and just make a few calls robust. Some clarity on the purpose of this trait would be very helpful.

Impl `Div`, `Mul` by scalar for both `Coordinate` and
`Point`.
@rmanoka rmanoka marked this pull request as ready for review September 7, 2020 21:58
@urschrei
Copy link
Member

urschrei commented Sep 7, 2020

Two things:

  1. ExtremePoint was an add-on to ExtremeIndices, and the fact that it could be implemented generically is actually a big disadvantage here, as you indirectly point out: in many cases, there's no need to ensure convexity.
    2. When it comes to collinearity, are you referring to sequential collinear points, or within some epsilon? I ask because the geometry model (JTS and GEOS, again) allows you to assume no repeating adjacent points in a geometry. Of course, we kicked the can down the road by writing non-validating constructors because we were worried about performance…

edit: no more late-night posting.

@rmanoka
Copy link
Contributor Author

rmanoka commented Sep 8, 2020

@urschrei Got it. We currently use polymax_naive but could switch to the binary search version at some point. Still, we can't compute convex_hull just for this purpose, as we might as well just do a loop through instead. The binary search version could probably be provided as an independent algo. that expects (but does not verify) its input to be a convex linestring.

+ remove `Float` requirement
+ doc fixes for stable rust
+ remove `<T>` from `Extreme{Point,Indices}`
@rmanoka
Copy link
Contributor Author

rmanoka commented Sep 8, 2020

On a side note: the check whether to use the "rotating calipers" fast path in euclidean_distance.rs may be wrong:

if poly2.is_convex() || !self.is_convex()

Shouldn't it check if both are convex? It seems to want one of them to not be convex.

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.

LGTM!

All my feedback was optional.

Point::new(T::one(), T::zero()),
Point::new(T::zero(), T::one()),
Point::new(-T::one(), T::zero()),
let directions: Vec<Coordinate<_>> = vec![
Copy link
Member

Choose a reason for hiding this comment

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

no biggie so take it or leave it, but since this is fixed size we could make this an array instead of a vec.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think fixed size works here because it is not an iterator. We can re-visit this once we have the ForEachCoord trait, and simplify the logic here.

if allow_collinear {
None
} else {
Some((i, orientation))
Copy link
Member

Choose a reason for hiding this comment

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

A little clarity nit:

At this point, find_first_non_collinear is in fact collinear, right?

To me that reads like let not_a_dog = 🐕;

Would something like starting_orientation be clearer than find_first_non_collinear?

No biggie though, it just tripped me up as I was trying to understand.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, the first loop is in fact finding the first non-collinear orientation; we return collinear in that loop only if !allow_collinear, and as a fast exit path. The apparent inversion is unfortunately how find / find_map works: it shorts at the first Some() return from the closure.

+ deprecate `Polygon::is_convex` in favour of the robust
algorithm in geo

+ fix fast path condition in euclidean distance between
`Polygon`s

+ doc fixes
@rmanoka rmanoka force-pushed the fix/robust-extremes branch from 1e32bcb to f5d1349 Compare September 8, 2020 18:32
@rmanoka
Copy link
Contributor Author

rmanoka commented Sep 8, 2020

bors r=michaelkirk

@bors
Copy link
Contributor

bors bot commented Sep 8, 2020

Build succeeded:

@bors bors bot merged commit ea5ce7e into georust:master Sep 8, 2020
@rmanoka rmanoka deleted the fix/robust-extremes branch September 17, 2020 03:12
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.

3 participants