From 3ea5cccd9c350c1a7d7b627f71360b9675060308 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 19 Feb 2021 15:18:53 -0800 Subject: [PATCH 1/2] approx for all Geometry types previously it was only implemented for Coord, Point, LineString, and a few others. --- geo-types/CHANGES.md | 5 + geo-types/src/geometry.rs | 206 +++++++++++++++++++++++++++ geo-types/src/geometry_collection.rs | 79 ++++++++++ geo-types/src/line.rs | 2 +- geo-types/src/line_string.rs | 2 +- geo-types/src/multi_line_string.rs | 79 ++++++++++ geo-types/src/multi_point.rs | 2 +- geo-types/src/multi_polygon.rs | 83 +++++++++++ geo-types/src/point.rs | 2 +- geo-types/src/polygon.rs | 83 +++++++++++ geo-types/src/rect.rs | 85 +++++++++++ geo-types/src/triangle.rs | 89 ++++++++++++ 12 files changed, 713 insertions(+), 4 deletions(-) diff --git a/geo-types/CHANGES.md b/geo-types/CHANGES.md index 79fba7e92..0311f368e 100644 --- a/geo-types/CHANGES.md +++ b/geo-types/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## Unreleased + +* Implement `RelativeEq` and `AbsDiffEq` for fuzzy comparison of remaining Geometry Types + * + ## 0.7.1 * Implement `Default` on `Coordinate` and `Point` structs (defaults to `(x: 0, y: 0)`) diff --git a/geo-types/src/geometry.rs b/geo-types/src/geometry.rs index fa2dba775..b990b7e0d 100644 --- a/geo-types/src/geometry.rs +++ b/geo-types/src/geometry.rs @@ -2,6 +2,9 @@ use crate::{ CoordNum, Error, GeometryCollection, Line, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, Rect, Triangle, }; + +#[cfg(any(feature = "approx", test))] +use approx::{AbsDiffEq, RelativeEq}; use core::any::type_name; use std::convert::TryFrom; @@ -234,3 +237,206 @@ where Geometry::Triangle(_) => type_name::>(), } } + +#[cfg(any(feature = "approx", test))] +impl RelativeEq for Geometry +where + T: AbsDiffEq + CoordNum + RelativeEq, +{ + #[inline] + fn default_max_relative() -> Self::Epsilon { + T::default_max_relative() + } + + /// Equality assertion within a relative limit. + /// + /// # Examples + /// + /// ``` + /// use geo_types::{Geometry, polygon}; + /// + /// let a: Geometry = polygon![(x: 0., y: 0.), (x: 5., y: 0.), (x: 7., y: 9.), (x: 0., y: 0.)].into(); + /// let b: Geometry = polygon![(x: 0., y: 0.), (x: 5., y: 0.), (x: 7.01, y: 9.), (x: 0., y: 0.)].into(); + /// + /// approx::assert_relative_eq!(a, b, max_relative=0.1); + /// approx::assert_relative_ne!(a, b, max_relative=0.001); + /// ``` + /// + fn relative_eq( + &self, + other: &Self, + epsilon: Self::Epsilon, + max_relative: Self::Epsilon, + ) -> bool { + match self { + Geometry::Point(g) => { + if let Geometry::Point(other) = other { + g.relative_eq(other, epsilon, max_relative) + } else { + false + } + } + Geometry::Line(g) => { + if let Geometry::Line(other) = other { + g.relative_eq(other, epsilon, max_relative) + } else { + false + } + } + Geometry::LineString(g) => { + if let Geometry::LineString(other) = other { + g.relative_eq(other, epsilon, max_relative) + } else { + false + } + } + Geometry::Polygon(g) => { + if let Geometry::Polygon(other) = other { + g.relative_eq(other, epsilon, max_relative) + } else { + false + } + } + Geometry::MultiPoint(g) => { + if let Geometry::MultiPoint(other) = other { + g.relative_eq(other, epsilon, max_relative) + } else { + false + } + } + Geometry::MultiLineString(g) => { + if let Geometry::MultiLineString(other) = other { + g.relative_eq(other, epsilon, max_relative) + } else { + false + } + } + Geometry::MultiPolygon(g) => { + if let Geometry::MultiPolygon(other) = other { + g.relative_eq(other, epsilon, max_relative) + } else { + false + } + } + Geometry::GeometryCollection(g) => { + if let Geometry::GeometryCollection(other) = other { + g.relative_eq(other, epsilon, max_relative) + } else { + false + } + } + Geometry::Rect(g) => { + if let Geometry::Rect(other) = other { + g.relative_eq(other, epsilon, max_relative) + } else { + false + } + } + Geometry::Triangle(g) => { + if let Geometry::Triangle(other) = other { + g.relative_eq(other, epsilon, max_relative) + } else { + false + } + } + } + } +} + +#[cfg(any(feature = "approx", test))] +impl + CoordNum> AbsDiffEq for Geometry { + type Epsilon = T; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + T::default_epsilon() + } + + /// Equality assertion with an absolute limit. + /// + /// # Examples + /// + /// ``` + /// use geo_types::{Geometry, polygon}; + /// + /// let a: Geometry = polygon![(x: 0., y: 0.), (x: 5., y: 0.), (x: 7., y: 9.), (x: 0., y: 0.)].into(); + /// let b: Geometry = polygon![(x: 0., y: 0.), (x: 5., y: 0.), (x: 7.01, y: 9.), (x: 0., y: 0.)].into(); + /// + /// approx::assert_abs_diff_eq!(a, b, epsilon=0.1); + /// approx::assert_abs_diff_ne!(a, b, epsilon=0.001); + /// ``` + fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { + match self { + Geometry::Point(g) => { + if let Geometry::Point(other) = other { + g.abs_diff_eq(other, epsilon) + } else { + false + } + } + Geometry::Line(g) => { + if let Geometry::Line(other) = other { + g.abs_diff_eq(other, epsilon) + } else { + false + } + } + Geometry::LineString(g) => { + if let Geometry::LineString(other) = other { + g.abs_diff_eq(other, epsilon) + } else { + false + } + } + Geometry::Polygon(g) => { + if let Geometry::Polygon(other) = other { + g.abs_diff_eq(other, epsilon) + } else { + false + } + } + Geometry::MultiPoint(g) => { + if let Geometry::MultiPoint(other) = other { + g.abs_diff_eq(other, epsilon) + } else { + false + } + } + Geometry::MultiLineString(g) => { + if let Geometry::MultiLineString(other) = other { + g.abs_diff_eq(other, epsilon) + } else { + false + } + } + Geometry::MultiPolygon(g) => { + if let Geometry::MultiPolygon(other) = other { + g.abs_diff_eq(other, epsilon) + } else { + false + } + } + Geometry::GeometryCollection(g) => { + if let Geometry::GeometryCollection(other) = other { + g.abs_diff_eq(other, epsilon) + } else { + false + } + } + Geometry::Rect(g) => { + if let Geometry::Rect(other) = other { + g.abs_diff_eq(other, epsilon) + } else { + false + } + } + Geometry::Triangle(g) => { + if let Geometry::Triangle(other) = other { + g.abs_diff_eq(other, epsilon) + } else { + false + } + } + } + } +} diff --git a/geo-types/src/geometry_collection.rs b/geo-types/src/geometry_collection.rs index c683398ca..66c763a51 100644 --- a/geo-types/src/geometry_collection.rs +++ b/geo-types/src/geometry_collection.rs @@ -1,4 +1,7 @@ use crate::{CoordNum, Geometry}; + +#[cfg(any(feature = "approx", test))] +use approx::{AbsDiffEq, RelativeEq}; use std::iter::FromIterator; use std::ops::{Index, IndexMut}; @@ -222,3 +225,79 @@ impl<'a, T: CoordNum> GeometryCollection { self.into_iter() } } + +#[cfg(any(feature = "approx", test))] +impl RelativeEq for GeometryCollection +where + T: AbsDiffEq + CoordNum + RelativeEq, +{ + #[inline] + fn default_max_relative() -> Self::Epsilon { + T::default_max_relative() + } + + /// Equality assertion within a relative limit. + /// + /// # Examples + /// + /// ``` + /// use geo_types::{GeometryCollection, point}; + /// + /// let a = GeometryCollection(vec![point![x: 1.0, y: 2.0].into()]); + /// let b = GeometryCollection(vec![point![x: 1.0, y: 2.01].into()]); + /// + /// approx::assert_relative_eq!(a, b, max_relative=0.1); + /// approx::assert_relative_ne!(a, b, max_relative=0.0001); + /// ``` + #[inline] + fn relative_eq( + &self, + other: &Self, + epsilon: Self::Epsilon, + max_relative: Self::Epsilon, + ) -> bool { + if self.0.len() != other.0.len() { + return false; + } + + let mut mp_zipper = self.iter().zip(other.iter()); + mp_zipper.all(|(lhs, rhs)| lhs.relative_eq(&rhs, epsilon, max_relative)) + } +} + +#[cfg(any(feature = "approx", test))] +impl AbsDiffEq for GeometryCollection +where + T: AbsDiffEq + CoordNum, + T::Epsilon: Copy, +{ + type Epsilon = T; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + T::default_epsilon() + } + + /// Equality assertion with an absolute limit. + /// + /// # Examples + /// + /// ``` + /// use geo_types::{GeometryCollection, point}; + /// + /// let a = GeometryCollection(vec![point![x: 0.0, y: 0.0].into()]); + /// let b = GeometryCollection(vec![point![x: 0.0, y: 0.1].into()]); + /// + /// approx::abs_diff_eq!(a, b, epsilon=0.1); + /// approx::abs_diff_ne!(a, b, epsilon=0.001); + /// ``` + #[inline] + fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { + if self.0.len() != other.0.len() { + return false; + } + + let mut mp_zipper = self.into_iter().zip(other.into_iter()); + mp_zipper.all(|(lhs, rhs)| lhs.abs_diff_eq(&rhs, epsilon)) + } +} diff --git a/geo-types/src/line.rs b/geo-types/src/line.rs index 3759cc102..c2823954e 100644 --- a/geo-types/src/line.rs +++ b/geo-types/src/line.rs @@ -209,7 +209,7 @@ impl + CoordNum> AbsDiffEq for Line { T::default_epsilon() } - /// Equality assertion with a absolute limit. + /// Equality assertion with an absolute limit. /// /// # Examples /// diff --git a/geo-types/src/line_string.rs b/geo-types/src/line_string.rs index 1509ed290..319ff644e 100644 --- a/geo-types/src/line_string.rs +++ b/geo-types/src/line_string.rs @@ -351,7 +351,7 @@ impl + CoordNum> AbsDiffEq for LineString { T::default_epsilon() } - /// Equality assertion with a absolute limit. + /// Equality assertion with an absolute limit. /// /// # Examples /// diff --git a/geo-types/src/multi_line_string.rs b/geo-types/src/multi_line_string.rs index 527421a69..9d54e6ba2 100644 --- a/geo-types/src/multi_line_string.rs +++ b/geo-types/src/multi_line_string.rs @@ -1,4 +1,7 @@ use crate::{CoordNum, LineString}; + +#[cfg(any(feature = "approx", test))] +use approx::{AbsDiffEq, RelativeEq}; use std::iter::FromIterator; /// A collection of @@ -110,6 +113,82 @@ impl MultiLineString { } } +#[cfg(any(feature = "approx", test))] +impl RelativeEq for MultiLineString +where + T: AbsDiffEq + CoordNum + RelativeEq, +{ + #[inline] + fn default_max_relative() -> Self::Epsilon { + T::default_max_relative() + } + + /// Equality assertion within a relative limit. + /// + /// # Examples + /// + /// ``` + /// use geo_types::{MultiLineString, line_string}; + /// + /// let a = MultiLineString(vec![line_string![(x: 0., y: 0.), (x: 10., y: 10.)]]); + /// let b = MultiLineString(vec![line_string![(x: 0., y: 0.), (x: 10.01, y: 10.)]]); + /// + /// approx::assert_relative_eq!(a, b, max_relative=0.1); + /// approx::assert_relative_ne!(a, b, max_relative=0.0001); + /// ``` + #[inline] + fn relative_eq( + &self, + other: &Self, + epsilon: Self::Epsilon, + max_relative: Self::Epsilon, + ) -> bool { + if self.0.len() != other.0.len() { + return false; + } + + let mut mp_zipper = self.iter().zip(other.iter()); + mp_zipper.all(|(lhs, rhs)| lhs.relative_eq(&rhs, epsilon, max_relative)) + } +} + +#[cfg(any(feature = "approx", test))] +impl AbsDiffEq for MultiLineString +where + T: AbsDiffEq + CoordNum, + T::Epsilon: Copy, +{ + type Epsilon = T; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + T::default_epsilon() + } + + /// Equality assertion with an absolute limit. + /// + /// # Examples + /// + /// ``` + /// use geo_types::{MultiLineString, line_string}; + /// + /// let a = MultiLineString(vec![line_string![(x: 0., y: 0.), (x: 10., y: 10.)]]); + /// let b = MultiLineString(vec![line_string![(x: 0., y: 0.), (x: 10.01, y: 10.)]]); + /// + /// approx::abs_diff_eq!(a, b, epsilon=0.1); + /// approx::abs_diff_ne!(a, b, epsilon=0.001); + /// ``` + #[inline] + fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { + if self.0.len() != other.0.len() { + return false; + } + + let mut mp_zipper = self.into_iter().zip(other.into_iter()); + mp_zipper.all(|(lhs, rhs)| lhs.abs_diff_eq(&rhs, epsilon)) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/geo-types/src/multi_point.rs b/geo-types/src/multi_point.rs index b0af933a1..0b0d18950 100644 --- a/geo-types/src/multi_point.rs +++ b/geo-types/src/multi_point.rs @@ -147,7 +147,7 @@ where T::default_epsilon() } - /// Equality assertion with a absolute limit. + /// Equality assertion with an absolute limit. /// /// # Examples /// diff --git a/geo-types/src/multi_polygon.rs b/geo-types/src/multi_polygon.rs index e01cca445..7e1fa1c4b 100644 --- a/geo-types/src/multi_polygon.rs +++ b/geo-types/src/multi_polygon.rs @@ -1,4 +1,7 @@ use crate::{CoordNum, Polygon}; + +#[cfg(any(feature = "approx", test))] +use approx::{AbsDiffEq, RelativeEq}; use std::iter::FromIterator; /// A collection of [`Polygon`s](struct.Polygon.html). Can @@ -83,6 +86,86 @@ impl MultiPolygon { } } +#[cfg(any(feature = "approx", test))] +impl RelativeEq for MultiPolygon +where + T: AbsDiffEq + CoordNum + RelativeEq, +{ + #[inline] + fn default_max_relative() -> Self::Epsilon { + T::default_max_relative() + } + + /// Equality assertion within a relative limit. + /// + /// # Examples + /// + /// ``` + /// use geo_types::{polygon, Polygon, MultiPolygon}; + /// + /// let a_el: Polygon = polygon![(x: 0., y: 0.), (x: 5., y: 0.), (x: 7., y: 9.), (x: 0., y: 0.)]; + /// let a = MultiPolygon(vec![a_el]); + /// let b_el: Polygon = polygon![(x: 0., y: 0.), (x: 5., y: 0.), (x: 7.01, y: 9.), (x: 0., y: 0.)]; + /// let b = MultiPolygon(vec![b_el]); + /// + /// approx::assert_relative_eq!(a, b, max_relative=0.1); + /// approx::assert_relative_ne!(a, b, max_relative=0.001); + /// ``` + #[inline] + fn relative_eq( + &self, + other: &Self, + epsilon: Self::Epsilon, + max_relative: Self::Epsilon, + ) -> bool { + if self.0.len() != other.0.len() { + return false; + } + + let mut mp_zipper = self.iter().zip(other.iter()); + mp_zipper.all(|(lhs, rhs)| lhs.relative_eq(&rhs, epsilon, max_relative)) + } +} + +#[cfg(any(feature = "approx", test))] +impl AbsDiffEq for MultiPolygon +where + T: AbsDiffEq + CoordNum, + T::Epsilon: Copy, +{ + type Epsilon = T; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + T::default_epsilon() + } + + /// Equality assertion with an absolute limit. + /// + /// # Examples + /// + /// ``` + /// use geo_types::{polygon, Polygon, MultiPolygon}; + /// + /// let a_el: Polygon = polygon![(x: 0., y: 0.), (x: 5., y: 0.), (x: 7., y: 9.), (x: 0., y: 0.)]; + /// let a = MultiPolygon(vec![a_el]); + /// let b_el: Polygon = polygon![(x: 0., y: 0.), (x: 5., y: 0.), (x: 7.01, y: 9.), (x: 0., y: 0.)]; + /// let b = MultiPolygon(vec![b_el]); + /// + /// approx::abs_diff_eq!(a, b, epsilon=0.1); + /// approx::abs_diff_ne!(a, b, epsilon=0.001); + /// ``` + #[inline] + fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { + if self.0.len() != other.0.len() { + return false; + } + + let mut mp_zipper = self.into_iter().zip(other.into_iter()); + mp_zipper.all(|(lhs, rhs)| lhs.abs_diff_eq(&rhs, epsilon)) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/geo-types/src/point.rs b/geo-types/src/point.rs index 4715fc7cd..c556163c5 100644 --- a/geo-types/src/point.rs +++ b/geo-types/src/point.rs @@ -458,7 +458,7 @@ where T::default_epsilon() } - /// Equality assertion with a absolute limit. + /// Equality assertion with an absolute limit. /// /// # Examples /// diff --git a/geo-types/src/polygon.rs b/geo-types/src/polygon.rs index 11c8139fa..6073c312e 100644 --- a/geo-types/src/polygon.rs +++ b/geo-types/src/polygon.rs @@ -1,6 +1,9 @@ use crate::{CoordFloat, CoordNum, LineString, Point, Rect, Triangle}; use num_traits::{Float, Signed}; +#[cfg(any(feature = "approx", test))] +use approx::{AbsDiffEq, RelativeEq}; + /// A bounded two-dimensional area. /// /// A `Polygon`’s outer boundary (_exterior ring_) is represented by a @@ -466,3 +469,83 @@ impl From> for Polygon { Polygon::new(vec![t.0, t.1, t.2, t.0].into(), Vec::new()) } } + +#[cfg(any(feature = "approx", test))] +impl RelativeEq for Polygon +where + T: AbsDiffEq + CoordNum + RelativeEq, +{ + #[inline] + fn default_max_relative() -> Self::Epsilon { + T::default_max_relative() + } + + /// Equality assertion within a relative limit. + /// + /// # Examples + /// + /// ``` + /// use geo_types::{Polygon, polygon}; + /// + /// let a: Polygon = polygon![(x: 0., y: 0.), (x: 5., y: 0.), (x: 7., y: 9.), (x: 0., y: 0.)]; + /// let b: Polygon = polygon![(x: 0., y: 0.), (x: 5., y: 0.), (x: 7.01, y: 9.), (x: 0., y: 0.)]; + /// + /// approx::assert_relative_eq!(a, b, max_relative=0.1); + /// approx::assert_relative_ne!(a, b, max_relative=0.001); + /// ``` + /// + fn relative_eq( + &self, + other: &Self, + epsilon: Self::Epsilon, + max_relative: Self::Epsilon, + ) -> bool { + if !self + .exterior + .relative_eq(&other.exterior, epsilon, max_relative) + { + return false; + } + + if self.interiors.len() != other.interiors.len() { + return false; + } + let mut zipper = self.interiors.iter().zip(other.interiors.iter()); + zipper.all(|(lhs, rhs)| lhs.relative_eq(&rhs, epsilon, max_relative)) + } +} + +#[cfg(any(feature = "approx", test))] +impl + CoordNum> AbsDiffEq for Polygon { + type Epsilon = T; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + T::default_epsilon() + } + + /// Equality assertion with an absolute limit. + /// + /// # Examples + /// + /// ``` + /// use geo_types::{Polygon, polygon}; + /// + /// let a: Polygon = polygon![(x: 0., y: 0.), (x: 5., y: 0.), (x: 7., y: 9.), (x: 0., y: 0.)]; + /// let b: Polygon = polygon![(x: 0., y: 0.), (x: 5., y: 0.), (x: 7.01, y: 9.), (x: 0., y: 0.)]; + /// + /// approx::assert_abs_diff_eq!(a, b, epsilon=0.1); + /// approx::assert_abs_diff_ne!(a, b, epsilon=0.001); + /// ``` + fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { + if !self.exterior.abs_diff_eq(&other.exterior, epsilon) { + return false; + } + + if self.interiors.len() != other.interiors.len() { + return false; + } + let mut zipper = self.interiors.iter().zip(other.interiors.iter()); + zipper.all(|(lhs, rhs)| lhs.abs_diff_eq(&rhs, epsilon)) + } +} diff --git a/geo-types/src/rect.rs b/geo-types/src/rect.rs index 041b7f3f6..1bb22c8c2 100644 --- a/geo-types/src/rect.rs +++ b/geo-types/src/rect.rs @@ -1,5 +1,8 @@ use crate::{polygon, CoordFloat, CoordNum, Coordinate, Polygon}; +#[cfg(any(feature = "approx", test))] +use approx::{AbsDiffEq, RelativeEq}; + /// An _axis-aligned_ bounded 2D rectangle whose area is /// defined by minimum and maximum `Coordinate`s. /// @@ -265,6 +268,88 @@ impl Rect { static RECT_INVALID_BOUNDS_ERROR: &str = "Failed to create Rect: 'min' coordinate's x/y value must be smaller or equal to the 'max' x/y value"; +#[cfg(any(feature = "approx", test))] +impl RelativeEq for Rect +where + T: AbsDiffEq + CoordNum + RelativeEq, +{ + #[inline] + fn default_max_relative() -> Self::Epsilon { + T::default_max_relative() + } + + /// Equality assertion within a relative limit. + /// + /// # Examples + /// + /// ``` + /// use geo_types::Rect; + /// + /// let a = Rect::new((0.0, 0.0), (10.0, 10.0)); + /// let b = Rect::new((0.0, 0.0), (10.01, 10.0)); + /// + /// approx::assert_relative_eq!(a, b, max_relative=0.1); + /// approx::assert_relative_ne!(a, b, max_relative=0.0001); + /// ``` + #[inline] + fn relative_eq( + &self, + other: &Self, + epsilon: Self::Epsilon, + max_relative: Self::Epsilon, + ) -> bool { + if !self.min.relative_eq(&other.min, epsilon, max_relative) { + return false; + } + + if !self.max.relative_eq(&other.max, epsilon, max_relative) { + return false; + } + + true + } +} + +#[cfg(any(feature = "approx", test))] +impl AbsDiffEq for Rect +where + T: AbsDiffEq + CoordNum, + T::Epsilon: Copy, +{ + type Epsilon = T; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + T::default_epsilon() + } + + /// Equality assertion with an absolute limit. + /// + /// # Examples + /// + /// ``` + /// use geo_types::{point, Rect}; + /// + /// let a = Rect::new((0.0, 0.0), (10.0, 10.0)); + /// let b = Rect::new((0.0, 0.0), (10.01, 10.0)); + /// + /// approx::abs_diff_eq!(a, b, epsilon=0.1); + /// approx::abs_diff_ne!(a, b, epsilon=0.001); + /// ``` + #[inline] + fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { + if !self.min.abs_diff_eq(&other.min, epsilon) { + return false; + } + + if !self.max.abs_diff_eq(&other.max, epsilon) { + return false; + } + + true + } +} + #[deprecated( since = "0.6.2", note = "Use `Rect::new` instead, since `Rect::try_new` will never Error" diff --git a/geo-types/src/triangle.rs b/geo-types/src/triangle.rs index 42725f07c..b5664d1d4 100644 --- a/geo-types/src/triangle.rs +++ b/geo-types/src/triangle.rs @@ -1,5 +1,8 @@ use crate::{polygon, CoordNum, Coordinate, Line, Polygon}; +#[cfg(any(feature = "approx", test))] +use approx::{AbsDiffEq, RelativeEq}; + /// A bounded 2D area whose three vertices are defined by /// `Coordinate`s. The semantics and validity are that of /// the equivalent [`Polygon`]; in addition, the three @@ -54,3 +57,89 @@ impl> + Copy, T: CoordNum> From<[IC; 3]> for Triangle Triangle(array[0].into(), array[1].into(), array[2].into()) } } + +#[cfg(any(feature = "approx", test))] +impl RelativeEq for Triangle +where + T: AbsDiffEq + CoordNum + RelativeEq, +{ + #[inline] + fn default_max_relative() -> Self::Epsilon { + T::default_max_relative() + } + + /// Equality assertion within a relative limit. + /// + /// # Examples + /// + /// ``` + /// use geo_types::{point, Triangle}; + /// + /// let a = Triangle((0.0, 0.0).into(), (10.0, 10.0).into(), (0.0, 5.0).into()); + /// let b = Triangle((0.0, 0.0).into(), (10.01, 10.0).into(), (0.0, 5.0).into()); + /// + /// approx::assert_relative_eq!(a, b, max_relative=0.1); + /// approx::assert_relative_ne!(a, b, max_relative=0.0001); + /// ``` + #[inline] + fn relative_eq( + &self, + other: &Self, + epsilon: Self::Epsilon, + max_relative: Self::Epsilon, + ) -> bool { + if !self.0.relative_eq(&other.0, epsilon, max_relative) { + return false; + } + if !self.1.relative_eq(&other.1, epsilon, max_relative) { + return false; + } + if !self.2.relative_eq(&other.2, epsilon, max_relative) { + return false; + } + + true + } +} + +#[cfg(any(feature = "approx", test))] +impl AbsDiffEq for Triangle +where + T: AbsDiffEq + CoordNum, + T::Epsilon: Copy, +{ + type Epsilon = T; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + T::default_epsilon() + } + + /// Equality assertion with an absolute limit. + /// + /// # Examples + /// + /// ``` + /// use geo_types::{point, Triangle}; + /// + /// let a = Triangle((0.0, 0.0).into(), (10.0, 10.0).into(), (0.0, 5.0).into()); + /// let b = Triangle((0.0, 0.0).into(), (10.01, 10.0).into(), (0.0, 5.0).into()); + /// + /// approx::abs_diff_eq!(a, b, epsilon=0.1); + /// approx::abs_diff_ne!(a, b, epsilon=0.001); + /// ``` + #[inline] + fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { + if !self.0.abs_diff_eq(&other.0, epsilon) { + return false; + } + if !self.1.abs_diff_eq(&other.1, epsilon) { + return false; + } + if !self.2.abs_diff_eq(&other.2, epsilon) { + return false; + } + + true + } +} From 6008397d61f1803d3158c28585e7bc4b25d0876e Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 22 Feb 2021 15:25:00 -0800 Subject: [PATCH 2/2] fixup! approx for all Geometry types stylistic change only --- geo-types/src/geometry.rs | 166 ++++++++------------------------------ 1 file changed, 33 insertions(+), 133 deletions(-) diff --git a/geo-types/src/geometry.rs b/geo-types/src/geometry.rs index b990b7e0d..77f91d98e 100644 --- a/geo-types/src/geometry.rs +++ b/geo-types/src/geometry.rs @@ -268,77 +268,32 @@ where epsilon: Self::Epsilon, max_relative: Self::Epsilon, ) -> bool { - match self { - Geometry::Point(g) => { - if let Geometry::Point(other) = other { - g.relative_eq(other, epsilon, max_relative) - } else { - false - } - } - Geometry::Line(g) => { - if let Geometry::Line(other) = other { - g.relative_eq(other, epsilon, max_relative) - } else { - false - } - } - Geometry::LineString(g) => { - if let Geometry::LineString(other) = other { - g.relative_eq(other, epsilon, max_relative) - } else { - false - } - } - Geometry::Polygon(g) => { - if let Geometry::Polygon(other) = other { - g.relative_eq(other, epsilon, max_relative) - } else { - false - } + match (self, other) { + (Geometry::Point(g1), Geometry::Point(g2)) => g1.relative_eq(g2, epsilon, max_relative), + (Geometry::Line(g1), Geometry::Line(g2)) => g1.relative_eq(g2, epsilon, max_relative), + (Geometry::LineString(g1), Geometry::LineString(g2)) => { + g1.relative_eq(g2, epsilon, max_relative) } - Geometry::MultiPoint(g) => { - if let Geometry::MultiPoint(other) = other { - g.relative_eq(other, epsilon, max_relative) - } else { - false - } + (Geometry::Polygon(g1), Geometry::Polygon(g2)) => { + g1.relative_eq(g2, epsilon, max_relative) } - Geometry::MultiLineString(g) => { - if let Geometry::MultiLineString(other) = other { - g.relative_eq(other, epsilon, max_relative) - } else { - false - } + (Geometry::MultiPoint(g1), Geometry::MultiPoint(g2)) => { + g1.relative_eq(g2, epsilon, max_relative) } - Geometry::MultiPolygon(g) => { - if let Geometry::MultiPolygon(other) = other { - g.relative_eq(other, epsilon, max_relative) - } else { - false - } + (Geometry::MultiLineString(g1), Geometry::MultiLineString(g2)) => { + g1.relative_eq(g2, epsilon, max_relative) } - Geometry::GeometryCollection(g) => { - if let Geometry::GeometryCollection(other) = other { - g.relative_eq(other, epsilon, max_relative) - } else { - false - } + (Geometry::MultiPolygon(g1), Geometry::MultiPolygon(g2)) => { + g1.relative_eq(g2, epsilon, max_relative) } - Geometry::Rect(g) => { - if let Geometry::Rect(other) = other { - g.relative_eq(other, epsilon, max_relative) - } else { - false - } + (Geometry::GeometryCollection(g1), Geometry::GeometryCollection(g2)) => { + g1.relative_eq(g2, epsilon, max_relative) } - Geometry::Triangle(g) => { - if let Geometry::Triangle(other) = other { - g.relative_eq(other, epsilon, max_relative) - } else { - false - } + (Geometry::Rect(g1), Geometry::Rect(g2)) => g1.relative_eq(g2, epsilon, max_relative), + (Geometry::Triangle(g1), Geometry::Triangle(g2)) => { + g1.relative_eq(g2, epsilon, max_relative) } + (_, _) => false, } } } @@ -366,77 +321,22 @@ impl + CoordNum> AbsDiffEq for Geometry { /// approx::assert_abs_diff_ne!(a, b, epsilon=0.001); /// ``` fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { - match self { - Geometry::Point(g) => { - if let Geometry::Point(other) = other { - g.abs_diff_eq(other, epsilon) - } else { - false - } - } - Geometry::Line(g) => { - if let Geometry::Line(other) = other { - g.abs_diff_eq(other, epsilon) - } else { - false - } + match (self, other) { + (Geometry::Point(g1), Geometry::Point(g2)) => g1.abs_diff_eq(g2, epsilon), + (Geometry::Line(g1), Geometry::Line(g2)) => g1.abs_diff_eq(g2, epsilon), + (Geometry::LineString(g1), Geometry::LineString(g2)) => g1.abs_diff_eq(g2, epsilon), + (Geometry::Polygon(g1), Geometry::Polygon(g2)) => g1.abs_diff_eq(g2, epsilon), + (Geometry::MultiPoint(g1), Geometry::MultiPoint(g2)) => g1.abs_diff_eq(g2, epsilon), + (Geometry::MultiLineString(g1), Geometry::MultiLineString(g2)) => { + g1.abs_diff_eq(g2, epsilon) } - Geometry::LineString(g) => { - if let Geometry::LineString(other) = other { - g.abs_diff_eq(other, epsilon) - } else { - false - } - } - Geometry::Polygon(g) => { - if let Geometry::Polygon(other) = other { - g.abs_diff_eq(other, epsilon) - } else { - false - } - } - Geometry::MultiPoint(g) => { - if let Geometry::MultiPoint(other) = other { - g.abs_diff_eq(other, epsilon) - } else { - false - } - } - Geometry::MultiLineString(g) => { - if let Geometry::MultiLineString(other) = other { - g.abs_diff_eq(other, epsilon) - } else { - false - } - } - Geometry::MultiPolygon(g) => { - if let Geometry::MultiPolygon(other) = other { - g.abs_diff_eq(other, epsilon) - } else { - false - } - } - Geometry::GeometryCollection(g) => { - if let Geometry::GeometryCollection(other) = other { - g.abs_diff_eq(other, epsilon) - } else { - false - } - } - Geometry::Rect(g) => { - if let Geometry::Rect(other) = other { - g.abs_diff_eq(other, epsilon) - } else { - false - } - } - Geometry::Triangle(g) => { - if let Geometry::Triangle(other) = other { - g.abs_diff_eq(other, epsilon) - } else { - false - } + (Geometry::MultiPolygon(g1), Geometry::MultiPolygon(g2)) => g1.abs_diff_eq(g2, epsilon), + (Geometry::GeometryCollection(g1), Geometry::GeometryCollection(g2)) => { + g1.abs_diff_eq(g2, epsilon) } + (Geometry::Rect(g1), Geometry::Rect(g2)) => g1.abs_diff_eq(g2, epsilon), + (Geometry::Triangle(g1), Geometry::Triangle(g2)) => g1.abs_diff_eq(g2, epsilon), + (_, _) => false, } } }