add detailed jts debugging info

pull/703/head
Mike Barry 2023-10-30 08:14:40 -04:00
rodzic 9f960022b8
commit 1b6618cb15
4 zmienionych plików z 46 dodań i 8 usunięć

Wyświetl plik

@ -19,6 +19,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.geotools.geometry.jts.WKTWriter2;
import org.locationtech.jts.algorithm.Area;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.Envelope;
@ -28,6 +29,7 @@ import org.locationtech.jts.geom.LineString;
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.index.strtree.STRtree;
import org.locationtech.jts.operation.buffer.BufferOp;
import org.locationtech.jts.operation.buffer.BufferParameters;
@ -410,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<Geometry> polygonGroup) {
private static Geometry bufferUnionUnbuffer(double buffer, List<Geometry> polygonGroup) throws GeometryException {
/*
* A simpler alternative that might initially appear faster would be:
*
@ -424,11 +426,27 @@ public class FeatureMerge {
* The following approach is slower most of the time, but faster on average because it does
* not choke on dense nearby polygons:
*/
for (int i = 0; i < polygonGroup.size(); i++) {
polygonGroup.set(i, buffer(buffer, polygonGroup.get(i)));
List<Geometry> buffered = new ArrayList<>(polygonGroup.size());
for (Geometry geometry : polygonGroup) {
buffered.add(buffer(buffer, geometry));
}
Geometry merged = GeoUtils.createGeometryCollection(buffered);
try {
merged = union(merged);
} catch (TopologyException e) {
throw new GeometryException("buffer_union_failure", "Error unioning buffered polygons", e).addDetails(() -> {
var wktWriter = new WKTWriter2();
return """
Original polygons: %s
Buffer: %f
Buffered: %s
""".formatted(
wktWriter.write(GeoUtils.createGeometryCollection(polygonGroup)),
buffer,
wktWriter.write(GeoUtils.createGeometryCollection(buffered))
);
});
}
Geometry merged = GeoUtils.createGeometryCollection(polygonGroup);
merged = union(merged);
merged = unbuffer(buffer, merged);
return merged;
}

Wyświetl plik

@ -488,7 +488,7 @@ public final class FeatureGroup implements Iterable<FeatureGroup.TileFeatures>,
// log failures, only throwing when it's a fatal error
if (e instanceof GeometryException geoe) {
geoe.log(stats, "postprocess_layer",
"Caught error postprocessing features for " + layer + " layer on " + tileCoord);
"Caught error postprocessing features for " + layer + " layer on " + tileCoord, config.logJtsExceptions());
} else if (e instanceof Error err) {
LOGGER.error("Caught fatal error postprocessing features {} {}", layer, tileCoord, e);
throw err;

Wyświetl plik

@ -58,7 +58,8 @@ public record PlanetilerConfig(
String debugUrlPattern,
Path tmpDir,
Path tileWeights,
double maxPointBuffer
double maxPointBuffer,
boolean logJtsExceptions
) {
public static final int MIN_MINZOOM = 0;
@ -208,7 +209,8 @@ public record PlanetilerConfig(
"Max tile pixels to include points outside tile bounds. Set to a lower value to reduce tile size for " +
"clients that handle label collisions across tiles (most web and native clients). NOTE: Do not reduce if you need to support " +
"raster tile rendering",
Double.POSITIVE_INFINITY)
Double.POSITIVE_INFINITY),
arguments.getBoolean("log_jts_exceptions", "Emit verbose details to debug JTS geometry errors", false)
);
}

Wyświetl plik

@ -1,6 +1,7 @@
package com.onthegomap.planetiler.geo;
import com.onthegomap.planetiler.stats.Stats;
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -14,6 +15,7 @@ public class GeometryException extends Exception {
private final String stat;
private final boolean nonFatal;
private Supplier<String> detailsSupplier;
/**
* Constructs a new exception with a detailed error message caused by {@code cause}.
@ -51,6 +53,11 @@ public class GeometryException extends Exception {
this.nonFatal = nonFatal;
}
public GeometryException addDetails(Supplier<String> detailsSupplier) {
this.detailsSupplier = detailsSupplier;
return this;
}
/** Returns the unique code for this error condition to use for counting the number of occurrences in stats. */
public String stat() {
return stat;
@ -72,6 +79,17 @@ public class GeometryException extends Exception {
assert nonFatal : log; // make unit tests fail if fatal
}
/** Logs the error but if {@code logDetails} is true, then also prints detailed debugging info. */
public void log(Stats stats, String statPrefix, String logPrefix, boolean logDetails) {
if (logDetails && detailsSupplier != null) {
stats.dataError(statPrefix + "_" + stat());
logMessage(logPrefix + ": " + getMessage() + "\n" + detailsSupplier.get());
} else {
log(stats, statPrefix, logPrefix);
}
}
/**
* An error that we expect to encounter often so should only be logged at {@code TRACE} level.
*/