Skip to content

Commit

Permalink
Merge pull request #118 from mbattifarano/line-type
Browse files Browse the repository at this point in the history
Add `Line` type
  • Loading branch information
frewsxcv authored Jun 4, 2017
2 parents f627e93 + 4ca324f commit c5bc209
Show file tree
Hide file tree
Showing 9 changed files with 508 additions and 16 deletions.
17 changes: 15 additions & 2 deletions src/algorithm/area.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use num_traits::Float;
use types::{LineString, Polygon, MultiPolygon, Bbox};
use types::{Line, LineString, Polygon, MultiPolygon, Bbox};

/// Calculation of the area.
Expand Down Expand Up @@ -31,6 +31,13 @@ fn get_linestring_area<T>(linestring: &LineString<T>) -> T where T: Float {
tmp / (T::one() + T::one())
}

impl<T> Area<T> for Line<T>
where T: Float
{
fn area(&self) -> T {
T::zero()
}
}

impl<T> Area<T> for Polygon<T>
where T: Float
Expand Down Expand Up @@ -59,7 +66,7 @@ impl<T> Area<T> for Bbox<T>

#[cfg(test)]
mod test {
use types::{Coordinate, Point, LineString, Polygon, MultiPolygon, Bbox};
use types::{Coordinate, Point, Line, LineString, Polygon, MultiPolygon, Bbox};
use algorithm::area::Area;

// Area of the polygon
Expand Down Expand Up @@ -111,4 +118,10 @@ mod test {
assert_eq!(mpoly.area(), 102.);
assert_relative_eq!(mpoly.area(), 102.);
}
#[test]
fn area_line_test() {
let p = |x, y| Point(Coordinate { x: x, y: y });
let line1 = Line::new(p(0.0, 0.0), p(1.0, 1.0));
assert_eq!(line1.area(), 0.);
}
}
27 changes: 25 additions & 2 deletions src/algorithm/boundingbox.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use num_traits::Float;

use types::{Bbox, Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon};
use types::{Bbox, Point, MultiPoint, Line, LineString, MultiLineString, Polygon, MultiPolygon};

/// Calculation of the bounding box of a geometry.
Expand Down Expand Up @@ -65,6 +65,19 @@ impl<T> BoundingBox<T> for MultiPoint<T>
}
}

impl<T> BoundingBox<T> for Line<T>
where T: Float
{
fn bbox(&self) -> Option<Bbox<T>> {
let a = self.start;
let b = self.end;
let (xmin, xmax) = if a.x() <= b.x() {(a.x(), b.x())} else {(b.x(), a.x())};
let (ymin, ymax) = if a.y() <= b.y() {(a.y(), b.y())} else {(b.y(), a.y())};
Some(Bbox {xmin: xmin, xmax: xmax,
ymin: ymin, ymax: ymax})
}
}

impl<T> BoundingBox<T> for LineString<T>
where T: Float
{
Expand Down Expand Up @@ -114,7 +127,7 @@ impl<T> BoundingBox<T> for MultiPolygon<T>

#[cfg(test)]
mod test {
use types::{Bbox, Coordinate, Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon};
use types::{Bbox, Coordinate, Point, MultiPoint, Line, LineString, MultiLineString, Polygon, MultiPolygon};
use algorithm::boundingbox::BoundingBox;

#[test]
Expand Down Expand Up @@ -175,4 +188,14 @@ mod test {
let bbox = Bbox{xmin: -60., ymax: 80., xmax: 50., ymin: -70.};
assert_eq!(bbox, mpoly.bbox().unwrap());
}
#[test]
fn line_test() {
let p = |x, y| Point(Coordinate { x: x, y: y });
let line1 = Line::new(p(0., 1.), p(2., 3.));
let line2 = Line::new(p(2., 3.), p(0., 1.));
assert_eq!(line1.bbox().unwrap(),
Bbox {xmin: 0., xmax: 2., ymin: 1., ymax: 3.});
assert_eq!(line2.bbox().unwrap(),
Bbox {xmin: 0., xmax: 2., ymin: 1., ymax: 3.});
}
}
21 changes: 19 additions & 2 deletions src/algorithm/centroid.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use num_traits::{Float, FromPrimitive};

use types::{Point, LineString, Polygon, MultiPolygon, Bbox};
use types::{Point, Line, LineString, Polygon, MultiPolygon, Bbox};
use algorithm::area::Area;
use algorithm::distance::Distance;

Expand Down Expand Up @@ -54,6 +54,17 @@ fn simple_polygon_centroid<T>(poly_ext: &LineString<T>) -> Option<Point<T>>
Some(Point::new(sum_x / (six * area), sum_y / (six * area)))
}

impl<T> Centroid<T> for Line<T>
where T: Float
{
fn centroid(&self) -> Option<Point<T>> {
let two = T::one() + T::one();
let x = (self.start.x() + self.end.x()) / two;
let y = (self.start.y() + self.end.y()) / two;
Some(Point::new(x, y))
}
}

