From 1fc2612972070d56576a788cd6511d91c62bb0e0 Mon Sep 17 00:00:00 2001 From: Martin Davis Date: Mon, 6 Nov 2023 17:17:44 -0800 Subject: [PATCH] Fix TopologyPreservingSimplifier endpoint handling to avoid self-intersections (#986) --- .../simplify/TaggedLineStringSimplifier.h | 18 ++++++++----- src/simplify/TaggedLineStringSimplifier.cpp | 27 +++++++++++-------- .../TopologyPreservingSimplifierTest.cpp | 26 ++++++++++++++++++ 3 files changed, 53 insertions(+), 18 deletions(-) diff --git a/include/geos/simplify/TaggedLineStringSimplifier.h b/include/geos/simplify/TaggedLineStringSimplifier.h index 835f6e870c..5ed98e7665 100644 --- a/include/geos/simplify/TaggedLineStringSimplifier.h +++ b/include/geos/simplify/TaggedLineStringSimplifier.h @@ -113,12 +113,12 @@ class GEOS_DLL TaggedLineStringSimplifier { double& maxDistance); bool hasBadIntersection(const TaggedLineString* parentLine, - const std::pair& sectionIndex, + const size_t excludeStart, const size_t excludeEnd, const geom::LineSegment& candidateSeg); bool hasBadInputIntersection(const TaggedLineString* parentLine, - const std::pair& sectionIndex, - const geom::LineSegment& candidateSeg); + const size_t excludeStart, const size_t excludeEnd, + const geom::LineSegment& candidateSeg); bool hasBadOutputIntersection(const geom::LineSegment& candidateSeg); @@ -129,16 +129,20 @@ class GEOS_DLL TaggedLineStringSimplifier { std::size_t start, std::size_t end); /** \brief - * Tests whether a segment is in a section of a TaggedLineString + * Tests whether a segment is in a section of a TaggedLineString. + * Sections may wrap around the endpoint of the line, + * to support ring endpoint simplification. + * This is indicated by excludedStart > excludedEnd * * @param line line to be checked for the presence of `seg` - * @param sectionIndex start and end indices of the section to check + * @param excludeStart the index of the first segment in the excluded section + * @param excludeEnd the index of the last segment in the excluded section * @param seg segment to look for in `line` - * @return + * @return true if the test segment intersects some segment in the line not in the excluded section */ static bool isInLineSection( const TaggedLineString* line, - const std::pair& sectionIndex, + const size_t excludeStart, const size_t excludeEnd, const TaggedLineSegment* seg); /** \brief diff --git a/src/simplify/TaggedLineStringSimplifier.cpp b/src/simplify/TaggedLineStringSimplifier.cpp index c266816fb5..185218f4b2 100644 --- a/src/simplify/TaggedLineStringSimplifier.cpp +++ b/src/simplify/TaggedLineStringSimplifier.cpp @@ -38,7 +38,6 @@ #endif using namespace geos::geom; -using std::pair; using std::unique_ptr; using std::vector; @@ -149,7 +148,7 @@ TaggedLineStringSimplifier::simplifySection(std::size_t i, // test if flattened section would cause intersection LineSegment candidateSeg(linePts->getAt(i), linePts->getAt(j)); - if(hasBadIntersection(line, std::make_pair(i, j), candidateSeg)) { + if(hasBadIntersection(line, i, j, candidateSeg)) { isValidToSimplify = false; } @@ -182,8 +181,8 @@ TaggedLineStringSimplifier::simplifyRingEndpoint() LineSegment candidateSeg(lastSeg->p0, firstSeg->p1); if (candidateSeg.distance(firstSeg->p0) <= distanceTolerance && - !hasBadIntersection(line, std::make_pair(0, line->getSegments().size()), candidateSeg)) { - auto newSeg = detail::make_unique(candidateSeg.p0, candidateSeg.p1); + ! hasBadIntersection(line, line->getSegments().size() - 2, 0, candidateSeg)) { + //auto newSeg = detail::make_unique(candidateSeg.p0, candidateSeg.p1); line->removeRingEndpoint(); } } @@ -207,14 +206,14 @@ TaggedLineStringSimplifier::flatten(std::size_t start, std::size_t end) bool TaggedLineStringSimplifier::hasBadIntersection( const TaggedLineString* parentLine, - const pair& sectionIndex, + const size_t excludeStart, const size_t excludeEnd, const LineSegment& candidateSeg) { if(hasBadOutputIntersection(candidateSeg)) { return true; } - if(hasBadInputIntersection(parentLine, sectionIndex, candidateSeg)) { + if(hasBadInputIntersection(parentLine, excludeStart, excludeEnd, candidateSeg)) { return true; } @@ -252,7 +251,7 @@ TaggedLineStringSimplifier::hasInteriorIntersection( bool TaggedLineStringSimplifier::hasBadInputIntersection( const TaggedLineString* parentLine, - const pair& sectionIndex, + const size_t excludeStart, const size_t excludeEnd, const LineSegment& candidateSeg) { const auto& foundSegs = inputIndex->query(&candidateSeg); @@ -260,7 +259,7 @@ TaggedLineStringSimplifier::hasBadInputIntersection( for(const LineSegment* ls : *foundSegs) { const TaggedLineSegment* foundSeg = static_cast(ls); - if(!isInLineSection(parentLine, sectionIndex, foundSeg) && hasInteriorIntersection(*foundSeg, candidateSeg)) { + if(!isInLineSection(parentLine, excludeStart, excludeEnd, foundSeg) && hasInteriorIntersection(*foundSeg, candidateSeg)) { return true; } } @@ -272,7 +271,7 @@ TaggedLineStringSimplifier::hasBadInputIntersection( bool TaggedLineStringSimplifier::isInLineSection( const TaggedLineString* line, - const pair& sectionIndex, + const size_t excludeStart, const size_t excludeEnd, const TaggedLineSegment* seg) { // not in this line @@ -281,10 +280,16 @@ TaggedLineStringSimplifier::isInLineSection( } std::size_t segIndex = seg->getIndex(); - if(segIndex >= sectionIndex.first && segIndex < sectionIndex.second) { + if (excludeStart <= excludeEnd) { + //-- section is contiguous + if (segIndex >= excludeStart && segIndex < excludeEnd) return true; } - + else { + //-- section wraps around the end of a ring + if (segIndex >= excludeStart || segIndex <= excludeEnd) + return true; + } return false; } diff --git a/tests/unit/simplify/TopologyPreservingSimplifierTest.cpp b/tests/unit/simplify/TopologyPreservingSimplifierTest.cpp index b36ad12a39..c21b2ce42c 100644 --- a/tests/unit/simplify/TopologyPreservingSimplifierTest.cpp +++ b/tests/unit/simplify/TopologyPreservingSimplifierTest.cpp @@ -34,6 +34,18 @@ struct test_tpsimp_data { , wktwriter() { } + + void + checkTPS(const std::string& wkt, double tolerance, const std::string& wkt_expected) + { + GeomPtr g(wktreader.read(wkt)); + GeomPtr simplified = TopologyPreservingSimplifier::simplify(g.get(), tolerance); + + ensure("Simplified geometry is invalid!", simplified->isValid()); + + GeomPtr exp(wktreader.read(wkt_expected)); + ensure_equals_geometry(exp.get(), simplified.get()); + } }; typedef test_group group; @@ -379,4 +391,18 @@ void object::test<18> ensure_equals_geometry(simplified.get(), g.get()); } +// testPolygonKeepEndpointWithCross +// Test that endpoint is not simplified if it breaks topology +template<> +template<> +void object::test<19> +() +{ + checkTPS( + "POLYGON ((50 52, 60 50, 90 60, 90 10, 10 10, 10 90, 60 90, 50 55, 40 80, 20 60, 40 50, 50 52))", + 10, + "POLYGON ((50 52, 90 60, 90 10, 10 10, 10 90, 60 90, 50 55, 40 80, 20 60, 50 52))" + ); +} + } // namespace tut