From 1bb98e578e76bd767cac74c1fa06154ac3f41f4b Mon Sep 17 00:00:00 2001 From: mattbatt Date: Wed, 17 May 2017 17:57:47 -0400 Subject: [PATCH 01/16] Add Line type --- src/types.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/types.rs b/src/types.rs index 9e54037b3..169dab9eb 100644 --- a/src/types.rs +++ b/src/types.rs @@ -318,6 +318,9 @@ impl AddAssign for Bbox #[derive(PartialEq, Clone, Debug)] pub struct MultiPoint(pub Vec>) where T: Float; +#[derive(PartialEq, Clone, Debug)] +pub struct Line(pub (Point, Point)) where T: Float; + #[derive(PartialEq, Clone, Debug)] pub struct LineString(pub Vec>) where T: Float; From 2570a580b122b49221b44f3bbb73506a3fe7d2e8 Mon Sep 17 00:00:00 2001 From: mattbatt Date: Wed, 17 May 2017 17:58:12 -0400 Subject: [PATCH 02/16] Implement Area for Line --- src/algorithm/area.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/algorithm/area.rs b/src/algorithm/area.rs index b1b747c00..32e700c53 100644 --- a/src/algorithm/area.rs +++ b/src/algorithm/area.rs @@ -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. @@ -31,6 +31,13 @@ fn get_linestring_area(linestring: &LineString) -> T where T: Float { tmp / (T::one() + T::one()) } +impl Area for Line + where T: Float +{ + fn area(&self) -> T { + T::zero() + } +} impl Area for Polygon where T: Float @@ -59,7 +66,7 @@ impl Area for Bbox #[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 @@ -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((p(0.0, 0.0), p(1.0, 1.0))); + assert_eq!(line1.area(), 0.); + } } From eeba5d28c11db73f41c7d825a7a8b1635b81569e Mon Sep 17 00:00:00 2001 From: mattbatt Date: Wed, 17 May 2017 17:58:29 -0400 Subject: [PATCH 03/16] Implement BoundingBox for Line --- src/algorithm/boundingbox.rs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/algorithm/boundingbox.rs b/src/algorithm/boundingbox.rs index 503f92e07..941675598 100644 --- a/src/algorithm/boundingbox.rs +++ b/src/algorithm/boundingbox.rs @@ -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. @@ -65,6 +65,18 @@ impl BoundingBox for MultiPoint } } +impl BoundingBox for Line + where T: Float +{ + fn bbox(&self) -> Option> { + let (a, b) = self.0; + 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 BoundingBox for LineString where T: Float { @@ -114,7 +126,7 @@ impl BoundingBox for MultiPolygon #[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] @@ -175,4 +187,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((p(0., 1.), p(2., 3.))); + let line2 = Line((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.}); + } } From 4c674a85a7001f0f31e8a9d0d270bed5c6f389dc Mon Sep 17 00:00:00 2001 From: mattbatt Date: Wed, 17 May 2017 17:58:44 -0400 Subject: [PATCH 04/16] Implement Centroid for Line --- src/algorithm/centroid.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/algorithm/centroid.rs b/src/algorithm/centroid.rs index 0bb7cd61e..80fef5a92 100644 --- a/src/algorithm/centroid.rs +++ b/src/algorithm/centroid.rs @@ -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; @@ -54,6 +54,18 @@ fn simple_polygon_centroid(poly_ext: &LineString) -> Option> Some(Point::new(sum_x / (six * area), sum_y / (six * area))) } +impl Centroid for Line + where T: Float +{ + fn centroid(&self) -> Option> { + let (a, b) = self.0; + let two = T::one() + T::one(); + let x = (a.x() + b.x()) / two; + let y = (a.y() + b.y()) / two; + Some(Point::new(x, y)) + } +} + impl Centroid for LineString where T: Float { @@ -170,7 +182,7 @@ impl Centroid for Point #[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 @@ -293,4 +305,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((p(0., 1.), p(1., 3.))); + assert_eq!(line1.centroid(), Some(p(0.5, 2.))); + } } From bbb96a4ea29ab205b7ea514dced908b148f147d0 Mon Sep 17 00:00:00 2001 From: mattbatt Date: Wed, 17 May 2017 17:58:58 -0400 Subject: [PATCH 05/16] Implement Contains for Line --- src/algorithm/contains.rs | 45 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/algorithm/contains.rs b/src/algorithm/contains.rs index 8d54bbe8d..032c839de 100644 --- a/src/algorithm/contains.rs +++ b/src/algorithm/contains.rs @@ -1,6 +1,7 @@ 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::boundingbox::BoundingBox; use algorithm::intersects::Intersects; use algorithm::distance::Distance; @@ -70,6 +71,29 @@ impl Contains> for LineString false } } + +impl Contains> for Line + where T: Float +{ + fn contains(&self, p: &Point) -> bool { + // if the point is not in the bounding box, it's not on the line + if !self.bbox().map_or(false, |b| b.contains(p)) { + return false; + } + let (a, b) = self.0; + // handle the special case where the line is vertical + if a.x() == b.x() { + return (p.x() - a.x()).to_f32().unwrap() <= COORD_PRECISION; + } + // solve a linear equation + let slope = (a.y() - b.y()) / (a.x() - b.x()); + let intercept = a.y() - slope * a.x(); + (p.y() - (slope * p.x() + intercept)).abs() + .to_f32() + .unwrap() <= COORD_PRECISION + } +} + #[derive(PartialEq, Clone, Debug)] enum PositionPoint { OnBoundary, @@ -173,7 +197,7 @@ impl Contains> for Bbox #[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] @@ -321,4 +345,21 @@ 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((p(2., 0.), p(2., 5.))); + // point on line, but outside line segment + let line2 = Line((p(0., 6.), p(1.5, 4.5))); + // point on line + let line3 = Line((p(0., 6.), p(3., 3.))); + // point within precision of line + let line4 = Line((p(0., 6.00001), p(3., 3.0001))); + assert!(line1.contains(&p0)); + assert!(!line2.contains(&p0)); + assert!(line3.contains(&p0)); + assert!(line4.contains(&p0)); + } } From 9811fee5b45b24da177e59b9c84df126bd27ff00 Mon Sep 17 00:00:00 2001 From: mattbatt Date: Thu, 18 May 2017 20:57:49 -0400 Subject: [PATCH 06/16] Refactor Line Struct Define Line as a struct with public members `start: Point` and `end: Point`. Attach helper methods `new` and `slope`. --- src/algorithm/area.rs | 2 +- src/algorithm/boundingbox.rs | 7 +-- src/algorithm/centroid.rs | 4 +- src/algorithm/contains.rs | 92 ++++++++++++++++++++++++++++-------- src/algorithm/intersects.rs | 30 +++++++++++- src/types.rs | 45 +++++++++++++++++- 6 files changed, 153 insertions(+), 27 deletions(-) diff --git a/src/algorithm/area.rs b/src/algorithm/area.rs index 32e700c53..5c1905681 100644 --- a/src/algorithm/area.rs +++ b/src/algorithm/area.rs @@ -121,7 +121,7 @@ mod test { #[test] fn area_line_test() { let p = |x, y| Point(Coordinate { x: x, y: y }); - let line1 = Line((p(0.0, 0.0), p(1.0, 1.0))); + let line1 = Line::new(p(0.0, 0.0), p(1.0, 1.0)); assert_eq!(line1.area(), 0.); } } diff --git a/src/algorithm/boundingbox.rs b/src/algorithm/boundingbox.rs index 941675598..0fe71e3fa 100644 --- a/src/algorithm/boundingbox.rs +++ b/src/algorithm/boundingbox.rs @@ -69,7 +69,8 @@ impl BoundingBox for Line where T: Float { fn bbox(&self) -> Option> { - let (a, b) = self.0; + 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, @@ -190,8 +191,8 @@ mod test { #[test] fn line_test() { let p = |x, y| Point(Coordinate { x: x, y: y }); - let line1 = Line((p(0., 1.), p(2., 3.))); - let line2 = Line((p(2., 3.), p(0., 1.))); + 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(), diff --git a/src/algorithm/centroid.rs b/src/algorithm/centroid.rs index 80fef5a92..9cc06f8f9 100644 --- a/src/algorithm/centroid.rs +++ b/src/algorithm/centroid.rs @@ -58,7 +58,7 @@ impl Centroid for Line where T: Float { fn centroid(&self) -> Option> { - let (a, b) = self.0; + let (a, b) = (self.start, self.end); let two = T::one() + T::one(); let x = (a.x() + b.x()) / two; let y = (a.y() + b.y()) / two; @@ -308,7 +308,7 @@ mod test { #[test] fn line_test() { let p = |x, y| Point(Coordinate { x: x, y: y }); - let line1 = Line((p(0., 1.), p(1., 3.))); + let line1 = Line::new(p(0., 1.), p(1., 3.)); assert_eq!(line1.centroid(), Some(p(0.5, 2.))); } } diff --git a/src/algorithm/contains.rs b/src/algorithm/contains.rs index 032c839de..39044cb26 100644 --- a/src/algorithm/contains.rs +++ b/src/algorithm/contains.rs @@ -76,21 +76,24 @@ impl Contains> for Line where T: Float { fn contains(&self, p: &Point) -> bool { - // if the point is not in the bounding box, it's not on the line - if !self.bbox().map_or(false, |b| b.contains(p)) { - return false; - } - let (a, b) = self.0; - // handle the special case where the line is vertical - if a.x() == b.x() { - return (p.x() - a.x()).to_f32().unwrap() <= COORD_PRECISION; - } - // solve a linear equation - let slope = (a.y() - b.y()) / (a.x() - b.x()); - let intercept = a.y() - slope * a.x(); - (p.y() - (slope * p.x() + intercept)).abs() - .to_f32() - .unwrap() <= COORD_PRECISION + self.intersects(p) + } +} + +impl Contains> for Line + where T: Float +{ + fn contains(&self, line: &Line) -> bool { + let (a, b) = (line.start, line.end); + self.contains(&a) & self.contains(&b) + } +} + +impl Contains> for Line + where T: Float +{ + fn contains(&self, linestring: &LineString) -> bool { + linestring.0.iter().all(|pt| self.contains(pt)) } } @@ -163,6 +166,14 @@ impl Contains> for MultiPolygon } } +impl Contains> for Polygon + where T: Float +{ + fn contains(&self, line: &Line) -> bool { + false + } +} + impl Contains> for Polygon where T: Float { @@ -350,16 +361,59 @@ mod test { let p = |x, y| Point(Coordinate { x: x, y: y }); let p0 = p(2., 4.); // vertical line - let line1 = Line((p(2., 0.), p(2., 5.))); + let line1 = Line::new(p(2., 0.), p(2., 5.)); // point on line, but outside line segment - let line2 = Line((p(0., 6.), p(1.5, 4.5))); + let line2 = Line::new(p(0., 6.), p(1.5, 4.5)); // point on line - let line3 = Line((p(0., 6.), p(3., 3.))); + let line3 = Line::new(p(0., 6.), p(3., 3.)); // point within precision of line - let line4 = Line((p(0., 6.00001), p(3., 3.0001))); + let line4 = Line::new(p(0., 6.00001), p(3., 3.0001)); assert!(line1.contains(&p0)); assert!(!line2.contains(&p0)); assert!(line3.contains(&p0)); assert!(line4.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)); + println!("{:?}", linestring3.0); + } + #[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(0., 0.), p(5., 0.), p(5., 5.), p(0., 5.), p(0., 0.)]); + let poly0 = Polygon::new(linestring0, Vec::new()); + let linestring1 = LineString(vec![p(0., 0.), p(2., 0.), p(2., 2.), p(0., 2.), p(0., 0.)]); + let poly1 = Polygon::new(linestring1, Vec::new()); + assert!(poly0.contains(&line)); + assert!(!poly1.contains(&line)); + } } diff --git a/src/algorithm/intersects.rs b/src/algorithm/intersects.rs index e509bc79d..bf77a7192 100644 --- a/src/algorithm/intersects.rs +++ b/src/algorithm/intersects.rs @@ -1,5 +1,6 @@ use num_traits::Float; -use types::{LineString, Polygon, Bbox, Point}; +use types::{COORD_PRECISION, Line, LineString, Polygon, Bbox, Point}; +use algorithm::boundingbox::BoundingBox; use algorithm::contains::Contains; /// Checks if the geometry A intersects the geometry B. @@ -22,6 +23,33 @@ pub trait Intersects { fn intersects(&self, rhs: &Rhs) -> bool; } +impl Intersects> for Line + where T: Float +{ + fn intersects(&self, p: &Point) -> bool { + // if the point is not in the bounding box, it's not on the line + if !self.bbox().map_or(false, |b| b.contains(p)) { + println!("NOT IN BBOX"); + return false; + } + match self.slope() { + None => p.x() - self.start.x(), + Some(m) => { + p.y() - ((m * (p.x() - self.start.x()) + self.start.y())) + } + }.abs().to_f32().unwrap() <= COORD_PRECISION + } +} + +impl Intersects> for Line + where T: Float +{ + fn intersects(&self, line: &Line) -> bool { + false + + } +} + impl Intersects> for LineString where T: Float { diff --git a/src/types.rs b/src/types.rs index 169dab9eb..f88d35b84 100644 --- a/src/types.rs +++ b/src/types.rs @@ -319,7 +319,50 @@ impl AddAssign for Bbox pub struct MultiPoint(pub Vec>) where T: Float; #[derive(PartialEq, Clone, Debug)] -pub struct Line(pub (Point, Point)) where T: Float; +pub struct Line + where T: Float +{ + pub start: Point, + pub end: Point +} + +impl Line + where T: Float +{ + /// Creates a new line segment. + /// + /// ``` + /// use geo::{Point, Line}; + /// + /// let line = Line::new(Point::new(0., 0.), Point::new(1., 2.)); + /// + /// assert_eq!(line.start, Point::new(0., 0.)); + /// assert_eq!(line.end, Point::new(1., 2.)); + /// ``` + pub fn new(start: Point, end: Point) -> Line { + Line {start: start, end: end} + } + + /// Compute the slope of the line segment. + /// + /// ``` + /// use geo::{Point, Line} + /// + /// let line = Line::new(Point::new(0., 0.), Point::new(1., 2.)); + /// let vline = Line::new(Point::new(0., 0.), Point::new(0., 3.)); + /// + /// assert_eq!(line.slope(), Some(2.)); + /// assert_eq!(vline.slope(), None); + /// ``` + pub fn slope(&self) -> Option { + if self.start.x() == self.end.x() { + None // Vertical lines do not have slope + } else { + Some((self.start.y() - self.end.y()) / + (self.start.x() - self.end.x())) + } + } +} #[derive(PartialEq, Clone, Debug)] pub struct LineString(pub Vec>) where T: Float; From 8b1e645ce05b25a36f81eb8ece6536bd2ee8521f Mon Sep 17 00:00:00 2001 From: mattbatt Date: Tue, 23 May 2017 21:45:04 -0400 Subject: [PATCH 07/16] Implement Intersects for Line + Implements intersects between Line and Point, Line, LineString, and Polygon. + Move slope into private function withn intersects.rs --- src/algorithm/contains.rs | 4 - src/algorithm/intersects.rs | 193 +++++++++++++++++++++++++++++++++--- src/types.rs | 20 ---- 3 files changed, 181 insertions(+), 36 deletions(-) diff --git a/src/algorithm/contains.rs b/src/algorithm/contains.rs index 39044cb26..8b94a2e65 100644 --- a/src/algorithm/contains.rs +++ b/src/algorithm/contains.rs @@ -366,12 +366,9 @@ mod test { 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.)); - // point within precision of line - let line4 = Line::new(p(0., 6.00001), p(3., 3.0001)); assert!(line1.contains(&p0)); assert!(!line2.contains(&p0)); assert!(line3.contains(&p0)); - assert!(line4.contains(&p0)); } #[test] fn line_in_line_test() { @@ -403,7 +400,6 @@ mod test { assert!(!line.contains(&linestring1)); assert!(!line.contains(&linestring2)); assert!(!line.contains(&linestring3)); - println!("{:?}", linestring3.0); } #[test] fn line_in_polygon_test() { diff --git a/src/algorithm/intersects.rs b/src/algorithm/intersects.rs index bf77a7192..1a4338221 100644 --- a/src/algorithm/intersects.rs +++ b/src/algorithm/intersects.rs @@ -23,21 +23,48 @@ pub trait Intersects { fn intersects(&self, rhs: &Rhs) -> bool; } +/// Compute the slope of the line segment. +/// +/// ``` +/// use geo::{Point, Line} +/// +/// let line = Line::new(Point::new(0., 0.), Point::new(1., 2.)); +/// let vline = Line::new(Point::new(0., 0.), Point::new(0., 3.)); +/// +/// assert_eq!(slope(&line), Some(2.)); +/// assert_eq!(slope(&vline), None); +/// ``` +fn slope(line: &Line) -> Option { + if line.start.x() == line.end.x() { + None // Vertical lines do not have slope + } else { + Some((line.start.y() - line.end.y()) / + (line.start.x() - line.end.x())) + } +} + impl Intersects> for Line where T: Float { fn intersects(&self, p: &Point) -> bool { - // if the point is not in the bounding box, it's not on the line - if !self.bbox().map_or(false, |b| b.contains(p)) { - println!("NOT IN BBOX"); - return false; - } - match self.slope() { - None => p.x() - self.start.x(), - Some(m) => { - p.y() - ((m * (p.x() - self.start.x()) + self.start.y())) + let (ymin, ymax) = if self.start.y() < self.end.y() { + (self.start.y(), self.end.y()) + } else { + (self.end.y(), self.start.y()) + }; + (p.y() >= ymin) & (p.y() <= ymax) & + match slope(self) { + None => p.x() == self.start.x(), + Some(m) => p.y() == m * (p.x() - self.start.x()) + self.start.y() } - }.abs().to_f32().unwrap() <= COORD_PRECISION + } +} + +impl Intersects> for Point + where T: Float +{ + fn intersects(&self, line: &Line) -> bool { + line.intersects(self) } } @@ -45,8 +72,69 @@ impl Intersects> for Line where T: Float { fn intersects(&self, line: &Line) -> bool { - false + // Using Cramer's Rule: + // https://en.wikipedia.org/wiki/Intersection_%28Euclidean_geometry%29#Two_line_segments + let (x1, y1, x2, y2) = (self.start.x(), self.start.y(), + self.end.x(), self.end.y()); + let (x3, y3, x4, y4) = (line.start.x(), line.start.y(), + line.end.x(), line.end.y()); + let a1 = x2 - x1; + let a2 = y2 - y1; + let b1 = x3 - x4; // == -(x4 - x3) + let b2 = y3 - y4; // == -(y4 - y3) + let c1 = x3 - x1; + let c2 = y3 - y1; + let d = a1*b2 - a2*b1; + if d == T::zero() { + // lines are parallel + // return true iff line and self are co-linear + self.start.intersects(&line) | self.end.intersects(&line) | + line.start.intersects(&self) | line.end.intersects(&self) + } else { + let s = (c1*b2 - c2*b1) / d; + let t = (a1*c2 - a2*c1) / d; + (T::zero() <= s) & (s <= T::one()) & + (T::zero() <= t) & (t <= T::one()) + } + } +} + +impl Intersects> for Line + where T: Float +{ + fn intersects(&self, linestring: &LineString) -> bool { + linestring.0 + .windows(2) + .map(|pts| Line::new(pts[0], pts[1])) + .any(|line| self.intersects(&line)) + } +} + +impl Intersects> for LineString + where T: Float +{ + fn intersects(&self, line: &Line) -> bool { + line.intersects(self) + } +} + +impl Intersects> for Line + where T: Float +{ + fn intersects(&self, p: &Polygon) -> bool { + p.exterior.intersects(self) || + p.interiors.iter().any(|inner| inner.intersects(self)) || + p.contains(&self.start) || + p.contains(&self.end) + } +} + +impl Intersects> for Polygon + where T: Float +{ + fn intersects(&self, line: &Line) -> bool { + line.intersects(self) } } @@ -146,7 +234,7 @@ impl Intersects> for Polygon #[cfg(test)] mod test { - use types::{Coordinate, Point, LineString, Polygon, Bbox}; + use types::{Coordinate, Point, Line, LineString, Polygon, Bbox}; use algorithm::intersects::Intersects; /// Tests: intersection LineString and LineString #[test] @@ -358,4 +446,85 @@ mod test { assert_eq!(true, bbox_sm.intersects(&bbox_s2)); assert_eq!(true, bbox_s2.intersects(&bbox_sm)); } + #[test] + fn point_intersects_line_test() { + let p0 = Point::new(2., 4.); + // vertical line + let line1 = Line::new(Point::new(2., 0.), Point::new(2., 5.)); + // point on line, but outside line segment + let line2 = Line::new(Point::new(0., 6.), Point::new(1.5, 4.5)); + // point on line + let line3 = Line::new(Point::new(0., 6.), Point::new(3., 3.)); + assert!(line1.intersects(&p0)); + assert!(p0.intersects(&line1)); + assert!(!line2.intersects(&p0)); + assert!(!p0.intersects(&line2)); + assert!(line3.intersects(&p0)); + assert!(p0.intersects(&line3)); + } + #[test] + fn line_intersects_line_test() { + let line0 = Line::new(Point::new(0., 0.), Point::new(3., 4.)); + let line1 = Line::new(Point::new(2., 0.), Point::new(2., 5.)); + let line2 = Line::new(Point::new(0., 7.), Point::new(5., 4.)); + let line3 = Line::new(Point::new(0., 0.), Point::new(-3., -4.)); + assert!(line0.intersects(&line0)); + assert!(line0.intersects(&line1)); + assert!(!line0.intersects(&line2)); + assert!(line0.intersects(&line3)); + + assert!(line1.intersects(&line0)); + assert!(line1.intersects(&line1)); + assert!(!line1.intersects(&line2)); + assert!(!line1.intersects(&line3)); + + assert!(!line2.intersects(&line0)); + assert!(!line2.intersects(&line1)); + assert!(line2.intersects(&line2)); + assert!(!line1.intersects(&line3)); + } + #[test] + fn line_intersects_linestring_test() { + let line0 = Line::new(Point::new(0., 0.), Point::new(3., 4.)); + let linestring0 = LineString( + vec![Point::new(0., 1.), Point::new(1., 0.), Point::new(2., 0.)] + ); + let linestring1 = LineString( + vec![Point::new(0.5, 0.2), Point::new(1., 0.), Point::new(2., 0.)] + ); + assert!(line0.intersects(&linestring0)); + assert!(!line0.intersects(&linestring1)); + assert!(linestring0.intersects(&line0)); + assert!(!linestring1.intersects(&line0)); + } + #[test] + fn line_intersects_polygon_test() { + let line0 = Line::new(Point::new(0.5, 0.5), Point::new(2., 1.)); + let poly0 = Polygon::new( + LineString(vec![Point::new(0.,0.), Point::new(1., 2.), + Point::new(1., 0.), Point::new(0., 0.)]), + vec![] + ); + let poly1 = Polygon::new( + LineString(vec![Point::new(1., -1.), Point::new(2., -1.), + Point::new(2., -2.), Point::new(1., -1.)]), + vec![] + ); + // line contained in the hole + let poly2 = Polygon::new( + LineString(vec![Point::new(-1., -1.), Point::new(-1., 10.), + Point::new(10., -1.), Point::new(-1., -1.)]), + vec![LineString(vec![Point::new(0., 0.), Point::new(3., 4.), + Point::new(3., 0.), Point::new(0., 0.)])] + ); + assert!(line0.intersects(&poly0)); + assert!(poly0.intersects(&line0)); + + assert!(!line0.intersects(&poly1)); + assert!(!poly1.intersects(&line0)); + + println!("line0 intersects poly2"); + assert!(!line0.intersects(&poly2)); + assert!(!poly2.intersects(&line0)); + } } diff --git a/src/types.rs b/src/types.rs index f88d35b84..a38b96e7a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -342,26 +342,6 @@ impl Line pub fn new(start: Point, end: Point) -> Line { Line {start: start, end: end} } - - /// Compute the slope of the line segment. - /// - /// ``` - /// use geo::{Point, Line} - /// - /// let line = Line::new(Point::new(0., 0.), Point::new(1., 2.)); - /// let vline = Line::new(Point::new(0., 0.), Point::new(0., 3.)); - /// - /// assert_eq!(line.slope(), Some(2.)); - /// assert_eq!(vline.slope(), None); - /// ``` - pub fn slope(&self) -> Option { - if self.start.x() == self.end.x() { - None // Vertical lines do not have slope - } else { - Some((self.start.y() - self.end.y()) / - (self.start.x() - self.end.x())) - } - } } #[derive(PartialEq, Clone, Debug)] From 2ea0a2f3e09e393cbd0d2bc360e3ba5404b8688f Mon Sep 17 00:00:00 2001 From: mattbatt Date: Wed, 24 May 2017 11:53:50 -0400 Subject: [PATCH 08/16] Implement Contains for Line --- src/algorithm/contains.rs | 67 ++++++++++++++++++++++++++++++++++--- src/algorithm/intersects.rs | 20 +++++------ 2 files changed, 70 insertions(+), 17 deletions(-) diff --git a/src/algorithm/contains.rs b/src/algorithm/contains.rs index 8b94a2e65..78d1dd895 100644 --- a/src/algorithm/contains.rs +++ b/src/algorithm/contains.rs @@ -84,8 +84,7 @@ impl Contains> for Line where T: Float { fn contains(&self, line: &Line) -> bool { - let (a, b) = (line.start, line.end); - self.contains(&a) & self.contains(&b) + self.contains(&line.start) & self.contains(&line.end) } } @@ -97,6 +96,40 @@ impl Contains> for Line } } +impl Contains> for LineString + where T: Float +{ + fn contains(&self, line: &Line) -> bool { + let (p0, p1) = (line.start, line.end); + let mut look_for: Option> = 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, @@ -170,7 +203,13 @@ impl Contains> for Polygon where T: Float { fn contains(&self, line: &Line) -> bool { - false + // both endpoints are contained in the polygon and the line + // does NOT intersect the exterior or any of the interior boundaries + println!("contains end?: {}", self.contains(&line.end)); + self.contains(&line.start) && + self.contains(&line.end) && + !self.exterior.intersects(line) && + !self.interiors.iter().any(|inner| inner.intersects(line)) } } @@ -405,11 +444,29 @@ mod 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(0., 0.), p(5., 0.), p(5., 5.), p(0., 5.), p(0., 0.)]); + 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(2., 0.), p(2., 2.), p(0., 2.), p(0., 0.)]); + 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)); + } } diff --git a/src/algorithm/intersects.rs b/src/algorithm/intersects.rs index 1a4338221..9cf376bbe 100644 --- a/src/algorithm/intersects.rs +++ b/src/algorithm/intersects.rs @@ -23,17 +23,6 @@ pub trait Intersects { fn intersects(&self, rhs: &Rhs) -> bool; } -/// Compute the slope of the line segment. -/// -/// ``` -/// use geo::{Point, Line} -/// -/// let line = Line::new(Point::new(0., 0.), Point::new(1., 2.)); -/// let vline = Line::new(Point::new(0., 0.), Point::new(0., 3.)); -/// -/// assert_eq!(slope(&line), Some(2.)); -/// assert_eq!(slope(&vline), None); -/// ``` fn slope(line: &Line) -> Option { if line.start.x() == line.end.x() { None // Vertical lines do not have slope @@ -235,7 +224,14 @@ impl Intersects> for Polygon #[cfg(test)] mod test { use types::{Coordinate, Point, Line, LineString, Polygon, Bbox}; - use algorithm::intersects::Intersects; + use algorithm::intersects::{Intersects, slope}; + #[test] + fn slope_test() { + let line = Line::new(Point::new(0., 0.), Point::new(1., 2.)); + let vline = Line::new(Point::new(0., 0.), Point::new(0., 3.)); + assert_eq!(slope(&line), Some(2.)); + assert_eq!(slope(&vline), None); + } /// Tests: intersection LineString and LineString #[test] fn empty_linestring1_test() { From c141a222547829b21fb26c48fc60e80df0580a01 Mon Sep 17 00:00:00 2001 From: mattbatt Date: Wed, 24 May 2017 12:01:37 -0400 Subject: [PATCH 09/16] Implement Length for Line --- src/algorithm/length.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/algorithm/length.rs b/src/algorithm/length.rs index 890f308a2..6924ff5fc 100644 --- a/src/algorithm/length.rs +++ b/src/algorithm/length.rs @@ -1,6 +1,6 @@ use num_traits::Float; -use types::{LineString, MultiLineString}; +use types::{Line, LineString, MultiLineString}; use algorithm::distance::Distance; /// Calculation of the length @@ -23,6 +23,14 @@ pub trait Length { fn length(&self) -> T; } +impl Length for Line + where T: Float +{ + fn length(&self) -> T { + self.start.distance(&self.end) + } +} + impl Length for LineString where T: Float { @@ -42,7 +50,7 @@ impl Length for MultiLineString #[cfg(test)] mod test { - use types::{Coordinate, Point, LineString, MultiLineString}; + use types::{Coordinate, Point, Line, LineString, MultiLineString}; use algorithm::length::Length; #[test] @@ -68,4 +76,11 @@ mod test { LineString(vec![p(0., 0.), p(0., 5.)])]); assert_eq!(15.0_f64, mline.length()); } + #[test] + fn line_test() { + let line0 = Line::new(Point::new(0., 0.), Point::new(0., 1.)); + let line1 = Line::new(Point::new(0., 0.), Point::new(3., 4.)); + assert_eq!(line0.length(), 1.); + assert_eq!(line1.length(), 5.); + } } From ed3d5102d5508c097ef0223e120cf42fbd537059 Mon Sep 17 00:00:00 2001 From: mattbatt Date: Wed, 24 May 2017 12:34:03 -0400 Subject: [PATCH 10/16] Implement Rotate for Line --- src/algorithm/rotate.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/algorithm/rotate.rs b/src/algorithm/rotate.rs index 2fc6c993d..287ca20c0 100644 --- a/src/algorithm/rotate.rs +++ b/src/algorithm/rotate.rs @@ -1,5 +1,5 @@ use num_traits::{Float, FromPrimitive}; -use types::{Point, Polygon, LineString, MultiPoint, MultiPolygon, MultiLineString}; +use types::{Point, Line, Polygon, LineString, MultiPoint, MultiPolygon, MultiLineString}; use algorithm::centroid::Centroid; // rotate a slice of points "angle" degrees about an origin @@ -92,6 +92,27 @@ impl RotatePoint for Point } } +impl Rotate for Line + where T: Float +{ + fn rotate(&self, angle: T) -> Self { + let pts = vec![self.start, self.end]; + let rotated = rotation_matrix(angle, &self.centroid().unwrap(), &pts); + Line::new(rotated[0], rotated[1]) + } +} + +impl RotatePoint for Line + where T: Float +{ + fn rotate_around_point(&self, angle: T, point: &Point) -> Self { + let pts = vec![self.start, self.end]; + let rotated = rotation_matrix(angle, point, &pts); + Line::new(rotated[0], rotated[1]) + + } +} + impl Rotate for LineString where T: Float { @@ -312,4 +333,16 @@ mod test { let rotated = p.rotate_around_point(-45., &Point::new(10., 34.)); assert_eq!(rotated, Point::new(-10.506096654409877, 20.564971157455595)); } + #[test] + fn test_rotate_line() { + let line0 = Line::new(Point::new(0., 0.), Point::new(0., 2.)); + let line1 = Line::new(Point::new(1., 0.9999999999999999), Point::new(-1., 1.)); + assert_eq!(line0.rotate(90.), line1); + } + #[test] + fn test_rotate_line_around_point() { + let line0 = Line::new(Point::new(0., 0.), Point::new(0., 2.)); + let line1 = Line::new(Point::new(0., 0.), Point::new(-2., 0.00000000000000012246467991473532)); + assert_eq!(line0.rotate_around_point(90., &Point::new(0., 0.)), line1); + } } From 8a637e7bd54a7c9f73f2cda973b2a3c6d40db01a Mon Sep 17 00:00:00 2001 From: mattbatt Date: Wed, 24 May 2017 12:44:13 -0400 Subject: [PATCH 11/16] Implement Distance for Line --- src/algorithm/distance.rs | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/algorithm/distance.rs b/src/algorithm/distance.rs index 39539ef15..17ce99742 100644 --- a/src/algorithm/distance.rs +++ b/src/algorithm/distance.rs @@ -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; @@ -263,9 +263,26 @@ impl Distance> for LineString } } +impl Distance> for Line + where T: Float +{ + /// Minimum distance from a Lined to a Point + fn distance(&self, point: &Point) -> T { + line_segment_distance(point, &self.start, &self.end) + } +} +impl Distance> for Point + where T: Float +{ + /// Minimum distance from a Line to a Point + fn distance(&self, line: &Line) -> 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] @@ -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.); + } } From 24a923314280c9bec8929b1f645a1becdff5cd93 Mon Sep 17 00:00:00 2001 From: mattbatt Date: Wed, 24 May 2017 13:41:31 -0400 Subject: [PATCH 12/16] Remove unused imports --- src/algorithm/contains.rs | 1 - src/algorithm/intersects.rs | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/algorithm/contains.rs b/src/algorithm/contains.rs index 78d1dd895..f9e9d1229 100644 --- a/src/algorithm/contains.rs +++ b/src/algorithm/contains.rs @@ -1,7 +1,6 @@ use num_traits::{Float, ToPrimitive}; use types::{COORD_PRECISION, Point, Line, LineString, Polygon, MultiPolygon, Bbox}; -use algorithm::boundingbox::BoundingBox; use algorithm::intersects::Intersects; use algorithm::distance::Distance; diff --git a/src/algorithm/intersects.rs b/src/algorithm/intersects.rs index 9cf376bbe..a5e602105 100644 --- a/src/algorithm/intersects.rs +++ b/src/algorithm/intersects.rs @@ -1,6 +1,5 @@ use num_traits::Float; -use types::{COORD_PRECISION, Line, LineString, Polygon, Bbox, Point}; -use algorithm::boundingbox::BoundingBox; +use types::{Line, LineString, Polygon, Bbox, Point}; use algorithm::contains::Contains; /// Checks if the geometry A intersects the geometry B. From c46619a229a4b9f871bb2eea287b63601d2a58a9 Mon Sep 17 00:00:00 2001 From: mattbatt Date: Wed, 24 May 2017 14:51:10 -0400 Subject: [PATCH 13/16] Remove debug print statements --- src/algorithm/contains.rs | 2 -- src/algorithm/intersects.rs | 1 - 2 files changed, 3 deletions(-) diff --git a/src/algorithm/contains.rs b/src/algorithm/contains.rs index f9e9d1229..b32034003 100644 --- a/src/algorithm/contains.rs +++ b/src/algorithm/contains.rs @@ -204,7 +204,6 @@ impl Contains> for Polygon fn contains(&self, line: &Line) -> bool { // both endpoints are contained in the polygon and the line // does NOT intersect the exterior or any of the interior boundaries - println!("contains end?: {}", self.contains(&line.end)); self.contains(&line.start) && self.contains(&line.end) && !self.exterior.intersects(line) && @@ -225,7 +224,6 @@ impl Contains> for Polygon } } - impl Contains> for Bbox where T: Float { diff --git a/src/algorithm/intersects.rs b/src/algorithm/intersects.rs index a5e602105..ca4616103 100644 --- a/src/algorithm/intersects.rs +++ b/src/algorithm/intersects.rs @@ -518,7 +518,6 @@ mod test { assert!(!line0.intersects(&poly1)); assert!(!poly1.intersects(&line0)); - println!("line0 intersects poly2"); assert!(!line0.intersects(&poly2)); assert!(!poly2.intersects(&line0)); } From a636483d9b927c5c98181fc6111ab23e82892ebe Mon Sep 17 00:00:00 2001 From: mattbatt Date: Wed, 24 May 2017 15:29:57 -0400 Subject: [PATCH 14/16] Clean up --- src/algorithm/centroid.rs | 5 ++--- src/algorithm/distance.rs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/algorithm/centroid.rs b/src/algorithm/centroid.rs index 9cc06f8f9..22a9caeab 100644 --- a/src/algorithm/centroid.rs +++ b/src/algorithm/centroid.rs @@ -58,10 +58,9 @@ impl Centroid for Line where T: Float { fn centroid(&self) -> Option> { - let (a, b) = (self.start, self.end); let two = T::one() + T::one(); - let x = (a.x() + b.x()) / two; - let y = (a.y() + b.y()) / two; + let x = (self.start.x() + self.end.x()) / two; + let y = (self.start.y() + self.end.y()) / two; Some(Point::new(x, y)) } } diff --git a/src/algorithm/distance.rs b/src/algorithm/distance.rs index 17ce99742..c198d6d3f 100644 --- a/src/algorithm/distance.rs +++ b/src/algorithm/distance.rs @@ -266,7 +266,7 @@ impl Distance> for LineString impl Distance> for Line where T: Float { - /// Minimum distance from a Lined to a Point + /// Minimum distance from a Line to a Point fn distance(&self, point: &Point) -> T { line_segment_distance(point, &self.start, &self.end) } From 8cf5c92101130eda5b92f8cef6c4b6295e807cd5 Mon Sep 17 00:00:00 2001 From: mattbatt Date: Wed, 24 May 2017 15:50:26 -0400 Subject: [PATCH 15/16] Point Intersects Line using parametric equations --- src/algorithm/intersects.rs | 53 +++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/algorithm/intersects.rs b/src/algorithm/intersects.rs index ca4616103..db8c8dfd6 100644 --- a/src/algorithm/intersects.rs +++ b/src/algorithm/intersects.rs @@ -22,29 +22,37 @@ pub trait Intersects { fn intersects(&self, rhs: &Rhs) -> bool; } -fn slope(line: &Line) -> Option { - if line.start.x() == line.end.x() { - None // Vertical lines do not have slope - } else { - Some((line.start.y() - line.end.y()) / - (line.start.x() - line.end.x())) - } -} - impl Intersects> for Line where T: Float { fn intersects(&self, p: &Point) -> bool { - let (ymin, ymax) = if self.start.y() < self.end.y() { - (self.start.y(), self.end.y()) - } else { - (self.end.y(), self.start.y()) - }; - (p.y() >= ymin) & (p.y() <= ymax) & - match slope(self) { - None => p.x() == self.start.x(), - Some(m) => p.y() == m * (p.x() - self.start.x()) + self.start.y() + let dx = self.end.x() - self.start.x(); + let dy = self.end.y() - self.start.y(); + let tx = if dx == T::zero() {None} else {Some((p.x() - self.start.x()) / dx)}; + let ty = if dy == T::zero() {None} else {Some((p.y() - self.start.y()) / dy)}; + match (tx, ty) { + (None, None) => { // Degenerate line + println!("degenerate"); + *p == self.start + }, + (Some(t), None) => { // Horizontal line + println!("horizontal"); + p.y() == self.start.y() && + T::zero() <= t && + t <= T::one() + }, + (None, Some(t)) => { // Vertical line + println!("vertical"); + p.x() == self.start.x() && + T::zero() <= t && + t <= T::one() + }, + (Some(t_x), Some(t_y)) => { // All other lines + t_x.abs_sub(t_y) <= T::epsilon() && + T::zero() <= t_x && + t_x <= T::one() } + } } } @@ -223,14 +231,7 @@ impl Intersects> for Polygon #[cfg(test)] mod test { use types::{Coordinate, Point, Line, LineString, Polygon, Bbox}; - use algorithm::intersects::{Intersects, slope}; - #[test] - fn slope_test() { - let line = Line::new(Point::new(0., 0.), Point::new(1., 2.)); - let vline = Line::new(Point::new(0., 0.), Point::new(0., 3.)); - assert_eq!(slope(&line), Some(2.)); - assert_eq!(slope(&vline), None); - } + use algorithm::intersects::Intersects; /// Tests: intersection LineString and LineString #[test] fn empty_linestring1_test() { From 4ca324f02e0b77b313b3c80dab397dad3a793a51 Mon Sep 17 00:00:00 2001 From: mattbatt Date: Wed, 24 May 2017 16:18:27 -0400 Subject: [PATCH 16/16] Clean up --- src/algorithm/intersects.rs | 13 +++++-------- src/algorithm/rotate.rs | 1 - 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/algorithm/intersects.rs b/src/algorithm/intersects.rs index db8c8dfd6..8e6e88a4e 100644 --- a/src/algorithm/intersects.rs +++ b/src/algorithm/intersects.rs @@ -32,17 +32,14 @@ impl Intersects> for Line let ty = if dy == T::zero() {None} else {Some((p.y() - self.start.y()) / dy)}; match (tx, ty) { (None, None) => { // Degenerate line - println!("degenerate"); *p == self.start }, (Some(t), None) => { // Horizontal line - println!("horizontal"); p.y() == self.start.y() && T::zero() <= t && t <= T::one() }, (None, Some(t)) => { // Vertical line - println!("vertical"); p.x() == self.start.x() && T::zero() <= t && t <= T::one() @@ -84,14 +81,14 @@ impl Intersects> for Line let d = a1*b2 - a2*b1; if d == T::zero() { // lines are parallel - // return true iff line and self are co-linear - self.start.intersects(&line) | self.end.intersects(&line) | - line.start.intersects(&self) | line.end.intersects(&self) + // return true iff at least one endpoint intersects the other line + self.start.intersects(&line) || self.end.intersects(&line) || + line.start.intersects(&self) || line.end.intersects(&self) } else { let s = (c1*b2 - c2*b1) / d; let t = (a1*c2 - a2*c1) / d; - (T::zero() <= s) & (s <= T::one()) & - (T::zero() <= t) & (t <= T::one()) + (T::zero() <= s) && (s <= T::one()) && + (T::zero() <= t) && (t <= T::one()) } } } diff --git a/src/algorithm/rotate.rs b/src/algorithm/rotate.rs index 287ca20c0..5b5c6014d 100644 --- a/src/algorithm/rotate.rs +++ b/src/algorithm/rotate.rs @@ -109,7 +109,6 @@ impl RotatePoint for Line let pts = vec![self.start, self.end]; let rotated = rotation_matrix(angle, point, &pts); Line::new(rotated[0], rotated[1]) - } }