Skip to content

Commit

Permalink
Merge #511
Browse files Browse the repository at this point in the history
511: Robust Intersect and Contains r=michaelkirk a=rmanoka

Use robust predicates in Intersects and Contains traits

+ use robust preds (doesn't need Float anymore)
+ define interior, boundary, and exterior of geometry types (similar to geos/jts)
+ define Intersect and Contains using DE-9IM model
+ fix contains logic in a few corner cases

Future work:

The polygon contains line / polygon logic is not yet fully correct.   I've added a few TODO comments, and an ignored test for cases where our predicate doesn't match the DE-9IM definition.  Unfortunately, it doesn't seem to be a simple change to fix these corner cases.  For instance, geos seems to use a concept called `GeometryGraph` for these cases which I'm yet to understand.

Co-authored-by: Rajsekar Manokaran <[email protected]>
Co-authored-by: rmanoka <[email protected]>
  • Loading branch information
bors[bot] and rmanoka authored Sep 20, 2020
2 parents 41baf51 + b93fd10 commit 6fb4fb7
Show file tree
Hide file tree
Showing 38 changed files with 1,124 additions and 825 deletions.
7 changes: 6 additions & 1 deletion geo-types/CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changes

## Master (not released)

* Add vector-space operations to `Coordinate` and `Point`
* <https://github.com/georust/geo/pull/505>

## 0.6.0

* Remove `COORD_PRECISION` which was an arbitrary constant of 0.1m
Expand Down Expand Up @@ -114,4 +119,4 @@
## 0.1.0

* New crate with core types from `geo`
* <https://github.com/georust/geo/pull/201>
* <https://github.com/georust/geo/pull/201>
14 changes: 11 additions & 3 deletions geo-types/src/line.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::{Coordinate, CoordinateType, Point};

/// A line segment made up of exactly two [`Point`s](struct.Point.html).
/// A line segment made up of exactly two
/// [`Coordinate`s](struct.Coordinate.html). The interior and
/// boundaries are defined as with a `LineString` with the
/// two end points.
#[derive(Eq, PartialEq, Clone, Copy, Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Line<T>
Expand Down Expand Up @@ -37,6 +40,11 @@ where
}
}

/// Calculate the difference in coordinates (Δx, Δy).
pub fn delta(&self) -> Coordinate<T> {
self.end - self.start
}

/// Calculate the difference in ‘x’ components (Δx).
///
/// Equivalent to:
Expand All @@ -53,7 +61,7 @@ where
/// # );
/// ```
pub fn dx(&self) -> T {
self.end.x - self.start.x
self.delta().x
}

/// Calculate the difference in ‘y’ components (Δy).
Expand All @@ -72,7 +80,7 @@ where
/// # );
/// ```
pub fn dy(&self) -> T {
self.end.y - self.start.y
self.delta().y
}

/// Calculate the slope (Δy/Δx).
Expand Down
26 changes: 20 additions & 6 deletions geo-types/src/line_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,25 @@ use crate::{Coordinate, CoordinateType, Line, Point, Triangle};
use std::iter::FromIterator;
use std::ops::{Index, IndexMut};

/// An ordered collection of two or more [`Coordinate`s](struct.Coordinate.html), representing a
/// An ordered collection of two or more
/// [`Coordinate`s](struct.Coordinate.html), representing a
/// path between locations.
///
/// A `LineString` is _closed_ if it is empty, or if the
/// first and last coordinates are the same. The _boundary_
/// of a `LineString` is empty if closed, and otherwise the
/// end points. The interior is the (infinite) set of all
/// points along the linestring _not including_ the
/// boundary.
///
/// # Validity
///
/// A `LineString` is valid if it is either empty or
/// contains 2 or more coordinates. Further, a closed
/// `LineString` must not self intersect. Note that the
/// validity is not enforced, and the operations and
/// predicates are undefined on invalid linestrings.
///
/// # Examples
///
/// Create a `LineString` by calling it directly:
Expand Down Expand Up @@ -180,11 +196,9 @@ impl<T: CoordinateType> LineString<T> {
self.0.len()
}

