From 764e7c527e3a7f268a8342b122504a8a1be19766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20H=C3=BCgel?= Date: Tue, 15 May 2018 16:52:52 +0100 Subject: [PATCH 1/5] Simplify distance algorithms using Clippy suggestions --- geo/src/algorithm/euclidean_distance.rs | 23 ++++++------- .../algorithm/polygon_distance_fast_path.rs | 34 +++++++++---------- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/geo/src/algorithm/euclidean_distance.rs b/geo/src/algorithm/euclidean_distance.rs index dea6c2871..aa28134ee 100644 --- a/geo/src/algorithm/euclidean_distance.rs +++ b/geo/src/algorithm/euclidean_distance.rs @@ -298,18 +298,15 @@ where fn euclidean_distance(&self, other: &Polygon) -> T { if self.intersects(other) || other.contains(self) { T::zero() - } else { - // still a possibility that the LineString's inside an interior ring - if !other.interiors.is_empty() && ring_contains_point(other, &self.0[0]) { - // check each ring distance, returning the minimum - let mut mindist: T = Float::max_value(); - for ring in &other.interiors { - mindist = mindist.min(nearest_neighbour_distance(self, ring)) - } - mindist - } else { - nearest_neighbour_distance(self, &other.exterior) + } else if !other.interiors.is_empty() && ring_contains_point(other, &self.0[0]) { + // check each ring distance, returning the minimum + let mut mindist: T = Float::max_value(); + for ring in &other.interiors { + mindist = mindist.min(nearest_neighbour_distance(self, ring)) } + mindist + } else { + nearest_neighbour_distance(self, &other.exterior) } } } @@ -373,7 +370,7 @@ where } return mindist; } - if !(self.is_convex() && !poly2.is_convex()) { + if poly2.is_convex() || !self.is_convex() { // fall back to R* nearest neighbour method nearest_neighbour_distance(&self.exterior, &poly2.exterior) } else { @@ -384,7 +381,7 @@ where /// Uses an R* tree and nearest-neighbour lookups to calculate minimum distances // This is somewhat slow and memory-inefficient, but certainly better than quadratic time -fn nearest_neighbour_distance(geom1: &LineString, geom2: &LineString) -> T +pub fn nearest_neighbour_distance(geom1: &LineString, geom2: &LineString) -> T where T: Float + SpadeFloat, { diff --git a/geo/src/algorithm/polygon_distance_fast_path.rs b/geo/src/algorithm/polygon_distance_fast_path.rs index dcf1d8b06..04dfbbb5f 100644 --- a/geo/src/algorithm/polygon_distance_fast_path.rs +++ b/geo/src/algorithm/polygon_distance_fast_path.rs @@ -20,11 +20,11 @@ where let ymax2 = poly2.exterior.0[poly2_extremes.ymax]; let mut state = Polydist { - poly1: poly1, - poly2: poly2, + poly1, + poly2, dist: T::infinity(), - ymin1: ymin1, - ymax2: ymax2, + ymin1, + ymax2, // initial polygon 1 min y idx p1_idx: poly1_extremes.ymin, // initial polygon 2 max y idx @@ -345,23 +345,21 @@ where // implies p.x() < pprev.x() punit = Point::new(p.x(), p.y() + hundred); } - } else { - if p.x() > pprev.x() { + } else if p.x() > pprev.x() { + punit = Point::new(p.x(), p.y() + hundred); + } else if p.x() == pprev.x() { + if p.y() > pprev.y() { punit = Point::new(p.x(), p.y() + hundred); - } else if p.x() == pprev.x() { - if p.y() > pprev.y() { - punit = Point::new(p.x(), p.y() + hundred); - } else { - // implies p.y() < pprev.y() - // it's safe not to explicitly cover p.y() == pprev.y() because that - // implies that the x values are equal, and the y values are equal, - // and this is impossible - punit = Point::new(p.x(), p.y() - hundred); - } } else { - // implies p.x() < pprev.x() + // implies p.y() < pprev.y() + // it's safe not to explicitly cover p.y() == pprev.y() because that + // implies that the x values are equal, and the y values are equal, + // and this is impossible punit = Point::new(p.x(), p.y() - hundred); } + } else { + // implies p.x() < pprev.x() + punit = Point::new(p.x(), p.y() - hundred); } let triarea = triangle_area(p, &punit, &pnext); let edgelen = p.euclidean_distance(&pnext); @@ -524,7 +522,7 @@ where } // A start value's been set, and both polygon indices are in their initial // positions -- we're finished, so return the minimum distance - if state.p1 == state.ymin1 && state.q2 == state.ymax2 && !state.start.is_none() { + if state.p1 == state.ymin1 && state.q2 == state.ymax2 && state.start.is_some() { state.finished = true; } else { state.start = Some(false); From 4c5a2889951cf64e66bac28de700cb5397173d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20H=C3=BCgel?= Date: Tue, 15 May 2018 17:50:49 +0100 Subject: [PATCH 2/5] Correctly implement Line-Polygon distance See https://github.com/georust/rust-geo/pull/219#pullrequestreview-120296643 --- geo/src/algorithm/euclidean_distance.rs | 38 ++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/geo/src/algorithm/euclidean_distance.rs b/geo/src/algorithm/euclidean_distance.rs index aa28134ee..2f79ceeee 100644 --- a/geo/src/algorithm/euclidean_distance.rs +++ b/geo/src/algorithm/euclidean_distance.rs @@ -324,12 +324,29 @@ where // Line to Polygon distance impl EuclideanDistance> for Line where - T: Float + FloatConst + Signed + SpadeFloat, + T: Float + Signed + SpadeFloat, { fn euclidean_distance(&self, other: &Polygon) -> T { - self.start - .euclidean_distance(other) - .min(self.end.euclidean_distance(other)) + if other.contains(self) || self.intersects(other) { + return T::zero(); + } + // point-line distance between each exterior polygon point and the line + let exterior_min = other.exterior.points().fold(T::max_value(), |acc, point| { + acc.min(self.euclidean_distance(point)) + }); + // point-line distance between each interior ring point and the line + // if there are no rings this just evaluates to max_float + let interior_min = other + .interiors + .iter() + .map(|ring| { + ring.points().fold(T::max_value(), |acc, point| { + acc.min(self.euclidean_distance(point)) + }) + }) + .fold(T::max_value(), |acc, ring_min| acc.min(ring_min)); + // return smaller of the two values + exterior_min.min(interior_min) } } @@ -828,4 +845,17 @@ mod test { let in_ring_ls: LineString = in_ring.into(); assert_eq!(ring_ls.euclidean_distance(&in_ring_ls), 5.992772737231033); } + #[test] + // Line-Polygon test: closest point on Polygon is NOT nearest to a Line end-point + fn flibble() { + let line = Line::new(Point::new(0.0, 0.0), Point::new(0.0, 3.0)); + let v = vec![ + (5.0, 1.0), + (5.0, 2.0), + (0.25, 1.5), + (5.0, 1.0) + ]; + let poly = Polygon::new(v.into(), vec![]); + assert_eq!(line.euclidean_distance(&poly), 0.25); + } } From 3b6c87956bd6ae785bd76ad470164401814267c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20H=C3=BCgel?= Date: Wed, 16 May 2018 18:30:40 +0100 Subject: [PATCH 3/5] Add Line-Line euclidean distance Closes #229 --- geo/src/algorithm/euclidean_distance.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/geo/src/algorithm/euclidean_distance.rs b/geo/src/algorithm/euclidean_distance.rs index 2f79ceeee..5010e1a03 100644 --- a/geo/src/algorithm/euclidean_distance.rs +++ b/geo/src/algorithm/euclidean_distance.rs @@ -321,6 +321,21 @@ where } } +impl EuclideanDistance> for Line +where + T: Float + FloatConst + Signed + SpadeFloat, +{ + fn euclidean_distance(&self, other: &Line) -> T { + if self.intersects(other) || self.contains(other) { + return T::zero(); + } + // minimum of two Point-Line distances + self.start + .euclidean_distance(other) + .min(self.end.euclidean_distance(other)) + } +} + // Line to Polygon distance impl EuclideanDistance> for Line where From 956c27af55f03a66f59776bd397760fb3c9d3bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20H=C3=BCgel?= Date: Wed, 16 May 2018 18:40:22 +0100 Subject: [PATCH 4/5] Fix Line-inside-ring special case --- geo/src/algorithm/euclidean_distance.rs | 40 ++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/geo/src/algorithm/euclidean_distance.rs b/geo/src/algorithm/euclidean_distance.rs index 5010e1a03..f6bb560b7 100644 --- a/geo/src/algorithm/euclidean_distance.rs +++ b/geo/src/algorithm/euclidean_distance.rs @@ -339,7 +339,7 @@ where // Line to Polygon distance impl EuclideanDistance> for Line where - T: Float + Signed + SpadeFloat, + T: Float + Signed + SpadeFloat + FloatConst, { fn euclidean_distance(&self, other: &Polygon) -> T { if other.contains(self) || self.intersects(other) { @@ -355,8 +355,8 @@ where .interiors .iter() .map(|ring| { - ring.points().fold(T::max_value(), |acc, point| { - acc.min(self.euclidean_distance(point)) + ring.lines().fold(T::max_value(), |acc, line| { + acc.min(self.euclidean_distance(&line)) }) }) .fold(T::max_value(), |acc, ring_min| acc.min(ring_min)); @@ -862,7 +862,7 @@ mod test { } #[test] // Line-Polygon test: closest point on Polygon is NOT nearest to a Line end-point - fn flibble() { + fn test_line_polygon_simple() { let line = Line::new(Point::new(0.0, 0.0), Point::new(0.0, 3.0)); let v = vec![ (5.0, 1.0), @@ -873,4 +873,36 @@ mod test { let poly = Polygon::new(v.into(), vec![]); assert_eq!(line.euclidean_distance(&poly), 0.25); } + #[test] + // Line-Polygon test: Line intersects Polygon + fn test_line_polygon_intersects() { + let line = Line::new(Point::new(0.5, 0.0), Point::new(0.0, 3.0)); + let v = vec![ + (5.0, 1.0), + (5.0, 2.0), + (0.25, 1.5), + (5.0, 1.0) + ]; + let poly = Polygon::new(v.into(), vec![]); + assert_eq!(line.euclidean_distance(&poly), 0.0); + } + #[test] + // Line-Polygon test: Line contained by interior ring + fn test_line_polygon_inside_ring() { + let line = Line::new(Point::new(4.4, 1.5), Point::new(4.45, 1.5)); + let v = vec![ + (5.0, 1.0), + (5.0, 2.0), + (0.25, 1.0), + (5.0, 1.0) + ]; + let v2 = vec![ + (4.5, 1.2), + (4.5, 1.8), + (3.5, 1.2), + (4.5, 1.2) + ]; + let poly = Polygon::new(v.into(), vec![v2.into()]); + assert_eq!(line.euclidean_distance(&poly), 0.04999999999999982); + } } From 8b71df6417ac2a0b281cce1649c3001e7c5ec375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20H=C3=BCgel?= Date: Wed, 16 May 2018 18:49:36 +0100 Subject: [PATCH 5/5] Update CHANGES and bump version --- CHANGES.md | 5 +++++ geo/Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index c3e6ce14c..9f72254cd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## geo 0.9.1 (unreleased) + +* Fix Line-Polygon euclidean distance + * + ## geo 0.9.0 * Make serde an optional dependency for `geo`, rename feature to `use-serde` diff --git a/geo/Cargo.toml b/geo/Cargo.toml index 8efddb2bc..f73a5f59c 100644 --- a/geo/Cargo.toml +++ b/geo/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "geo" description = "Geospatial primitives and algorithms" -version = "0.9.0" +version = "0.9.1" authors = ["Corey Farwell "] license = "MIT/Apache-2.0" repository = "https://github.com/georust/rust-geo"