Skip to content

Commit

Permalink
Fix TopologyPreservingSimplifier endpoint handling to avoid self-inte…
Browse files Browse the repository at this point in the history
…rsections (#986)
  • Loading branch information
dr-jts authored Nov 7, 2023
1 parent bb2db23 commit 1fc2612
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 18 deletions.
18 changes: 11 additions & 7 deletions include/geos/simplify/TaggedLineStringSimplifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,12 @@ class GEOS_DLL TaggedLineStringSimplifier {
double& maxDistance);

bool hasBadIntersection(const TaggedLineString* parentLine,
const std::pair<std::size_t, std::size_t>& sectionIndex,
const size_t excludeStart, const size_t excludeEnd,
const geom::LineSegment& candidateSeg);

bool hasBadInputIntersection(const TaggedLineString* parentLine,
const std::pair<std::size_t, std::size_t>& sectionIndex,
const geom::LineSegment& candidateSeg);
const size_t excludeStart, const size_t excludeEnd,
const geom::LineSegment& candidateSeg);

bool hasBadOutputIntersection(const geom::LineSegment& candidateSeg);

Expand All @@ -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<std::size_t, std::size_t>& sectionIndex,
const size_t excludeStart, const size_t excludeEnd,
const TaggedLineSegment* seg);

/** \brief
Expand Down
27 changes: 16 additions & 11 deletions src/simplify/TaggedLineStringSimplifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
#endif

using namespace geos::geom;
using std::pair;
using std::unique_ptr;
using std::vector;

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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<TaggedLineSegment>(candidateSeg.p0, candidateSeg.p1);
! hasBadIntersection(line, line->getSegments().size() - 2, 0, candidateSeg)) {
//auto newSeg = detail::make_unique<TaggedLineSegment>(candidateSeg.p0, candidateSeg.p1);
line->removeRingEndpoint();
}
}
Expand All @@ -207,14 +206,14 @@ TaggedLineStringSimplifier::flatten(std::size_t start, std::size_t end)
bool
TaggedLineStringSimplifier::hasBadIntersection(
const TaggedLineString* parentLine,
const pair<size_t, size_t>& 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;
}

Expand Down Expand Up @@ -252,15 +251,15 @@ TaggedLineStringSimplifier::hasInteriorIntersection(
bool
TaggedLineStringSimplifier::hasBadInputIntersection(
const TaggedLineString* parentLine,
const pair<std::size_t, std::size_t>& sectionIndex,
const size_t excludeStart, const size_t excludeEnd,
const LineSegment& candidateSeg)
{
const auto& foundSegs = inputIndex->query(&candidateSeg);

for(const LineSegment* ls : *foundSegs) {
const TaggedLineSegment* foundSeg = static_cast<const TaggedLineSegment*>(ls);

if(!isInLineSection(parentLine, sectionIndex, foundSeg) && hasInteriorIntersection(*foundSeg, candidateSeg)) {
if(!isInLineSection(parentLine, excludeStart, excludeEnd, foundSeg) && hasInteriorIntersection(*foundSeg, candidateSeg)) {
return true;
}
}
Expand All @@ -272,7 +271,7 @@ TaggedLineStringSimplifier::hasBadInputIntersection(
bool
TaggedLineStringSimplifier::isInLineSection(
const TaggedLineString* line,
const pair<size_t, size_t>& sectionIndex,
const size_t excludeStart, const size_t excludeEnd,
const TaggedLineSegment* seg)
{
// not in this line
Expand All @@ -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;
}

Expand Down
26 changes: 26 additions & 0 deletions tests/unit/simplify/TopologyPreservingSimplifierTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<test_tpsimp_data> group;
Expand Down Expand Up @@ -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

0 comments on commit 1fc2612

Please sign in to comment.