Skip to content

Commit 040802a

Browse files
authored
Use buffer hole erosion heuristic for rings (#1117)
1 parent 027df7d commit 040802a

File tree

2 files changed

+53
-22
lines changed

2 files changed

+53
-22
lines changed

modules/core/src/main/java/org/locationtech/jts/operation/buffer/BufferCurveSetBuilder.java

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,9 @@ private void addPolygon(Polygon p)
216216

217217
LinearRing shell = p.getExteriorRing();
218218
Coordinate[] shellCoord = clean(shell.getCoordinates());
219-
// optimization - don't bother computing buffer
219+
// optimization - don't compute buffer
220220
// if the polygon would be completely eroded
221-
if (distance < 0.0 && isErodedCompletely(shell, distance))
221+
if (distance < 0.0 && isRingFullyEroded(shell, false, distance))
222222
return;
223223
// don't attempt to buffer a polygon with too few distinct vertices
224224
if (distance <= 0.0 && shellCoord.length < 3)
@@ -236,9 +236,9 @@ private void addPolygon(Polygon p)
236236
LinearRing hole = p.getInteriorRingN(i);
237237
Coordinate[] holeCoord = clean(hole.getCoordinates());
238238

239-
// optimization - don't bother computing buffer for this hole
239+
// optimization - don't compute buffer for this hole
240240
// if the hole would be completely covered
241-
if (distance > 0.0 && isErodedCompletely(hole, -distance))
241+
if (distance > 0.0 && isRingFullyEroded(hole, true, distance))
242242
continue;
243243

244244
// Holes are topologically labelled opposite to the shell, since
@@ -255,14 +255,27 @@ private void addPolygon(Polygon p)
255255

256256
private void addRingBothSides(Coordinate[] coord, double distance)
257257
{
258-
addRingSide(coord, distance,
259-
Position.LEFT,
260-
Location.EXTERIOR, Location.INTERIOR);
261-
/* Add the opposite side of the ring
262-
*/
263-
addRingSide(coord, distance,
264-
Position.RIGHT,
265-
Location.INTERIOR, Location.EXTERIOR);
258+
/*
259+
* (f "hole" side will be eroded completely, avoid generating it.
260+
* This prevents hole artifacts (e.g. https://github.com/libgeos/geos/issues/1223)
261+
*/
262+
//-- distance is assumed positive, due to previous checks
263+
boolean isHoleComputed = ! isRingFullyEroded(coord, CoordinateArrays.envelope(coord), true, distance);
264+
265+
boolean isCCW = isRingCCW(coord);
266+
267+
boolean isShellLeft = ! isCCW;
268+
if (isShellLeft || isHoleComputed) {
269+
addRingSide(coord, distance,
270+
Position.LEFT,
271+
Location.EXTERIOR, Location.INTERIOR);
272+
}
273+
boolean isShellRight = isCCW;
274+
if (isShellRight || isHoleComputed) {
275+
addRingSide(coord, distance,
276+
Position.RIGHT,
277+
Location.INTERIOR, Location.EXTERIOR);
278+
}
266279
}
267280

268281
/**
@@ -411,25 +424,32 @@ private static boolean hasPointOnBuffer(Coordinate[] inputRing, double distance,
411424
* @param offsetDistance
412425
* @return
413426
*/
414-
private static boolean isErodedCompletely(LinearRing ring, double bufferDistance)
427+
private static boolean isRingFullyEroded(LinearRing ring, boolean isHole, double bufferDistance)
428+
{
429+
return isRingFullyEroded(ring.getCoordinates(), ring.getEnvelopeInternal(), isHole, bufferDistance);
430+
}
431+
432+
private static boolean isRingFullyEroded(Coordinate[] ringCoord, Envelope ringEnv, boolean isHole, double bufferDistance)
415433
{
416-
Coordinate[] ringCoord = ring.getCoordinates();
417434
// degenerate ring has no area
418435
if (ringCoord.length < 4)
419-
return bufferDistance < 0;
436+
return true;
420437

421438
// important test to eliminate inverted triangle bug
422439
// also optimizes erosion test for triangles
423440
if (ringCoord.length == 4)
424441
return isTriangleErodedCompletely(ringCoord, bufferDistance);
425442

426-
// if envelope is narrower than twice the buffer distance, ring is eroded
427-
Envelope env = ring.getEnvelopeInternal();
428-
double envMinDimension = Math.min(env.getHeight(), env.getWidth());
429-
if (bufferDistance < 0.0
430-
&& 2 * Math.abs(bufferDistance) > envMinDimension)
431-
return true;
432-
443+
boolean isErodable =
444+
( isHole && bufferDistance > 0) ||
445+
(! isHole && bufferDistance < 0);
446+
447+
if (isErodable) {
448+
//-- if envelope is narrower than twice the buffer distance, ring is eroded
449+
double envMinDimension = Math.min(ringEnv.getHeight(), ringEnv.getWidth());
450+
if (2 * Math.abs(bufferDistance) > envMinDimension)
451+
return true;
452+
}
433453
return false;
434454
}
435455

modules/core/src/test/java/org/locationtech/jts/operation/buffer/BufferTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,17 @@ public void testPolygonQS_1KeepHoles() {
631631
assertEquals(geom.getNumInteriorRing(), buf.getNumInteriorRing());
632632
}
633633

634+
//See https://github.com/libgeos/geos/issues/1223
635+
public void testRingHoleEroded() {
636+
String wkt = "LINESTRING (25 44, 31 44, 32 38, 29 37, 25 37, 25 38, 24 40, 24 44, 25 44)";
637+
checkBuffer(wkt, 100,
638+
"POLYGON ((50.95 141.99, 70.09 136.04, 87.66 126.4, 102.96 113.44, 115.36 97.69, 124.38 79.78, 129.64 60.44, 130.64 54.44, 131.93 34.31, 129.16 14.34, 122.44 -4.68, 112.03 -21.96, 98.37 -36.8, 82.02 -48.59, 63.62 -56.87, 60.62 -57.87, 45.02 -61.71, 29 -63, 25 -63, 4.33 -60.84, -15.44 -54.46, -33.47 -44.12, -48.97 -30.29, -61.28 -13.55, -69.87 5.38, -70.87 8.38, -74.71 23.98, -76 40, -76 44, -74.08 63.51, -68.39 82.27, -59.15 99.56, -46.71 114.71, -31.56 127.15, -14.27 136.39, 4.49 142.08, 24 144, 31 144, 50.95 141.99))");
639+
checkBuffer(wkt, 10,
640+
"POLYGON ((15.06 35.53, 14.27 37.7, 14 40, 14 44, 14.19 45.95, 14.76 47.83, 15.69 49.56, 16.93 51.07, 18.44 52.31, 20.17 53.24, 22.05 53.81, 24 54, 31 54, 32.99 53.8, 34.91 53.2, 36.67 52.24, 38.2 50.94, 39.44 49.37, 40.34 47.58, 40.86 45.64, 41.86 39.64, 41.99 37.63, 41.72 35.63, 41.04 33.73, 40 32, 38.64 30.52, 37 29.34, 35.16 28.51, 32.16 27.51, 30.6 27.13, 29 27, 25 27, 23.05 27.19, 21.17 27.76, 19.44 28.69, 17.93 29.93, 16.69 31.44, 15.76 33.17, 15.19 35.05, 15.17 35.31, 15.06 35.53))");
641+
checkBuffer(wkt, 2,
642+
"POLYGON ((31.4 45.96, 31.78 45.84, 32.13 45.65, 32.44 45.39, 32.69 45.07, 32.87 44.72, 32.97 44.33, 33.97 38.33, 34 37.93, 33.94 37.53, 33.81 37.15, 33.6 36.8, 33.33 36.5, 33 36.27, 32.63 36.1, 29.63 35.1, 29.32 35.03, 29 35, 25 35, 24.61 35.04, 24.23 35.15, 23.89 35.34, 23.59 35.59, 23.34 35.89, 23.15 36.23, 23.04 36.61, 23 37, 23 37.53, 22.21 39.11, 22.05 39.54, 22 40, 22 44, 22.04 44.39, 22.15 44.77, 22.34 45.11, 22.59 45.41, 22.89 45.66, 23.23 45.85, 23.61 45.96, 24 46, 31 46, 31.4 45.96), (26 40.47, 26.74 39, 28.68 39, 29.75 39.36, 29.31 42, 26 42, 26 40.47))");
643+
}
644+
634645
//===================================================
635646

636647
private static BufferParameters bufParamRoundMitre(double mitreLimit) {

0 commit comments

Comments
 (0)