impl<T> Centroid<T> for LineString<T>
where T: Float
{
Expand Down Expand Up @@ -170,7 +181,7 @@ impl<T> Centroid<T> for Point<T>

#[cfg(test)]
mod test {
use types::{COORD_PRECISION, Coordinate, Point, LineString, Polygon, MultiPolygon, Bbox};
use types::{COORD_PRECISION, Coordinate, Point, Line, LineString, Polygon, MultiPolygon, Bbox};
use algorithm::centroid::Centroid;
use algorithm::distance::Distance;
// Tests: Centroid of LineString
Expand Down Expand Up @@ -293,4 +304,10 @@ mod test {
let point = Point(Coordinate { x: 2., y: 75. });
assert_eq!(point, bbox.centroid().unwrap());
}
#[test]
fn line_test() {
let p = |x, y| Point(Coordinate { x: x, y: y });
let line1 = Line::new(p(0., 1.), p(1., 3.));
assert_eq!(line1.centroid(), Some(p(0.5, 2.)));
}
}
151 changes: 148 additions & 3 deletions src/algorithm/contains.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use num_traits::{Float, ToPrimitive};

use types::{COORD_PRECISION, Point, LineString, Polygon, MultiPolygon, Bbox};
use types::{COORD_PRECISION, Point, Line, LineString, Polygon, MultiPolygon, Bbox};
use algorithm::intersects::Intersects;
use algorithm::distance::Distance;

Expand Down Expand Up @@ -70,6 +70,65 @@ impl<T> Contains<Point<T>> for LineString<T>
false
}
}

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

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

impl<T> Contains<LineString<T>> for Line<T>
where T: Float
{
fn contains(&self, linestring: &LineString<T>) -> bool {
linestring.0.iter().all(|pt| self.contains(pt))
}
}

impl<T> Contains<Line<T>> for LineString<T>
where T: Float
{
fn contains(&self, line: &Line<T>) -> bool {
let (p0, p1) = (line.start, line.end);
let mut look_for: Option<Point<T>> = None;
for l in self.0.windows(2) {
let segment = Line::new(l[0], l[1]);
if look_for.is_none() {
// If segment contains an endpoint of line, we mark the other endpoint as the
// one we are looking for.
if segment.contains(&p0) {
look_for = Some(p1);
} else if segment.contains(&p1) {
look_for = Some(p0);
}
}
if let Some(p) = look_for {
// If we are looking for an endpoint, we need to either find it, or show that we
// should continue to look for it
if segment.contains(&p) {
// If the segment contains the endpoint we are looking for we are done
return true;
} else if !line.contains(&segment.end) {
// If not, and the end of the segment is not on the line, we should stop
// looking
look_for = None
}
}
}
return false;
}
}

#[derive(PartialEq, Clone, Debug)]
enum PositionPoint {
OnBoundary,
Expand Down Expand Up @@ -139,6 +198,19 @@ impl<T> Contains<Point<T>> for MultiPolygon<T>
}
}

impl<T> Contains<Line<T>> for Polygon<T>
where T: Float
{
fn contains(&self, line: &Line<T>) -> bool {
// both endpoints are contained in the polygon and the line
// does NOT intersect the exterior or any of the interior boundaries
self.contains(&line.start) &&
self.contains(&line.end) &&
!self.exterior.intersects(line) &&
!self.interiors.iter().any(|inner| inner.intersects(line))
}
}

impl<T> Contains<LineString<T>> for Polygon<T>
where T: Float
{
Expand All @@ -152,7 +224,6 @@ impl<T> Contains<LineString<T>> for Polygon<T>
}
}


