Browse Source

Vector tiles: Add support for Geometry collections (#75697)

It creates a feature fir each entry in the collection
Ignacio Vera 4 years ago
parent
commit
dd2dff41a2

+ 97 - 14
x-pack/plugin/vector-tile/src/javaRestTest/java/org/elasticsearch/xpack/vectortile/VectorTileRestIT.java

@@ -10,7 +10,6 @@ package org.elasticsearch.xpack.vectortile;
 import com.wdtinc.mapbox_vector_tile.VectorTile;
 
 import org.apache.http.HttpStatus;
-import org.apache.http.client.methods.HttpDelete;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPut;
@@ -21,7 +20,7 @@ import org.elasticsearch.geometry.Rectangle;
 import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils;
 import org.elasticsearch.test.rest.ESRestTestCase;
 import org.hamcrest.Matchers;
-import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Before;
 
 import java.io.IOException;
@@ -35,20 +34,27 @@ public class VectorTileRestIT extends ESRestTestCase {
 
     private static final String INDEX_POINTS = "index-points";
     private static final String INDEX_SHAPES = "index-shapes";
+    private static final String INDEX_COLLECTION = "index-collection";
+    private static final String INDEX_POINTS_SHAPES = INDEX_POINTS + "," + INDEX_SHAPES;
     private static final String INDEX_ALL = "index*";
     private static final String META_LAYER = "meta";
     private static final String HITS_LAYER = "hits";
     private static final String AGGS_LAYER = "aggs";
 
-    private int x, y, z;
+    private static boolean oneTimeSetup = false;
+    private static int x, y, z;
 
     @Before
     public void indexDocuments() throws IOException {
-        z = randomIntBetween(1, GeoTileUtils.MAX_ZOOM - 10);
-        x = randomIntBetween(0, (1 << z) - 1);
-        y = randomIntBetween(0, (1 << z) - 1);
-        indexPoints();
-        indexShapes();
+        if (oneTimeSetup == false) {
+            z = randomIntBetween(1, GeoTileUtils.MAX_ZOOM - 10);
+            x = randomIntBetween(0, (1 << z) - 1);
+            y = randomIntBetween(0, (1 << z) - 1);
+            indexPoints();
+            indexShapes();
+            indexCollection();
+            oneTimeSetup = true;
+        }
     }
 
     private void indexPoints() throws IOException {
@@ -151,13 +157,79 @@ public class VectorTileRestIT extends ESRestTestCase {
         assertThat(response.getStatusLine().getStatusCode(), Matchers.equalTo(HttpStatus.SC_OK));
     }
 
-    @After
-    public void deleteData() throws IOException {
-        final Request deleteRequest = new Request(HttpDelete.METHOD_NAME, INDEX_POINTS);
-        final Response response = client().performRequest(deleteRequest);
+    private void indexCollection() throws IOException {
+        final Request createRequest = new Request(HttpPut.METHOD_NAME, INDEX_COLLECTION);
+        Response response = client().performRequest(createRequest);
+        assertThat(response.getStatusLine().getStatusCode(), Matchers.equalTo(HttpStatus.SC_OK));
+        final Request mappingRequest = new Request(HttpPut.METHOD_NAME, INDEX_COLLECTION + "/_mapping");
+        mappingRequest.setJsonEntity(
+            "{\n"
+                + "  \"properties\": {\n"
+                + "    \"location\": {\n"
+                + "      \"type\": \"geo_shape\"\n"
+                + "    },\n"
+                + "    \"name\": {\n"
+                + "      \"type\": \"keyword\"\n"
+                + "    }\n"
+                + "  }\n"
+                + "}"
+        );
+        response = client().performRequest(mappingRequest);
+        assertThat(response.getStatusLine().getStatusCode(), Matchers.equalTo(HttpStatus.SC_OK));
+
+        final Rectangle r = GeoTileUtils.toBoundingBox(x, y, z);
+        double x = (r.getMaxX() + r.getMinX()) / 2;
+        double y = (r.getMaxY() + r.getMinY()) / 2;
+        final Request putRequest = new Request(HttpPost.METHOD_NAME, INDEX_COLLECTION + "/_doc");
+        String collection = "GEOMETRYCOLLECTION (BBOX ("
+            + r.getMinLon()
+            + ", "
+            + r.getMaxLon()
+            + ","
+            + r.getMaxLat()
+            + ","
+            + r.getMinLat()
+            + "), POINT("
+            + x
+            + " "
+            + y
+            + "))";
+        putRequest.setJsonEntity(
+            "{\n"
+                + "  \"location\": \""
+                + collection
+                + "\""
+                + ", \"name\": \"collection\""
+                + ", \"value1\": "
+                + 1
+                + ", \"value2\": "
+                + 2
+                + "\n"
+                + "}"
+        );
+        response = client().performRequest(putRequest);
+        assertThat(response.getStatusLine().getStatusCode(), Matchers.equalTo(HttpStatus.SC_CREATED));
+
+        final Request flushRequest = new Request(HttpPost.METHOD_NAME, INDEX_COLLECTION + "/_refresh");
+        response = client().performRequest(flushRequest);
         assertThat(response.getStatusLine().getStatusCode(), Matchers.equalTo(HttpStatus.SC_OK));
     }
 
+    @AfterClass
+    public static void deleteData() throws IOException {
+        try {
+            wipeAllIndices();
+        } finally {
+            // Clear the setup state
+            oneTimeSetup = false;
+        }
+    }
+
+    @Override
+    protected boolean preserveIndicesUponCompletion() {
+        return true;
+    }
+
     public void testBasicGet() throws Exception {
         final Request mvtRequest = new Request(getHttpMethod(), INDEX_POINTS + "/_mvt/location/" + z + "/" + x + "/" + y);
         mvtRequest.setJsonEntity("{\"size\" : 100}");
@@ -168,6 +240,17 @@ public class VectorTileRestIT extends ESRestTestCase {
         assertLayer(tile, META_LAYER, 4096, 1, 14);
     }
 
+    public void testIndexAllGet() throws Exception {
+        final Request mvtRequest = new Request(getHttpMethod(), INDEX_ALL + "/_mvt/location/" + z + "/" + x + "/" + y);
+        mvtRequest.setJsonEntity("{\"size\" : 100}");
+        final VectorTile.Tile tile = execute(mvtRequest);
+        assertThat(tile.getLayersCount(), Matchers.equalTo(3));
+        // 33 points, 1 polygon and two from geometry collection
+        assertLayer(tile, HITS_LAYER, 4096, 36, 1);
+        assertLayer(tile, AGGS_LAYER, 4096, 256 * 256, 1);
+        assertLayer(tile, META_LAYER, 4096, 1, 14);
+    }
+
     public void testExtent() throws Exception {
         final Request mvtRequest = new Request(getHttpMethod(), INDEX_POINTS + "/_mvt/location/" + z + "/" + x + "/" + y);
         mvtRequest.setJsonEntity("{\"size\" : 100, \"extent\" : 256}");
@@ -346,7 +429,7 @@ public class VectorTileRestIT extends ESRestTestCase {
             + "}\n";
         {
             // desc order, polygon should be the first hit
-            final Request mvtRequest = new Request(getHttpMethod(), INDEX_ALL + "/_mvt/location/" + z + "/" + x + "/" + y);
+            final Request mvtRequest = new Request(getHttpMethod(), INDEX_POINTS_SHAPES + "/_mvt/location/" + z + "/" + x + "/" + y);
             mvtRequest.setJsonEntity(
                 "{\n"
                     + "  \"size\" : 100,\n"
@@ -372,7 +455,7 @@ public class VectorTileRestIT extends ESRestTestCase {
         }
         {
             // asc order, polygon should be the last hit
-            final Request mvtRequest = new Request(getHttpMethod(), INDEX_ALL + "/_mvt/location/" + z + "/" + x + "/" + y);
+            final Request mvtRequest = new Request(getHttpMethod(), INDEX_POINTS_SHAPES + "/_mvt/location/" + z + "/" + x + "/" + y);
             mvtRequest.setJsonEntity(
                 "{\n"
                     + "  \"size\" : 100,\n"

+ 9 - 6
x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactory.java

@@ -60,7 +60,7 @@ public class FeatureFactory {
     }
 
     public List<VectorTile.Tile.Feature> getFeatures(Geometry geometry, IUserDataConverter userData) {
-        TileGeomResult tileGeom = JtsAdapter.createTileGeom(
+        final TileGeomResult tileGeom = JtsAdapter.createTileGeom(
             JtsAdapter.flatFeatureList(geometry.visit(builder)),
             tileEnvelope,
             clipEnvelope,
@@ -91,8 +91,11 @@ public class FeatureFactory {
 
         @Override
         public org.locationtech.jts.geom.Geometry visit(GeometryCollection<?> collection) {
-            // TODO: Geometry collections are not supported by the vector tile specification.
-            throw new IllegalArgumentException("GeometryCollection is not supported");
+            final org.locationtech.jts.geom.Geometry[] geometries = new org.locationtech.jts.geom.Geometry[collection.size()];
+            for (int i = 0; i < collection.size(); i++) {
+                geometries[i] = collection.get(i).visit(this);
+            }
+            return geomFactory.createGeometryCollection(geometries);
         }
 
         @Override
@@ -127,7 +130,7 @@ public class FeatureFactory {
 
         @Override
         public org.locationtech.jts.geom.Geometry visit(MultiLine multiLine) throws RuntimeException {
-            LineString[] lineStrings = new LineString[multiLine.size()];
+            final LineString[] lineStrings = new LineString[multiLine.size()];
             for (int i = 0; i < multiLine.size(); i++) {
                 lineStrings[i] = buildLine(multiLine.get(i));
             }
@@ -151,7 +154,7 @@ public class FeatureFactory {
 
         @Override
         public org.locationtech.jts.geom.Geometry visit(MultiPolygon multiPolygon) throws RuntimeException {
-            org.locationtech.jts.geom.Polygon[] polygons = new org.locationtech.jts.geom.Polygon[multiPolygon.size()];
+            final org.locationtech.jts.geom.Polygon[] polygons = new org.locationtech.jts.geom.Polygon[multiPolygon.size()];
             for (int i = 0; i < multiPolygon.size(); i++) {
                 polygons[i] = buildPolygon(multiPolygon.get(i));
             }
@@ -163,7 +166,7 @@ public class FeatureFactory {
             if (polygon.getNumberOfHoles() == 0) {
                 return geomFactory.createPolygon(outerShell);
             }
-            org.locationtech.jts.geom.LinearRing[] holes = new org.locationtech.jts.geom.LinearRing[polygon.getNumberOfHoles()];
+            final org.locationtech.jts.geom.LinearRing[] holes = new org.locationtech.jts.geom.LinearRing[polygon.getNumberOfHoles()];
             for (int i = 0; i < polygon.getNumberOfHoles(); i++) {
                 holes[i] = buildLinearRing(polygon.getHole(i));
             }

+ 62 - 157
x-pack/plugin/vector-tile/src/test/java/org/elasticsearch/xpack/vectortile/feature/FeatureFactoryTests.java

@@ -28,198 +28,99 @@ import org.hamcrest.Matchers;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
 public class FeatureFactoryTests extends ESTestCase {
 
     public void testPoint() {
-        int z = randomIntBetween(3, 10);
-        int x = randomIntBetween(0, (1 << z) - 1);
-        int y = randomIntBetween(0, (1 << z) - 1);
-        int extent = randomIntBetween(1 << 8, 1 << 14);
-        FeatureFactory builder = new FeatureFactory(z, x, y, extent);
-        Rectangle rectangle = GeoTileUtils.toBoundingBox(x, y, z);
-        {
-            double lat = randomValueOtherThanMany((l) -> rectangle.getMinY() >= l || rectangle.getMaxY() <= l, GeoTestUtil::nextLatitude);
-            double lon = randomValueOtherThanMany((l) -> rectangle.getMinX() >= l || rectangle.getMaxX() <= l, GeoTestUtil::nextLongitude);
-            List<VectorTile.Tile.Feature> features = builder.getFeatures(new Point(lon, lat), new UserDataIgnoreConverter());
+        doTestGeometry(this::buildPoint, features -> {
             assertThat(features.size(), Matchers.equalTo(1));
-            VectorTile.Tile.Feature feature = features.get(0);
-            assertThat(feature.getType(), Matchers.equalTo(VectorTile.Tile.GeomType.POINT));
-        }
-        {
-            int xNew = randomValueOtherThanMany(v -> Math.abs(v - x) < 2, () -> randomIntBetween(0, (1 << z) - 1));
-            int yNew = randomValueOtherThanMany(v -> Math.abs(v - y) < 2, () -> randomIntBetween(0, (1 << z) - 1));
-            Rectangle rectangleNew = GeoTileUtils.toBoundingBox(xNew, yNew, z);
-            double lat = randomValueOtherThanMany(
-                (l) -> rectangleNew.getMinY() >= l || rectangleNew.getMaxY() <= l,
-                GeoTestUtil::nextLatitude
-            );
-            double lon = randomValueOtherThanMany(
-                (l) -> rectangleNew.getMinX() >= l || rectangleNew.getMaxX() <= l,
-                GeoTestUtil::nextLongitude
-            );
-            List<VectorTile.Tile.Feature> features = builder.getFeatures(new Point(lon, lat), new UserDataIgnoreConverter());
-            assertThat(features.size(), Matchers.equalTo(0));
-        }
+            assertThat(features.get(0).getType(), Matchers.equalTo(VectorTile.Tile.GeomType.POINT));
+        });
     }
 
     public void testMultiPoint() {
-        int z = randomIntBetween(3, 10);
-        int x = randomIntBetween(0, (1 << z) - 1);
-        int y = randomIntBetween(0, (1 << z) - 1);
-        int extent = randomIntBetween(1 << 8, 1 << 14);
-        FeatureFactory builder = new FeatureFactory(z, x, y, extent);
-        Rectangle rectangle = GeoTileUtils.toBoundingBox(x, y, z);
-        int numPoints = randomIntBetween(2, 10);
-        {
-            List<Point> points = new ArrayList<>();
-            double lat = randomValueOtherThanMany((l) -> rectangle.getMinY() >= l || rectangle.getMaxY() <= l, GeoTestUtil::nextLatitude);
-            double lon = randomValueOtherThanMany((l) -> rectangle.getMinX() >= l || rectangle.getMaxX() <= l, GeoTestUtil::nextLongitude);
-            points.add(new Point(lon, lat));
-            for (int i = 0; i < numPoints - 1; i++) {
-                points.add(new Point(GeoTestUtil.nextLongitude(), GeoTestUtil.nextLatitude()));
-            }
-            List<VectorTile.Tile.Feature> features = builder.getFeatures(new MultiPoint(points), new UserDataIgnoreConverter());
+        doTestGeometry(this::buildMultiPoint, features -> {
             assertThat(features.size(), Matchers.equalTo(1));
-            VectorTile.Tile.Feature feature = features.get(0);
-            assertThat(feature.getType(), Matchers.equalTo(VectorTile.Tile.GeomType.POINT));
-        }
-        {
-            int xNew = randomValueOtherThanMany(v -> Math.abs(v - x) < 2, () -> randomIntBetween(0, (1 << z) - 1));
-            int yNew = randomValueOtherThanMany(v -> Math.abs(v - y) < 2, () -> randomIntBetween(0, (1 << z) - 1));
-            Rectangle rectangleNew = GeoTileUtils.toBoundingBox(xNew, yNew, z);
-            List<Point> points = new ArrayList<>();
-            for (int i = 0; i < numPoints; i++) {
-                double lat = randomValueOtherThanMany(
-                    (l) -> rectangleNew.getMinY() >= l || rectangleNew.getMaxY() <= l,
-                    GeoTestUtil::nextLatitude
-                );
-                double lon = randomValueOtherThanMany(
-                    (l) -> rectangleNew.getMinX() >= l || rectangleNew.getMaxX() <= l,
-                    GeoTestUtil::nextLongitude
-                );
-                points.add(new Point(lon, lat));
-            }
-            List<VectorTile.Tile.Feature> features = builder.getFeatures(new MultiPoint(points), new UserDataIgnoreConverter());
-            assertThat(features.size(), Matchers.equalTo(0));
-        }
+            assertThat(features.get(0).getType(), Matchers.equalTo(VectorTile.Tile.GeomType.POINT));
+        });
     }
 
     public void testRectangle() {
-        int z = randomIntBetween(3, 10);
-        int x = randomIntBetween(2, (1 << z) - 1);
-        int y = randomIntBetween(2, (1 << z) - 1);
-        int extent = randomIntBetween(1 << 8, 1 << 14);
-        FeatureFactory builder = new FeatureFactory(z, x, y, extent);
-        {
-            Rectangle r = GeoTileUtils.toBoundingBox(x, y, z);
-            List<VectorTile.Tile.Feature> features = builder.getFeatures(r, new UserDataIgnoreConverter());
+        doTestGeometry(r -> r, features -> {
             assertThat(features.size(), Matchers.equalTo(1));
-            VectorTile.Tile.Feature feature = features.get(0);
-            assertThat(feature.getType(), Matchers.equalTo(VectorTile.Tile.GeomType.POLYGON));
-        }
-        {
-            Rectangle r = GeoTileUtils.toBoundingBox(x - 2, y, z);
-            List<VectorTile.Tile.Feature> features = builder.getFeatures(r, new UserDataIgnoreConverter());
-            assertThat(features.size(), Matchers.equalTo(0));
-        }
+            assertThat(features.get(0).getType(), Matchers.equalTo(VectorTile.Tile.GeomType.POLYGON));
+        });
     }
 
     public void testLine() {
-        int z = randomIntBetween(3, 10);
-        int x = randomIntBetween(2, (1 << z) - 1);
-        int y = randomIntBetween(2, (1 << z) - 1);
-        int extent = randomIntBetween(1 << 8, 1 << 14);
-        FeatureFactory builder = new FeatureFactory(z, x, y, extent);
-        {
-            Rectangle r = GeoTileUtils.toBoundingBox(x, y, z);
-            List<VectorTile.Tile.Feature> features = builder.getFeatures(buildLine(r), new UserDataIgnoreConverter());
+        doTestGeometry(this::buildLine, features -> {
             assertThat(features.size(), Matchers.equalTo(1));
-            VectorTile.Tile.Feature feature = features.get(0);
-            assertThat(feature.getType(), Matchers.equalTo(VectorTile.Tile.GeomType.LINESTRING));
-        }
-        {
-            Rectangle r = GeoTileUtils.toBoundingBox(x - 2, y, z);
-            List<VectorTile.Tile.Feature> features = builder.getFeatures(buildLine(r), new UserDataIgnoreConverter());
-            assertThat(features.size(), Matchers.equalTo(0));
-        }
+            assertThat(features.get(0).getType(), Matchers.equalTo(VectorTile.Tile.GeomType.LINESTRING));
+        });
     }
 
     public void testMultiLine() {
-        int z = randomIntBetween(3, 10);
-        int x = randomIntBetween(2, (1 << z) - 1);
-        int y = randomIntBetween(2, (1 << z) - 1);
-        int extent = randomIntBetween(1 << 8, 1 << 14);
-        FeatureFactory builder = new FeatureFactory(z, x, y, extent);
-        {
-            Rectangle r = GeoTileUtils.toBoundingBox(x, y, z);
-            List<VectorTile.Tile.Feature> features = builder.getFeatures(buildMultiLine(r), new UserDataIgnoreConverter());
+        doTestGeometry(this::buildMultiLine, features -> {
             assertThat(features.size(), Matchers.equalTo(1));
-            VectorTile.Tile.Feature feature = features.get(0);
-            assertThat(feature.getType(), Matchers.equalTo(VectorTile.Tile.GeomType.LINESTRING));
-        }
-        {
-            Rectangle r = GeoTileUtils.toBoundingBox(x - 2, y, z);
-            List<VectorTile.Tile.Feature> features = builder.getFeatures(buildMultiLine(r), new UserDataIgnoreConverter());
-            assertThat(features.size(), Matchers.equalTo(0));
-        }
+            assertThat(features.get(0).getType(), Matchers.equalTo(VectorTile.Tile.GeomType.LINESTRING));
+        });
     }
 
     public void testPolygon() {
-        int z = randomIntBetween(3, 10);
-        int x = randomIntBetween(2, (1 << z) - 1);
-        int y = randomIntBetween(2, (1 << z) - 1);
-        int extent = randomIntBetween(1 << 8, 1 << 14);
-        FeatureFactory builder = new FeatureFactory(z, x, y, extent);
-        {
-            Rectangle r = GeoTileUtils.toBoundingBox(x, y, z);
-            List<VectorTile.Tile.Feature> features = builder.getFeatures(buildPolygon(r), new UserDataIgnoreConverter());
+        doTestGeometry(this::buildPolygon, features -> {
             assertThat(features.size(), Matchers.equalTo(1));
-            VectorTile.Tile.Feature feature = features.get(0);
-            assertThat(feature.getType(), Matchers.equalTo(VectorTile.Tile.GeomType.POLYGON));
-        }
-        {
-            Rectangle r = GeoTileUtils.toBoundingBox(x - 2, y, z);
-            List<VectorTile.Tile.Feature> features = builder.getFeatures(buildPolygon(r), new UserDataIgnoreConverter());
-            assertThat(features.size(), Matchers.equalTo(0));
-        }
+            assertThat(features.get(0).getType(), Matchers.equalTo(VectorTile.Tile.GeomType.POLYGON));
+        });
     }
 
     public void testMultiPolygon() {
-        int z = randomIntBetween(3, 10);
-        int x = randomIntBetween(2, (1 << z) - 1);
-        int y = randomIntBetween(2, (1 << z) - 1);
-        int extent = randomIntBetween(1 << 8, 1 << 14);
-        FeatureFactory builder = new FeatureFactory(z, x, y, extent);
-        {
-            Rectangle r = GeoTileUtils.toBoundingBox(x, y, z);
-            List<VectorTile.Tile.Feature> features = builder.getFeatures(buildMultiPolygon(r), new UserDataIgnoreConverter());
+        doTestGeometry(this::buildMultiPolygon, features -> {
             assertThat(features.size(), Matchers.equalTo(1));
-            VectorTile.Tile.Feature feature = features.get(0);
-            assertThat(feature.getType(), Matchers.equalTo(VectorTile.Tile.GeomType.POLYGON));
+            assertThat(features.get(0).getType(), Matchers.equalTo(VectorTile.Tile.GeomType.POLYGON));
+        });
+    }
+
+    public void testGeometryCollection() {
+        doTestGeometry(this::buildGeometryCollection, features -> {
+            assertThat(features.size(), Matchers.equalTo(2));
+            assertThat(features.get(0).getType(), Matchers.equalTo(VectorTile.Tile.GeomType.LINESTRING));
+            assertThat(features.get(1).getType(), Matchers.equalTo(VectorTile.Tile.GeomType.POLYGON));
+        });
+    }
+
+    private void doTestGeometry(Function<Rectangle, Geometry> provider, Consumer<List<VectorTile.Tile.Feature>> consumer) {
+        final int z = randomIntBetween(3, 10);
+        final int x = randomIntBetween(2, (1 << z) - 1);
+        final int y = randomIntBetween(2, (1 << z) - 1);
+        final int extent = randomIntBetween(1 << 8, 1 << 14);
+        final FeatureFactory builder = new FeatureFactory(z, x, y, extent);
+        {
+            final Rectangle r = GeoTileUtils.toBoundingBox(x, y, z);
+            final List<VectorTile.Tile.Feature> features = builder.getFeatures(provider.apply(r), new UserDataIgnoreConverter());
+            consumer.accept(features);
         }
         {
-            Rectangle r = GeoTileUtils.toBoundingBox(x - 2, y, z);
-            List<VectorTile.Tile.Feature> features = builder.getFeatures(buildMultiPolygon(r), new UserDataIgnoreConverter());
+            final Rectangle r = GeoTileUtils.toBoundingBox(x - 2, y, z);
+            final List<VectorTile.Tile.Feature> features = builder.getFeatures(provider.apply(r), new UserDataIgnoreConverter());
             assertThat(features.size(), Matchers.equalTo(0));
         }
     }
 
-    public void testGeometryCollection() {
-        int z = randomIntBetween(3, 10);
-        int x = randomIntBetween(2, (1 << z) - 1);
-        int y = randomIntBetween(2, (1 << z) - 1);
-        int extent = randomIntBetween(1 << 8, 1 << 14);
-        FeatureFactory builder = new FeatureFactory(z, x, y, extent);
-        {
-            Rectangle r = GeoTileUtils.toBoundingBox(x, y, z);
-            List<Geometry> geometries = List.of(buildPolygon(r), buildLine(r));
-            IllegalArgumentException ex = expectThrows(
-                IllegalArgumentException.class,
-                () -> builder.getFeatures(new GeometryCollection<>(geometries), new UserDataIgnoreConverter())
-            );
-            assertThat(ex.getMessage(), Matchers.equalTo("GeometryCollection is not supported"));
+    private Point buildPoint(Rectangle r) {
+        final double lat = randomValueOtherThanMany((l) -> r.getMinY() >= l || r.getMaxY() <= l, GeoTestUtil::nextLatitude);
+        final double lon = randomValueOtherThanMany((l) -> r.getMinX() >= l || r.getMaxX() <= l, GeoTestUtil::nextLongitude);
+        return new Point(lon, lat);
+    }
+
+    private MultiPoint buildMultiPoint(Rectangle r) {
+        final int numPoints = randomIntBetween(2, 10);
+        final List<Point> points = new ArrayList<>(numPoints);
+        for (int i = 0; i < numPoints; i++) {
+            points.add(buildPoint(r));
         }
+        return new MultiPoint(points);
     }
 
     private Line buildLine(Rectangle r) {
@@ -231,7 +132,7 @@ public class FeatureFactoryTests extends ESTestCase {
     }
 
     private Polygon buildPolygon(Rectangle r) {
-        LinearRing ring = new LinearRing(
+        final LinearRing ring = new LinearRing(
             new double[] { r.getMinX(), r.getMaxX(), r.getMaxX(), r.getMinX(), r.getMinX() },
             new double[] { r.getMinY(), r.getMinY(), r.getMaxY(), r.getMaxY(), r.getMinY() }
         );
@@ -241,4 +142,8 @@ public class FeatureFactoryTests extends ESTestCase {
     private MultiPolygon buildMultiPolygon(Rectangle r) {
         return new MultiPolygon(Collections.singletonList(buildPolygon(r)));
     }
+
+    private GeometryCollection<Geometry> buildGeometryCollection(Rectangle r) {
+        return new GeometryCollection<>(List.of(buildPolygon(r), buildLine(r)));
+    }
 }