From 1bd61e594641ea60df5f331875fb4df16d942b2c Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Wed, 26 Jul 2023 15:49:59 -0400 Subject: [PATCH 1/7] add hausdorff distance trait --- geo/src/algorithm/hausdorff_distance.rs | 194 ++++++++++++++++++++++++ geo/src/algorithm/mod.rs | 4 + geo/src/lib.rs | 1 + 3 files changed, 199 insertions(+) create mode 100644 geo/src/algorithm/hausdorff_distance.rs diff --git a/geo/src/algorithm/hausdorff_distance.rs b/geo/src/algorithm/hausdorff_distance.rs new file mode 100644 index 000000000..e358125cf --- /dev/null +++ b/geo/src/algorithm/hausdorff_distance.rs @@ -0,0 +1,194 @@ +use crate::GeoFloat; +use crate::CoordsIter; +use crate::algorithm::EuclideanDistance; +use num_traits::Bounded; // used to have T as generic type in folding +use geo_types::*; + +/// Determine the distance between two geometries using the [Hausdorff distance formula]. +/// +/// [haversine formula]: https://en.wikipedia.org/wiki/Hausdorff_distance +pub trait HausdorffDistance { + fn hausdorff_distance(&self, rhs: &Rhs) -> T; +} + +// We can take advantage of the coords_iter() method for all geometries +// to iterate across all combos of coords (infinum). Simplifies the +// implementations for all geometries. +macro_rules! impl_hausdorff_distance_coord_iter { + ($from:ident, [$($to:ident),*]) => { + $( + impl HausdorffDistance> for $from + where + T: GeoFloat + { + fn hausdorff_distance(&self, geom: &$to) -> T { + // calculate from A -> B + let hd1 = self + .coords_iter() + .map(|c| { + geom + .coords_iter() + .map(|c2| { + c.euclidean_distance(&c2) + }) + .fold(::max_value(), |accum, val| accum.min(val)) + }) + .fold(::min_value(), |accum, val| accum.max(val)); + + // Calculate from B -> A + let hd2 = geom + .coords_iter() + .map(|c| { + self + .coords_iter() + .map(|c2| { + c.euclidean_distance(&c2) + }) + .fold(::max_value(), |accum, val| accum.min(val)) + }) + .fold(::min_value(), |accum, val| accum.max(val)); + + // The max of the two + hd1.max(hd2) + } + } + )* + }; +} + + +impl_hausdorff_distance_coord_iter!{ + Line, + [ + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, + Polygon, MultiPolygon, + Geometry, GeometryCollection + ] +} + + +impl_hausdorff_distance_coord_iter!{ + Triangle, + [ + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, + Polygon, MultiPolygon, + Geometry, GeometryCollection + ] +} + +impl_hausdorff_distance_coord_iter!{ + Rect, + [ + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, + Polygon, MultiPolygon, + Geometry, GeometryCollection + ] +} + +impl_hausdorff_distance_coord_iter!{ + Point, [ + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, + Polygon, MultiPolygon, + Geometry, GeometryCollection + ] +} + +impl_hausdorff_distance_coord_iter!{ + LineString, [ + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, + Polygon, MultiPolygon, + Geometry, GeometryCollection + ] +} + +impl_hausdorff_distance_coord_iter!{ + Polygon, [ + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, + Polygon, MultiPolygon, + Geometry, GeometryCollection + ] +} + + +impl_hausdorff_distance_coord_iter!{ + MultiPoint, [ + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, + Polygon, MultiPolygon, + Geometry, GeometryCollection + ] +} + +impl_hausdorff_distance_coord_iter!{ + MultiLineString, [ + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, + Polygon, MultiPolygon, + Geometry, GeometryCollection + ] +} + +impl_hausdorff_distance_coord_iter!{ + MultiPolygon, [ + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, + Polygon, MultiPolygon, + Geometry, GeometryCollection + ] +} + +impl_hausdorff_distance_coord_iter!{ + GeometryCollection, [ + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, + Polygon, MultiPolygon, + Geometry, GeometryCollection + ] +} + +impl_hausdorff_distance_coord_iter!{ + Geometry, [ + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, + Polygon, MultiPolygon, + Geometry, GeometryCollection + ] +} + + +// ┌───────────────────────────┐ +// │ Implementations for Coord │ +// └───────────────────────────┘ + +// since Coord does not have coords_iter() method we have +// to make a macro for it specifically +macro_rules! impl_haussdorf_distance_coord { + ($($for:ident),*) => { + $( + impl HausdorffDistance> for Coord + where + T: GeoFloat + { + fn hausdorff_distance(&self, geom: &$for) -> T { + let p = Point::from(*self); + p.hausdorff_distance(geom) + } + } + )* + }; +} + +// Implement methods for all other geometries +impl_haussdorf_distance_coord!( + Line, Rect, Triangle, + Point, MultiPoint, + LineString, MultiLineString, + Polygon, MultiPolygon, + Geometry, GeometryCollection +); diff --git a/geo/src/algorithm/mod.rs b/geo/src/algorithm/mod.rs index ba455597b..b6033a605 100644 --- a/geo/src/algorithm/mod.rs +++ b/geo/src/algorithm/mod.rs @@ -120,6 +120,10 @@ pub use geodesic_intermediate::GeodesicIntermediate; pub mod geodesic_length; pub use geodesic_length::GeodesicLength; +/// Calculate the Hausdorff distance between two geometries. +pub mod hausdorff_distance; +pub use hausdorff_distance::HausdorffDistance; + /// Calculate the bearing to another `Point`, in degrees. pub mod haversine_bearing; pub use haversine_bearing::HaversineBearing; diff --git a/geo/src/lib.rs b/geo/src/lib.rs index c4f4f8183..4f61f9b9e 100644 --- a/geo/src/lib.rs +++ b/geo/src/lib.rs @@ -45,6 +45,7 @@ //! //! - **[`EuclideanDistance`](EuclideanDistance)**: Calculate the minimum euclidean distance between geometries //! - **[`GeodesicDistance`](GeodesicDistance)**: Calculate the minimum geodesic distance between geometries using the algorithm presented in _Algorithms for geodesics_ by Charles Karney (2013) +//! - **[`HausdorffDistance`](HausdorffDistance)**: Calculate "the maximum of the distances from a point in any of the sets to the nearest point in the other set." (Rote, 1991) //! - **[`HaversineDistance`](HaversineDistance)**: Calculate the minimum geodesic distance between geometries using the haversine formula //! - **[`VincentyDistance`](VincentyDistance)**: Calculate the minimum geodesic distance between geometries using Vincenty’s formula //! From bf5977aa66fbaaf91262015a75c9cde389cf6868 Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Thu, 27 Jul 2023 08:03:33 -0400 Subject: [PATCH 2/7] resolve PR comments --- geo/src/algorithm/hausdorff_distance.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/geo/src/algorithm/hausdorff_distance.rs b/geo/src/algorithm/hausdorff_distance.rs index e358125cf..fe950e73d 100644 --- a/geo/src/algorithm/hausdorff_distance.rs +++ b/geo/src/algorithm/hausdorff_distance.rs @@ -6,7 +6,11 @@ use geo_types::*; /// Determine the distance between two geometries using the [Hausdorff distance formula]. /// -/// [haversine formula]: https://en.wikipedia.org/wiki/Hausdorff_distance +/// Hausdorff distance is used to compare two point sets. It measures the maximum euclidean +/// distance of a point in one set to the nearest point in another set. Hausdorff distance +/// is often used to measure the amount of mismatch between two sets. ` +/// +/// [Hausdorff distance formula]: https://en.wikipedia.org/wiki/Hausdorff_distance pub trait HausdorffDistance { fn hausdorff_distance(&self, rhs: &Rhs) -> T; } From 1c44e3b5578b4e2af24bf3755827ca2b87c7e2eb Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Thu, 27 Jul 2023 08:29:16 -0400 Subject: [PATCH 3/7] add a couple of tests --- geo/src/algorithm/hausdorff_distance.rs | 82 ++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/geo/src/algorithm/hausdorff_distance.rs b/geo/src/algorithm/hausdorff_distance.rs index fe950e73d..452d378b7 100644 --- a/geo/src/algorithm/hausdorff_distance.rs +++ b/geo/src/algorithm/hausdorff_distance.rs @@ -8,7 +8,7 @@ use geo_types::*; /// /// Hausdorff distance is used to compare two point sets. It measures the maximum euclidean /// distance of a point in one set to the nearest point in another set. Hausdorff distance -/// is often used to measure the amount of mismatch between two sets. ` +/// is often used to measure the amount of mismatch between two sets. /// /// [Hausdorff distance formula]: https://en.wikipedia.org/wiki/Hausdorff_distance pub trait HausdorffDistance { @@ -196,3 +196,83 @@ impl_haussdorf_distance_coord!( Polygon, MultiPolygon, Geometry, GeometryCollection ); + + +#[cfg(test)] +mod test { + use crate::HausdorffDistance; + use crate::{MultiPoint, polygon, line_string, MultiPolygon}; + + #[test] + fn hd_mpnt_mpnt() { + let p1: MultiPoint<_> = vec![(0., 0.), (1., 2.)].into(); + let p2: MultiPoint<_> = vec![(2., 3.), (1., 2.)].into(); + assert_relative_eq!( + p1.hausdorff_distance(&p2), + 2.236068, + epsilon = 1.0e-6 + ); + } + + #[test] + fn hd_mpnt_poly() { + + let p1: MultiPoint<_> = vec![(0., 0.), (1., 2.)].into(); + let poly = polygon![ + (x: 1., y: -3.1), (x: 3.7, y: 2.7), + (x: 0.9, y: 7.6), (x: -4.8, y: 6.7), + (x: -7.5, y: 0.9), (x: -4.7, y: -4.), + (x: 1., y: -3.1) + ]; + + assert_relative_eq!( + p1.hausdorff_distance(&poly), + 7.553807, + epsilon = 1.0e-6 + ) + } + + #[test] + fn hd_mpnt_lns() { + let p1: MultiPoint<_> = vec![(0., 0.), (1., 2.)].into(); + let lns = line_string![ + (x: 1., y: -3.1), (x: 3.7, y: 2.7), + (x: 0.9, y: 7.6), (x: -4.8, y: 6.7), + (x: -7.5, y: 0.9), (x: -4.7, y: -4.), + (x: 1., y: -3.1) + ]; + + assert_relative_eq!( + p1.hausdorff_distance(&lns), + 7.553807, + epsilon = 1.0e-6 + ) + } + + #[test] + fn hd_mpnt_mply() { + let p1: MultiPoint<_> = vec![(0., 0.), (1., 2.)].into(); + let multi_polygon = MultiPolygon::new(vec![ + polygon![ + (x: 0.0f32, y: 0.0), + (x: 2.0, y: 0.0), + (x: 2.0, y: 1.0), + (x: 0.0, y: 1.0), + ], + polygon![ + (x: 1.0, y: 1.0), + (x: -2.0, y: 1.0), + (x: -2.0, y: -1.0), + (x: 1.0, y: -1.0), + ] + ]); + + assert_relative_eq!( + p1.hausdorff_distance(&multi_polygon), + 2.236068, + epsilon = 1.0e-6 + ) + + } + +} From 4a7b2b9e5773f75b08d5739036c19349b7eea97c Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Thu, 27 Jul 2023 10:38:11 -0400 Subject: [PATCH 4/7] run cargo fmt to tidy up --- geo/src/algorithm/hausdorff_distance.rs | 176 +++++++++++------------- 1 file changed, 80 insertions(+), 96 deletions(-) diff --git a/geo/src/algorithm/hausdorff_distance.rs b/geo/src/algorithm/hausdorff_distance.rs index 452d378b7..b4d0c9128 100644 --- a/geo/src/algorithm/hausdorff_distance.rs +++ b/geo/src/algorithm/hausdorff_distance.rs @@ -1,32 +1,32 @@ -use crate::GeoFloat; -use crate::CoordsIter; use crate::algorithm::EuclideanDistance; -use num_traits::Bounded; // used to have T as generic type in folding +use crate::CoordsIter; +use crate::GeoFloat; use geo_types::*; +use num_traits::Bounded; // used to have T as generic type in folding /// Determine the distance between two geometries using the [Hausdorff distance formula]. /// -/// Hausdorff distance is used to compare two point sets. It measures the maximum euclidean +/// Hausdorff distance is used to compare two point sets. It measures the maximum euclidean /// distance of a point in one set to the nearest point in another set. Hausdorff distance -/// is often used to measure the amount of mismatch between two sets. -/// +/// is often used to measure the amount of mismatch between two sets. +/// /// [Hausdorff distance formula]: https://en.wikipedia.org/wiki/Hausdorff_distance pub trait HausdorffDistance { fn hausdorff_distance(&self, rhs: &Rhs) -> T; } // We can take advantage of the coords_iter() method for all geometries -// to iterate across all combos of coords (infinum). Simplifies the +// to iterate across all combos of coords (infinum). Simplifies the // implementations for all geometries. macro_rules! impl_hausdorff_distance_coord_iter { ($from:ident, [$($to:ident),*]) => { $( impl HausdorffDistance> for $from where - T: GeoFloat + T: GeoFloat { fn hausdorff_distance(&self, geom: &$to) -> T { - // calculate from A -> B + // calculate from A -> B let hd1 = self .coords_iter() .map(|c| { @@ -38,7 +38,7 @@ macro_rules! impl_hausdorff_distance_coord_iter { .fold(::max_value(), |accum, val| accum.min(val)) }) .fold(::min_value(), |accum, val| accum.max(val)); - + // Calculate from B -> A let hd2 = geom .coords_iter() @@ -51,7 +51,7 @@ macro_rules! impl_hausdorff_distance_coord_iter { .fold(::max_value(), |accum, val| accum.min(val)) }) .fold(::min_value(), |accum, val| accum.max(val)); - + // The max of the two hd1.max(hd2) } @@ -60,124 +60,118 @@ macro_rules! impl_hausdorff_distance_coord_iter { }; } - -impl_hausdorff_distance_coord_iter!{ - Line, - [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, +impl_hausdorff_distance_coord_iter! { + Line, [ + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, Polygon, MultiPolygon, Geometry, GeometryCollection ] } - -impl_hausdorff_distance_coord_iter!{ - Triangle, - [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, +impl_hausdorff_distance_coord_iter! { + Triangle, [ + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, Polygon, MultiPolygon, Geometry, GeometryCollection ] } -impl_hausdorff_distance_coord_iter!{ - Rect, +impl_hausdorff_distance_coord_iter! { + Rect, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, Polygon, MultiPolygon, Geometry, GeometryCollection ] } -impl_hausdorff_distance_coord_iter!{ +impl_hausdorff_distance_coord_iter! { Point, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, Polygon, MultiPolygon, Geometry, GeometryCollection ] } -impl_hausdorff_distance_coord_iter!{ +impl_hausdorff_distance_coord_iter! { LineString, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, Polygon, MultiPolygon, Geometry, GeometryCollection ] } -impl_hausdorff_distance_coord_iter!{ +impl_hausdorff_distance_coord_iter! { Polygon, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, Polygon, MultiPolygon, Geometry, GeometryCollection ] } - -impl_hausdorff_distance_coord_iter!{ +impl_hausdorff_distance_coord_iter! { MultiPoint, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, Polygon, MultiPolygon, Geometry, GeometryCollection ] } -impl_hausdorff_distance_coord_iter!{ +impl_hausdorff_distance_coord_iter! { MultiLineString, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, Polygon, MultiPolygon, Geometry, GeometryCollection ] } - -impl_hausdorff_distance_coord_iter!{ + +impl_hausdorff_distance_coord_iter! { MultiPolygon, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, Polygon, MultiPolygon, Geometry, GeometryCollection ] } - -impl_hausdorff_distance_coord_iter!{ + +impl_hausdorff_distance_coord_iter! { GeometryCollection, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, Polygon, MultiPolygon, Geometry, GeometryCollection ] } -impl_hausdorff_distance_coord_iter!{ +impl_hausdorff_distance_coord_iter! { Geometry, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, + Line, Rect, Triangle, Point, MultiPoint, + LineString, MultiLineString, Polygon, MultiPolygon, Geometry, GeometryCollection ] } - // ┌───────────────────────────┐ // │ Implementations for Coord │ // └───────────────────────────┘ -// since Coord does not have coords_iter() method we have +// since Coord does not have coords_iter() method we have // to make a macro for it specifically macro_rules! impl_haussdorf_distance_coord { ($($for:ident),*) => { $( impl HausdorffDistance> for Coord where - T: GeoFloat + T: GeoFloat { fn hausdorff_distance(&self, geom: &$for) -> T { let p = Point::from(*self); @@ -190,63 +184,55 @@ macro_rules! impl_haussdorf_distance_coord { // Implement methods for all other geometries impl_haussdorf_distance_coord!( - Line, Rect, Triangle, - Point, MultiPoint, - LineString, MultiLineString, - Polygon, MultiPolygon, - Geometry, GeometryCollection + Line, + Rect, + Triangle, + Point, + MultiPoint, + LineString, + MultiLineString, + Polygon, + MultiPolygon, + Geometry, + GeometryCollection ); - #[cfg(test)] mod test { use crate::HausdorffDistance; - use crate::{MultiPoint, polygon, line_string, MultiPolygon}; + use crate::{line_string, polygon, MultiPoint, MultiPolygon}; #[test] fn hd_mpnt_mpnt() { let p1: MultiPoint<_> = vec![(0., 0.), (1., 2.)].into(); let p2: MultiPoint<_> = vec![(2., 3.), (1., 2.)].into(); - assert_relative_eq!( - p1.hausdorff_distance(&p2), - 2.236068, - epsilon = 1.0e-6 - ); + assert_relative_eq!(p1.hausdorff_distance(&p2), 2.236068, epsilon = 1.0e-6); } #[test] fn hd_mpnt_poly() { - let p1: MultiPoint<_> = vec![(0., 0.), (1., 2.)].into(); let poly = polygon![ - (x: 1., y: -3.1), (x: 3.7, y: 2.7), - (x: 0.9, y: 7.6), (x: -4.8, y: 6.7), - (x: -7.5, y: 0.9), (x: -4.7, y: -4.), - (x: 1., y: -3.1) - ]; + (x: 1., y: -3.1), (x: 3.7, y: 2.7), + (x: 0.9, y: 7.6), (x: -4.8, y: 6.7), + (x: -7.5, y: 0.9), (x: -4.7, y: -4.), + (x: 1., y: -3.1) + ]; - assert_relative_eq!( - p1.hausdorff_distance(&poly), - 7.553807, - epsilon = 1.0e-6 - ) + assert_relative_eq!(p1.hausdorff_distance(&poly), 7.553807, epsilon = 1.0e-6) } #[test] fn hd_mpnt_lns() { let p1: MultiPoint<_> = vec![(0., 0.), (1., 2.)].into(); let lns = line_string![ - (x: 1., y: -3.1), (x: 3.7, y: 2.7), - (x: 0.9, y: 7.6), (x: -4.8, y: 6.7), - (x: -7.5, y: 0.9), (x: -4.7, y: -4.), - (x: 1., y: -3.1) - ]; + (x: 1., y: -3.1), (x: 3.7, y: 2.7), + (x: 0.9, y: 7.6), (x: -4.8, y: 6.7), + (x: -7.5, y: 0.9), (x: -4.7, y: -4.), + (x: 1., y: -3.1) + ]; - assert_relative_eq!( - p1.hausdorff_distance(&lns), - 7.553807, - epsilon = 1.0e-6 - ) + assert_relative_eq!(p1.hausdorff_distance(&lns), 7.553807, epsilon = 1.0e-6) } #[test] @@ -264,15 +250,13 @@ mod test { (x: -2.0, y: 1.0), (x: -2.0, y: -1.0), (x: 1.0, y: -1.0), - ] - ]); + ], + ]); assert_relative_eq!( p1.hausdorff_distance(&multi_polygon), - 2.236068, - epsilon = 1.0e-6 + 2.236068, + epsilon = 1.0e-6 ) - } - } From 2254696703ded0cc85ee254155cc6ca1cf4fd5a3 Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Thu, 27 Jul 2023 10:39:29 -0400 Subject: [PATCH 5/7] cargo fmt line breaks --- geo/src/algorithm/hausdorff_distance.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/geo/src/algorithm/hausdorff_distance.rs b/geo/src/algorithm/hausdorff_distance.rs index b4d0c9128..66f418cbb 100644 --- a/geo/src/algorithm/hausdorff_distance.rs +++ b/geo/src/algorithm/hausdorff_distance.rs @@ -79,8 +79,7 @@ impl_hausdorff_distance_coord_iter! { } impl_hausdorff_distance_coord_iter! { - Rect, - [ + Rect, [ Line, Rect, Triangle, Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon, From e8cba425440a835630b37fe09dd4c1fdde2cc85b Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Sat, 29 Jul 2023 12:00:59 -0400 Subject: [PATCH 6/7] address comments --- geo/src/algorithm/hausdorff_distance.rs | 227 ++++++------------------ 1 file changed, 51 insertions(+), 176 deletions(-) diff --git a/geo/src/algorithm/hausdorff_distance.rs b/geo/src/algorithm/hausdorff_distance.rs index 66f418cbb..a99946f09 100644 --- a/geo/src/algorithm/hausdorff_distance.rs +++ b/geo/src/algorithm/hausdorff_distance.rs @@ -1,8 +1,8 @@ use crate::algorithm::EuclideanDistance; use crate::CoordsIter; use crate::GeoFloat; -use geo_types::*; -use num_traits::Bounded; // used to have T as generic type in folding +use geo_types::{Coord, Point}; +use num_traits::Bounded; /// Determine the distance between two geometries using the [Hausdorff distance formula]. /// @@ -11,191 +11,66 @@ use num_traits::Bounded; // used to have T as generic type in folding /// is often used to measure the amount of mismatch between two sets. /// /// [Hausdorff distance formula]: https://en.wikipedia.org/wiki/Hausdorff_distance -pub trait HausdorffDistance { - fn hausdorff_distance(&self, rhs: &Rhs) -> T; -} - -// We can take advantage of the coords_iter() method for all geometries -// to iterate across all combos of coords (infinum). Simplifies the -// implementations for all geometries. -macro_rules! impl_hausdorff_distance_coord_iter { - ($from:ident, [$($to:ident),*]) => { - $( - impl HausdorffDistance> for $from - where - T: GeoFloat - { - fn hausdorff_distance(&self, geom: &$to) -> T { - // calculate from A -> B - let hd1 = self - .coords_iter() - .map(|c| { - geom - .coords_iter() - .map(|c2| { - c.euclidean_distance(&c2) - }) - .fold(::max_value(), |accum, val| accum.min(val)) - }) - .fold(::min_value(), |accum, val| accum.max(val)); - - // Calculate from B -> A - let hd2 = geom - .coords_iter() - .map(|c| { - self - .coords_iter() - .map(|c2| { - c.euclidean_distance(&c2) - }) - .fold(::max_value(), |accum, val| accum.min(val)) - }) - .fold(::min_value(), |accum, val| accum.max(val)); - - // The max of the two - hd1.max(hd2) - } - } - )* - }; -} - -impl_hausdorff_distance_coord_iter! { - Line, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, - Polygon, MultiPolygon, - Geometry, GeometryCollection - ] -} - -impl_hausdorff_distance_coord_iter! { - Triangle, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, - Polygon, MultiPolygon, - Geometry, GeometryCollection - ] -} - -impl_hausdorff_distance_coord_iter! { - Rect, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, - Polygon, MultiPolygon, - Geometry, GeometryCollection - ] -} - -impl_hausdorff_distance_coord_iter! { - Point, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, - Polygon, MultiPolygon, - Geometry, GeometryCollection - ] -} -impl_hausdorff_distance_coord_iter! { - LineString, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, - Polygon, MultiPolygon, - Geometry, GeometryCollection - ] +pub trait HausdorffDistance +where + T: GeoFloat, +{ + fn hausdorff_distance(&self, rhs: &Rhs) -> T + where + Rhs: for<'a> CoordsIter<'a, Scalar = T>; } -impl_hausdorff_distance_coord_iter! { - Polygon, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, - Polygon, MultiPolygon, - Geometry, GeometryCollection - ] -} - -impl_hausdorff_distance_coord_iter! { - MultiPoint, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, - Polygon, MultiPolygon, - Geometry, GeometryCollection - ] -} - -impl_hausdorff_distance_coord_iter! { - MultiLineString, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, - Polygon, MultiPolygon, - Geometry, GeometryCollection - ] -} - -impl_hausdorff_distance_coord_iter! { - MultiPolygon, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, - Polygon, MultiPolygon, - Geometry, GeometryCollection - ] -} - -impl_hausdorff_distance_coord_iter! { - GeometryCollection, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, - Polygon, MultiPolygon, - Geometry, GeometryCollection - ] -} - -impl_hausdorff_distance_coord_iter! { - Geometry, [ - Line, Rect, Triangle, Point, MultiPoint, - LineString, MultiLineString, - Polygon, MultiPolygon, - Geometry, GeometryCollection - ] +impl HausdorffDistance for G +where + T: GeoFloat, + G: for<'a> CoordsIter<'a, Scalar = T>, +{ + fn hausdorff_distance(&self, rhs: &Rhs) -> T + where + Rhs: for<'a> CoordsIter<'a, Scalar = T>, + { + // calculate from A -> B + let hd1 = self + .coords_iter() + .map(|c| { + rhs.coords_iter() + .map(|c2| c.euclidean_distance(&c2)) + .fold(::max_value(), |accum, val| accum.min(val)) + }) + .fold(::min_value(), |accum, val| accum.max(val)); + + // Calculate from B -> A + let hd2 = rhs + .coords_iter() + .map(|c| { + self.coords_iter() + .map(|c2| c.euclidean_distance(&c2)) + .fold(::max_value(), |accum, val| accum.min(val)) + }) + .fold(::min_value(), |accum, val| accum.max(val)); + + // The max of the two + hd1.max(hd2) + } } // ┌───────────────────────────┐ // │ Implementations for Coord │ // └───────────────────────────┘ -// since Coord does not have coords_iter() method we have -// to make a macro for it specifically -macro_rules! impl_haussdorf_distance_coord { - ($($for:ident),*) => { - $( - impl HausdorffDistance> for Coord - where - T: GeoFloat - { - fn hausdorff_distance(&self, geom: &$for) -> T { - let p = Point::from(*self); - p.hausdorff_distance(geom) - } - } - )* - }; +impl HausdorffDistance for Coord +where + T: GeoFloat, +{ + fn hausdorff_distance(&self, rhs: &Rhs) -> T + where + Rhs: for<'a> CoordsIter<'a, Scalar = T>, + { + Point::from(*self).hausdorff_distance(rhs) + } } -// Implement methods for all other geometries -impl_haussdorf_distance_coord!( - Line, - Rect, - Triangle, - Point, - MultiPoint, - LineString, - MultiLineString, - Polygon, - MultiPolygon, - Geometry, - GeometryCollection -); - #[cfg(test)] mod test { use crate::HausdorffDistance; From 17d48221d8af7b8e19cc92b9f8adec90140a2be6 Mon Sep 17 00:00:00 2001 From: Josiah Parry Date: Mon, 31 Jul 2023 08:34:33 -0400 Subject: [PATCH 7/7] add changelog not on hausdorff distance --- geo/CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/geo/CHANGES.md b/geo/CHANGES.md index 46a1b1270..46112abb7 100644 --- a/geo/CHANGES.md +++ b/geo/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## Development version + +* Add `HausdorffDistance` algorithm trait to calculate the Hausdorff distance between any two geometries. + ## 0.26.0 * Implement "Closest Point" from a `Point` on a `Geometry` using spherical geometry.