From 0eef2191a0563fca2996139ce130d3bd5d33997b Mon Sep 17 00:00:00 2001 From: Martin Davis Date: Tue, 11 Feb 2025 15:19:28 -0800 Subject: [PATCH] Fix CCW ring buffer generation --- .../operation/buffer/BufferCurveSetBuilder.h | 8 +- .../buffer/BufferCurveSetBuilder.cpp | 77 ++++++++++--------- tests/unit/operation/buffer/BufferOpTest.cpp | 13 ++++ tests/xmltester/tests/general/TestBuffer.xml | 51 ++++++++++++ 4 files changed, 111 insertions(+), 38 deletions(-) diff --git a/include/geos/operation/buffer/BufferCurveSetBuilder.h b/include/geos/operation/buffer/BufferCurveSetBuilder.h index f1461871c8..789b8c9359 100644 --- a/include/geos/operation/buffer/BufferCurveSetBuilder.h +++ b/include/geos/operation/buffer/BufferCurveSetBuilder.h @@ -120,7 +120,7 @@ class GEOS_DLL BufferCurveSetBuilder { void addPolygon(const geom::Polygon* p); - void addRingBothSides(const geom::CoordinateSequence* coord, double p_distance); + void addLinearRingSides(const geom::CoordinateSequence* coord, double p_distance); /** * Add an offset curve for a polygon ring. @@ -140,10 +140,14 @@ class GEOS_DLL BufferCurveSetBuilder { * @param cwRightLoc the location on the R side of the ring * (if it is CW) */ - void addRingSide(const geom::CoordinateSequence* coord, + void addPolygonRingSide(const geom::CoordinateSequence* coord, double offsetDistance, int side, geom::Location cwLeftLoc, geom::Location cwRightLoc); + void addRingSide(const geom::CoordinateSequence* coord, + double offsetDistance, int side, geom::Location leftLoc, + geom::Location rightLoc); + /** * Tests whether the offset curve for a ring is fully inverted. * An inverted ("inside-out") curve occurs in some specific situations diff --git a/src/operation/buffer/BufferCurveSetBuilder.cpp b/src/operation/buffer/BufferCurveSetBuilder.cpp index cb2b1c5108..7fc39e77c2 100644 --- a/src/operation/buffer/BufferCurveSetBuilder.cpp +++ b/src/operation/buffer/BufferCurveSetBuilder.cpp @@ -210,16 +210,44 @@ BufferCurveSetBuilder::addLineString(const LineString* line) * Singled-sided buffers currently treat rings as if they are lines. */ if (coord->isRing() && ! curveBuilder.getBufferParameters().isSingleSided()) { - addRingBothSides(coord.get(), distance); + addLinearRingSides(coord.get(), distance); } else { std::vector lineList; curveBuilder.getLineCurve(coord.get(), distance, lineList); addCurves(lineList, Location::EXTERIOR, Location::INTERIOR); } - } +/* private */ +void +BufferCurveSetBuilder::addLinearRingSides(const CoordinateSequence* coord, double p_distance) +{ + /* + * (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); + } +} /*private*/ void @@ -257,7 +285,7 @@ BufferCurveSetBuilder::addPolygon(const Polygon* p) return; } - addRingSide( + addPolygonRingSide( shellCoord.get(), offsetDistance, offsetSide, @@ -279,7 +307,7 @@ BufferCurveSetBuilder::addPolygon(const Polygon* p) // Holes are topologically labelled opposite to the shell, // since the interior of the polygon lies on their opposite // side (on the left, if the hole is oriented CCW) - addRingSide( + addPolygonRingSide( holeCoord.get(), offsetDistance, Position::opposite(offsetSide), @@ -290,38 +318,7 @@ BufferCurveSetBuilder::addPolygon(const Polygon* p) /* private */ void -BufferCurveSetBuilder::addRingBothSides(const CoordinateSequence* coord, double p_distance) -{ - /* - * (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); - } -} - - -/* private */ -void -BufferCurveSetBuilder::addRingSide(const CoordinateSequence* coord, +BufferCurveSetBuilder::addPolygonRingSide(const CoordinateSequence* coord, double offsetDistance, int side, geom::Location cwLeftLoc, geom::Location cwRightLoc) { @@ -353,6 +350,14 @@ BufferCurveSetBuilder::addRingSide(const CoordinateSequence* coord, #endif side = Position::opposite(side); } + addRingSide(coord, offsetDistance, side, leftLoc, rightLoc); +} + +/* private */ +void +BufferCurveSetBuilder::addRingSide(const CoordinateSequence* coord, + double offsetDistance, int side, geom::Location leftLoc, geom::Location rightLoc) +{ std::vector lineList; curveBuilder.getRingCurve(coord, side, offsetDistance, lineList); // ASSERT: lineList contains exactly 1 curve (this is the JTS semantics) diff --git a/tests/unit/operation/buffer/BufferOpTest.cpp b/tests/unit/operation/buffer/BufferOpTest.cpp index f67d1a12bc..63d8fb4bc1 100644 --- a/tests/unit/operation/buffer/BufferOpTest.cpp +++ b/tests/unit/operation/buffer/BufferOpTest.cpp @@ -585,4 +585,17 @@ void object::test<25> checkArea(*result2, 107); } +// testRingCCW +// See /~https://github.com/libgeos/geos/issues/1236 +template<> +template<> +void object::test<26> +() +{ + std::string wkt("LINEARRING (-0.25 0.25, -0.25 0.75, -0.75 0.75, -0.75 0.25, -0.25 0.25)"); + + checkBuffer(wkt, 1, 0.1, + "POLYGON ((0.73 0.05, 0.67 -0.13, 0.58 -0.31, 0.46 -0.46, 0.31 -0.58, 0.13 -0.67, -0.05 -0.73, -0.25 -0.75, -0.75 -0.75, -0.95 -0.73, -1.13 -0.67, -1.31 -0.58, -1.46 -0.46, -1.58 -0.31, -1.67 -0.13, -1.73 0.05, -1.75 0.25, -1.75 0.75, -1.73 0.95, -1.67 1.13, -1.58 1.31, -1.46 1.46, -1.31 1.58, -1.13 1.67, -0.95 1.73, -0.75 1.75, -0.25 1.75, -0.05 1.73, 0.13 1.67, 0.31 1.58, 0.46 1.46, 0.58 1.31, 0.67 1.13, 0.73 0.95, 0.75 0.75, 0.75 0.25, 0.73 0.05))"); +} + } // namespace tut diff --git a/tests/xmltester/tests/general/TestBuffer.xml b/tests/xmltester/tests/general/TestBuffer.xml index 865a7ac07f..acc8efc2f5 100644 --- a/tests/xmltester/tests/general/TestBuffer.xml +++ b/tests/xmltester/tests/general/TestBuffer.xml @@ -33,6 +33,57 @@ + + + Closed Line + + + LINESTRING (1 9, 9 9, 9 1, 1 1, 1 9) + + POLYGON EMPTY + POLYGON EMPTY + +POLYGON ((1 10, 9 10, 9.195090322016128 9.98078528040323, 9.38268343236509 9.923879532511286, 9.555570233019603 9.831469612302545, 9.707106781186548 9.707106781186548, 9.831469612302545 9.555570233019601, 9.923879532511286 9.38268343236509, 9.98078528040323 9.195090322016128, 10 9, 10 1, 9.98078528040323 0.8049096779838718, 9.923879532511286 0.6173165676349102, 9.831469612302545 0.4444297669803978, 9.707106781186548 0.2928932188134525, 9.555570233019603 0.1685303876974548, 9.38268343236509 0.0761204674887133, 9.195090322016128 0.0192147195967696, 9 0, 1 0, 0.8049096779838718 0.0192147195967696, 0.6173165676349103 0.0761204674887133, 0.444429766980398 0.1685303876974547, 0.2928932188134525 0.2928932188134524, 0.1685303876974547 0.4444297669803978, 0.0761204674887133 0.6173165676349102, 0.0192147195967696 0.8049096779838714, 0 1, 0 9, 0.0192147195967696 9.195090322016128, 0.0761204674887133 9.38268343236509, 0.1685303876974547 9.555570233019601, 0.2928932188134525 9.707106781186548, 0.444429766980398 9.831469612302545, 0.6173165676349103 9.923879532511286, 0.8049096779838718 9.98078528040323, 1 10), (2 8, 2 2, 8 2, 8 8, 2 8)) + + +POLYGON ((1 19, 9 19, 10.950903220161283 18.807852804032304, 12.826834323650898 18.238795325112868, 14.555702330196024 17.314696123025453, 16.071067811865476 16.071067811865476, 17.314696123025453 14.555702330196022, 18.238795325112868 12.826834323650898, 18.807852804032304 10.950903220161283, 19 9, 19 1, 18.807852804032304 -0.9509032201612824, 18.238795325112868 -2.826834323650898, 17.314696123025453 -4.555702330196022, 16.071067811865476 -6.071067811865475, 14.555702330196024 -7.314696123025453, 12.826834323650898 -8.238795325112868, 10.950903220161283 -8.807852804032304, 9 -9, 1 -9, -0.9509032201612819 -8.807852804032304, -2.826834323650897 -8.238795325112868, -4.55570233019602 -7.314696123025453, -6.071067811865475 -6.0710678118654755, -7.314696123025453 -4.555702330196022, -8.238795325112868 -2.826834323650899, -8.807852804032304 -0.9509032201612861, -9 1, -9 9, -8.807852804032304 10.950903220161287, -8.238795325112868 12.8268343236509, -7.314696123025453 14.555702330196022, -6.071067811865475 16.071067811865476, -4.55570233019602 17.314696123025453, -2.826834323650897 18.238795325112868, -0.9509032201612819 18.807852804032304, 1 19)) + + + + + + Closed Line - CCW + + + LINESTRING (1 9, 1 1, 9 1, 9 9, 1 9) + + POLYGON EMPTY + POLYGON EMPTY + +POLYGON ((1 10, 9 10, 9.195090322016128 9.98078528040323, 9.38268343236509 9.923879532511286, 9.555570233019603 9.831469612302545, 9.707106781186548 9.707106781186548, 9.831469612302545 9.555570233019601, 9.923879532511286 9.38268343236509, 9.98078528040323 9.195090322016128, 10 9, 10 1, 9.98078528040323 0.8049096779838718, 9.923879532511286 0.6173165676349102, 9.831469612302545 0.4444297669803978, 9.707106781186548 0.2928932188134525, 9.555570233019603 0.1685303876974548, 9.38268343236509 0.0761204674887133, 9.195090322016128 0.0192147195967696, 9 0, 1 0, 0.8049096779838718 0.0192147195967696, 0.6173165676349103 0.0761204674887133, 0.444429766980398 0.1685303876974547, 0.2928932188134525 0.2928932188134524, 0.1685303876974547 0.4444297669803978, 0.0761204674887133 0.6173165676349102, 0.0192147195967696 0.8049096779838714, 0 1, 0 9, 0.0192147195967696 9.195090322016128, 0.0761204674887133 9.38268343236509, 0.1685303876974547 9.555570233019601, 0.2928932188134525 9.707106781186548, 0.444429766980398 9.831469612302545, 0.6173165676349103 9.923879532511286, 0.8049096779838718 9.98078528040323, 1 10), (2 8, 2 2, 8 2, 8 8, 2 8)) + + +POLYGON ((1 19, 9 19, 10.950903220161283 18.807852804032304, 12.826834323650898 18.238795325112868, 14.555702330196024 17.314696123025453, 16.071067811865476 16.071067811865476, 17.314696123025453 14.555702330196022, 18.238795325112868 12.826834323650898, 18.807852804032304 10.950903220161283, 19 9, 19 1, 18.807852804032304 -0.9509032201612824, 18.238795325112868 -2.826834323650898, 17.314696123025453 -4.555702330196022, 16.071067811865476 -6.071067811865475, 14.555702330196024 -7.314696123025453, 12.826834323650898 -8.238795325112868, 10.950903220161283 -8.807852804032304, 9 -9, 1 -9, -0.9509032201612819 -8.807852804032304, -2.826834323650897 -8.238795325112868, -4.55570233019602 -7.314696123025453, -6.071067811865475 -6.0710678118654755, -7.314696123025453 -4.555702330196022, -8.238795325112868 -2.826834323650899, -8.807852804032304 -0.9509032201612861, -9 1, -9 9, -8.807852804032304 10.950903220161287, -8.238795325112868 12.8268343236509, -7.314696123025453 14.555702330196022, -6.071067811865475 16.071067811865476, -4.55570233019602 17.314696123025453, -2.826834323650897 18.238795325112868, -0.9509032201612819 18.807852804032304, 1 19)) + + + + + + Linear Ring + + + LINEARRING (1 9, 1 1, 9 1, 9 9, 1 9) + + POLYGON EMPTY + POLYGON EMPTY + +POLYGON ((1 10, 9 10, 9.195090322016128 9.98078528040323, 9.38268343236509 9.923879532511286, 9.555570233019603 9.831469612302545, 9.707106781186548 9.707106781186548, 9.831469612302545 9.555570233019601, 9.923879532511286 9.38268343236509, 9.98078528040323 9.195090322016128, 10 9, 10 1, 9.98078528040323 0.8049096779838718, 9.923879532511286 0.6173165676349102, 9.831469612302545 0.4444297669803978, 9.707106781186548 0.2928932188134525, 9.555570233019603 0.1685303876974548, 9.38268343236509 0.0761204674887133, 9.195090322016128 0.0192147195967696, 9 0, 1 0, 0.8049096779838718 0.0192147195967696, 0.6173165676349103 0.0761204674887133, 0.444429766980398 0.1685303876974547, 0.2928932188134525 0.2928932188134524, 0.1685303876974547 0.4444297669803978, 0.0761204674887133 0.6173165676349102, 0.0192147195967696 0.8049096779838714, 0 1, 0 9, 0.0192147195967696 9.195090322016128, 0.0761204674887133 9.38268343236509, 0.1685303876974547 9.555570233019601, 0.2928932188134525 9.707106781186548, 0.444429766980398 9.831469612302545, 0.6173165676349103 9.923879532511286, 0.8049096779838718 9.98078528040323, 1 10), (2 8, 2 2, 8 2, 8 8, 2 8)) + + +POLYGON ((1 19, 9 19, 10.950903220161283 18.807852804032304, 12.826834323650898 18.238795325112868, 14.555702330196024 17.314696123025453, 16.071067811865476 16.071067811865476, 17.314696123025453 14.555702330196022, 18.238795325112868 12.826834323650898, 18.807852804032304 10.950903220161283, 19 9, 19 1, 18.807852804032304 -0.9509032201612824, 18.238795325112868 -2.826834323650898, 17.314696123025453 -4.555702330196022, 16.071067811865476 -6.071067811865475, 14.555702330196024 -7.314696123025453, 12.826834323650898 -8.238795325112868, 10.950903220161283 -8.807852804032304, 9 -9, 1 -9, -0.9509032201612819 -8.807852804032304, -2.826834323650897 -8.238795325112868, -4.55570233019602 -7.314696123025453, -6.071067811865475 -6.0710678118654755, -7.314696123025453 -4.555702330196022, -8.238795325112868 -2.826834323650899, -8.807852804032304 -0.9509032201612861, -9 1, -9 9, -8.807852804032304 10.950903220161287, -8.238795325112868 12.8268343236509, -7.314696123025453 14.555702330196022, -6.071067811865475 16.071067811865476, -4.55570233019602 17.314696123025453, -2.826834323650897 18.238795325112868, -0.9509032201612819 18.807852804032304, 1 19)) + + + Polygon