diff --git a/geo-types/src/algorithms.rs b/geo-types/src/algorithms.rs index 34228fd8a..a659245ae 100644 --- a/geo-types/src/algorithms.rs +++ b/geo-types/src/algorithms.rs @@ -2,7 +2,56 @@ // wouldn't have this duplication use num_traits::{Float, ToPrimitive}; -use {CoordinateType, Line, Point}; +use {Coordinate, CoordinateType, Line, LineString, Point}; + +pub static COORD_PRECISION: f32 = 1e-1; // 0.1m + +pub trait Contains { + fn contains(&self, rhs: &Rhs) -> bool; +} + +impl Contains> for Point +where + T: Float + ToPrimitive, +{ + fn contains(&self, p: &Point) -> bool { + self.euclidean_distance(p).to_f32().unwrap() < COORD_PRECISION + } +} + +impl Contains> for LineString +where + T: Float, +{ + fn contains(&self, p: &Point) -> bool { + // LineString without points + if self.0.is_empty() { + return false; + } + // LineString with one point equal p + if self.0.len() == 1 { + return Point(self.0[0]).contains(p); + } + // check if point is a vertex + if self.0.contains(&p.0) { + return true; + } + for line in self.lines() { + if ((line.start.y == line.end.y) + && (line.start.y == p.y()) + && (p.x() > line.start.x.min(line.end.x)) + && (p.x() < line.start.x.max(line.end.x))) + || ((line.start.x == line.end.x) + && (line.start.x == p.x()) + && (p.y() > line.start.y.min(line.end.y)) + && (p.y() < line.start.y.max(line.end.y))) + { + return true; + } + } + false + } +} pub trait EuclideanDistance { fn euclidean_distance(&self, rhs: &Rhs) -> T; @@ -39,6 +88,33 @@ where } } +impl EuclideanDistance> for Point +where + T: Float, +{ + /// Minimum distance from a Point to a LineString + fn euclidean_distance(&self, linestring: &LineString) -> T { + // No need to continue if the point is on the LineString, or it's empty + if linestring.contains(self) || linestring.0.is_empty() { + return T::zero(); + } + linestring + .lines() + .map(|line| line_segment_distance(*self, line.start_point(), line.end_point())) + .fold(T::max_value(), |accum, val| accum.min(val)) + } +} + +impl EuclideanDistance> for LineString +where + T: Float, +{ + /// Minimum distance from a LineString to a Point + fn euclidean_distance(&self, point: &Point) -> T { + point.euclidean_distance(self) + } +} + impl EuclideanDistance> for Line where T: Float, @@ -55,6 +131,43 @@ pub trait BoundingBox { fn bbox(&self) -> Self::Output; } +fn get_min_max(p: T, min: T, max: T) -> (T, T) +where + T: CoordinateType, +{ + if p > max { + (min, p) + } else if p < min { + (p, max) + } else { + (min, max) + } +} + +fn get_bbox(collection: I) -> Option> +where + T: CoordinateType, + I: IntoIterator>, +{ + let mut iter = collection.into_iter(); + if let Some(pnt) = iter.next() { + let mut xrange = (pnt.x, pnt.x); + let mut yrange = (pnt.y, pnt.y); + for pnt in iter { + let (px, py) = pnt.x_y(); + xrange = get_min_max(px, xrange.0, xrange.1); + yrange = get_min_max(py, yrange.0, yrange.1); + } + return Some(Bbox { + xmin: xrange.0, + xmax: xrange.1, + ymin: yrange.0, + ymax: yrange.1, + }); + } + None +} + impl BoundingBox for Line where T: CoordinateType, @@ -75,6 +188,20 @@ where } } +impl BoundingBox for LineString +where + T: CoordinateType, +{ + type Output = Option>; + + /// + /// Return the BoundingBox for a LineString + /// + fn bbox(&self) -> Self::Output { + get_bbox(self.0.iter().cloned()) + } +} + #[derive(PartialEq, Clone, Copy, Debug)] pub struct Bbox where diff --git a/geo-types/src/line_string.rs b/geo-types/src/line_string.rs index a38cecf26..73313badc 100644 --- a/geo-types/src/line_string.rs +++ b/geo-types/src/line_string.rs @@ -1,6 +1,9 @@ use std::iter::FromIterator; use {Coordinate, CoordinateType, Line, Point, Triangle}; +#[cfg(feature = "spade")] +use algorithms::{BoundingBox, EuclideanDistance}; + /// An ordered collection of two or more [`Coordinate`s](struct.Coordinate.html), representing a /// path between locations. /// @@ -162,3 +165,38 @@ impl IntoIterator for LineString { self.0.into_iter() } } + +#[cfg(feature = "spade")] +impl ::spade::SpatialObject for LineString +where + T: ::num_traits::Float + ::spade::SpadeNum + ::std::fmt::Debug, +{ + type Point = Point; + + fn mbr(&self) -> ::spade::BoundingRect { + let bbox = self.bbox(); + match bbox { + None => { + ::spade::BoundingRect::from_corners( + &Point::new(T::min_value(), T::min_value()), + &Point::new(T::max_value(), T::max_value()), + ) + }, + Some(b) => { + ::spade::BoundingRect::from_corners( + &Point::new(b.xmin, b.ymin), + &Point::new(b.xmax, b.ymax), + ) + }, + } + } + + fn distance2(&self, point: &Self::Point) -> ::Scalar { + let d = self.euclidean_distance(point); + if d == T::zero() { + d + } else { + d.powi(2) + } + } +}