diff --git a/geo-types/src/lib.rs b/geo-types/src/lib.rs index 845943112..89350ee55 100644 --- a/geo-types/src/lib.rs +++ b/geo-types/src/lib.rs @@ -104,8 +104,8 @@ mod test { ])]; let p = Polygon::new(exterior.clone(), interiors.clone()); - assert_eq!(p.exterior, exterior); - assert_eq!(p.interiors, interiors); + assert_eq!(p.exterior(), &exterior); + assert_eq!(p.interiors(), &interiors[..]); } #[test] diff --git a/geo-types/src/line_string.rs b/geo-types/src/line_string.rs index 7e1d5fd8b..7142d5571 100644 --- a/geo-types/src/line_string.rs +++ b/geo-types/src/line_string.rs @@ -137,6 +137,17 @@ impl LineString { } }) } + + /// 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. + pub(crate) fn close(&mut self) { + if let (Some(first), Some(last)) = (self.0.first().map(|n| *n), self.0.last().map(|n| *n)) { + if first != last { + self.0.push(first); + } + } + } } /// Turn a `Vec` of `Point`-ish objects into a `LineString`. diff --git a/geo-types/src/polygon.rs b/geo-types/src/polygon.rs index 03fb3049c..c86e842cf 100644 --- a/geo-types/src/polygon.rs +++ b/geo-types/src/polygon.rs @@ -1,64 +1,361 @@ use num_traits::{Float, Signed}; use {CoordinateType, LineString, Point, Rect}; -/// A bounded 2D area. Its outer boundary (_shell_) is represented by a [`LineString`](struct.LineString.html) -/// that is both closed and simple (non-intersecting). It may contain 0 or more non-intersecting holes (_rings_), each represented by -/// a closed simple `LineString`. +/// A bounded two-dimensional area. /// -/// It has one exterior *ring* or *shell*, and zero or more interior rings, representing holes. +/// A `Polygon`’s outer boundary (_exterior ring_) is represented by a +/// [`LineString`]. It may contain zero or more holes (_interior rings_), also +/// represented by `LineString`s. /// -/// # Examples +/// The `Polygon` structure guarantees that all exterior and interior rings will +/// be _closed_, such that the first and last `Coordinate` of each ring has +/// the same value. /// -/// Polygons can be created from collections of `Point`-like objects, such as arrays or tuples: +/// # Validity /// -/// ``` -/// use geo_types::{Point, LineString, Polygon}; -/// let poly1 = Polygon::new(vec![[0., 0.], [10., 0.]].into(), vec![]); -/// let poly2 = Polygon::new(vec![(0., 0.), (10., 0.)].into(), vec![]); -/// ``` +/// Besides the closed `LineString` rings guarantee, the `Polygon` structure +/// does not enforce validity at this time. For example, it is possible to +/// construct a `Polygon` that has: +/// +/// - fewer than 3 coordinates per `LineString` ring +/// - interior rings that intersect other interior rings +/// - interior rings that extend beyond the exterior ring +/// +/// # `LineString` closing operation +/// +/// Some APIs on `Polygon` result in a closing operation on a `LineString`. The +/// operation is as follows: +/// +/// If a `LineString`’s first and last `Coordinate` have different values, a +/// new `Coordinate` will be appended to the `LineString` with a value equal to +/// the first `Coordinate`. +/// +/// [`LineString`]: struct.LineString.html #[derive(PartialEq, Clone, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Polygon where T: CoordinateType, { - pub exterior: LineString, - pub interiors: Vec>, + exterior: LineString, + interiors: Vec>, } impl Polygon where T: CoordinateType, { - /// Creates a new polygon. + /// Create a new `Polygon` with the provided exterior `LineString` ring and + /// interior `LineString` rings. + /// + /// Upon calling `new`, the exterior and interior `LineString` rings [will + /// be closed]. + /// + /// [will be closed]: #linestring-closing-operation /// /// # Examples /// + /// Creating a `Polygon` with no interior rings: + /// /// ``` - /// use geo_types::{Coordinate, LineString, Polygon}; + /// use geo_types::{LineString, Polygon}; + /// + /// let polygon = Polygon::new(LineString::from(vec![ + /// (0., 0.), + /// (1., 1.), + /// (1., 0.), + /// (0., 0.), + /// ]), vec![]); + /// ``` + /// + /// Creating a `Polygon` with an interior ring: + /// + /// ``` + /// use geo_types::{LineString, Polygon}; /// - /// let exterior = LineString(vec![ - /// Coordinate { x: 0., y: 0. }, - /// Coordinate { x: 1., y: 1. }, - /// Coordinate { x: 1., y: 0. }, - /// Coordinate { x: 0., y: 0. }, + /// let polygon = Polygon::new(LineString::from(vec![ + /// (0., 0.), + /// (1., 1.), + /// (1., 0.), + /// (0., 0.), + /// ]), vec![ + /// LineString::from(vec![ + /// (0.1, 0.1), + /// (0.9, 0.9), + /// (0.9, 0.1), + /// (0.1, 0.1), + /// ]) /// ]); - /// let interiors = vec![LineString(vec![ - /// Coordinate { x: 0.1, y: 0.1 }, - /// Coordinate { x: 0.9, y: 0.9 }, - /// Coordinate { x: 0.9, y: 0.1 }, - /// Coordinate { x: 0.1, y: 0.1 }, - /// ])]; - /// let p = Polygon::new(exterior.clone(), interiors.clone()); - /// assert_eq!(p.exterior, exterior); - /// assert_eq!(p.interiors, interiors); /// ``` - pub fn new(exterior: LineString, interiors: Vec>) -> Polygon { + /// + /// If the first and last `Coordinate`s of the exterior or interior + /// `LineString`s no longer match, those `LineString`s [will be closed]: + /// + /// ``` + /// use geo_types::{Coordinate, LineString, Polygon}; + /// + /// let mut polygon = Polygon::new(LineString::from(vec![ + /// (0., 0.), + /// (1., 1.), + /// (1., 0.), + /// ]), vec![]); + /// + /// assert_eq!(polygon.exterior(), &LineString::from(vec![ + /// (0., 0.), + /// (1., 1.), + /// (1., 0.), + /// (0., 0.), + /// ])); + /// ``` + pub fn new(mut exterior: LineString, mut interiors: Vec>) -> Polygon { + exterior.close(); + for interior in &mut interiors { + interior.close(); + } Polygon { exterior, interiors, } } + + /// Consume the `Polygon`, returning the exterior `LineString` ring and + /// a vector of the interior `LineString` rings. + /// + /// # Examples + /// + /// ``` + /// use geo_types::{LineString, Polygon}; + /// + /// let mut polygon = Polygon::new(LineString::from(vec![ + /// (0., 0.), + /// (1., 1.), + /// (1., 0.), + /// (0., 0.), + /// ]), vec![ + /// LineString::from(vec![ + /// (0.1, 0.1), + /// (0.9, 0.9), + /// (0.9, 0.1), + /// (0.1, 0.1), + /// ]) + /// ]); + /// + /// let (exterior, interiors) = polygon.into_inner(); + /// + /// assert_eq!(exterior, LineString::from(vec![ + /// (0., 0.), + /// (1., 1.), + /// (1., 0.), + /// (0., 0.), + /// ])); + /// + /// assert_eq!(interiors, vec![LineString::from(vec![ + /// (0.1, 0.1), + /// (0.9, 0.9), + /// (0.9, 0.1), + /// (0.1, 0.1), + /// ])]); + /// ``` + pub fn into_inner(self) -> (LineString, Vec>) { + (self.exterior, self.interiors) + } + + /// Return a reference to the exterior `LineString` ring. + /// + /// # Examples + /// + /// ``` + /// use geo_types::{LineString, Polygon}; + /// + /// let exterior = LineString::from(vec![ + /// (0., 0.), + /// (1., 1.), + /// (1., 0.), + /// (0., 0.), + /// ]); + /// + /// let polygon = Polygon::new(exterior.clone(), vec![]); + /// + /// assert_eq!(polygon.exterior(), &exterior); + /// ``` + pub fn exterior(&self) -> &LineString { + &self.exterior + } + + /// Execute the provided closure `f`, which is provided with a mutable + /// reference to the exterior `LineString` ring. + /// + /// After the closure executes, the exterior `LineString` [will be closed]. + /// + /// # Examples + /// + /// ``` + /// use geo_types::{Coordinate, LineString, Polygon}; + /// + /// let mut polygon = Polygon::new(LineString::from(vec![ + /// (0., 0.), + /// (1., 1.), + /// (1., 0.), + /// (0., 0.), + /// ]), vec![]); + /// + /// polygon.exterior_mut(|exterior| { + /// exterior.0[1] = Coordinate { x: 1., y: 2. }; + /// }); + /// + /// assert_eq!(polygon.exterior(), &LineString::from(vec![ + /// (0., 0.), + /// (1., 2.), + /// (1., 0.), + /// (0., 0.), + /// ])); + /// ``` + /// + /// If the first and last `Coordinate`s of the exterior `LineString` no + /// longer match, the `LineString` [will be closed]: + /// + /// ``` + /// use geo_types::{Coordinate, LineString, Polygon}; + /// + /// let mut polygon = Polygon::new(LineString::from(vec![ + /// (0., 0.), + /// (1., 1.), + /// (1., 0.), + /// (0., 0.), + /// ]), vec![]); + /// + /// polygon.exterior_mut(|exterior| { + /// exterior.0[0] = Coordinate { x: 0., y: 1. }; + /// }); + /// + /// assert_eq!(polygon.exterior(), &LineString::from(vec![ + /// (0., 1.), + /// (1., 1.), + /// (1., 0.), + /// (0., 0.), + /// (0., 1.), + /// ])); + /// ``` + /// + /// [will be closed]: #linestring-closing-operation + pub fn exterior_mut(&mut self, mut f: F) + where F: FnMut(&mut LineString) + { + f(&mut self.exterior); + self.exterior.close(); + } + + /// Return a slice of the interior `LineString` rings. + /// + /// # Examples + /// + /// ``` + /// use geo_types::{Coordinate, LineString, Polygon}; + /// + /// let interiors = vec![LineString::from(vec![ + /// (0.1, 0.1), + /// (0.9, 0.9), + /// (0.9, 0.1), + /// (0.1, 0.1), + /// ])]; + /// + /// let polygon = Polygon::new(LineString::from(vec![ + /// (0., 0.), + /// (1., 1.), + /// (1., 0.), + /// (0., 0.), + /// ]), interiors.clone()); + /// + /// assert_eq!(interiors, polygon.interiors()); + /// ``` + pub fn interiors(&self) -> &[LineString] { + &self.interiors + } + + /// Execute the provided closure `f`, which is provided with a mutable + /// reference to the interior `LineString` rings. + /// + /// After the closure executes, each of the interior `LineString`s [will be + /// closed]. + /// + /// # Examples + /// + /// ``` + /// use geo_types::{Coordinate, LineString, Polygon}; + /// + /// let mut polygon = Polygon::new(LineString::from(vec![ + /// (0., 0.), + /// (1., 1.), + /// (1., 0.), + /// (0., 0.), + /// ]), vec![ + /// LineString::from(vec![ + /// (0.1, 0.1), + /// (0.9, 0.9), + /// (0.9, 0.1), + /// (0.1, 0.1), + /// ]) + /// ]); + /// + /// polygon.interiors_mut(|interiors| { + /// interiors[0].0[1] = Coordinate { x: 0.8, y: 0.8 }; + /// }); + /// + /// assert_eq!(polygon.interiors(), &[ + /// LineString::from(vec![ + /// (0.1, 0.1), + /// (0.8, 0.8), + /// (0.9, 0.1), + /// (0.1, 0.1), + /// ]) + /// ]); + /// ``` + /// + /// If the first and last `Coordinate`s of any interior `LineString` no + /// longer match, those `LineString`s [will be closed]: + /// + /// ``` + /// use geo_types::{Coordinate, LineString, Polygon}; + /// + /// let mut polygon = Polygon::new(LineString::from(vec![ + /// (0., 0.), + /// (1., 1.), + /// (1., 0.), + /// (0., 0.), + /// ]), vec![ + /// LineString::from(vec![ + /// (0.1, 0.1), + /// (0.9, 0.9), + /// (0.9, 0.1), + /// (0.1, 0.1), + /// ]) + /// ]); + /// + /// polygon.interiors_mut(|interiors| { + /// interiors[0].0[0] = Coordinate { x: 0.1, y: 0.2 }; + /// }); + /// + /// assert_eq!(polygon.interiors(), &[ + /// LineString::from(vec![ + /// (0.1, 0.2), + /// (0.9, 0.9), + /// (0.9, 0.1), + /// (0.1, 0.1), + /// (0.1, 0.2), + /// ]) + /// ]); + /// ``` + /// + /// [will be closed]: #linestring-closing-operation + pub fn interiors_mut(&mut self, mut f: F) + where F: FnMut(&mut [LineString]) + { + f(&mut self.interiors); + for mut interior in &mut self.interiors { + interior.close(); + } + } + /// Wrap-around previous-vertex fn previous_vertex(&self, current_vertex: &usize) -> usize where diff --git a/geo/src/algorithm/area.rs b/geo/src/algorithm/area.rs index 4317a30cb..2fe801da3 100644 --- a/geo/src/algorithm/area.rs +++ b/geo/src/algorithm/area.rs @@ -27,7 +27,9 @@ where /// /// assert_eq!(polygon.area(), 30.); /// - /// polygon.exterior.0.reverse(); + /// polygon.exterior_mut(|line_string| { + /// line_string.0.reverse(); + /// }); /// /// assert_eq!(polygon.area(), -30.); /// ``` @@ -56,9 +58,9 @@ where T: Float, { fn area(&self) -> T { - self.interiors + self.interiors() .iter() - .fold(get_linestring_area(&self.exterior), |total, next| { + .fold(get_linestring_area(self.exterior()), |total, next| { total - get_linestring_area(next) }) } diff --git a/geo/src/algorithm/bounding_rect.rs b/geo/src/algorithm/bounding_rect.rs index 797876d74..5c230b2d8 100644 --- a/geo/src/algorithm/bounding_rect.rs +++ b/geo/src/algorithm/bounding_rect.rs @@ -102,7 +102,7 @@ where /// Return the BoundingRect for a Polygon /// fn bounding_rect(&self) -> Self::Output { - let line = &self.exterior; + let line = self.exterior(); get_bounding_rect(line.0.iter().cloned()) } } @@ -120,7 +120,7 @@ where get_bounding_rect( self.0 .iter() - .flat_map(|poly| (poly.exterior).0.iter().cloned()), + .flat_map(|poly| poly.exterior().0.iter().cloned()), ) } } diff --git a/geo/src/algorithm/centroid.rs b/geo/src/algorithm/centroid.rs index a2992065f..e829cbbef 100644 --- a/geo/src/algorithm/centroid.rs +++ b/geo/src/algorithm/centroid.rs @@ -146,7 +146,7 @@ where // See here for a formula: http://math.stackexchange.com/a/623849 // See here for detail on alternative methods: https://fotino.me/calculating-centroids/ fn centroid(&self) -> Self::Output { - let linestring = &self.exterior; + let linestring = &self.exterior(); let vect = &linestring.0; if vect.is_empty() { return None; @@ -154,14 +154,14 @@ where if vect.len() == 1 { Some(Point::new(vect[0].x, vect[0].y)) } else { - let external_centroid = simple_polygon_centroid(&self.exterior)?; - if self.interiors.is_empty() { + let external_centroid = simple_polygon_centroid(self.exterior())?; + if self.interiors().is_empty() { Some(external_centroid) } else { - let external_area = get_linestring_area(&self.exterior).abs(); + let external_area = get_linestring_area(self.exterior()).abs(); // accumulate interior Polygons let (totals_x, totals_y, internal_area) = self - .interiors + .interiors() .iter() .filter_map(|ring| { let area = get_linestring_area(ring).abs(); @@ -215,7 +215,7 @@ where sum_area_y = sum_area_y + area * p.y(); } else { // the polygon is 'flat', we consider it as a linestring - let ls_len = poly.exterior.euclidean_length(); + let ls_len = poly.exterior().euclidean_length(); if ls_len == T::zero() { sum_x = sum_x + p.x(); sum_y = sum_y + p.x(); diff --git a/geo/src/algorithm/closest_point.rs b/geo/src/algorithm/closest_point.rs index 55b69788c..0a69dbdb2 100644 --- a/geo/src/algorithm/closest_point.rs +++ b/geo/src/algorithm/closest_point.rs @@ -115,7 +115,7 @@ impl ClosestPoint for LineString { impl ClosestPoint for Polygon { fn closest_point(&self, p: &Point) -> Closest { - let prospectives = self.interiors.iter().chain(iter::once(&self.exterior)); + let prospectives = self.interiors().iter().chain(iter::once(self.exterior())); closest_of(prospectives, *p) } } @@ -264,12 +264,12 @@ mod tests { let poly = holy_polygon(); let p = Point::new(1000.0, 12345.6789); assert!( - !poly.exterior.contains(&p), + !poly.exterior().contains(&p), "`p` should be outside the polygon!" ); let poly_closest = poly.closest_point(&p); - let exterior_closest = poly.exterior.closest_point(&p); + let exterior_closest = poly.exterior().closest_point(&p); assert_eq!(poly_closest, exterior_closest); } @@ -277,7 +277,7 @@ mod tests { #[test] fn polygon_with_point_on_interior_ring() { let poly = holy_polygon(); - let p = poly.interiors[0].0[3]; + let p = poly.interiors()[0].0[3]; let should_be = Closest::Intersection(p.into()); let got = poly.closest_point(&p.into()); @@ -288,7 +288,7 @@ mod tests { #[test] fn polygon_with_point_near_interior_ring() { let poly = holy_polygon(); - let random_ring_corner = poly.interiors[0].0[3]; + let random_ring_corner = poly.interiors()[0].0[3]; let p = Point(random_ring_corner).translate(-3.0, 3.0); let should_be = Closest::SinglePoint(random_ring_corner.into()); diff --git a/geo/src/algorithm/contains.rs b/geo/src/algorithm/contains.rs index 465186b4b..238342d61 100644 --- a/geo/src/algorithm/contains.rs +++ b/geo/src/algorithm/contains.rs @@ -164,10 +164,10 @@ where T: Float, { fn contains(&self, p: &Point) -> bool { - match get_position(*p, &self.exterior) { + match get_position(*p, &self.exterior()) { PositionPoint::OnBoundary | PositionPoint::Outside => false, _ => self - .interiors + .interiors() .iter() .all(|ls| get_position(*p, ls) == PositionPoint::Outside), } @@ -192,8 +192,8 @@ where // does NOT intersect the exterior or any of the interior boundaries self.contains(&line.start_point()) && self.contains(&line.end_point()) - && !self.exterior.intersects(line) - && !self.interiors.iter().any(|inner| inner.intersects(line)) + && !self.exterior().intersects(line) + && !self.interiors().iter().any(|inner| inner.intersects(line)) } } @@ -203,7 +203,7 @@ where { fn contains(&self, poly: &Polygon) -> bool { // decompose poly's exterior ring into Lines, and check each for containment - poly.exterior.lines().all(|line| self.contains(&line)) + poly.exterior().lines().all(|line| self.contains(&line)) } } @@ -217,7 +217,7 @@ where // The Polygon interior is allowed to intersect with the LineString // but the Polygon's rings are not !self - .interiors + .interiors() .iter() .any(|ring| ring.intersects(linestring)) } else { @@ -404,18 +404,19 @@ mod test { fn point_polygon_with_inner_test() { let linestring = LineString::from(vec![(0., 0.), (2., 0.), (2., 2.), (0., 2.), (0., 0.)]); let inner_linestring = LineString::from(vec![ - (0.5, 0.5), - (1.5, 0.5), - (1.5, 1.5), - (0.0, 1.5), - (0.0, 0.0), + [0.5, 0.5], + [1.5, 0.5], + [1.5, 1.5], + [0.0, 1.5], + [0.0, 0.0], ]); let poly = Polygon::new(linestring, vec![inner_linestring]); - assert!(poly.contains(&Point::new(0.25, 0.25))); + assert!(!poly.contains(&Point::new(0.25, 0.25))); assert!(!poly.contains(&Point::new(1., 1.))); assert!(!poly.contains(&Point::new(1.5, 1.5))); assert!(!poly.contains(&Point::new(1.5, 1.))); } + /// Tests: Point in MultiPolygon #[test] fn empty_multipolygon_test() { diff --git a/geo/src/algorithm/convexhull.rs b/geo/src/algorithm/convexhull.rs index bf50195bb..ed6130884 100644 --- a/geo/src/algorithm/convexhull.rs +++ b/geo/src/algorithm/convexhull.rs @@ -126,7 +126,7 @@ pub trait ConvexHull { /// let correct_hull: LineString<_> = hull_coords.iter().map(|e| Point::new(e.0, e.1)).collect(); /// /// let res = poly.convex_hull(); - /// assert_eq!(res.exterior, correct_hull); + /// assert_eq!(res.exterior(), &correct_hull); /// ``` fn convex_hull(&self) -> Polygon where @@ -139,7 +139,7 @@ where { fn convex_hull(&self) -> Polygon { Polygon::new( - LineString::from(quick_hull(&mut self.exterior.clone().into_points())), + LineString::from(quick_hull(&mut self.exterior().clone().into_points())), vec![], ) } @@ -153,7 +153,7 @@ where let mut aggregated: Vec> = self .0 .iter() - .flat_map(|elem| elem.exterior.0.iter().map(|c| Point(*c))) + .flat_map(|elem| elem.exterior().0.iter().map(|c| Point(*c))) .collect(); Polygon::new(LineString::from(quick_hull(&mut aggregated)), vec![]) } @@ -328,7 +328,7 @@ mod test { Coordinate::from((0.0, -10.0)), ]; let res = mp.convex_hull(); - assert_eq!(res.exterior.0, correct); + assert_eq!(res.exterior().0, correct); } #[test] fn quick_hull_linestring_test() { @@ -352,7 +352,7 @@ mod test { Coordinate::from((0.0, -10.0)), ]; let res = mp.convex_hull(); - assert_eq!(res.exterior.0, correct); + assert_eq!(res.exterior().0, correct); } #[test] fn quick_hull_multilinestring_test() { @@ -367,7 +367,7 @@ mod test { Coordinate::from((2.0, 0.0)), ]; let res = mls.convex_hull(); - assert_eq!(res.exterior.0, correct); + assert_eq!(res.exterior().0, correct); } #[test] fn quick_hull_multipolygon_test() { @@ -384,6 +384,6 @@ mod test { Coordinate::from((5.0, 0.0)), ]; let res = mp.convex_hull(); - assert_eq!(res.exterior.0, correct); + assert_eq!(res.exterior().0, correct); } } diff --git a/geo/src/algorithm/euclidean_distance.rs b/geo/src/algorithm/euclidean_distance.rs index 9de411771..6821b2218 100644 --- a/geo/src/algorithm/euclidean_distance.rs +++ b/geo/src/algorithm/euclidean_distance.rs @@ -113,19 +113,19 @@ where /// Minimum distance from a Point to a Polygon fn euclidean_distance(&self, polygon: &Polygon) -> T { // No need to continue if the polygon contains the point, or is zero-length - if polygon.contains(self) || polygon.exterior.0.is_empty() { + if polygon.contains(self) || polygon.exterior().0.is_empty() { return T::zero(); } // fold the minimum interior ring distance if any, followed by the exterior // shell distance, returning the minimum of the two distances polygon - .interiors + .interiors() .iter() .map(|ring| self.euclidean_distance(ring)) .fold(T::max_value(), |accum, val| accum.min(val)) .min( polygon - .exterior + .exterior() .lines() .map(|line| { ::geo_types::private_utils::line_segment_distance( @@ -258,7 +258,7 @@ fn ring_contains_point(poly: &Polygon, p: Point) -> bool where T: Float, { - match get_position(p, &poly.exterior) { + match get_position(p, &poly.exterior()) { PositionPoint::Inside => true, PositionPoint::OnBoundary | PositionPoint::Outside => false, } @@ -294,15 +294,15 @@ where fn euclidean_distance(&self, other: &Polygon) -> T { if self.intersects(other) || other.contains(self) { T::zero() - } else if !other.interiors.is_empty() && ring_contains_point(other, Point(self.0[0])) { + } else if !other.interiors().is_empty() && ring_contains_point(other, Point(self.0[0])) { // check each ring distance, returning the minimum let mut mindist: T = Float::max_value(); - for ring in &other.interiors { + for ring in other.interiors() { mindist = mindist.min(nearest_neighbour_distance(self, ring)) } mindist } else { - nearest_neighbour_distance(self, &other.exterior) + nearest_neighbour_distance(self, &other.exterior()) } } } @@ -368,7 +368,7 @@ where } // point-line distance between each exterior polygon point and the line let exterior_min = other - .exterior + .exterior() .points_iter() .fold(::max_value(), |acc, point| { acc.min(self.euclidean_distance(&point)) @@ -376,7 +376,7 @@ where // point-line distance between each interior ring point and the line // if there are no rings this just evaluates to max_float let interior_min = other - .interiors + .interiors() .iter() .map(|ring| { ring.lines().fold(::max_value(), |acc, line| { @@ -414,25 +414,25 @@ where return T::zero(); } // Containment check - if !self.interiors.is_empty() && ring_contains_point(self, Point(poly2.exterior.0[0])) { + if !self.interiors().is_empty() && ring_contains_point(self, Point(poly2.exterior().0[0])) { // check each ring distance, returning the minimum let mut mindist: T = Float::max_value(); - for ring in &self.interiors { - mindist = mindist.min(nearest_neighbour_distance(&poly2.exterior, ring)) + for ring in self.interiors() { + mindist = mindist.min(nearest_neighbour_distance(&poly2.exterior(), ring)) } return mindist; - } else if !poly2.interiors.is_empty() - && ring_contains_point(poly2, Point(self.exterior.0[0])) + } else if !poly2.interiors().is_empty() + && ring_contains_point(poly2, Point(self.exterior().0[0])) { let mut mindist: T = Float::max_value(); - for ring in &poly2.interiors { - mindist = mindist.min(nearest_neighbour_distance(&self.exterior, ring)) + for ring in poly2.interiors() { + mindist = mindist.min(nearest_neighbour_distance(&self.exterior(), ring)) } return mindist; } if poly2.is_convex() || !self.is_convex() { // fall back to R* nearest neighbour method - nearest_neighbour_distance(&self.exterior, &poly2.exterior) + nearest_neighbour_distance(&self.exterior(), &poly2.exterior()) } else { min_poly_dist(self, poly2) } @@ -829,7 +829,7 @@ mod test { .collect::>(); let poly2 = Polygon::new(LineString::from(points2), vec![]); let dist = min_poly_dist(&poly1.convex_hull(), &poly2.convex_hull()); - let dist2 = nearest_neighbour_distance(&poly1.exterior, &poly2.exterior); + let dist2 = nearest_neighbour_distance(&poly1.exterior(), &poly2.exterior()); assert_eq!(dist, 21.0); assert_eq!(dist2, 21.0); } @@ -862,7 +862,7 @@ mod test { .collect::>(); let poly2 = Polygon::new(LineString::from(points2), vec![]); let dist = min_poly_dist(&poly1.convex_hull(), &poly2.convex_hull()); - let dist2 = nearest_neighbour_distance(&poly1.exterior, &poly2.exterior); + let dist2 = nearest_neighbour_distance(&poly1.exterior(), &poly2.exterior()); assert_eq!(dist, 29.274562336608895); assert_eq!(dist2, 29.274562336608895); } @@ -895,7 +895,7 @@ mod test { .collect::>(); let poly2 = Polygon::new(LineString::from(points2), vec![]); let dist = min_poly_dist(&poly1.convex_hull(), &poly2.convex_hull()); - let dist2 = nearest_neighbour_distance(&poly1.exterior, &poly2.exterior); + let dist2 = nearest_neighbour_distance(&poly1.exterior(), &poly2.exterior()); assert_eq!(dist, 12.0); assert_eq!(dist2, 12.0); } diff --git a/geo/src/algorithm/extremes.rs b/geo/src/algorithm/extremes.rs index 3b96429bd..2b8d326bc 100644 --- a/geo/src/algorithm/extremes.rs +++ b/geo/src/algorithm/extremes.rs @@ -73,7 +73,7 @@ fn polymax_naive_indices(u: Point, poly: &Polygon) -> Result where T: Float, { - let vertices = &poly.exterior.0; + let vertices = &poly.exterior().0; let mut max: usize = 0; for (i, _) in vertices.iter().enumerate() { // if vertices[i] is above prior vertices[max] @@ -169,10 +169,10 @@ where // safe to unwrap, since we're guaranteeing the polygon's convexity let indices = ch.extreme_indices().unwrap(); ExtremePoint { - ymin: Point(ch.exterior.0[indices.ymin]), - xmax: Point(ch.exterior.0[indices.xmax]), - ymax: Point(ch.exterior.0[indices.ymax]), - xmin: Point(ch.exterior.0[indices.xmin]), + ymin: Point(ch.exterior().0[indices.ymin]), + xmax: Point(ch.exterior().0[indices.xmax]), + ymax: Point(ch.exterior().0[indices.ymax]), + xmin: Point(ch.exterior().0[indices.xmin]), } } } diff --git a/geo/src/algorithm/from_postgis.rs b/geo/src/algorithm/from_postgis.rs index 496921225..20a35d2c6 100644 --- a/geo/src/algorithm/from_postgis.rs +++ b/geo/src/algorithm/from_postgis.rs @@ -46,10 +46,7 @@ where return None; } let exterior = rings.remove(0); - Some(Polygon { - exterior, - interiors: rings, - }) + Some(Polygon::new(exterior, rings)) } } impl<'a, T> FromPostgis<&'a T> for MultiPoint diff --git a/geo/src/algorithm/intersects.rs b/geo/src/algorithm/intersects.rs index c10f8e0c3..61f3ed199 100644 --- a/geo/src/algorithm/intersects.rs +++ b/geo/src/algorithm/intersects.rs @@ -123,8 +123,8 @@ where T: Float, { fn intersects(&self, p: &Polygon) -> bool { - p.exterior.intersects(self) - || p.interiors.iter().any(|inner| inner.intersects(self)) + p.exterior().intersects(self) + || p.interiors().iter().any(|inner| inner.intersects(self)) || p.contains(&self.start_point()) || p.contains(&self.end_point()) } @@ -177,9 +177,9 @@ where { fn intersects(&self, linestring: &LineString) -> bool { // line intersects inner or outer polygon edge - if self.exterior.intersects(linestring) + if self.exterior().intersects(linestring) || self - .interiors + .interiors() .iter() .any(|inner| inner.intersects(linestring)) { @@ -251,10 +251,10 @@ where { fn intersects(&self, polygon: &Polygon) -> bool { // self intersects (or contains) any line in polygon - self.intersects(&polygon.exterior) || - polygon.interiors.iter().any(|inner_line_string| self.intersects(inner_line_string)) || + self.intersects(polygon.exterior()) || + polygon.interiors().iter().any(|inner_line_string| self.intersects(inner_line_string)) || // self is contained inside polygon - polygon.intersects(&self.exterior) + polygon.intersects(self.exterior()) } } diff --git a/geo/src/algorithm/map_coords.rs b/geo/src/algorithm/map_coords.rs index f0cd8abc8..1764a63af 100644 --- a/geo/src/algorithm/map_coords.rs +++ b/geo/src/algorithm/map_coords.rs @@ -193,8 +193,11 @@ impl MapCoords for Polygon { fn map_coords(&self, func: &Fn(&(T, T)) -> (NT, NT)) -> Self::Output { Polygon::new( - self.exterior.map_coords(func), - self.interiors.iter().map(|l| l.map_coords(func)).collect(), + self.exterior().map_coords(func), + self.interiors() + .iter() + .map(|l| l.map_coords(func)) + .collect(), ) } } @@ -207,8 +210,8 @@ impl TryMapCoords for Polygon { func: &Fn(&(T, T)) -> Result<(NT, NT), Error>, ) -> Result { Ok(Polygon::new( - self.exterior.try_map_coords(func)?, - self.interiors + self.exterior().try_map_coords(func)?, + self.interiors() .iter() .map(|l| l.try_map_coords(func)) .collect::, Error>>()?, @@ -218,10 +221,15 @@ impl TryMapCoords for Polygon { impl MapCoordsInplace for Polygon { fn map_coords_inplace(&mut self, func: &Fn(&(T, T)) -> (T, T)) { - self.exterior.map_coords_inplace(func); - for p in &mut self.interiors { - p.map_coords_inplace(func); - } + self.exterior_mut(|line_string| { + line_string.map_coords_inplace(func); + }); + + self.interiors_mut(|line_strings| { + for line_string in line_strings { + line_string.map_coords_inplace(func); + } + }); } } diff --git a/geo/src/algorithm/orient.rs b/geo/src/algorithm/orient.rs index b397dacb6..2f3e2deff 100644 --- a/geo/src/algorithm/orient.rs +++ b/geo/src/algorithm/orient.rs @@ -26,8 +26,8 @@ pub trait Orient { /// let oriented_int_ls = LineString::from(oriented_int); /// // build corrected Polygon /// let oriented = poly.orient(Direction::Default); - /// assert_eq!(oriented.exterior.0, oriented_ext_ls.0); - /// assert_eq!(oriented.interiors[0].0, oriented_int_ls.0); + /// assert_eq!(oriented.exterior().0, oriented_ext_ls.0); + /// assert_eq!(oriented.interiors()[0].0, oriented_int_ls.0); /// ``` fn orient(&self, orientation: Direction) -> Self; } @@ -69,7 +69,7 @@ where T: CoordinateType, { let interiors = poly - .interiors + .interiors() .iter() .map(|l| { l.clone_to_winding_order(match direction { @@ -79,7 +79,7 @@ where }) .collect(); - let ext_ring = poly.exterior.clone_to_winding_order(match direction { + let ext_ring = poly.exterior().clone_to_winding_order(match direction { Direction::Default => WindingOrder::CounterClockwise, Direction::Reversed => WindingOrder::Clockwise, }); @@ -109,7 +109,7 @@ mod test { let oriented_int_ls = LineString::from(oriented_int_raw); // build corrected Polygon let oriented = orient(&poly1, Direction::Default); - assert_eq!(oriented.exterior.0, oriented_ext_ls.0); - assert_eq!(oriented.interiors[0].0, oriented_int_ls.0); + assert_eq!(oriented.exterior().0, oriented_ext_ls.0); + assert_eq!(oriented.interiors()[0].0, oriented_int_ls.0); } } diff --git a/geo/src/algorithm/polygon_distance_fast_path.rs b/geo/src/algorithm/polygon_distance_fast_path.rs index 5674e3f72..8725cd312 100644 --- a/geo/src/algorithm/polygon_distance_fast_path.rs +++ b/geo/src/algorithm/polygon_distance_fast_path.rs @@ -16,8 +16,8 @@ where { let poly1_extremes = poly1.extreme_indices().unwrap(); let poly2_extremes = poly2.extreme_indices().unwrap(); - let ymin1 = Point(poly1.exterior.0[poly1_extremes.ymin]); - let ymax2 = Point(poly2.exterior.0[poly2_extremes.ymax]); + let ymin1 = Point(poly1.exterior().0[poly1_extremes.ymin]); + let ymax2 = Point(poly2.exterior().0[poly2_extremes.ymax]); let mut state = Polydist { poly1, @@ -66,7 +66,7 @@ fn prev_vertex(poly: &Polygon, current_vertex: usize) -> usize where T: Float, { - (current_vertex + (poly.exterior.0.len() - 1) - 1) % (poly.exterior.0.len() - 1) + (current_vertex + (poly.exterior().0.len() - 1) - 1) % (poly.exterior().0.len() - 1) } /// Wrap-around next Polygon index @@ -74,7 +74,7 @@ fn next_vertex(poly: &Polygon, current_vertex: usize) -> usize where T: Float, { - (current_vertex + 1) % (poly.exterior.0.len() - 1) + (current_vertex + 1) % (poly.exterior().0.len() - 1) } #[derive(Debug)] @@ -126,8 +126,8 @@ where let sinsq = T::one() - cossq; let mut cos = T::zero(); let mut sin; - let pnext = poly.exterior.0[next_vertex(poly, idx)]; - let pprev = poly.exterior.0[prev_vertex(poly, idx)]; + let pnext = poly.exterior().0[next_vertex(poly, idx)]; + let pprev = poly.exterior().0[prev_vertex(poly, idx)]; let clockwise = Point(pprev).cross_prod(Point(p.0), Point(pnext)) < T::zero(); let slope_prev; let slope_next; @@ -320,8 +320,8 @@ where T: Float + FloatConst + Signed, { let hundred = T::from(100).unwrap(); - let pnext = poly.exterior.0[next_vertex(poly, idx)]; - let pprev = poly.exterior.0[prev_vertex(poly, idx)]; + let pnext = poly.exterior().0[next_vertex(poly, idx)]; + let pprev = poly.exterior().0[prev_vertex(poly, idx)]; let clockwise = Point(pprev).cross_prod(Point(p.0), Point(pnext)) < T::zero(); let punit; if !vertical { @@ -456,14 +456,14 @@ where if (state.ap1 - minangle).abs() < T::from(0.002).unwrap() { state.ip1 = true; let p1next = next_vertex(state.poly1, state.p1_idx); - state.p1next = Point(state.poly1.exterior.0[p1next]); + state.p1next = Point(state.poly1.exterior().0[p1next]); state.p1_idx = p1next; state.alignment = Some(AlignedEdge::VertexP); } if (state.aq2 - minangle).abs() < T::from(0.002).unwrap() { state.iq2 = true; let q2next = next_vertex(state.poly2, state.q2_idx); - state.q2next = Point(state.poly2.exterior.0[q2next]); + state.q2next = Point(state.poly2.exterior().0[q2next]); state.q2_idx = q2next; state.alignment = match state.alignment { None => Some(AlignedEdge::VertexQ), diff --git a/geo/src/algorithm/rotate.rs b/geo/src/algorithm/rotate.rs index a244c89a9..8a6b0eae4 100644 --- a/geo/src/algorithm/rotate.rs +++ b/geo/src/algorithm/rotate.rs @@ -165,14 +165,14 @@ where /// Rotate the Polygon about its centroid by the given number of degrees fn rotate(&self, angle: T) -> Self { // if a polygon has holes, use the centroid of its outer shell as the rotation origin - let centroid = if self.interiors.is_empty() { + let centroid = if self.interiors().is_empty() { self.centroid().unwrap() } else { - self.exterior.centroid().unwrap() + self.exterior().centroid().unwrap() }; Polygon::new( - rotate_many(angle, centroid, self.exterior.points_iter()).collect(), - self.interiors + rotate_many(angle, centroid, self.exterior().points_iter()).collect(), + self.interiors() .iter() .map(|ring| rotate_many(angle, centroid, ring.points_iter()).collect()) .collect(), @@ -309,8 +309,8 @@ mod test { Coordinate::from((5.672380059021509, 1.2114794859018578)), Coordinate::from((4.706454232732441, 1.4702985310043786)), ]; - assert_eq!(rotated.exterior.0, correct_outside); - assert_eq!(rotated.interiors[0].0, correct_inside); + assert_eq!(rotated.exterior().0, correct_outside); + assert_eq!(rotated.interiors()[0].0, correct_inside); } #[test] fn test_rotate_around_point_arbitrary() { diff --git a/geo/src/algorithm/simplify.rs b/geo/src/algorithm/simplify.rs index f6a2bd948..2c0880f45 100644 --- a/geo/src/algorithm/simplify.rs +++ b/geo/src/algorithm/simplify.rs @@ -93,8 +93,11 @@ where { fn simplify(&self, epsilon: &T) -> Polygon { Polygon::new( - self.exterior.simplify(epsilon), - self.interiors.iter().map(|l| l.simplify(epsilon)).collect(), + self.exterior().simplify(epsilon), + self.interiors() + .iter() + .map(|l| l.simplify(epsilon)) + .collect(), ) } } diff --git a/geo/src/algorithm/simplifyvw.rs b/geo/src/algorithm/simplifyvw.rs index b13652bbc..e696a4d6a 100644 --- a/geo/src/algorithm/simplifyvw.rs +++ b/geo/src/algorithm/simplifyvw.rs @@ -526,7 +526,7 @@ where min_points: 6, geomtype: GeomType::Ring, }; - let mut simplified = vwp_wrapper(>, &self.exterior, Some(&self.interiors), epsilon); + let mut simplified = vwp_wrapper(>, self.exterior(), Some(self.interiors()), epsilon); let exterior = LineString::from(simplified.remove(0)); let interiors = simplified.into_iter().map(LineString::from).collect(); Polygon::new(exterior, interiors) @@ -571,8 +571,8 @@ where { fn simplifyvw(&self, epsilon: &T) -> Polygon { Polygon::new( - self.exterior.simplifyvw(epsilon), - self.interiors + self.exterior().simplifyvw(epsilon), + self.interiors() .iter() .map(|l| l.simplifyvw(epsilon)) .collect(), @@ -696,7 +696,7 @@ mod test { ]); let poly = Polygon::new(outer.clone(), vec![inner]); let simplified = poly.simplifyvw_preserve(&95.4); - assert_eq!(simplified.exterior, outer); + assert_eq!(simplified.exterior(), &outer); } #[test] fn remove_inner_point_vwp_test() { @@ -727,8 +727,8 @@ mod test { ]); let poly = Polygon::new(outer.clone(), vec![inner]); let simplified = poly.simplifyvw_preserve(&95.4); - assert_eq!(simplified.exterior, outer); - assert_eq!(simplified.interiors[0], correct_inner); + assert_eq!(simplified.exterior(), &outer); + assert_eq!(simplified.interiors()[0], correct_inner); } #[test] fn very_long_vwp_test() { diff --git a/geo/src/algorithm/to_postgis.rs b/geo/src/algorithm/to_postgis.rs index 1ef4dc9e9..d05734be3 100644 --- a/geo/src/algorithm/to_postgis.rs +++ b/geo/src/algorithm/to_postgis.rs @@ -41,8 +41,8 @@ impl ToPostgis for Line { } impl ToPostgis for Polygon { fn to_postgis_with_srid(&self, srid: Option) -> ewkb::Polygon { - let rings = ::std::iter::once(&self.exterior) - .chain(self.interiors.iter()) + let rings = ::std::iter::once(self.exterior()) + .chain(self.interiors().iter()) .map(|x| (*x).to_postgis_with_srid(srid)) .collect(); ewkb::Polygon { rings, srid } diff --git a/geo/src/algorithm/translate.rs b/geo/src/algorithm/translate.rs index ac1fc50b3..8ad005041 100644 --- a/geo/src/algorithm/translate.rs +++ b/geo/src/algorithm/translate.rs @@ -145,7 +145,7 @@ mod test { Coordinate::from((23.0, 19.3)), Coordinate::from((22.0, 19.3)), ]; - assert_eq!(rotated.exterior.0, correct_outside); - assert_eq!(rotated.interiors[0].0, correct_inside); + assert_eq!(rotated.exterior().0, correct_outside); + assert_eq!(rotated.interiors()[0].0, correct_inside); } }