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 more implementations for Contains algorithm. #451

Merged
merged 1 commit into from May 8, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
274 changes: 205 additions & 69 deletions 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
Expand Down Expand Up @@ -30,6 +32,19 @@ pub trait Contains<Rhs = Self> {
fn contains(&self, rhs: &Rhs) -> bool;
}

// ┌───────────────────────────┐
// │ Implementations for Point │
// └───────────────────────────┘

impl<T> Contains<Coordinate<T>> for Point<T>
where
T: Float,
{
fn contains(&self, coord: &Coordinate<T>) -> bool {
self.contains(&Point(*coord))
}
}

impl<T> Contains<Point<T>> for Point<T>
where
T: Float,
Expand All @@ -39,12 +54,38 @@ where
}
}

impl<T> Contains<Point<T>> for LineString<T>
// ┌────────────────────────────────┐
// │ Implementations for MultiPoint │
// └────────────────────────────────┘

impl<T> Contains<Coordinate<T>> for MultiPoint<T>
where
T: Float,
{
fn contains(&self, p: &Point<T>) -> bool {
::geo_types::private_utils::line_string_contains_point(self, *p)
fn contains(&self, coord: &Coordinate<T>) -> bool {
self.0.iter().any(|point| point.contains(coord))
}
}

impl<T> Contains<Point<T>> for MultiPoint<T>
where
T: Float,
{
fn contains(&self, point: &Point<T>) -> bool {
self.contains(&point.0)
}
}

// ┌──────────────────────────┐
// │ Implementations for Line │
// └──────────────────────────┘

impl<T> Contains<Coordinate<T>> for Line<T>
where
T: Float,
{
fn contains(&self, coord: &Coordinate<T>) -> bool {
self.contains(&Point(*coord))
}
}

Expand Down Expand Up @@ -75,6 +116,28 @@ where
}
}

// ┌────────────────────────────────┐
// │ Implementations for LineString │
// └────────────────────────────────┘

impl<T> Contains<Coordinate<T>> for LineString<T>
where
T: Float,
{
fn contains(&self, coord: &Coordinate<T>) -> bool {
self.contains(&Point(*coord))
}
}

impl<T> Contains<Point<T>> for LineString<T>
where
T: Float,
{
fn contains(&self, p: &Point<T>) -> bool {
Copy link
Member

Choose a reason for hiding this comment

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

This example I contrived fails:

use geo::algorithm::contains::Contains;
use geo::{LineString, Point};

let line_string = LineString::from(vec![(0., 0.), (3., 3.)]);
let point_on_line = Point::new(1., 1.);
assert!(line_string.contains(&point_on_line)); // <- 💥 currently fails

I don't think this was a change in behavior with this PR, but at least one point of comparison, postgis, agrees that we should change it so that my example passes:

psql> SELECT ST_Contains(
  ST_MakeLine(ST_MakePoint(0,0), ST_MakePoint(3,3)),
  ST_MakePoint(1,1)
);

st_contains 
-------------
 t

LMK what you think. If you agree that we should change it and want to punt on the implementation, I can take a look!

Copy link
Member Author

Choose a reason for hiding this comment

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

ooo good find. i'm going to merge this as-is, and open a separate issue for that

::geo_types::private_utils::line_string_contains_point(self, *p)
}
}

impl<T> Contains<Line<T>> for LineString<T>
where
T: Float,
Expand Down Expand Up @@ -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<T>(p: Point<T>, linestring: &LineString<T>) -> PositionPoint
impl<T> Contains<Coordinate<T>> for MultiLineString<T>
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<T>) -> bool {
self.0.iter().any(|line_string| line_string.contains(coord))
}
if crossings % 2 == 1 {
PositionPoint::Inside
} else {
PositionPoint::Outside
}

impl<T> Contains<Point<T>> for MultiLineString<T>
where
T: Float,
{
fn contains(&self, point: &Point<T>) -> bool {
self.contains(&point.0)
}
}

impl<T> Contains<Point<T>> for Polygon<T>
// ┌─────────────────────────────┐
// │ Implementations for Polygon │
// └─────────────────────────────┘

impl<T> Contains<Coordinate<T>> for Polygon<T>
where
T: Float,
{
fn contains(&self, p: &Point<T>) -> 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<T>) -> 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<T> Contains<Point<T>> for MultiPolygon<T>
impl<T> Contains<Point<T>> for Polygon<T>
where
T: Float,
{
fn contains(&self, p: &Point<T>) -> bool {
self.0.iter().any(|poly| poly.contains(p))
self.contains(&p.0)
}
}

Expand Down Expand Up @@ -226,15 +264,50 @@ where
}
}

// ┌──────────────────────────────────┐
// │ Implementations for MultiPolygon │
// └──────────────────────────────────┘

impl<T> Contains<Coordinate<T>> for MultiPolygon<T>
where
T: Float,
{
fn contains(&self, coord: &Coordinate<T>) -> bool {
self.0.iter().any(|poly| poly.contains(coord))
}
}

impl<T> Contains<Point<T>> for MultiPolygon<T>
where
T: Float,
{
fn contains(&self, p: &Point<T>) -> bool {
self.contains(&p.0)
}
}

// ┌──────────────────────────┐
// │ Implementations for Rect │
// └──────────────────────────┘

impl<T> Contains<Coordinate<T>> for Rect<T>
where
T: CoordinateType,
{
fn contains(&self, coord: &Coordinate<T>) -> bool {
coord.x >= self.min().x
&& coord.x <= self.max().x
&& coord.y >= self.min().y
&& coord.y <= self.max().y
}
}

impl<T> Contains<Point<T>> for Rect<T>
where
T: CoordinateType,
{
fn contains(&self, p: &Point<T>) -> 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)
}
}

Expand All @@ -251,28 +324,91 @@ where
}
}

// ┌──────────────────────────────┐
// │ Implementations for Triangle │
// └──────────────────────────────┘

impl<T> Contains<Coordinate<T>> for Triangle<T>
where
T: CoordinateType,
{
fn contains(&self, coord: &Coordinate<T>) -> 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<T> Contains<Point<T>> for Triangle<T>
where
T: CoordinateType,
{
fn contains(&self, point: &Point<T>) -> 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<T> Contains<Coordinate<T>> for Geometry<T>
where
T: Float,
{
fn contains(&self, coord: &Coordinate<T>) -> 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<T>(point_1: &Coordinate<T>, point_2: &Coordinate<T>, point_3: &Coordinate<T>) -> bool
impl<T> Contains<Point<T>> for Geometry<T>
where
T: CoordinateType,
T: Float,
{
fn contains(&self, point: &Point<T>) -> bool {
self.contains(&point.0)
}
}

// ┌────────────────────────────────────────┐
// │ Implementations for GeometryCollection │
// └────────────────────────────────────────┘

impl<T> Contains<Coordinate<T>> for GeometryCollection<T>
where
T: Float,
{
fn contains(&self, coord: &Coordinate<T>) -> bool {
self.0.iter().any(|geometry| geometry.contains(coord))
}
}

impl<T> Contains<Point<T>> for GeometryCollection<T>
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<T>) -> bool {
self.contains(&point.0)
}
}

// ┌───────┐
// │ Tests │
// └───────┘

#[cfg(test)]
mod test {
use crate::algorithm::contains::Contains;
Expand Down
9 changes: 5 additions & 4 deletions 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,
};
Expand Down Expand Up @@ -260,9 +261,9 @@ fn ring_contains_point<T>(poly: &Polygon<T>, p: Point<T>) -> 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,
}
}

Expand Down