diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/FeatureMerge.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/FeatureMerge.java index 2901907d..6e0a5482 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/FeatureMerge.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/FeatureMerge.java @@ -29,6 +29,7 @@ import org.locationtech.jts.geom.LinearRing; import org.locationtech.jts.geom.Polygon; import org.locationtech.jts.geom.Polygonal; import org.locationtech.jts.geom.TopologyException; +import org.locationtech.jts.geom.util.GeometryFixer; import org.locationtech.jts.index.strtree.STRtree; import org.locationtech.jts.operation.buffer.BufferOp; import org.locationtech.jts.operation.buffer.BufferParameters; @@ -323,7 +324,7 @@ public class FeatureMerge { // spinning for a very long time on very dense tiles. // TODO use some heuristic to choose bufferUnbuffer vs. bufferUnionUnbuffer based on the number small // polygons in the group? - merged = bufferUnionUnbuffer(buffer, polygonGroup); + merged = bufferUnionUnbuffer(buffer, polygonGroup, stats); } else { merged = buffer(buffer, GeoUtils.createGeometryCollection(polygonGroup)); } @@ -411,7 +412,7 @@ public class FeatureMerge { * Merges nearby polygons by expanding each individual polygon by {@code buffer}, unioning them, and contracting the * result. */ - private static Geometry bufferUnionUnbuffer(double buffer, List polygonGroup) throws GeometryException { + static Geometry bufferUnionUnbuffer(double buffer, List polygonGroup, Stats stats) { /* * A simpler alternative that might initially appear faster would be: * @@ -433,10 +434,11 @@ public class FeatureMerge { try { merged = union(merged); } catch (TopologyException e) { - throw new GeometryException("buffer_union_failure", "Error unioning buffered polygons", e) - .addGeometryDetails("original", GeoUtils.createGeometryCollection(polygonGroup)) - .addDetails(() -> "buffer: " + buffer) - .addGeometryDetails("buffered", GeoUtils.createGeometryCollection(buffered)); + // buffer result is sometimes invalid, which makes union throw so fix + // it and try again (see #700) + stats.dataError("buffer_union_unbuffer_union_failed"); + merged = GeometryFixer.fix(merged); + merged = union(merged); } merged = unbuffer(buffer, merged); return merged; diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/FeatureMergeTest.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/FeatureMergeTest.java index 32b6527e..0fd1628c 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/FeatureMergeTest.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/FeatureMergeTest.java @@ -10,7 +10,9 @@ import com.onthegomap.planetiler.collection.Hppc; import com.onthegomap.planetiler.geo.GeometryException; import com.onthegomap.planetiler.geo.GeometryType; import com.onthegomap.planetiler.mbtiles.Mbtiles; +import com.onthegomap.planetiler.stats.Stats; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -20,11 +22,14 @@ import java.util.function.UnaryOperator; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryCollection; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.Point; import org.locationtech.jts.geom.Polygon; +import org.locationtech.jts.io.ParseException; +import org.locationtech.jts.io.WKBReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -853,4 +858,27 @@ class FeatureMergeTest { ) ); } + + @ParameterizedTest + @ValueSource(strings = { + "/issue_700/exception_1.wkb", + "/issue_700/exception_2.wkb", + "/issue_700/exception_3.wkb", + "/issue_700/exception_4.wkb", + "/issue_700/exception_5.wkb", + "/issue_700/exception_6.wkb", + "/issue_700/exception_7.wkb", + "/issue_700/exception_8.wkb", + "/issue_700/exception_9.wkb", + }) + void testIssue700BufferUnionUnbufferFailure(String path) throws IOException, ParseException { + try (var is = getClass().getResource(path).openStream()) { + GeometryCollection collection = (GeometryCollection) new WKBReader().read(is.readAllBytes()); + List geometries = new ArrayList<>(); + for (int i = 0; i < collection.getNumGeometries(); i++) { + geometries.add(collection.getGeometryN(i)); + } + FeatureMerge.bufferUnionUnbuffer(0.5, geometries, Stats.inMemory()); + } + } } diff --git a/planetiler-core/src/test/resources/issue_700/exception_1.wkb b/planetiler-core/src/test/resources/issue_700/exception_1.wkb new file mode 100644 index 00000000..4b2108d4 Binary files /dev/null and b/planetiler-core/src/test/resources/issue_700/exception_1.wkb differ diff --git a/planetiler-core/src/test/resources/issue_700/exception_2.wkb b/planetiler-core/src/test/resources/issue_700/exception_2.wkb new file mode 100644 index 00000000..7f4c774f Binary files /dev/null and b/planetiler-core/src/test/resources/issue_700/exception_2.wkb differ diff --git a/planetiler-core/src/test/resources/issue_700/exception_3.wkb b/planetiler-core/src/test/resources/issue_700/exception_3.wkb new file mode 100644 index 00000000..e75c9684 Binary files /dev/null and b/planetiler-core/src/test/resources/issue_700/exception_3.wkb differ diff --git a/planetiler-core/src/test/resources/issue_700/exception_4.wkb b/planetiler-core/src/test/resources/issue_700/exception_4.wkb new file mode 100644 index 00000000..f16cc015 Binary files /dev/null and b/planetiler-core/src/test/resources/issue_700/exception_4.wkb differ diff --git a/planetiler-core/src/test/resources/issue_700/exception_5.wkb b/planetiler-core/src/test/resources/issue_700/exception_5.wkb new file mode 100644 index 00000000..e259e5f9 Binary files /dev/null and b/planetiler-core/src/test/resources/issue_700/exception_5.wkb differ diff --git a/planetiler-core/src/test/resources/issue_700/exception_6.wkb b/planetiler-core/src/test/resources/issue_700/exception_6.wkb new file mode 100644 index 00000000..9e14a471 Binary files /dev/null and b/planetiler-core/src/test/resources/issue_700/exception_6.wkb differ diff --git a/planetiler-core/src/test/resources/issue_700/exception_7.wkb b/planetiler-core/src/test/resources/issue_700/exception_7.wkb new file mode 100644 index 00000000..ecbbe0b9 Binary files /dev/null and b/planetiler-core/src/test/resources/issue_700/exception_7.wkb differ diff --git a/planetiler-core/src/test/resources/issue_700/exception_8.wkb b/planetiler-core/src/test/resources/issue_700/exception_8.wkb new file mode 100644 index 00000000..fcf0537b Binary files /dev/null and b/planetiler-core/src/test/resources/issue_700/exception_8.wkb differ diff --git a/planetiler-core/src/test/resources/issue_700/exception_9.wkb b/planetiler-core/src/test/resources/issue_700/exception_9.wkb new file mode 100644 index 00000000..b66fca19 Binary files /dev/null and b/planetiler-core/src/test/resources/issue_700/exception_9.wkb differ