Skip to content

Commit

Permalink
Add ring buffer hole removal heuristic (#1233)
Browse files Browse the repository at this point in the history
  • Loading branch information
dr-jts committed Feb 5, 2025
1 parent b77be45 commit c0616d6
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 109 deletions.
7 changes: 6 additions & 1 deletion include/geos/operation/buffer/BufferCurveSetBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ namespace buffer { // geos.operation.buffer
*
*/
class GEOS_DLL BufferCurveSetBuilder {
using CoordinateSequence = geos::geom::CoordinateSequence;
using Envelope = geos::geom::Envelope;

private:

Expand Down Expand Up @@ -192,7 +194,10 @@ class GEOS_DLL BufferCurveSetBuilder {
* @param bufferDistance
* @return
*/
bool isErodedCompletely(const geom::LinearRing* ringCoord,
bool isRingFullyEroded(const geom::LinearRing* ring, bool isHole,
double bufferDistance);

bool isRingFullyEroded(const CoordinateSequence* ringCoord, const Envelope* env, bool isHole,
double bufferDistance);

/**
Expand Down
62 changes: 46 additions & 16 deletions src/operation/buffer/BufferCurveSetBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ BufferCurveSetBuilder::addPolygon(const Polygon* p)

// optimization - don't bother computing buffer
// if the polygon would be completely eroded
if(distance < 0.0 && isErodedCompletely(shell, distance)) {
if(distance < 0.0 && isRingFullyEroded(shell, false, distance)) {
#if GEOS_DEBUG
std::cerr << __FUNCTION__ << ": polygon is eroded completely " << std::endl;
#endif
Expand Down Expand Up @@ -270,7 +270,7 @@ BufferCurveSetBuilder::addPolygon(const Polygon* p)

// optimization - don't bother computing buffer for this hole
// if the hole would be completely covered
if(distance > 0.0 && isErodedCompletely(hole, -distance)) {
if(distance > 0.0 && isRingFullyEroded(hole, true, distance)) {
continue;
}

Expand All @@ -292,14 +292,30 @@ BufferCurveSetBuilder::addPolygon(const Polygon* p)
void
BufferCurveSetBuilder::addRingBothSides(const CoordinateSequence* coord, double p_distance)
{
addRingSide(coord, p_distance,
Position::LEFT,
Location::EXTERIOR, Location::INTERIOR);
/* Add the opposite side of the ring
*/
addRingSide(coord, p_distance,
Position::RIGHT,
Location::INTERIOR, Location::EXTERIOR);
/*
* (f "hole" side will be eroded completely, avoid generating it.
* This prevents hole artifacts (e.g. https://github.com/libgeos/geos/issues/1223)
*/
//-- distance is assumed positive, due to previous checks
Envelope env;
coord->expandEnvelope(env);
bool isHoleComputed = ! isRingFullyEroded(coord, &env, true, distance);

bool isCCW = isRingCCW(coord);

bool isShellLeft = ! isCCW;
if (isShellLeft || isHoleComputed) {
addRingSide(coord, p_distance,
Position::LEFT,
Location::EXTERIOR, Location::INTERIOR);
}

bool isShellRight = isCCW;
if (isShellRight || isHoleComputed) {
addRingSide(coord, p_distance,
Position::RIGHT,
Location::INTERIOR, Location::EXTERIOR);
}
}


Expand Down Expand Up @@ -424,14 +440,22 @@ BufferCurveSetBuilder::hasPointOnBuffer(

/*private*/
bool
BufferCurveSetBuilder::isErodedCompletely(const LinearRing* ring,
BufferCurveSetBuilder::isRingFullyEroded(const LinearRing* ring, bool isHole,
double bufferDistance)
{
const CoordinateSequence* ringCoord = ring->getCoordinatesRO();
const Envelope* env = ring->getEnvelopeInternal();
return isRingFullyEroded(ringCoord, env, isHole, bufferDistance);
}

/*private*/
bool
BufferCurveSetBuilder::isRingFullyEroded(const CoordinateSequence* ringCoord, const Envelope* env, bool isHole,
double bufferDistance)
{
// degenerate ring has no area
if(ringCoord->getSize() < 4) {
return bufferDistance < 0;
return true;
}

// important test to eliminate inverted triangle bug
Expand All @@ -440,10 +464,16 @@ BufferCurveSetBuilder::isErodedCompletely(const LinearRing* ring,
return isTriangleErodedCompletely(ringCoord, bufferDistance);
}

const Envelope* env = ring->getEnvelopeInternal();
double envMinDimension = std::min(env->getHeight(), env->getWidth());
if(bufferDistance < 0.0 && 2 * std::abs(bufferDistance) > envMinDimension) {
return true;
bool isErodable =
( isHole && bufferDistance > 0) ||
(! isHole && bufferDistance < 0);

if (isErodable) {
//-- if envelope is narrower than twice the buffer distance, ring is eroded
double envMinDimension = std::min(env->getHeight(), env->getWidth());
if (2 * std::abs(bufferDistance) > envMinDimension) {
return true;
}
}
return false;
}
Expand Down
Loading

0 comments on commit c0616d6

Please sign in to comment.