Browse Source

GEO: Switch to using GeoTestUtil to generate random geo shapes (#44635)

Switches to more robust way of generating random test geometries by
reusing lucene's GeoTestUtil. Removes duplicate random geometry
generators by moving them to the test framework.

Closes #37278
Igor Motov 6 years ago
parent
commit
612e7e5776

+ 0 - 122
libs/geo/src/test/java/org/elasticsearch/geo/geometry/BaseGeometryTestCase.java

@@ -28,10 +28,7 @@ import org.elasticsearch.test.AbstractWireTestCase;
 
 import java.io.IOException;
 import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Function;
 
 abstract class BaseGeometryTestCase<T extends Geometry> extends AbstractWireTestCase<T> {
 
@@ -131,123 +128,4 @@ abstract class BaseGeometryTestCase<T extends Geometry> extends AbstractWireTest
         assertEquals("result", result);
     }
 
-    public static double randomLat() {
-        return randomDoubleBetween(-90, 90, true);
-    }
-
-    public static double randomLon() {
-        return randomDoubleBetween(-180, 180, true);
-    }
-
-    public static Circle randomCircle(boolean hasAlt) {
-        if (hasAlt) {
-            return new Circle(randomDoubleBetween(-90, 90, true), randomDoubleBetween(-180, 180, true), randomDouble(),
-                randomDoubleBetween(0, 100, false));
-        } else {
-            return new Circle(randomDoubleBetween(-90, 90, true), randomDoubleBetween(-180, 180, true), randomDoubleBetween(0, 100, false));
-        }
-    }
-
-    public static Line randomLine() {
-        return randomLine(randomBoolean());
-    }
-
-    public static Line randomLine(boolean hasAlts) {
-        int size = randomIntBetween(2, 10);
-        double[] lats = new double[size];
-        double[] lons = new double[size];
-        double[] alts = hasAlts ? new double[size] : null;
-        for (int i = 0; i < size; i++) {
-            lats[i] = randomLat();
-            lons[i] = randomLon();
-            if (hasAlts) {
-                alts[i] = randomDouble();
-            }
-        }
-        if (hasAlts) {
-            return new Line(lats, lons, alts);
-        }
-        return new Line(lats, lons);
-    }
-
-    public static Point randomPoint() {
-        return randomPoint(randomBoolean());
-    }
-
-    public static Point randomPoint(boolean hasAlt) {
-        if (hasAlt) {
-            return new Point(randomLat(), randomLon(), randomDouble());
-        } else {
-            return new Point(randomLat(), randomLon());
-        }
-    }
-
-    public static LinearRing randomLinearRing(boolean hasAlt) {
-        int size = randomIntBetween(3, 10);
-        double[] lats = new double[size + 1];
-        double[] lons = new double[size + 1];
-        double[] alts;
-        if (hasAlt) {
-            alts = new double[size + 1];
-        } else {
-            alts = null;
-        }
-        for (int i = 0; i < size; i++) {
-            lats[i] = randomLat();
-            lons[i] = randomLon();
-            if (hasAlt) {
-                alts[i] = randomDouble();
-            }
-        }
-        lats[size] = lats[0];
-        lons[size] = lons[0];
-        if (hasAlt) {
-            alts[size] = alts[0];
-            return new LinearRing(lats, lons, alts);
-        } else {
-            return new LinearRing(lats, lons);
-        }
-    }
-
-    public static Polygon randomPolygon(boolean hasAlt) {
-        int size = randomIntBetween(0, 10);
-        List<LinearRing> holes = new ArrayList<>();
-        for (int i = 0; i < size; i++) {
-            holes.add(randomLinearRing(hasAlt));
-        }
-        if (holes.size() > 0) {
-            return new Polygon(randomLinearRing(hasAlt), holes);
-        } else {
-            return new Polygon(randomLinearRing(hasAlt));
-        }
-    }
-
-    public static Rectangle randomRectangle() {
-        double lat1 = randomLat();
-        double lat2 = randomLat();
-        double minLon = randomLon();
-        double maxLon = randomLon();
-        return new Rectangle(Math.min(lat1, lat2), Math.max(lat1, lat2), minLon, maxLon);
-    }
-
-    public static GeometryCollection<Geometry> randomGeometryCollection(boolean hasAlt) {
-        return randomGeometryCollection(0, hasAlt);
-    }
-
-    private static GeometryCollection<Geometry> randomGeometryCollection(int level, boolean hasAlt) {
-        int size = randomIntBetween(1, 10);
-        List<Geometry> shapes = new ArrayList<>();
-        for (int i = 0; i < size; i++) {
-            @SuppressWarnings("unchecked") Function<Boolean, Geometry> geometry = randomFrom(
-                BaseGeometryTestCase::randomCircle,
-                BaseGeometryTestCase::randomLine,
-                BaseGeometryTestCase::randomPoint,
-                BaseGeometryTestCase::randomPolygon,
-                hasAlt ? BaseGeometryTestCase::randomPoint : (b) -> randomRectangle(),
-                level < 3 ? (b) -> randomGeometryCollection(level + 1, b) : BaseGeometryTestCase::randomPoint // don't build too deep
-            );
-            shapes.add(geometry.apply(hasAlt));
-        }
-        return new GeometryCollection<>(shapes);
-    }
 }

