Skip to content

Commit

Permalink
Fix CCW ring buffer generation
Browse files Browse the repository at this point in the history
  • Loading branch information
dr-jts committed Feb 12, 2025
1 parent a6552f8 commit bcd4c34
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 38 deletions.
8 changes: 6 additions & 2 deletions include/geos/operation/buffer/BufferCurveSetBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
77 changes: 41 additions & 36 deletions src/operation/buffer/BufferCurveSetBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<CoordinateSequence*> 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
Expand Down Expand Up @@ -257,7 +285,7 @@ BufferCurveSetBuilder::addPolygon(const Polygon* p)
return;
}

addRingSide(
addPolygonRingSide(
shellCoord.get(),
offsetDistance,
offsetSide,
Expand All @@ -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),
Expand All @@ -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)
{

Expand Down Expand Up @@ -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<CoordinateSequence*> lineList;
curveBuilder.getRingCurve(coord, side, offsetDistance, lineList);
// ASSERT: lineList contains exactly 1 curve (this is teh JTS semantics)
Expand Down
13 changes: 13 additions & 0 deletions tests/unit/operation/buffer/BufferOpTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
51 changes: 51 additions & 0 deletions tests/xmltester/tests/general/TestBuffer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,57 @@
</op></test>
</case>

<case>
<desc>
Closed Line
</desc>
<a>
LINESTRING (1 9, 9 9, 9 1, 1 1, 1 9)
</a>
<test><op name='buffer' arg1='A' arg2='-1.0'> POLYGON EMPTY </op></test>
<test><op name='buffer' arg1='A' arg2='0.0'> POLYGON EMPTY </op></test>
<test><op name='buffer' arg1='A' arg2='1'>
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))
</op></test>
<test><op name='buffer' arg1='A' arg2='10.0'>
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))
</op></test>
</case>

<case>
<desc>
Closed Line - CCW
</desc>
<a>
LINESTRING (1 9, 1 1, 9 1, 9 9, 1 9)
</a>
<test><op name='buffer' arg1='A' arg2='-1.0'> POLYGON EMPTY </op></test>
<test><op name='buffer' arg1='A' arg2='0.0'> POLYGON EMPTY </op></test>
<test><op name='buffer' arg1='A' arg2='1'>
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))
</op></test>
<test><op name='buffer' arg1='A' arg2='10.0'>
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))
</op></test>
</case>

<case>
<desc>
Linear Ring
</desc>
<a>
LINEARRING (1 9, 1 1, 9 1, 9 9, 1 9)
</a>
<test><op name='buffer' arg1='A' arg2='-1.0'> POLYGON EMPTY </op></test>
<test><op name='buffer' arg1='A' arg2='0.0'> POLYGON EMPTY </op></test>
<test><op name='buffer' arg1='A' arg2='1'>
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))
</op></test>
<test><op name='buffer' arg1='A' arg2='10.0'>
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))
</op></test>
</case>

<case>
<desc>
Polygon
Expand Down

0 comments on commit bcd4c34

Please sign in to comment.