/// Checks if the linestring is closed; i.e. the first
/// and last points have the same coords. Note that a
/// single point is considered closed, but the output on
/// an empty linestring is _unspecified_ and must not be
/// relied upon.
/// Checks if the linestring is closed; i.e. it is
/// either empty or, the first and last points are the
/// same.
///
/// # Examples
///
Expand Down
11 changes: 8 additions & 3 deletions geo-types/src/multi_line_string.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use crate::{CoordinateType, LineString};
use std::iter::FromIterator;

/// A collection of [`LineString`s](line_string/struct.LineString.html).
/// A collection of
/// [`LineString`s](line_string/struct.LineString.html). The
/// interior and the boundary are the union of the interior
/// or the boundary of the constituent line strings.
///
/// Can be created from a `Vec` of `LineString`s, or from an Iterator which yields `LineString`s.
/// Can be created from a `Vec` of `LineString`s, or from an
/// Iterator which yields `LineString`s.
///
/// Iterating over this objects, yields the component `LineString`s.
/// Iterating over this objects, yields the component
/// `LineString`s.
#[derive(Eq, PartialEq, Clone, Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct MultiLineString<T>(pub Vec<LineString<T>>)
Expand Down
4 changes: 3 additions & 1 deletion geo-types/src/multi_point.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::{CoordinateType, Point};
use std::iter::FromIterator;

/// A collection of [`Point`s](struct.Point.html).
/// A collection of [`Point`s](struct.Point.html). The
/// interior and the boundary are the union of the interior
/// or the boundary of the constituent points.
///
/// # Examples
///
Expand Down
4 changes: 3 additions & 1 deletion geo-types/src/multi_polygon.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::{CoordinateType, Polygon};
use std::iter::FromIterator;

/// A collection of [`Polygon`s](struct.Polygon.html).
/// A collection of [`Polygon`s](struct.Polygon.html). The
/// interior and the boundary are the union of the interior
/// or the boundary of the constituent polygons.
///
/// Can be created from a `Vec` of `Polygon`s, or `collect`ed from an Iterator which yields `Polygon`s.
///
Expand Down
4 changes: 4 additions & 0 deletions geo-types/src/point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ use std::ops::{Add, Div, Mul, Neg, Sub};
/// tuples, or arrays – see the `From` impl section for a
/// complete list.
///
/// A point is _valid_ iff neither coordinate is `NaN`. The
/// _interior_ of the point is itself (a singleton set), and
/// its _boundary_ is empty.
///
/// # Examples
///
/// ```
Expand Down
5 changes: 5 additions & 0 deletions geo-types/src/polygon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ use num_traits::{Float, Signed};
/// [`LineString`]. It may contain zero or more holes (_interior rings_), also
/// represented by `LineString`s.
///
/// The _boundary_ of the polygon is the union of the
/// boundaries of the exterior and interiors. The interior
/// is all the points inside the polygon (not on the
/// boundary).
///
/// 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.
Expand Down
10 changes: 10 additions & 0 deletions geo/CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changes

## Master (not released)

* Add robust predicates
* <https://github.com/georust/geo/pull/511>
* <https://github.com/georust/geo/pull/505>
* <https://github.com/georust/geo/pull/504>
* <https://github.com/georust/geo/pull/502>
* Improve numerical stability in centroid computation
* <https://github.com/georust/geo/pull/510>

## 0.14.2

* Bump proj version to 0.20.3
Expand Down
13 changes: 7 additions & 6 deletions geo/src/algorithm/closest_point.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::kernels::*;
use crate::prelude::*;
use crate::{Closest, Line, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon};
use num_traits::Float;
Expand Down Expand Up @@ -47,7 +48,7 @@ impl<F: Float> ClosestPoint<F> for Point<F> {
}
}