+ 2 - 1
libs/geo/src/test/java/org/elasticsearch/geo/geometry/GeometryCollectionTests.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.geo.geometry;
 
+import org.elasticsearch.geo.GeometryTestUtils;
 import org.elasticsearch.geo.utils.GeographyValidator;
 import org.elasticsearch.geo.utils.StandardValidator;
 import org.elasticsearch.geo.utils.WellKnownText;
@@ -31,7 +32,7 @@ import java.util.Collections;
 public class GeometryCollectionTests extends BaseGeometryTestCase<GeometryCollection<Geometry>> {
     @Override
     protected GeometryCollection<Geometry> createTestInstance(boolean hasAlt) {
-        return randomGeometryCollection(hasAlt);
+        return GeometryTestUtils.randomGeometryCollection(hasAlt);
     }
 
 

+ 2 - 1
libs/geo/src/test/java/org/elasticsearch/geo/geometry/LineTests.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.geo.geometry;
 
+import org.elasticsearch.geo.GeometryTestUtils;
 import org.elasticsearch.geo.utils.GeographyValidator;
 import org.elasticsearch.geo.utils.GeometryValidator;
 import org.elasticsearch.geo.utils.StandardValidator;
@@ -30,7 +31,7 @@ import java.text.ParseException;
 public class LineTests extends BaseGeometryTestCase<Line> {
     @Override
     protected Line createTestInstance(boolean hasAlt) {
-        return randomLine(hasAlt);
+        return GeometryTestUtils.randomLine(hasAlt);
     }
 
     public void testBasicSerialization() throws IOException, ParseException {

+ 2 - 1
libs/geo/src/test/java/org/elasticsearch/geo/geometry/MultiLineTests.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.geo.geometry;
 
+import org.elasticsearch.geo.GeometryTestUtils;
 import org.elasticsearch.geo.utils.GeographyValidator;
 import org.elasticsearch.geo.utils.StandardValidator;
 import org.elasticsearch.geo.utils.WellKnownText;
@@ -36,7 +37,7 @@ public class MultiLineTests extends BaseGeometryTestCase<MultiLine> {
         int size = randomIntBetween(1, 10);
         List<Line> arr = new ArrayList<Line>();
         for (int i = 0; i < size; i++) {
-            arr.add(randomLine(hasAlt));
+            arr.add(GeometryTestUtils.randomLine(hasAlt));
         }
         return new MultiLine(arr);
     }

+ 2 - 1
libs/geo/src/test/java/org/elasticsearch/geo/geometry/MultiPointTests.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.geo.geometry;
 
+import org.elasticsearch.geo.GeometryTestUtils;
 import org.elasticsearch.geo.utils.GeographyValidator;
 import org.elasticsearch.geo.utils.StandardValidator;
 import org.elasticsearch.geo.utils.WellKnownText;
@@ -37,7 +38,7 @@ public class MultiPointTests extends BaseGeometryTestCase<MultiPoint> {
         int size = randomIntBetween(1, 10);
         List<Point> arr = new ArrayList<>();
         for (int i = 0; i < size; i++) {
-            arr.add(randomPoint(hasAlt));
+            arr.add(GeometryTestUtils.randomPoint(hasAlt));
         }
         return new MultiPoint(arr);
     }

+ 2 - 1
libs/geo/src/test/java/org/elasticsearch/geo/geometry/MultiPolygonTests.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.geo.geometry;
 
+import org.elasticsearch.geo.GeometryTestUtils;
 import org.elasticsearch.geo.utils.GeographyValidator;
 import org.elasticsearch.geo.utils.StandardValidator;
 import org.elasticsearch.geo.utils.WellKnownText;
@@ -36,7 +37,7 @@ public class MultiPolygonTests extends BaseGeometryTestCase<MultiPolygon> {
         int size = randomIntBetween(1, 10);
         List<Polygon> arr = new ArrayList<>();
         for (int i = 0; i < size; i++) {
-            arr.add(randomPolygon(hasAlt));
+            arr.add(GeometryTestUtils.randomPolygon(hasAlt));
         }
         return new MultiPolygon(arr);
     }

+ 2 - 1
libs/geo/src/test/java/org/elasticsearch/geo/geometry/PointTests.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.geo.geometry;
 
+import org.elasticsearch.geo.GeometryTestUtils;
 import org.elasticsearch.geo.utils.GeographyValidator;
 import org.elasticsearch.geo.utils.GeometryValidator;
 import org.elasticsearch.geo.utils.StandardValidator;
@@ -30,7 +31,7 @@ import java.text.ParseException;
 public class PointTests extends BaseGeometryTestCase<Point> {
     @Override
     protected Point createTestInstance(boolean hasAlt) {
-        return randomPoint(hasAlt);
+        return GeometryTestUtils.randomPoint(hasAlt);
     }
 
     public void testBasicSerialization() throws IOException, ParseException {

+ 2 - 1
libs/geo/src/test/java/org/elasticsearch/geo/geometry/PolygonTests.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.geo.geometry;
 
+import org.elasticsearch.geo.GeometryTestUtils;
 import org.elasticsearch.geo.utils.GeographyValidator;
 import org.elasticsearch.geo.utils.StandardValidator;
 import org.elasticsearch.geo.utils.WellKnownText;
@@ -30,7 +31,7 @@ import java.util.Collections;
 public class PolygonTests extends BaseGeometryTestCase<Polygon> {
     @Override
     protected Polygon createTestInstance(boolean hasAlt) {
-        return randomPolygon(hasAlt);
+        return GeometryTestUtils.randomPolygon(hasAlt);
     }
 
     public void testBasicSerialization() throws IOException, ParseException {

+ 2 - 1
libs/geo/src/test/java/org/elasticsearch/geo/geometry/RectangleTests.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.geo.geometry;
 
+import org.elasticsearch.geo.GeometryTestUtils;
 import org.elasticsearch.geo.utils.GeographyValidator;
 import org.elasticsearch.geo.utils.GeometryValidator;
 import org.elasticsearch.geo.utils.StandardValidator;
@@ -31,7 +32,7 @@ public class RectangleTests extends BaseGeometryTestCase<Rectangle> {
     @Override
     protected Rectangle createTestInstance(boolean hasAlt) {
         assumeFalse("3rd dimension is not supported yet", hasAlt);
-        return randomRectangle();
+        return GeometryTestUtils.randomRectangle();
     }
 
     public void testBasicSerialization() throws IOException, ParseException {

+ 11 - 153
server/src/test/java/org/elasticsearch/common/geo/GeoJsonSerializationTests.java

@@ -23,28 +23,25 @@ import org.elasticsearch.common.xcontent.ToXContent;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
-import org.elasticsearch.geo.geometry.Circle;
+import org.elasticsearch.geo.GeometryTestUtils;
 import org.elasticsearch.geo.geometry.Geometry;
-import org.elasticsearch.geo.geometry.GeometryCollection;
-import org.elasticsearch.geo.geometry.Line;
-import org.elasticsearch.geo.geometry.LinearRing;
-import org.elasticsearch.geo.geometry.MultiLine;
-import org.elasticsearch.geo.geometry.MultiPoint;
-import org.elasticsearch.geo.geometry.MultiPolygon;
-import org.elasticsearch.geo.geometry.Point;
-import org.elasticsearch.geo.geometry.Polygon;
-import org.elasticsearch.geo.geometry.Rectangle;
 import org.elasticsearch.geo.utils.GeographyValidator;
 import org.elasticsearch.test.AbstractXContentTestCase;
 import org.elasticsearch.test.ESTestCase;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Objects;
-import java.util.function.Function;
 import java.util.function.Supplier;
 
+import static org.elasticsearch.geo.GeometryTestUtils.randomCircle;
+import static org.elasticsearch.geo.GeometryTestUtils.randomGeometryCollection;
+import static org.elasticsearch.geo.GeometryTestUtils.randomLine;
+import static org.elasticsearch.geo.GeometryTestUtils.randomMultiLine;
+import static org.elasticsearch.geo.GeometryTestUtils.randomMultiPoint;
+import static org.elasticsearch.geo.GeometryTestUtils.randomMultiPolygon;
+import static org.elasticsearch.geo.GeometryTestUtils.randomPoint;
+import static org.elasticsearch.geo.GeometryTestUtils.randomPolygon;
+
 public class GeoJsonSerializationTests extends ESTestCase {
 
     private static class GeometryWrapper implements ToXContentObject {
@@ -119,7 +116,7 @@ public class GeoJsonSerializationTests extends ESTestCase {
     }
 
     public void testEnvelope() throws IOException {
-        xContentTest(GeoJsonSerializationTests::randomRectangle);
+        xContentTest(GeometryTestUtils::randomRectangle);
     }
 
     public void testGeometryCollection() throws IOException {
@@ -129,143 +126,4 @@ public class GeoJsonSerializationTests extends ESTestCase {
     public void testCircle() throws IOException {
         xContentTest(() -> randomCircle(randomBoolean()));
     }
-
-    public static double randomLat() {
-        return randomDoubleBetween(-90, 90, true);
-    }
-
-    public static double randomLon() {
-        return randomDoubleBetween(-180, 180, true);
-    }
-
-    public static Circle randomCircle(boolean hasAlt) {
-        if (hasAlt) {
-            return new Circle(randomDoubleBetween(-90, 90, true), randomDoubleBetween(-180, 180, true), randomDouble(),
-                randomDoubleBetween(0, 100, false));
-        } else {
-            return new Circle(randomDoubleBetween(-90, 90, true), randomDoubleBetween(-180, 180, true), randomDoubleBetween(0, 100, false));
-        }
-    }
-
-    public static Line randomLine(boolean hasAlts) {
-        int size = randomIntBetween(2, 10);
-        double[] lats = new double[size];
-        double[] lons = new double[size];
-        double[] alts = hasAlts ? new double[size] : null;
-        for (int i = 0; i < size; i++) {
-            lats[i] = randomLat();
-            lons[i] = randomLon();
-            if (hasAlts) {
-                alts[i] = randomDouble();
-            }
-        }
-        if (hasAlts) {
-            return new Line(lats, lons, alts);
-        }
-        return new Line(lats, lons);
-    }
-
-    public static Point randomPoint(boolean hasAlt) {
-        if (hasAlt) {
-            return new Point(randomLat(), randomLon(), randomDouble());
-        } else {
-            return new Point(randomLat(), randomLon());
-        }
-    }
-
-    public static MultiPoint randomMultiPoint(boolean hasAlt) {
-        int size = randomIntBetween(3, 10);
-        List<Point> points = new ArrayList<>();
-        for (int i = 0; i < size; i++) {
-            points.add(randomPoint(hasAlt));
-        }
-        return new MultiPoint(points);
-    }
-
-    public static MultiLine randomMultiLine(boolean hasAlt) {
-        int size = randomIntBetween(3, 10);
-        List<Line> lines = new ArrayList<>();
-        for (int i = 0; i < size; i++) {
-            lines.add(randomLine(hasAlt));
-        }
-        return new MultiLine(lines);
-    }
-
-    public static MultiPolygon randomMultiPolygon(boolean hasAlt) {
-        int size = randomIntBetween(3, 10);
-        List<Polygon> polygons = new ArrayList<>();
-        for (int i = 0; i < size; i++) {
-            polygons.add(randomPolygon(hasAlt));
-        }
-        return new MultiPolygon(polygons);
-    }
-
-    public static LinearRing randomLinearRing(boolean hasAlt) {
-        int size = randomIntBetween(3, 10);
-        double[] lats = new double[size + 1];
-        double[] lons = new double[size + 1];
-        double[] alts;
-        if (hasAlt) {
-            alts = new double[size + 1];
-        } else {
-            alts = null;
-        }
-        for (int i = 0; i < size; i++) {
-            lats[i] = randomLat();
-            lons[i] = randomLon();
-            if (hasAlt) {
-                alts[i] = randomDouble();
-            }
-        }
-        lats[size] = lats[0];
-        lons[size] = lons[0];
-        if (hasAlt) {
-            alts[size] = alts[0];
-            return new LinearRing(lats, lons, alts);
-        } else {
-            return new LinearRing(lats, lons);
-        }
-    }
-
-    public static Polygon randomPolygon(boolean hasAlt) {
-        int size = randomIntBetween(0, 10);
-        List<LinearRing> holes = new ArrayList<>();
-        for (int i = 0; i < size; i++) {
-            holes.add(randomLinearRing(hasAlt));
-        }
-        if (holes.size() > 0) {
-            return new Polygon(randomLinearRing(hasAlt), holes);
-        } else {
-            return new Polygon(randomLinearRing(hasAlt));
-        }
-    }
-
-    public static Rectangle randomRectangle() {
-        double lat1 = randomLat();
-        double lat2 = randomLat();
-        double minLon = randomLon();
-        double maxLon = randomLon();
-        return new Rectangle(Math.min(lat1, lat2), Math.max(lat1, lat2), minLon, maxLon);
-    }
-
-    public static GeometryCollection<Geometry> randomGeometryCollection(boolean hasAlt) {
-        return randomGeometryCollection(0, hasAlt);
-    }
-
-    private static GeometryCollection<Geometry> randomGeometryCollection(int level, boolean hasAlt) {
-        int size = randomIntBetween(1, 10);
-        List<Geometry> shapes = new ArrayList<>();
-        for (int i = 0; i < size; i++) {
-            @SuppressWarnings("unchecked") Function<Boolean, Geometry> geometry = randomFrom(
-                GeoJsonSerializationTests::randomCircle,
-                GeoJsonSerializationTests::randomLine,
-                GeoJsonSerializationTests::randomPoint,
-                GeoJsonSerializationTests::randomPolygon,
-                hasAlt ? GeoJsonSerializationTests::randomPoint : (b) -> randomRectangle(),
-                level < 3 ? (b) -> randomGeometryCollection(level + 1, b) : GeoJsonSerializationTests::randomPoint // don't build too deep
-            );
-            shapes.add(geometry.apply(hasAlt));
-        }
-        return new GeometryCollection<>(shapes);
-    }
 }

+ 179 - 0
test/framework/src/main/java/org/elasticsearch/geo/GeometryTestUtils.java

@@ -0,0 +1,179 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.geo;
+
+import org.apache.lucene.geo.GeoTestUtil;
+import org.elasticsearch.geo.geometry.Circle;
+import org.elasticsearch.geo.geometry.Geometry;
+import org.elasticsearch.geo.geometry.GeometryCollection;
+import org.elasticsearch.geo.geometry.Line;
+import org.elasticsearch.geo.geometry.LinearRing;
+import org.elasticsearch.geo.geometry.MultiLine;
+import org.elasticsearch.geo.geometry.MultiPoint;
+import org.elasticsearch.geo.geometry.MultiPolygon;
+import org.elasticsearch.geo.geometry.Point;
+import org.elasticsearch.geo.geometry.Polygon;
+import org.elasticsearch.geo.geometry.Rectangle;
+import org.elasticsearch.test.ESTestCase;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+public class GeometryTestUtils {
+
+    public static double randomLat() {
+        return GeoTestUtil.nextLatitude();
+    }
+
+    public static double randomLon() {
+        return GeoTestUtil.nextLongitude();
+    }
+
+    public static double randomAlt() {
+        return ESTestCase.randomDouble();
+    }
+
+    public static Circle randomCircle(boolean hasAlt) {
+        if (hasAlt) {
+            return new Circle(randomLat(), randomLon(), ESTestCase.randomDouble(),
+                ESTestCase.randomDoubleBetween(0, 100, false));
+        } else {
+            return new Circle(randomLat(), randomLon(), ESTestCase.randomDoubleBetween(0, 100, false));
+        }
+    }
+
+    public static Line randomLine(boolean hasAlts) {
+        int size = ESTestCase.randomIntBetween(2, 10);
+        double[] lats = new double[size];
+        double[] lons = new double[size];
+        double[] alts = hasAlts ? new double[size] : null;
+        for (int i = 0; i < size; i++) {
+            lats[i] = randomLat();
+            lons[i] = randomLon();
+            if (hasAlts) {
+                alts[i] = randomAlt();
+            }
+        }
+        if (hasAlts) {
+            return new Line(lats, lons, alts);
+        }
+        return new Line(lats, lons);
+    }
+
+    public static Point randomPoint() {
+        return randomPoint(ESTestCase.randomBoolean());
+    }
+
+    public static Point randomPoint(boolean hasAlt) {
+        if (hasAlt) {
+            return new Point(randomLat(), randomLon(), randomAlt());
+        } else {
+            return new Point(randomLat(), randomLon());
+        }
+    }
+
+    public static Polygon randomPolygon(boolean hasAlt) {
+        org.apache.lucene.geo.Polygon lucenePolygon = GeoTestUtil.nextPolygon();
+        if (lucenePolygon.numHoles() > 0) {
+            org.apache.lucene.geo.Polygon[] luceneHoles = lucenePolygon.getHoles();
+            List<LinearRing> holes = new ArrayList<>();
+            for (int i = 0; i < lucenePolygon.numHoles(); i++) {
+                holes.add(linearRing(luceneHoles[i], hasAlt));
+            }
+            return new Polygon(linearRing(lucenePolygon, hasAlt), holes);
+        }
+        return new Polygon(linearRing(lucenePolygon, hasAlt));
+    }
+
+
+    private static double[] randomAltRing(int size) {
+        double[] alts = new double[size];
+        for (int i = 0; i < size - 1; i++) {
+            alts[i] = randomAlt();
+        }
+        alts[size - 1] = alts[0];
+        return alts;
+    }
+
+    private static LinearRing linearRing(org.apache.lucene.geo.Polygon polygon, boolean generateAlts) {
+        if (generateAlts) {
+            return new LinearRing(polygon.getPolyLats(), polygon.getPolyLons(), randomAltRing(polygon.numPoints()));
+        } else {
+            return new LinearRing(polygon.getPolyLats(), polygon.getPolyLons());
+        }
+    }
+
+    public static Rectangle randomRectangle() {
+        org.apache.lucene.geo.Rectangle rectangle = GeoTestUtil.nextBox();
+        return new Rectangle(rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon);
+    }
+
+    public static MultiPoint randomMultiPoint(boolean hasAlt) {
+        int size = ESTestCase.randomIntBetween(3, 10);
+        List<Point> points = new ArrayList<>();
+        for (int i = 0; i < size; i++) {
+            points.add(randomPoint(hasAlt));
+        }
+        return new MultiPoint(points);
+    }
+
+    public static MultiLine randomMultiLine(boolean hasAlt) {
+        int size = ESTestCase.randomIntBetween(3, 10);
+        List<Line> lines = new ArrayList<>();
+        for (int i = 0; i < size; i++) {
+            lines.add(randomLine(hasAlt));
+        }
+        return new MultiLine(lines);
+    }
+
+    public static MultiPolygon randomMultiPolygon(boolean hasAlt) {
+        int size = ESTestCase.randomIntBetween(3, 10);
+        List<Polygon> polygons = new ArrayList<>();
+        for (int i = 0; i < size; i++) {
+            polygons.add(randomPolygon(hasAlt));
+        }
+        return new MultiPolygon(polygons);
+    }
+
+    public static GeometryCollection<Geometry> randomGeometryCollection(boolean hasAlt) {
+        return randomGeometryCollection(0, hasAlt);
+    }
+
+    private static GeometryCollection<Geometry> randomGeometryCollection(int level, boolean hasAlt) {
+        int size = ESTestCase.randomIntBetween(1, 10);
+        List<Geometry> shapes = new ArrayList<>();
+        for (int i = 0; i < size; i++) {
+            @SuppressWarnings("unchecked") Function<Boolean, Geometry> geometry = ESTestCase.randomFrom(
+                GeometryTestUtils::randomCircle,
+                GeometryTestUtils::randomLine,
+                GeometryTestUtils::randomPoint,
+                GeometryTestUtils::randomPolygon,
+                GeometryTestUtils::randomMultiLine,
+                GeometryTestUtils::randomMultiPoint,
+                GeometryTestUtils::randomMultiPolygon,
+                hasAlt ? GeometryTestUtils::randomPoint : (b) -> randomRectangle(),
+                level < 3 ? (b) -> randomGeometryCollection(level + 1, b) : GeometryTestUtils::randomPoint // don't build too deep
+            );
+            shapes.add(geometry.apply(hasAlt));
+        }
+        return new GeometryCollection<>(shapes);
+    }
+}