From e620fe176966f2d42bb5619bdbe8378de766dc1d Mon Sep 17 00:00:00 2001 From: Rajsekar Manokaran Date: Sun, 28 Feb 2021 13:27:33 +0530 Subject: [PATCH] Allow checks on rings and polygons to support permutes --- src/lib.rs | 1 - src/runner.rs | 91 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 80 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3c5f224..ce6a2af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,7 +52,6 @@ mod tests { #[test] // several of the ConvexHull tests are currently failing - #[ignore] fn test_all_general() { init_logging(); let mut runner = TestRunner::new(); diff --git a/src/runner.rs b/src/runner.rs index f3caf37..0347c24 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -1,8 +1,10 @@ -use include_dir::{include_dir, Dir, DirEntry}; +use std::collections::BTreeSet; + use approx::relative_eq; +use include_dir::{include_dir, Dir, DirEntry}; -use geo::Geometry; use super::{input, Operation, Result}; +use geo::{Coordinate, Geometry, LineString, Polygon}; const GENERAL_TEST_XML: Dir = include_dir!("resources/testxml/general"); @@ -99,7 +101,7 @@ impl TestRunner { debug!("ConvexHull not implemented for this geometry (yet?)"); self.unsupported.push(test_case); continue; - }, + } Geometry::Line(_g) => { debug!("ConvexHull not implemented for this geometry (yet?)"); self.unsupported.push(test_case); @@ -142,14 +144,25 @@ impl TestRunner { // whereas geo *alway* returns a polygon // // This is currently the cause of some test failures. - let actual = Geometry::from(actual_polygon); - if relative_eq!(actual, expected) { + let expected = match expected { + Geometry::LineString(ext) => Polygon::new(ext.clone(), vec![]), + Geometry::Polygon(p) => p.clone(), + _ => { + let error_description = format!("expected result for convex hull is not a polygon or a linestring: {:?}", expected); + self.failures.push(TestFailure { + test_case, + error_description, + }); + continue; + }, + }; + if is_polygon_rotated_eq(&actual_polygon, &expected, |c1, c2| relative_eq!(c1, c2)) { debug!("ConvexHull success: actual == expected"); self.successes.push(test_case); } else { debug!("ConvexHull failure: actual != expected"); let error_description = - format!("expected {:?}, actual: {:?}", expected, actual); + format!("expected {:?}, actual: {:?}", expected, actual_polygon); self.failures.push(TestFailure { test_case, error_description, @@ -158,7 +171,11 @@ impl TestRunner { } } } - info!("successes: {}, failures: {}", self.successes.len(), self.failures.len()); + info!( + "successes: {}, failures: {}", + self.successes.len(), + self.failures.len() + ); Ok(()) } @@ -173,8 +190,11 @@ impl TestRunner { for entry in GENERAL_TEST_XML.find(&filename_filter)? { let file = match entry { - DirEntry::Dir(_) => { debug_assert!(false, "unexpectedly found dir.xml"); continue } - DirEntry::File(file) => file + DirEntry::Dir(_) => { + debug_assert!(false, "unexpectedly found dir.xml"); + continue; + } + DirEntry::File(file) => file, }; debug!("deserializing from {:?}", file.path()); let file_reader = std::io::BufReader::new(file.contents()); @@ -204,7 +224,10 @@ impl TestRunner { let geometry = match geometry_try_from_wkt_str(&case.a) { Ok(g) => g, Err(e) => { - warn!("skipping case after failing to parse wkt into geometry: {:?}", e); + warn!( + "skipping case after failing to parse wkt into geometry: {:?}", + e + ); continue; } }; @@ -240,8 +263,54 @@ impl TestRunner { } fn geometry_try_from_wkt_str(wkt_str: &str) -> Result> - where T: wkt::WktFloat + std::str::FromStr + std::default::Default +where + T: wkt::WktFloat + std::str::FromStr + std::default::Default, { use std::convert::TryInto; Ok(wkt::Wkt::from_str(&wkt_str)?.try_into()?) } + +/// Test if two polygons are equal upto rotation, and permutation of iteriors +pub fn is_polygon_rotated_eq(p1: &Polygon, p2: &Polygon, coord_matcher: F) -> bool +where + T: geo::GeoNum, + F: Fn(&Coordinate, &Coordinate) -> bool, +{ + if p1.interiors().len() != p2.interiors().len() { + return false; + } + if !is_ring_rotated_eq(p1.exterior(), p2.exterior(), &coord_matcher) { + return false; + } + + let mut matched_in_p2: BTreeSet = BTreeSet::new(); + for r1 in p1.interiors().iter() { + let did_match = p2.interiors().iter().enumerate().find(|(j, r2)| { + !matched_in_p2.contains(&j) && is_ring_rotated_eq(r1, r2, &coord_matcher) + }); + if let Some((j, _)) = did_match { + matched_in_p2.insert(j); + } else { + return false; + } + } + return true; +} + +/// Test if two rings are equal upto rotation / reversal +pub fn is_ring_rotated_eq(r1: &LineString, r2: &LineString, coord_matcher: F) -> bool +where + T: geo::GeoNum, + F: Fn(&Coordinate, &Coordinate) -> bool, +{ + assert!(r1.is_closed(), "r1 is not closed"); + assert!(r2.is_closed(), "r2 is not closed"); + if r1.0.len() != r2.0.len() { + return false; + } + let len = r1.0.len() - 1; + (0..len).any(|shift| { + (0..len).all(|i| coord_matcher(&r1.0[i], &r2.0[(i + shift) % len])) + || (0..len).all(|i| coord_matcher(&r1.0[len - i], &r2.0[(i + shift) % len])) + }) +}