-
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
Add is_closed
for MultiLineString and fix is_closed
for empty LineString
#523
Conversation
@@ -98,7 +98,7 @@ where | |||
// | |||
// See: https://en.wikipedia.org/wiki/Point_in_polygon | |||
|
|||
assert!(linestring.is_closed()); | |||
debug_assert!(linestring.is_closed()); |
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 not sure if this is controversial - we have some "undefined behavior" for invalid geometries, yet we don't (yet) have a way of asserting validity of geometries.
I think we should be a little more conservative about crashing in release builds.
// Officially, we require the exterior to be a valid ring, but this isn't curently enforced | ||
// by the Polygon contructor. We bail out here rather than trigger later debug asserts. | ||
if self.exterior().0.is_empty() { | ||
return false; |
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.
This is to avoid hitting the assert in coord_pos_relative_to_ring
.
Without this, a unit test exercises that path and fails.
There's arguably other approaches - like deleting that unit test. I think ultimately a more blessed path for validating geometries or perhaps having these method return Result types which return Err
where we're currently asserting. But I didn't particularly feel like taking that larger change on 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.
lgtm! should we consider this a breaking change since it's a change in logic for public methods? i don't feel strongly one way or the other, especially since things are probably gonna be broken in general for folks working with empty linestrings
I'd say no, because there's no change to the existing API, and the behavioral change is strictly a bug fix. And the behavior was even documented, though not implemented:
|
Though actually I have another PR coming up in a minute which is maybe a big enough new feature to bump the minor version so it's maybe a moot point. pre-1.0 semver is... "hard" to reason about. |
4a185b2
to
e7dfb7f
Compare
@michaelkirk the new logic doesn't consider an empty linestring as closed? I think this is not consistent with other libs. See for eg. JTS linear ring which accepts empty ones. Any reason for considering empty as not-closed? |
Thanks for taking a look @rmanoka. That's interesting.
Indeed - JTS allows empty LinearRings, and
And yet, looking a little deeper, JTS does the opposite for their public boolean isClosed() {
if (isEmpty()) {
return false;
}
return getCoordinateN(0).equals2D(getCoordinateN(getNumPoints() - 1));
} Since we don't have a LinearRing type, our LineString type sometimes does double duty as LinearRings. I guess I don't yet understand the consequences of having it behave like JTS's LineString or whether it should behave like JTS's LinearRing in all cases... I'm going to keep looking. |
@michaelkirk Interesting find on JTS LineString. I'm inclined to argue that LineString logic is a bug: SFS defines LinearRing as a closed and simple LineString (so empty is automatically a LinearRing). From SFS defn, I'm also hoping we can get away with LineString also proxying for LinearRing when closed. Can you think of any reason for requiring an empty LineString that's not a LinearRing? |
LineString#is_closed like a LinearRing.
Thanks for your thoughts there. I'm compelled!
After digging in more, I can see no reason for an empty LineString to behave differently from a LinearRing. Other than just "thinking about it", mainly my approach was to dig through the JTS code base and see where they used the method call. It seems like it wouldn't change any of the libraries internal functionality (though a unit test which specifically tests this behavior would fail). Thanks for catching this! PTAL @rmanoka |
geo-types/src/multi_line_string.rs
Outdated
/// assert!(!MultiLineString::<f32>(vec![]).is_closed()); | ||
/// ``` | ||
pub fn is_closed(&self) -> bool { | ||
if self.0.is_empty() { |
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.
Note this bit @rmanoka - it mirrors JTS's MultiLineString#is_closed, but seems to be on similarly shaky ground.
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.
@michaelkirk Ya, not a fan of returning false
if empty here either. Usually, it is mathematically neat to extend predicates on list that are defined as a for-all over elements in the list to be true on the empty list. See for instance std::Iterator::all. Since is_closed is such a predicate, I would have liked it to be true on empty list. I have no idea why JTS chose the other way. There is no MultiLinearRing in JTS, so I don't have a compelling argument either :( .
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.
Ultimately, I can't find a reason why it'd matter, so I'm willing to defer to your intuition here.
Ok I think I've addressed all your feedback @rmanoka. PTAL |
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.
LGTM! Thanks @michaelkirk
bors r=rmanoka |
Build succeeded: |
524: Geometry dimensions via HasDimensions trait r=rmanoka a=michaelkirk Some efforts extracted from the geomgraph work that seemed generally useful, and thus worth separating. (I'm also trying to do what I can to keep the upcoming geomgraph PR just a little bit smaller.) *Note this PR represents separate functionality from #523, but it relies on #523 for correct behavior, so I'm targeting #523 as the base branch. Before merging we should update the base to target master.* Co-authored-by: Michael Kirk <[email protected]>
639: Introduce the geomgraph module for DE-9IM Relate trait r=michaelkirk a=michaelkirk - [x] I agree to follow the project's [code of conduct](https://github.com/georust/geo/blob/master/CODE_OF_CONDUCT.md). - [x] I added an entry to `CHANGES.md` if knowledge of this change could be valuable to users. --- Fixes #513, #515 (I'm sorry it's so large) ~~I'm going to leave it as a draft (edit: 🤦 I failed to actually open the PR as a draft) while I wait to merge #636 and #638 and then do some rebasing, but I don't anticipate doing other large changes before review.~~ *update: ready for review!* Here's some of the earlier work in pursuit of this: #514 #516 #523 #524 #538 #552 #561 #611 #628 #629 #636 Primarily, this introduces the geomgraph module for a DE-9IM `Relate` trait. geomgraph implements a topology graph largely inspired by JTS's module of the same name: https://github.com/locationtech/jts/tree/jts-1.18.1/modules/core/src/main/java/org/locationtech/jts/geomgraph You can see some of the reference code if you omit the "REMOVE JTS COMMENTS" commit. In some places the implementation is quite close to the JTS source. The overall "flow" is pretty similar to that of JTS, but in the small, there were some divergences. It's not easy (or desirable) to literally translate a Java codebase making heavy use of inheritance and pointers to rust. Additionally, I chose to take advantage of `Option` and rust's enums with associated data to make some case analysis more explicit. There is a corresponding PR in our [jts-test-runner](georust/jts-test-runner#6) crate which includes the bulk of the tests for the new Relate trait. ## Algorithm Overview This functionality is accessed on geometries, via the `Relate` trait, e.g. `line.relate(point)` which returns a DE-9IM [`IntersectionMatrix`](https://en.wikipedia.org/wiki/DE-9IM#Matrix_model). The `Relate` trait is driven by the `RelateOperation`. The `RelateOperation` builds a `GeometryGraph` for each of the two geometries being related. A `GeometryGraph` is a systematic way to organize the "interesting" parts of a geometry's structure - e.g. where its vertices, lines, and areas lie relative to one another. Once the `RelateOperation` has built the two `GeometryGraph`s, it uses them to efficiently compare the two Geometries's structures, outputting the `IntesectionMatrix`. Co-authored-by: Michael Kirk <[email protected]> Co-authored-by: bors[bot] <26634292+bors[bot]@users.noreply.github.com>
639: Introduce the geomgraph module for DE-9IM Relate trait r=frewsxcv,rmanoka a=michaelkirk - [x] I agree to follow the project's [code of conduct](https://github.com/georust/geo/blob/master/CODE_OF_CONDUCT.md). - [x] I added an entry to `CHANGES.md` if knowledge of this change could be valuable to users. --- Fixes #513, #515 (I'm sorry it's so large) ~~I'm going to leave it as a draft (edit: 🤦 I failed to actually open the PR as a draft) while I wait to merge #636 and #638 and then do some rebasing, but I don't anticipate doing other large changes before review.~~ *update: ready for review!* Here's some of the earlier work in pursuit of this: #514 #516 #523 #524 #538 #552 #561 #611 #628 #629 #636 Primarily, this introduces the geomgraph module for a DE-9IM `Relate` trait. geomgraph implements a topology graph largely inspired by JTS's module of the same name: https://github.com/locationtech/jts/tree/jts-1.18.1/modules/core/src/main/java/org/locationtech/jts/geomgraph You can see some of the reference code if you omit the "REMOVE JTS COMMENTS" commit. In some places the implementation is quite close to the JTS source. The overall "flow" is pretty similar to that of JTS, but in the small, there were some divergences. It's not easy (or desirable) to literally translate a Java codebase making heavy use of inheritance and pointers to rust. Additionally, I chose to take advantage of `Option` and rust's enums with associated data to make some case analysis more explicit. There is a corresponding PR in our [jts-test-runner](georust/jts-test-runner#6) crate which includes the bulk of the tests for the new Relate trait. ## Algorithm Overview This functionality is accessed on geometries, via the `Relate` trait, e.g. `line.relate(point)` which returns a DE-9IM [`IntersectionMatrix`](https://en.wikipedia.org/wiki/DE-9IM#Matrix_model). The `Relate` trait is driven by the `RelateOperation`. The `RelateOperation` builds a `GeometryGraph` for each of the two geometries being related. A `GeometryGraph` is a systematic way to organize the "interesting" parts of a geometry's structure - e.g. where its vertices, lines, and areas lie relative to one another. Once the `RelateOperation` has built the two `GeometryGraph`s, it uses them to efficiently compare the two Geometries's structures, outputting the `IntesectionMatrix`. Co-authored-by: Michael Kirk <[email protected]> Co-authored-by: bors[bot] <26634292+bors[bot]@users.noreply.github.com>
Some commits extracted from my geomgraph work