Skip to content

Commit

Permalink
Reduce artifacts in single-sided buffer output
Browse files Browse the repository at this point in the history
Includes unit test

Fixes libgeosGH-665
  • Loading branch information
strk committed May 4, 2023
1 parent 2bda4d2 commit d8a173d
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 1 deletion.
87 changes: 86 additions & 1 deletion src/operation/buffer/BufferBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -411,13 +411,98 @@ BufferBuilder::buffer(const Geometry* g, double distance)
std::cerr << std::endl << edgeList << std::endl;
#endif

std::vector<Edge*> *edges = &edgeList.getEdges();
std::vector<Edge*> reducedEdges; // only needed if bufParams.isSingleSided

if ( bufParams.isSingleSided() )
{
#if GEOS_DEBUG
std::cerr << "Single-sided buffer was desired, we'll drop edges not being in the full buffer" << std::endl;
#endif

// First, generate the two-sided buffer using a butt-cap.
BufferParameters modParams = bufParams;
modParams.setEndCapStyle(BufferParameters::CAP_FLAT);
modParams.setSingleSided(false);
BufferBuilder tmpBB(modParams);
std::unique_ptr<Geometry> buf(tmpBB.buffer(g, std::abs(distance)));
// Create MultiLineStrings from this polygon.
std::unique_ptr<Geometry> bufLineString(buf->getBoundary());

#if GEOS_DEBUG
std::cerr << "Boundaries of full buffer:" << *bufLineString << std::endl;
#endif

// Get linework of input geom
const Geometry *inputLineString = g;
std::unique_ptr<Geometry> inputPolygonBoundary;
if ( g->getDimension() > 1 )
{
inputPolygonBoundary = g->getBoundary();
inputLineString = inputPolygonBoundary.get();
}

#if GEOS_DEBUG
std::cerr << "Input linework: " << *inputLineString << std::endl;
#endif


// Union input line and full buffer line
std::unique_ptr<Geometry> usableLines = inputLineString->Union( bufLineString.get() );

#if GEOS_DEBUG
std::cerr << "Usable lines: " << *usableLines << std::endl;
#endif


for ( auto& e : *edges )
{
std::unique_ptr<Geometry> edgeGeom = geomFact->createLineString(*(e->getCoordinates()));

// NOTE: we use Snapped overlay because the actual buffer boundary might
// diverge from original offset curves due to the addition of
// intersections with caps and joins curves
using geos::operation::overlay::snap::SnapOverlayOp;
std::unique_ptr<Geometry> xset = SnapOverlayOp::overlayOp(
*edgeGeom, *usableLines,
overlayng::OverlayNG::INTERSECTION
);
#if GEOS_DEBUG > 1
std::cerr << "Intersection: " << *xset << std::endl;
#endif

// Drop edges not having linear intersection with
// the union of full buffer boundary and input line
if ( xset->getDimension() == 1 )
{
#if GEOS_DEBUG > 1
std::cerr << "Covered edge: " << *e << std::endl;
#endif
reducedEdges.push_back(e);
}
else
{
#if GEOS_DEBUG > 1
std::cerr << "Non-covered edge: " << *e << std::endl;
#endif
}
}
if ( reducedEdges.empty() )
{
// Or we could maybe be tolerant here
throw util::GEOSException("Unable to find single-sided Buffer Curves covered by boundary of full Buffer");
}
edges = &reducedEdges;

}

std::unique_ptr<Geometry> resultGeom(nullptr);
std::vector<std::unique_ptr<Geometry>> resultPolyList;
std::vector<BufferSubgraph*> subgraphList;

try {
PlanarGraph graph(OverlayNodeFactory::instance());
graph.addEdges(edgeList.getEdges());
graph.addEdges(*edges);

GEOS_CHECK_FOR_INTERRUPTS();

Expand Down
21 changes: 21 additions & 0 deletions tests/unit/operation/buffer/BufferOpTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -549,4 +549,25 @@ void object::test<20>
ensure( 0 == dynamic_cast<const geos::geom::Polygon*>(result1.get())->getNumInteriorRing() );
}

// Test for single-sided buffer
// See https://github.com/libgeos/geos/issues/665
template<>
template<>
void object::test<21>
()
{
using geos::operation::buffer::BufferOp;
using geos::operation::buffer::BufferParameters;

std::string wkt("LINESTRING (50 50, 150 150, 150 100, 150 0)");
GeomPtr geom(wktreader.read(wkt));

geos::operation::buffer::BufferParameters bp;
bp.setSingleSided(true);
geos::operation::buffer::BufferOp op(geom.get(), bp);

std::unique_ptr<Geometry> result = op.getResultGeometry(-21);
ensure_equals(int(result->getArea()), 5055);
}

} // namespace tut

0 comments on commit d8a173d

Please sign in to comment.