impl<T> Contains<Point<T>> for Bbox<T>
where T: Float
{
Expand All @@ -173,7 +244,7 @@ impl<T> Contains<Bbox<T>> for Bbox<T>

#[cfg(test)]
mod test {
use types::{Coordinate, Point, LineString, Polygon, MultiPolygon, Bbox};
use types::{Coordinate, Point, Line, LineString, Polygon, MultiPolygon, Bbox};
use algorithm::contains::Contains;
/// Tests: Point in LineString
#[test]
Expand Down Expand Up @@ -321,4 +392,78 @@ mod test {
assert_eq!(true, bbox_xl.contains(&bbox_sm));
assert_eq!(false, bbox_sm.contains(&bbox_xl));
}
#[test]
fn point_in_line_test() {
let p = |x, y| Point(Coordinate { x: x, y: y });
let p0 = p(2., 4.);
// vertical line
let line1 = Line::new(p(2., 0.), p(2., 5.));
// point on line, but outside line segment
let line2 = Line::new(p(0., 6.), p(1.5, 4.5));
// point on line
let line3 = Line::new(p(0., 6.), p(3., 3.));
assert!(line1.contains(&p0));
assert!(!line2.contains(&p0));
assert!(line3.contains(&p0));
}
#[test]
fn line_in_line_test() {
let p = |x, y| Point(Coordinate { x: x, y: y });
let line0 = Line::new(p(0., 1.), p(3., 4.));
// first point on line0, second not
let line1 = Line::new(p(1., 2.), p(2., 2.));
// co-linear, but extends past the end of line0
let line2 = Line::new(p(1., 2.), p(4., 5.));
// contained in line0
let line3 = Line::new(p(1., 2.), p(3., 4.));
assert!(!line0.contains(&line1));
assert!(!line0.contains(&line2));
assert!(line0.contains(&line3));
}
#[test]
fn linestring_in_line_test() {
let p = |x, y| Point(Coordinate { x: x, y: y });
let line = Line::new(p(0., 1.), p(3., 4.));
// linestring0 in line
let linestring0 = LineString(vec![p(0.1, 1.1), p(1., 2.), p(1.5, 2.5)]);
// linestring1 starts and ends in line, but wanders in the middle
let linestring1 = LineString(vec![p(0.1, 1.1), p(2., 2.), p(1.5, 2.5)]);
// linestring2 is co-linear, but extends beyond line
let linestring2 = LineString(vec![p(0.1, 1.1), p(1., 2.), p(4., 5.)]);
// no part of linestring3 is contained in line
let linestring3 = LineString(vec![p(1.1, 1.1), p(2., 2.), p(2.5, 2.5)]);
assert!(line.contains(&linestring0));
assert!(!line.contains(&linestring1));
assert!(!line.contains(&linestring2));
assert!(!line.contains(&linestring3));
}
#[test]
fn line_in_polygon_test() {
let p = |x, y| Point(Coordinate { x: x, y: y });
let line = Line::new(p(0., 1.), p(3., 4.));
let linestring0 = LineString(vec![p(-1., 0.), p(5., 0.), p(5., 5.), p(0., 5.), p(-1., 0.)]);
let poly0 = Polygon::new(linestring0, Vec::new());
let linestring1 = LineString(vec![p(0., 0.), p(0., 2.), p(2., 2.), p(2., 0.), p(0., 0.)]);
let poly1 = Polygon::new(linestring1, Vec::new());
assert!(poly0.contains(&line));
assert!(!poly1.contains(&line));
}
#[test]
fn line_in_linestring_test() {
let line0 = Line::new(Point::new(1., 1.), Point::new(2., 2.));
// line0 is completely contained in the second segment
let linestring0 = LineString(vec![Point::new(0., 0.5), Point::new(0.5, 0.5),
Point::new(3., 3.)]);
// line0 is contained in the last three segments
let linestring1 = LineString(vec![Point::new(0., 0.5), Point::new(0.5, 0.5),
Point::new(1.2, 1.2), Point::new(1.5, 1.5),
Point::new(3., 3.)]);
// line0 endpoints are contained in the linestring, but the fourth point is off the line
let linestring2 = LineString(vec![Point::new(0., 0.5), Point::new(0.5, 0.5),
Point::new(1.2, 1.2), Point::new(1.5, 0.),
Point::new(2., 2.), Point::new(3., 3.)]);
assert!(linestring0.contains(&line0));
assert!(linestring1.contains(&line0));
assert!(!linestring2.contains(&line0));
}
}
36 changes: 34 additions & 2 deletions src/algorithm/distance.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::cmp::Ordering;
use std::collections::BinaryHeap;
use num_traits::{Float, ToPrimitive};
use types::{Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon};
use types::{Point, Line, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon};
use algorithm::contains::Contains;
use num_traits::pow::pow;

Expand Down Expand Up @@ -263,9 +263,26 @@ impl<T> Distance<T, Point<T>> for LineString<T>
}
}

impl<T> Distance<T, Point<T>> for Line<T>
where T: Float
{
/// Minimum distance from a Line to a Point
fn distance(&self, point: &Point<T>) -> T {
line_segment_distance(point, &self.start, &self.end)
}
}
impl<T> Distance<T, Line<T>> for Point<T>
where T: Float
{
/// Minimum distance from a Line to a Point
fn distance(&self, line: &Line<T>) -> T {
line.distance(self)
}
}

#[cfg(test)]
mod test {
use types::{Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon};
use types::{Point, Line, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon};
use algorithm::distance::{Distance, line_segment_distance};

#[test]
Expand Down Expand Up @@ -457,4 +474,19 @@ mod test {
let p = Point::new(50.0, 50.0);
assert_eq!(p.distance(&mp), 64.03124237432849)
}
#[test]
fn distance_line_test() {
let line0 = Line::new(Point::new(0., 0.), Point::new(5., 0.));
let p0 = Point::new(2., 3.);
let p1 = Point::new(3., 0.);
let p2 = Point::new(6., 0.);
assert_eq!(line0.distance(&p0), 3.);
assert_eq!(p0.distance(&line0), 3.);

assert_eq!(line0.distance(&p1), 0.);
assert_eq!(p1.distance(&line0), 0.);

assert_eq!(line0.distance(&p2), 1.);
assert_eq!(p2.distance(&line0), 1.);
}
}
Loading

0 comments on commit c5bc209

Please sign in to comment.