impl<F: Float> ClosestPoint<F> for Line<F> {
impl<F: Float + HasKernel> ClosestPoint<F> for Line<F> {
fn closest_point(&self, p: &Point<F>) -> Closest<F> {
let line_length = self.euclidean_length();
if line_length == F::zero() {
Expand Down Expand Up @@ -77,7 +78,7 @@ impl<F: Float> ClosestPoint<F> for Line<F> {
let y = direction_vector.y();
let c = Point(self.start + (t * x, t * y).into());

if self.contains(p) {
if self.intersects(p) {
Closest::Intersection(c)
} else {
Closest::SinglePoint(c)
Expand Down Expand Up @@ -106,20 +107,20 @@ where
best
}

impl<F: Float> ClosestPoint<F> for LineString<F> {
impl<F: Float + HasKernel> ClosestPoint<F> for LineString<F> {
fn closest_point(&self, p: &Point<F>) -> Closest<F> {
closest_of(self.lines(), *p)
}
}

impl<F: Float> ClosestPoint<F> for Polygon<F> {
impl<F: Float + HasKernel> ClosestPoint<F> for Polygon<F> {
fn closest_point(&self, p: &Point<F>) -> Closest<F> {
let prospectives = self.interiors().iter().chain(iter::once(self.exterior()));
closest_of(prospectives, *p)
}
}

impl<F: Float> ClosestPoint<F> for MultiPolygon<F> {
impl<F: Float + HasKernel> ClosestPoint<F> for MultiPolygon<F> {
fn closest_point(&self, p: &Point<F>) -> Closest<F> {
closest_of(self.0.iter(), *p)
}
Expand All @@ -131,7 +132,7 @@ impl<F: Float> ClosestPoint<F> for MultiPoint<F> {
}
}

impl<F: Float> ClosestPoint<F> for MultiLineString<F> {
impl<F: Float + HasKernel> ClosestPoint<F> for MultiLineString<F> {
fn closest_point(&self, p: &Point<F>) -> Closest<F> {
closest_of(self.0.iter(), *p)
}
Expand Down
58 changes: 58 additions & 0 deletions geo/src/algorithm/contains/geometry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use super::Contains;
use crate::kernels::*;
use crate::*;

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

impl<T> Contains<Coordinate<T>> for Geometry<T>
where
T: HasKernel,
{
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),
}
}
}

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

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

impl<T> Contains<Coordinate<T>> for GeometryCollection<T>
where
T: HasKernel,
{
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: HasKernel,
{
fn contains(&self, point: &Point<T>) -> bool {
self.contains(&point.0)
}
}
84 changes: 84 additions & 0 deletions geo/src/algorithm/contains/line.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use super::Contains;
use crate::intersects::Intersects;
use crate::kernels::*;
use crate::*;

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

impl<T> Contains<Coordinate<T>> for Line<T>
where
T: HasKernel,
{
fn contains(&self, coord: &Coordinate<T>) -> bool {
if self.start == self.end {
&self.start == coord
} else {
coord != &self.start && coord != &self.end && self.intersects(coord)
}
}
}

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

impl<T> Contains<Line<T>> for Line<T>
where
T: HasKernel,
{
fn contains(&self, line: &Line<T>) -> bool {
if line.start == line.end {
self.contains(&line.start)
} else {
self.intersects(&line.start) && self.intersects(&line.end)
}
}
}

impl<T> Contains<LineString<T>> for Line<T>
where
T: HasKernel,
{
fn contains(&self, linestring: &LineString<T>) -> bool {
// Empty linestring has no interior, and not
// contained in anything.
if linestring.0.is_empty() {
return false;
}

// The interior of the linestring should have some
// intersection with the interior of self. Two cases
// arise:
//
// 1. There are at least two distinct points in the
// linestring. Then, if both intersect, the interior
// between these two must have non-empty intersection.
//
// 2. Otherwise, all the points on the linestring
// are the same. In this case, the interior is this
// specific point, and it should be contained in the
// line.
let first = linestring.0.first().unwrap();
let mut all_equal = true;

// If all the vertices of the linestring intersect
// self, then the interior or boundary of the
// linestring cannot have non-empty intersection
// with the exterior.
let all_intersects = linestring.0.iter().all(|c| {
if c != first {
all_equal = false;
}
self.intersects(c)
});

all_intersects && (!all_equal || self.contains(first))
}
}
Loading

0 comments on commit 6fb4fb7

Please sign in to comment.