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 is_closed for MultiLineString and fix is_closed for empty LineString #523

Merged
merged 8 commits into from
Oct 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion geo-types/CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# Changes

## 0.6.2
## Unreleased

* `Rect::new` automatically determines min/max points
* <https://github.com/georust/geo/pull/519>
* Add `MultiLineString::is_closed` method
* <https://github.com/georust/geo/pull/523>

## 0.6.1

Expand Down
18 changes: 15 additions & 3 deletions geo-types/src/line_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,13 @@ impl<T: CoordinateType> LineString<T> {
})
}

/// Close the `LineString`. Specifically, if the `LineString` has is at least one coordinate,
/// and the value of the first coordinate does not equal the value of the last coordinate, then
/// a new coordinate is added to the end with the value of the first coordinate.
/// Close the `LineString`. Specifically, if the `LineString` has at least one coordinate, and
/// the value of the first coordinate does not equal the value of the last coordinate, then a
/// new coordinate is added to the end with the value of the first coordinate.
pub fn close(&mut self) {
if !self.is_closed() {
// by definition, we treat empty LineString's as closed.
debug_assert!(self.0.len() > 0);
self.0.push(self.0[0]);
}
}
Expand Down Expand Up @@ -215,6 +217,16 @@ impl<T: CoordinateType> LineString<T> {
/// let line_string: LineString<f32> = coords.into_iter().collect();
/// assert!(line_string.is_closed());
/// ```
///
/// Note that we diverge from some libraries (JTS et al), which have a LinearRing type,
/// separate from LineString. Those libraries treat an empty LinearRing as closed, by
/// definition, while treating an empty LineString as open. Since we don't have a separate
/// LinearRing type, and use a LineString in its place, we adopt the JTS LinearRing `is_closed`
/// behavior in all places, that is, we consider an empty LineString as closed.
///
/// This is expected when used in the context of a Polygon.exterior and elswhere; And there
/// seems to be no reason to maintain the separate behavior for LineStrings used in
/// non-LinearRing contexts.
pub fn is_closed(&self) -> bool {
self.0.first() == self.0.last()
}
Expand Down
29 changes: 28 additions & 1 deletion geo-types/src/multi_line_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::iter::FromIterator;

/// A collection of
/// [`LineString`s](line_string/struct.LineString.html). Can
/// be created from a `Vec` of `LineString`s, or from an
/// be created from a `Vec` of `LineString`s or from an
/// Iterator which yields `LineString`s. Iterating over this
/// object yields the component `LineString`s.
///
Expand Down Expand Up @@ -34,6 +34,33 @@ pub struct MultiLineString<T>(pub Vec<LineString<T>>)
where
T: CoordinateType;

impl<T: CoordinateType> MultiLineString<T> {
/// True if the MultiLineString is empty or if all of its LineStrings are closed - see
/// [`LineString::is_closed`].
///
/// # Examples
///
/// ```
/// use geo_types::{MultiLineString, LineString, line_string};
///
/// let open_line_string: LineString<f32> = line_string![(x: 0., y: 0.), (x: 5., y: 0.)];
/// assert!(!MultiLineString(vec![open_line_string.clone()]).is_closed());
///
/// let closed_line_string: LineString<f32> = line_string![(x: 0., y: 0.), (x: 5., y: 0.), (x: 0., y: 0.)];
/// assert!(MultiLineString(vec![closed_line_string.clone()]).is_closed());
///
/// // MultiLineString is not closed if *any* of it's LineStrings are not closed
/// assert!(!MultiLineString(vec![open_line_string, closed_line_string]).is_closed());
///
/// // An empty MultiLineString is closed
/// assert!(MultiLineString::<f32>(vec![]).is_closed());
/// ```
pub fn is_closed(&self) -> bool {
// Note: Unlike JTS et al, we consider an empty MultiLineString as closed.
self.0.iter().all(LineString::is_closed)
}
}

impl<T: CoordinateType, ILS: Into<LineString<T>>> From<ILS> for MultiLineString<T> {
fn from(ls: ILS) -> Self {
MultiLineString(vec![ls.into()])
Expand Down
2 changes: 1 addition & 1 deletion geo/src/algorithm/is_convex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::{Coordinate, LineString};
///
/// - This definition is closely related to the notion
/// of [convexity of polygons][convex set]. In particular, a
/// [`Polygon`] is convex, if and only if its `exterior` is
/// [`Polygon`](crate::Polygon) is convex, if and only if its `exterior` is
/// convex, and `interiors` is empty.
///
/// - The [`ConvexHull`] algorithm always returns a strictly
Expand Down
2 changes: 1 addition & 1 deletion geo/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ where
//
// See: https://en.wikipedia.org/wiki/Point_in_polygon

assert!(linestring.is_closed());
debug_assert!(linestring.is_closed());
Copy link
Member Author

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.


// LineString without points
if linestring.0.is_empty() {
Expand Down