diff --git a/x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactory.java b/x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactory.java index e35bafe1591cb..9ccefae3332ed 100644 --- a/x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactory.java +++ b/x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactory.java @@ -49,12 +49,15 @@ public class FeatureFactory { private final Envelope tileEnvelope; private final Envelope clipEnvelope; + private static final double MERCATOR_BOUDS = 20037508.34; + public FeatureFactory(int z, int x, int y, int extent) { final Rectangle r = SphericalMercatorUtils.recToSphericalMercator(GeoTileUtils.toBoundingBox(x, y, z)); this.tileEnvelope = new Envelope(r.getMinX(), r.getMaxX(), r.getMinY(), r.getMaxY()); this.clipEnvelope = new Envelope(tileEnvelope); this.clipEnvelope.expandBy(tileEnvelope.getWidth() * 0.1d, tileEnvelope.getHeight() * 0.1d); - this.builder = new JTSGeometryBuilder(geomFactory); + final double pixelPrecision = 2 * MERCATOR_BOUDS / ((1L << z) * extent); + this.builder = new JTSGeometryBuilder(geomFactory, pixelPrecision); // TODO: Not sure what is the difference between extent and tile size? this.layerParams = new MvtLayerParams(extent, extent); } @@ -79,8 +82,10 @@ public MvtLayerProps getLayerProps() { private static class JTSGeometryBuilder implements GeometryVisitor { private final GeometryFactory geomFactory; + double pixelPrecision; - JTSGeometryBuilder(GeometryFactory geomFactory) { + JTSGeometryBuilder(GeometryFactory geomFactory, double pixelPrecision) { + this.pixelPrecision = pixelPrecision; this.geomFactory = geomFactory; } @@ -163,6 +168,9 @@ public org.locationtech.jts.geom.Geometry visit(MultiPolygon multiPolygon) throw private org.locationtech.jts.geom.Polygon buildPolygon(Polygon polygon) { final org.locationtech.jts.geom.LinearRing outerShell = buildLinearRing(polygon.getPolygon()); + if (isValidPolygon(outerShell) == false) { + return geomFactory.createPolygon(); // empty polygon + } if (polygon.getNumberOfHoles() == 0) { return geomFactory.createPolygon(outerShell); } @@ -173,6 +181,12 @@ private org.locationtech.jts.geom.Polygon buildPolygon(Polygon polygon) { return geomFactory.createPolygon(outerShell, holes); } + private boolean isValidPolygon(org.locationtech.jts.geom.LinearRing outerShell) { + final Envelope envelope = outerShell.getEnvelopeInternal(); + final double polMinimumSize = pixelPrecision * outerShell.getNumPoints() / 1000; + return envelope.getMaxX() - envelope.getMinX() >= polMinimumSize && envelope.getMaxY() - envelope.getMinY() >= polMinimumSize; + } + private org.locationtech.jts.geom.LinearRing buildLinearRing(LinearRing ring) throws RuntimeException { final Coordinate[] coordinates = new Coordinate[ring.length()]; for (int i = 0; i < ring.length(); i++) { diff --git a/x-pack/plugin/vector-tile/src/test/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactoryTests.java b/x-pack/plugin/vector-tile/src/test/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactoryTests.java index c8d9710b43841..b2299003d0e95 100644 --- a/x-pack/plugin/vector-tile/src/test/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactoryTests.java +++ b/x-pack/plugin/vector-tile/src/test/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactoryTests.java @@ -21,15 +21,22 @@ import org.elasticsearch.geometry.Point; import org.elasticsearch.geometry.Polygon; import org.elasticsearch.geometry.Rectangle; +import org.elasticsearch.geometry.utils.StandardValidator; +import org.elasticsearch.geometry.utils.WellKnownText; import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils; import org.elasticsearch.test.ESTestCase; import org.hamcrest.Matchers; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; +import java.util.zip.GZIPInputStream; public class FeatureFactoryTests extends ESTestCase { @@ -146,4 +153,15 @@ private MultiPolygon buildMultiPolygon(Rectangle r) { private GeometryCollection buildGeometryCollection(Rectangle r) { return new GeometryCollection<>(List.of(buildPolygon(r), buildLine(r))); } + + public void testStackOverflowError() throws Exception { + final InputStream is = new GZIPInputStream(getClass().getResourceAsStream("polygon.wkt.gz")); + final BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); + final Geometry geometry = WellKnownText.fromWKT(StandardValidator.instance(true), true, reader.readLine()); + // just make sure we don't blow + for (int i = 0; i < 10; i++) { + final FeatureFactory builder = new FeatureFactory(0, 0, 0, randomIntBetween(128, 8012)); + builder.getFeatures(geometry, new UserDataIgnoreConverter()); + } + } } diff --git a/x-pack/plugin/vector-tile/src/test/resources/org/elasticsearch/xpack/vectortile/feature/polygon.wkt.gz b/x-pack/plugin/vector-tile/src/test/resources/org/elasticsearch/xpack/vectortile/feature/polygon.wkt.gz new file mode 100644 index 0000000000000..707bcef41ce0f Binary files /dev/null and b/x-pack/plugin/vector-tile/src/test/resources/org/elasticsearch/xpack/vectortile/feature/polygon.wkt.gz differ