Skip to content

Commit 727943b

Browse files
authored
Add buffer artifact removal heuristic for single-element inputs (#1161)
1 parent 7cd112d commit 727943b

File tree

2 files changed

+54
-1
lines changed

2 files changed

+54
-1
lines changed

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.locationtech.jts.geom.GeometryFactory;
2828
import org.locationtech.jts.geom.LineString;
2929
import org.locationtech.jts.geom.Location;
30+
import org.locationtech.jts.geom.Polygon;
3031
import org.locationtech.jts.geom.Position;
3132
import org.locationtech.jts.geom.PrecisionModel;
3233
import org.locationtech.jts.geom.TopologyException;
@@ -176,10 +177,42 @@ public Geometry buffer(Geometry g, double distance)
176177
return createEmptyResultGeometry();
177178
}
178179

180+
/**
181+
* Heuristic to remove artifacts caused by topology robustness problems
182+
* or buffer curve generation anomalies.
183+
* Uses fact that for distance > 0 single-element inputs must create single element buffers.
184+
* This does not hold if distance <= 0;
185+
* distance = 0 can create multipolygon results due to topology collapse,
186+
* and distance < 0 may erode polygons so they are disconnected.
187+
*/
188+
if (distance > 0 && g.getNumGeometries() == 1 && resultPolyList.size() > 1) {
189+
resultPolyList = keepLargestArea(resultPolyList);
190+
}
191+
179192
Geometry resultGeom = geomFact.buildGeometry(resultPolyList);
180193
return resultGeom;
181194
}
182195

196+
private static List<Polygon> keepLargestArea(List<Polygon> polyList) {
197+
Polygon largest = findLargestArea(polyList);
198+
List<Polygon> largestPolyList = new ArrayList<Polygon>();
199+
largestPolyList.add(largest);
200+
return largestPolyList;
201+
}
202+
203+
private static Polygon findLargestArea(List<Polygon> polyList) {
204+
double largestArea = 0;
205+
Polygon largest = null;
206+
for (Polygon poly : polyList) {
207+
double polyArea = poly.getArea();
208+
if (largest == null || polyArea > largestArea) {
209+
largest = poly;
210+
largestArea = polyArea;
211+
}
212+
}
213+
return largest;
214+
}
215+
183216
private Noder getNoder(PrecisionModel precisionModel)
184217
{
185218
if (workingNoder != null) return workingNoder;

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,20 @@ public void testBufferByZeroKeepsAllElements() {
668668
"MULTIPOLYGON (((24 95.239, 24 96, 24 99, 24.816 99, 24 95.239)), ((3 90, 3 93, 3 96, 3 99, 21 99, 21 96, 21 93, 21 90, 3 90)))");
669669
}
670670

671+
//-- see https://github.com/libgeos/geos/issues/1321
672+
public void testArtifactsRemovedFromLineBuffer() {
673+
String wkt = "LINESTRING (640734.77510795 216861.236982439, 640733.832969266 216862.938074642, 640732.814325629 216865.039674844, 640731.225095251 216869.012141189, 640729.979984761 216871.879095724, 640729.445974092 216873.02148841, 640729.002794006 216873.679857725, 640728.952197105 216873.745389857, 640728.676962154 216874.089814544)";
674+
checkBufferNumGeometries(wkt, 100, 1);
675+
}
676+
677+
//-- see https://github.com/r-spatial/sf/issues/2552
678+
public void testArtifactsRemovedFromLineBufferFlatEnd() {
679+
String wkt = "LINESTRING (245184.6 6045650, 245193.3 6045649, 245201.7 6045651, 245204.3 6045653)";
680+
Geometry geom = read(wkt);
681+
Geometry buf = BufferOp.bufferOp(geom, 50, bufParamEndCapFlat());
682+
assertEquals(1, buf.getNumGeometries());
683+
}
684+
671685
//===================================================
672686

673687
private static BufferParameters bufParamRoundMitre(double mitreLimit) {
@@ -677,6 +691,12 @@ private static BufferParameters bufParamRoundMitre(double mitreLimit) {
677691
return param;
678692
}
679693

694+
private static BufferParameters bufParamEndCapFlat() {
695+
BufferParameters param = new BufferParameters();
696+
param.setEndCapStyle(BufferParameters.CAP_FLAT);
697+
return param;
698+
}
699+
680700
private void checkBuffer(String wkt, double dist, BufferParameters param, String wktExpected) {
681701
Geometry geom = read(wkt);
682702
Geometry result = BufferOp.bufferOp(geom, dist, param);
@@ -706,7 +726,7 @@ private void checkBufferHasHole(String wkt, double dist, boolean isHoleExpected)
706726
private void checkBufferNumGeometries(String wkt, double dist, int numExpected) {
707727
Geometry a = read(wkt);
708728
Geometry result = a.buffer(dist);
709-
assertTrue(numExpected == result.getNumGeometries());
729+
assertEquals(numExpected, result.getNumGeometries());
710730
}
711731

712732
private boolean hasHole(Geometry geom) {

0 commit comments

Comments
 (0)