From e0a23acc281fadad267c1bc21f82448b1e811735 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Fri, 8 May 2020 16:27:04 -0400 Subject: [PATCH] Add more implementations for Contains algorithm. --- geo/src/algorithm/contains.rs | 274 ++++++++++++++++++------ geo/src/algorithm/euclidean_distance.rs | 9 +- geo/src/utils.rs | 68 ++++++ 3 files changed, 278 insertions(+), 73 deletions(-) diff --git a/geo/src/algorithm/contains.rs b/geo/src/algorithm/contains.rs index b6733a602..12d2d2906 100644 --- a/geo/src/algorithm/contains.rs +++ b/geo/src/algorithm/contains.rs @@ -1,8 +1,10 @@ use num_traits::Float; use crate::algorithm::intersects::Intersects; +use crate::utils; use crate::{ - Coordinate, CoordinateType, Line, LineString, MultiPolygon, Point, Polygon, Rect, Triangle, + Coordinate, CoordinateType, Geometry, GeometryCollection, Line, LineString, MultiLineString, + MultiPoint, MultiPolygon, Point, Polygon, Rect, Triangle, }; /// Checks if the geometry A is completely inside the B geometry @@ -30,6 +32,19 @@ pub trait Contains { fn contains(&self, rhs: &Rhs) -> bool; } +// ┌───────────────────────────┐ +// │ Implementations for Point │ +// └───────────────────────────┘ + +impl Contains> for Point +where + T: Float, +{ + fn contains(&self, coord: &Coordinate) -> bool { + self.contains(&Point(*coord)) + } +} + impl Contains> for Point where T: Float, @@ -39,12 +54,38 @@ where } } -impl Contains> for LineString +// ┌────────────────────────────────┐ +// │ Implementations for MultiPoint │ +// └────────────────────────────────┘ + +impl Contains> for MultiPoint where T: Float, { - fn contains(&self, p: &Point) -> bool { - ::geo_types::private_utils::line_string_contains_point(self, *p) + fn contains(&self, coord: &Coordinate) -> bool { + self.0.iter().any(|point| point.contains(coord)) + } +} + +impl Contains> for MultiPoint +where + T: Float, +{ + fn contains(&self, point: &Point) -> bool { + self.contains(&point.0) + } +} + +// ┌──────────────────────────┐ +// │ Implementations for Line │ +// └──────────────────────────┘ + +impl Contains> for Line +where + T: Float, +{ + fn contains(&self, coord: &Coordinate) -> bool { + self.contains(&Point(*coord)) } } @@ -75,6 +116,28 @@ where } } +// ┌────────────────────────────────┐ +// │ Implementations for LineString │ +// └────────────────────────────────┘ + +impl Contains> for LineString +where + T: Float, +{ + fn contains(&self, coord: &Coordinate) -> bool { + self.contains(&Point(*coord)) + } +} + +impl Contains> for LineString +where + T: Float, +{ + fn contains(&self, p: &Point) -> bool { + ::geo_types::private_utils::line_string_contains_point(self, *p) + } +} + impl Contains> for LineString where T: Float, @@ -109,77 +172,52 @@ where } } -/// The position of a `Point` with respect to a `LineString` -#[derive(PartialEq, Clone, Debug)] -pub(crate) enum PositionPoint { - OnBoundary, - Inside, - Outside, -} +// ┌─────────────────────────────────────┐ +// │ Implementations for MultiLineString │ +// └─────────────────────────────────────┘ -/// Calculate the position of `Point` p relative to a linestring -pub(crate) fn get_position(p: Point, linestring: &LineString) -> PositionPoint +impl Contains> for MultiLineString where T: Float, { - // See: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html - // http://geospatialpython.com/search - // ?updated-min=2011-01-01T00:00:00-06:00&updated-max=2012-01-01T00:00:00-06:00&max-results=19 - - // LineString without points - if linestring.0.is_empty() { - return PositionPoint::Outside; - } - // Point is on linestring - if linestring.contains(&p) { - return PositionPoint::OnBoundary; - } - - let mut xints = T::zero(); - let mut crossings = 0; - for line in linestring.lines() { - if p.y() > line.start.y.min(line.end.y) - && p.y() <= line.start.y.max(line.end.y) - && p.x() <= line.start.x.max(line.end.x) - { - if line.start.y != line.end.y { - xints = (p.y() - line.start.y) * (line.end.x - line.start.x) - / (line.end.y - line.start.y) - + line.start.x; - } - if (line.start.x == line.end.x) || (p.x() <= xints) { - crossings += 1; - } - } + fn contains(&self, coord: &Coordinate) -> bool { + self.0.iter().any(|line_string| line_string.contains(coord)) } - if crossings % 2 == 1 { - PositionPoint::Inside - } else { - PositionPoint::Outside +} + +impl Contains> for MultiLineString +where + T: Float, +{ + fn contains(&self, point: &Point) -> bool { + self.contains(&point.0) } } -impl Contains> for Polygon +// ┌─────────────────────────────┐ +// │ Implementations for Polygon │ +// └─────────────────────────────┘ + +impl Contains> for Polygon where T: Float, { - fn contains(&self, p: &Point) -> bool { - match get_position(*p, &self.exterior()) { - PositionPoint::OnBoundary | PositionPoint::Outside => false, - _ => self - .interiors() - .iter() - .all(|ls| get_position(*p, ls) == PositionPoint::Outside), + fn contains(&self, coord: &Coordinate) -> bool { + match utils::coord_pos_relative_to_line_string(*coord, &self.exterior()) { + utils::CoordPos::OnBoundary | utils::CoordPos::Outside => false, + _ => self.interiors().iter().all(|ls| { + utils::coord_pos_relative_to_line_string(*coord, ls) == utils::CoordPos::Outside + }), } } } -impl Contains> for MultiPolygon +impl Contains> for Polygon where T: Float, { fn contains(&self, p: &Point) -> bool { - self.0.iter().any(|poly| poly.contains(p)) + self.contains(&p.0) } } @@ -226,15 +264,50 @@ where } } +// ┌──────────────────────────────────┐ +// │ Implementations for MultiPolygon │ +// └──────────────────────────────────┘ + +impl Contains> for MultiPolygon +where + T: Float, +{ + fn contains(&self, coord: &Coordinate) -> bool { + self.0.iter().any(|poly| poly.contains(coord)) + } +} + +impl Contains> for MultiPolygon +where + T: Float, +{ + fn contains(&self, p: &Point) -> bool { + self.contains(&p.0) + } +} + +// ┌──────────────────────────┐ +// │ Implementations for Rect │ +// └──────────────────────────┘ + +impl Contains> for Rect +where + T: CoordinateType, +{ + fn contains(&self, coord: &Coordinate) -> bool { + coord.x >= self.min().x + && coord.x <= self.max().x + && coord.y >= self.min().y + && coord.y <= self.max().y + } +} + impl Contains> for Rect where T: CoordinateType, { fn contains(&self, p: &Point) -> bool { - p.x() >= self.min().x - && p.x() <= self.max().x - && p.y() >= self.min().y - && p.y() <= self.max().y + self.contains(&p.0) } } @@ -251,28 +324,91 @@ where } } +// ┌──────────────────────────────┐ +// │ Implementations for Triangle │ +// └──────────────────────────────┘ + +impl Contains> for Triangle +where + T: CoordinateType, +{ + fn contains(&self, coord: &Coordinate) -> bool { + let sign_1 = utils::sign(coord, &self.0, &self.1); + let sign_2 = utils::sign(coord, &self.1, &self.2); + let sign_3 = utils::sign(coord, &self.2, &self.0); + + (sign_1 == sign_2) && (sign_2 == sign_3) + } +} + impl Contains> for Triangle where T: CoordinateType, { fn contains(&self, point: &Point) -> bool { - let sign_1 = sign(&point.0, &self.0, &self.1); - let sign_2 = sign(&point.0, &self.1, &self.2); - let sign_3 = sign(&point.0, &self.2, &self.0); + self.contains(&point.0) + } +} - ((sign_1 == sign_2) && (sign_2 == sign_3)) +// ┌──────────────────────────────┐ +// │ Implementations for Geometry │ +// └──────────────────────────────┘ + +impl Contains> for Geometry +where + T: Float, +{ + fn contains(&self, coord: &Coordinate) -> bool { + match self { + Geometry::Point(g) => g.contains(coord), + Geometry::Line(g) => g.contains(coord), + Geometry::LineString(g) => g.contains(coord), + Geometry::Polygon(g) => g.contains(coord), + Geometry::MultiPoint(g) => g.contains(coord), + Geometry::MultiLineString(g) => g.contains(coord), + Geometry::MultiPolygon(g) => g.contains(coord), + Geometry::GeometryCollection(g) => g.contains(coord), + Geometry::Rect(g) => g.contains(coord), + Geometry::Triangle(g) => g.contains(coord), + } } } -fn sign(point_1: &Coordinate, point_2: &Coordinate, point_3: &Coordinate) -> bool +impl Contains> for Geometry where - T: CoordinateType, + T: Float, +{ + fn contains(&self, point: &Point) -> bool { + self.contains(&point.0) + } +} + +// ┌────────────────────────────────────────┐ +// │ Implementations for GeometryCollection │ +// └────────────────────────────────────────┘ + +impl Contains> for GeometryCollection +where + T: Float, +{ + fn contains(&self, coord: &Coordinate) -> bool { + self.0.iter().any(|geometry| geometry.contains(coord)) + } +} + +impl Contains> for GeometryCollection +where + T: Float, { - (point_1.x - point_3.x) * (point_2.y - point_3.y) - - (point_2.x - point_3.x) * (point_1.y - point_3.y) - < T::zero() + fn contains(&self, point: &Point) -> bool { + self.contains(&point.0) + } } +// ┌───────┐ +// │ Tests │ +// └───────┘ + #[cfg(test)] mod test { use crate::algorithm::contains::Contains; diff --git a/geo/src/algorithm/euclidean_distance.rs b/geo/src/algorithm/euclidean_distance.rs index e0cff6a11..b3fe5f01e 100644 --- a/geo/src/algorithm/euclidean_distance.rs +++ b/geo/src/algorithm/euclidean_distance.rs @@ -1,7 +1,8 @@ -use crate::algorithm::contains::{get_position, Contains, PositionPoint}; +use crate::algorithm::contains::Contains; use crate::algorithm::euclidean_length::EuclideanLength; use crate::algorithm::intersects::Intersects; use crate::algorithm::polygon_distance_fast_path::*; +use crate::utils::{coord_pos_relative_to_line_string, CoordPos}; use crate::{ Line, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, Triangle, }; @@ -260,9 +261,9 @@ fn ring_contains_point(poly: &Polygon, p: Point) -> bool where T: Float, { - match get_position(p, &poly.exterior()) { - PositionPoint::Inside => true, - PositionPoint::OnBoundary | PositionPoint::Outside => false, + match coord_pos_relative_to_line_string(p.0, &poly.exterior()) { + CoordPos::Inside => true, + CoordPos::OnBoundary | CoordPos::Outside => false, } } diff --git a/geo/src/utils.rs b/geo/src/utils.rs index 0b7eb0a95..8bd09b1c2 100644 --- a/geo/src/utils.rs +++ b/geo/src/utils.rs @@ -1,5 +1,7 @@ //! Internal utility functions, types, and data structures. +use crate::contains::Contains; + /// Partition a mutable slice in-place so that it contains all elements for /// which `predicate(e)` is `true`, followed by all elements for which /// `predicate(e)` is `false`. Returns sub-slices to all predicated and @@ -72,6 +74,72 @@ pub fn partial_min(a: T, b: T) -> T { } } +/// The position of a `Coordinate` relative to a `LineString` +#[derive(PartialEq, Clone, Debug)] +pub enum CoordPos { + OnBoundary, + Inside, + Outside, +} + +/// Calculate the position of a `Coordinate` relative to a `LineString` +pub fn coord_pos_relative_to_line_string( + coord: crate::Coordinate, + linestring: &crate::LineString, +) -> CoordPos +where + T: num_traits::Float, +{ + // See: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html + // http://geospatialpython.com/search + // ?updated-min=2011-01-01T00:00:00-06:00&updated-max=2012-01-01T00:00:00-06:00&max-results=19 + + // LineString without points + if linestring.0.is_empty() { + return CoordPos::Outside; + } + // Point is on linestring + if linestring.contains(&coord) { + return CoordPos::OnBoundary; + } + + let mut xints = T::zero(); + let mut crossings = 0; + for line in linestring.lines() { + if coord.y > line.start.y.min(line.end.y) + && coord.y <= line.start.y.max(line.end.y) + && coord.x <= line.start.x.max(line.end.x) + { + if line.start.y != line.end.y { + xints = (coord.y - line.start.y) * (line.end.x - line.start.x) + / (line.end.y - line.start.y) + + line.start.x; + } + if (line.start.x == line.end.x) || (coord.x <= xints) { + crossings += 1; + } + } + } + if crossings % 2 == 1 { + CoordPos::Inside + } else { + CoordPos::Outside + } +} + +pub fn sign( + point_1: &crate::Coordinate, + point_2: &crate::Coordinate, + point_3: &crate::Coordinate, +) -> bool +where + T: crate::CoordinateType, +{ + (point_1.x - point_3.x) * (point_2.y - point_3.y) + - (point_2.x - point_3.x) * (point_1.y - point_3.y) + < T::zero() +} + #[cfg(test)] mod test { use super::{partial_max, partial_min};