diff --git a/geo/CHANGES.md b/geo/CHANGES.md index da7cd2ad5..cbc6e577f 100644 --- a/geo/CHANGES.md +++ b/geo/CHANGES.md @@ -11,6 +11,8 @@ - - Add Voronoi diagram generation functionality - +- Fix Euclidean distance fast path for open `LineString`s to consider the last vertex (avoids incorrect `LineString`-to-`LineString` distances for separable geometries). + - ## 0.32.0 - 2025-12-05 diff --git a/geo/src/algorithm/line_measures/metric_spaces/euclidean/distance.rs b/geo/src/algorithm/line_measures/metric_spaces/euclidean/distance.rs index 0078c735c..dc502664f 100644 --- a/geo/src/algorithm/line_measures/metric_spaces/euclidean/distance.rs +++ b/geo/src/algorithm/line_measures/metric_spaces/euclidean/distance.rs @@ -708,9 +708,17 @@ fn calculate_vertex_intercepts( perpendicular_slope: F, use_x_intercept: bool, ) -> Vec> { - // Skip the last coordinate as it duplicates the first in closed polygon rings. + // If this is a closed ring (polygon exterior/interior), skip the duplicate closing vertex. + // For open LineStrings, we must include the last vertex; otherwise the fast path can miss + // the true nearest neighbour. // We maintain the original index for later segment construction. - coords[..coords.len().saturating_sub(1)] + let is_closed = coords.first() == coords.last(); + let coords = if is_closed { + &coords[..coords.len().saturating_sub(1)] + } else { + coords + }; + coords .iter() .enumerate() .map(|(idx, &coord)| { @@ -854,6 +862,7 @@ mod test { let zero_dist = line_segment_distance(p1, p1, p2); assert_relative_eq!(zero_dist, 0.0); } + #[test] // Point to Polygon, outside point fn point_polygon_distance_outside_test() { @@ -1339,6 +1348,28 @@ mod test { assert_relative_eq!(Euclidean.distance(&ls, &line), 1.0); } + #[test] + fn test_linestring_linestring_distance() { + let ls1: LineString = + LineString::from(vec![(-13.242, 2.942), (-27.982, -18.803), (-6.811, -4.642)]); + let ls2: LineString = LineString::from(vec![ + (18.297, 31.208), + (29.368, 30.533), + (26.45, 0.543), + (14.711, -1.764), + ]); + + let rect_a = ls1.bounding_rect().unwrap(); + let rect_b = ls2.bounding_rect().unwrap(); + let x_separated = rect_a.max().x < rect_b.min().x || rect_b.max().x < rect_a.min().x; + let y_separated = rect_a.max().y < rect_b.min().y || rect_b.max().y < rect_a.min().y; + assert!(x_separated || y_separated); + + let expected = 21.713575661323034_f64; + let d = Euclidean.distance(&ls1, &ls2); + assert_relative_eq!(d, expected, epsilon = 1e-12); + } + #[test] // Triangle-Point test: point on vertex fn test_triangle_point_on_vertex_distance() {