This is an automated email from the ASF dual-hosted git repository. amanin pushed a commit to branch refactor/sql-store in repository https://gitbox.apache.org/repos/asf/sis.git
commit 0d34ed83aee1f8eadf3c40ac305277ccf149855f Author: Alexis Manin <[email protected]> AuthorDate: Mon Oct 7 18:16:03 2019 +0200 feat(Feature): Add a tool to convert envelopes to geometries --- .../java/org/apache/sis/internal/feature/ESRI.java | 52 ++++--- .../apache/sis/internal/feature/Geometries.java | 170 +++++++++++++++------ .../java/org/apache/sis/internal/feature/JTS.java | 44 +++++- .../org/apache/sis/internal/feature/Java2D.java | 111 +++++++++++++- .../sis/internal/feature/GeometriesTestCase.java | 94 +++++++++++- 5 files changed, 398 insertions(+), 73 deletions(-) diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/ESRI.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/ESRI.java index 601b6b7..ce05495 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/ESRI.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/ESRI.java @@ -18,25 +18,14 @@ package org.apache.sis.internal.feature; import java.util.Iterator; -import org.opengis.geometry.Envelope; - import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.math.Vector; import org.apache.sis.setup.GeometryLibrary; import org.apache.sis.util.Classes; -import com.esri.core.geometry.Envelope2D; -import com.esri.core.geometry.Geometry; -import com.esri.core.geometry.MultiPath; -import com.esri.core.geometry.OperatorExportToWkt; -import com.esri.core.geometry.OperatorImportFromWkt; -import com.esri.core.geometry.Point; -import com.esri.core.geometry.Point2D; -import com.esri.core.geometry.Point3D; -import com.esri.core.geometry.Polygon; -import com.esri.core.geometry.Polyline; -import com.esri.core.geometry.WktExportFlags; -import com.esri.core.geometry.WktImportFlags; +import com.esri.core.geometry.*; + +import static org.apache.sis.util.ArgumentChecks.ensureNonNull; /** @@ -88,11 +77,6 @@ final class ESRI extends Geometries<Geometry> { return null; } - @Override - Object tryConvertToGeometry(Envelope env) { - throw new UnsupportedOperationException("Not supported yet"); // "Alexis Manin (Geomatys)" on 03/10/2019 - } - /** * If the given point is an implementation of this library, returns its coordinate. * Otherwise returns {@code null}. If non-null, the returned array may have a length of 2 or 3. @@ -203,6 +187,36 @@ add: for (;;) { return path; } + @Override + double[] getPoints(Object geometry) { + if (geometry instanceof GeometryWrapper) geometry = ((GeometryWrapper) geometry).geometry; + ensureNonNull("Geometry", geometry); + if (geometry instanceof MultiVertexGeometry) { + MultiVertexGeometry vertices = (MultiVertexGeometry) geometry; + final Point2D[] coords = vertices.getCoordinates2D(); + final double[] ordinates = new double[coords.length*2]; + int idx = 0; + for (Point2D coord : coords) { + ordinates[idx++] = coord.x; + ordinates[idx++] = coord.y; + } + return ordinates; + } + throw new UnsupportedOperationException("Unsupported geometry type: "+geometry.getClass().getCanonicalName()); + } + + @Override + Object createMultiPolygonImpl(Object... polygonsOrLinearRings) { + final Polygon poly = new Polygon(); + for (final Object polr : polygonsOrLinearRings) { + if (polr instanceof MultiPath) { + poly.add((MultiPath) polr, false); + } else throw new UnsupportedOperationException("Unsupported geometry type: "+polr == null? "null" : polr.getClass().getCanonicalName()); + } + + return poly; + } + /** * Parses the given WKT. */ diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java index 8f4fcfb..31094c9 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java @@ -22,12 +22,18 @@ import java.util.function.Function; import java.util.logging.Level; import java.util.logging.LogRecord; +import org.opengis.geometry.DirectPosition; import org.opengis.geometry.Envelope; import org.opengis.geometry.Geometry; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.crs.SingleCRS; +import org.opengis.referencing.cs.CoordinateSystemAxis; import org.apache.sis.geometry.GeneralEnvelope; +import org.apache.sis.internal.referencing.AxisDirections; import org.apache.sis.internal.system.Loggers; import org.apache.sis.math.Vector; +import org.apache.sis.referencing.CRS; import org.apache.sis.setup.GeometryLibrary; import org.apache.sis.util.collection.BackingStoreException; import org.apache.sis.util.logging.Logging; @@ -151,13 +157,19 @@ public abstract class Geometries<G> { return false; } + /** + * Transforms an envelope to a polygon whose start point is lower corner, and points composing result are the + * envelope corners in clockwise order. + * @param env The envelope to convert. + * @param wraparound How to resolve wrap-around ambiguities on the envelope. + * @return If any geometric implementation is installed, return a polygon (or two polygons in case of + * {@link WrapResolution#SPLIT split handling of wrap-around}. + */ public static Optional<Geometry> toGeometry(final Envelope env, WrapResolution wraparound) { return findStrategy(g -> g.tryConvertToGeometry(env, wraparound)) .map(result -> new GeometryWrapper(result, env)); } - abstract Object tryConvertToGeometry(final Envelope env, WrapResolution wraparound); - /** * If the given point is an implementation of this library, returns its coordinate. * Otherwise returns {@code null}. @@ -343,60 +355,132 @@ public abstract class Geometries<G> { return Optional.empty(); } - private Object envelope2Polygon(final Envelope env, WrapResolution resolution) { - double[] ordinates; - double[] secondEnvelopeIfSplit = null; - if (WrapResolution.NONE.equals(resolution)) { - ordinates = new double[] { - env.getMinimum(0), - env.getMinimum(1), - env.getMaximum(0), - env.getMaximum(1) - }; + /** + * See {@link Geometries#toGeometry(Envelope, WrapResolution)}. + */ + Object tryConvertToGeometry(final Envelope env, WrapResolution resolution) { + // Ensure that we can isolate an horizontal part in the given envelope. + final int x; + if (env.getDimension() == 2) { + x = 0; } else { - final boolean xWrap = env.getMinimum(0) > env.getMaximum(0); - final boolean yWrap = env.getMinimum(1) > env.getMaximum(1); - - //TODO - switch (resolution) { - case EXPAND: - case SPLIT: - case CONTIGUOUS: - default: throw new IllegalArgumentException("Unknown or unset wrap resolution: "+resolution); + final CoordinateReferenceSystem crs = env.getCoordinateReferenceSystem(); + if (crs == null) throw new IllegalArgumentException("Envelope with more than 2 dimensions, but without CRS: cannot isolate horizontal part."); + final SingleCRS hCrs = CRS.getHorizontalComponent(crs); + if (hCrs == null) throw new IllegalArgumentException("Cannot find an horizontal part in given CRS"); + x = AxisDirections.indexOfColinear(crs.getCoordinateSystem(), hCrs.getCoordinateSystem()); + } + + final int y = x+1; + + final DirectPosition lc = env.getLowerCorner(); + final DirectPosition uc = env.getUpperCorner(); + double minX = lc.getOrdinate(x); + double minY = lc.getOrdinate(y); + double maxX = uc.getOrdinate(x); + double maxY = uc.getOrdinate(y); + double[] splittedLeft = null; + // We start by short-circuiting simplest case for minor simplicity/performance reason. + if (!WrapResolution.NONE.equals(resolution)) { + // ensure the envelope is correctly defined, by forcing non-authorized wrapped axes to take entire crs span. + final GeneralEnvelope fixedEnv = new GeneralEnvelope(env); + fixedEnv.normalize(); + int wrapAxis = -1; + for (int i = x ; i <= y && wrapAxis < x ; i++) { + if (fixedEnv.getLower(i) > fixedEnv.getUpper(i)) wrapAxis = i; + } + if (wrapAxis >= x) { + final CoordinateReferenceSystem crs = env.getCoordinateReferenceSystem(); + if (crs == null) throw new IllegalArgumentException("Cannot resolve wrap-around for an envelope without any system defined"); + final CoordinateSystemAxis axis = crs.getCoordinateSystem().getAxis(wrapAxis); + final double wrapRange = axis.getMaximumValue() - axis.getMinimumValue(); + switch (resolution) { + case EXPAND: + // simpler and more performant than a call to GeneralEnvelope.simplify() + if (wrapAxis == x) { + minX = axis.getMinimumValue(); + maxX = axis.getMaximumValue(); + } else { + minY = axis.getMinimumValue(); + maxY = axis.getMaximumValue(); + } + break; + case SPLIT: + if (wrapAxis == x) { + splittedLeft = new double[]{axis.getMinimumValue(), minY, maxX, maxY}; + maxX = axis.getMaximumValue(); + } + else { + splittedLeft = new double[] {minX, axis.getMinimumValue(), maxX, maxY}; + maxY = axis.getMaximumValue(); + } + break; + case CONTIGUOUS: + if (wrapAxis == x) maxX += wrapRange; + else maxY += wrapRange; + break; + default: + throw new IllegalArgumentException("Unknown or unset wrap resolution: " + resolution); + } } + } + + Vector[] points = clockwiseRing(minX, minY, maxX, maxY); + final G mainRect = createPolyline(2, points); + if (splittedLeft != null) { + minX = splittedLeft[0]; + minY = splittedLeft[1]; + maxX = splittedLeft[2]; + maxY = splittedLeft[3]; + Vector[] points2 = clockwiseRing(minX, minY, maxX, maxY); + final G secondRect = createPolyline(2, points2); + return createMultiPolygonImpl(mainRect, secondRect); } + /* Geotk original method had an option to insert a median point on wrappped around axis, but we have not ported + * it, because in an orthonormal space, I don't see any case where it could be useful. However, in case it + * have to be added, we can do it here by amending created ring(s). + */ + return mainRect; + } - double minX = ordinates[0]; - double minY = ordinates[1]; - double maxX = ordinates[2]; - double maxY = ordinates[3]; - Vector[] points = { + /** + * Create a sequence of points describing a rectangle whose start point is the lower left one. The sequence of + * points describe each corner, going in clockwise order and repeating starting point to properly close the ring. + * + * @param minX Lower coordinate of first axis. + * @param minY Lower coordinate of second axis. + * @param maxX Upper coordinate of first axis. + * @param maxY Upper coordinate of second axis. + * + * @return A set of 5 points describing given rectangle. + */ + private static Vector[] clockwiseRing(final double minX, final double minY, final double maxX, final double maxY) { + return new Vector[]{ Vector.create(new double[]{minX, minY}), Vector.create(new double[]{minX, maxY}), Vector.create(new double[]{maxX, maxY}), Vector.create(new double[]{maxX, minY}), Vector.create(new double[]{minX, minY}) }; + } - final G mainRect = createPolyline(2, points); - if (secondEnvelopeIfSplit != null) { - minX = secondEnvelopeIfSplit[0]; - minY = secondEnvelopeIfSplit[1]; - maxX = secondEnvelopeIfSplit[2]; - maxY = secondEnvelopeIfSplit[3]; - Vector[] points2 = { - Vector.create(new double[]{minX, minY}), - Vector.create(new double[]{minX, maxY}), - Vector.create(new double[]{maxX, maxY}), - Vector.create(new double[]{maxX, minY}), - Vector.create(new double[]{minX, minY}) - }; - final G secondRect = createPolyline(2, points2); - // TODO: merge then send back - } + /** + * Extract all points from input geometry, and return them as a contiguous set of ordinates. + * For rings, point order (clockwise/counter-clockwise) is implementation dependant. + * + * @param geometry The geometry to extract point from. + */ + public static Optional<double[]> getOrdinates(Geometry geometry) { + return findStrategy(g -> g.getPoints(geometry)); + } - return mainRect; + abstract double[] getPoints(Object geometry); + + abstract Object createMultiPolygonImpl(final Object... polygonsOrLinearRings); + + public static Object createMultiPolygon(final Object... polygonsOrLinearRings) { + return findStrategy(g -> g.createMultiPolygonImpl(polygonsOrLinearRings)); } } diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java index 034bd65..90fa183 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java @@ -31,6 +31,7 @@ import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.LinearRing; import org.locationtech.jts.geom.MultiLineString; import org.locationtech.jts.geom.Point; import org.locationtech.jts.geom.Polygon; @@ -113,13 +114,6 @@ final class JTS extends Geometries<Geometry> { return null; } - @Override - Geometry tryConvertToGeometry(org.opengis.geometry.Envelope env, final WrapResolution resolution) { - final int dim = env.getDimension(); - if (dim > 2) throw new UnsupportedOperationException("Cannot manage more than 2 dimensions, but input envelope has "+dim); - throw new UnsupportedOperationException("Not yet"); - } - /** * If the given point is an implementation of this library, returns its coordinate. * Otherwise returns {@code null}. If non-null, the returned array may have a length of 2 or 3. @@ -273,4 +267,40 @@ add: for (;;) { toLineString(coordinates, lines); return toGeometry(lines); } + + @Override + double[] getPoints(Object geometry) { + if (geometry instanceof GeometryWrapper) { + geometry = ((GeometryWrapper) geometry).geometry; + } + + if (geometry instanceof Geometry) { + Geometry geom = (Geometry) geometry; + final int nbPts = geom.getNumPoints(); + final Coordinate[] coords = geom.getCoordinates(); + double[] ordinates = new double[nbPts * 2]; + for (int i = 0, j=0; i < nbPts ; i++) { + final Coordinate coord = coords[i]; + ordinates[j++] = coord.x; + ordinates[j++] = coord.y; + } + return ordinates; + } + + return null; + } + + @Override + Object createMultiPolygonImpl(Object... polygonsOrLinearRings) { + final Polygon[] polys = new Polygon[polygonsOrLinearRings.length]; + for (int i = 0 ; i < polys.length ; i++) { + Object o = polygonsOrLinearRings[i]; + if (o instanceof GeometryWrapper) o = ((GeometryWrapper) o).geometry; + + if (o instanceof Polygon) polys[i] = (Polygon) o; + else if (o instanceof LinearRing) polys[i] = factory.createPolygon((LinearRing) o); + } + + return factory.createMultiPolygon(polys); + } } diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Java2D.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Java2D.java index f0a70f3..38f78d8 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Java2D.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Java2D.java @@ -16,20 +16,30 @@ */ package org.apache.sis.internal.feature; -import java.util.Iterator; import java.awt.Shape; import java.awt.geom.Line2D; import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.stream.DoubleStream; +import java.util.stream.StreamSupport; + import org.apache.sis.geometry.GeneralEnvelope; -import org.apache.sis.setup.GeometryLibrary; import org.apache.sis.internal.feature.j2d.ShapeProperties; import org.apache.sis.internal.referencing.j2d.ShapeUtilities; import org.apache.sis.math.Vector; +import org.apache.sis.setup.GeometryLibrary; import org.apache.sis.util.Classes; import org.apache.sis.util.Numbers; +import static org.apache.sis.util.ArgumentChecks.ensureNonEmpty; +import static org.apache.sis.util.ArgumentChecks.ensureNonNull; + /** * Centralizes usages of some (not all) Java2D geometry API by Apache SIS. @@ -219,6 +229,29 @@ add: for (;;) { return ShapeUtilities.toPrimitive(path); } + @Override + double[] getPoints(Object geometry) { + if (geometry instanceof GeometryWrapper) geometry = ((GeometryWrapper) geometry).geometry; + ensureNonNull("Geometry", geometry); + if (geometry instanceof Point2D) return getCoordinate(geometry); + if (geometry instanceof Shape) { + final PathIterator it = ((Shape) geometry).getPathIterator(null); + return StreamSupport.stream(new PathSpliterator(it), false) + .flatMapToDouble(Segment::ordinates) + .toArray(); + } + + throw new UnsupportedOperationException("Unsupported geometry type: "+geometry.getClass().getCanonicalName()); + } + + @Override + Object createMultiPolygonImpl(Object... polygonsOrLinearRings) { + ensureNonEmpty("Polygons or linear rings to merge", polygonsOrLinearRings); + if (polygonsOrLinearRings.length == 1) return polygonsOrLinearRings[0]; + final Iterator<Object> it = Arrays.asList(polygonsOrLinearRings).iterator(); + return tryMergePolylines(it.next(), it); + } + /** * If the given object is a Java2D shape, builds its WKT representation. * Current implementation assumes that all closed shapes are polygons and that polygons have no hole @@ -238,4 +271,78 @@ add: for (;;) { public Object parseWKT(final String wkt) { throw unsupported(2); } + + /** + * An abstraction over {@link PathIterator} to use it in a streaming context. + */ + private static class PathSpliterator implements Spliterator<Segment> { + + private final PathIterator source; + + private PathSpliterator(PathIterator source) { + this.source = source; + } + + @Override + public boolean tryAdvance(Consumer<? super Segment> action) { + if (source.isDone()) return false; + final double[] coords = new double[6]; + final int segmentType = source.currentSegment(coords); + action.accept(new Segment(segmentType, coords)); + source.next(); + return true; + } + + @Override + public Spliterator<Segment> trySplit() { + return null; + } + + @Override + public long estimateSize() { + return Long.MAX_VALUE; + } + + @Override + public int characteristics() { + return Spliterator.NONNULL | Spliterator.ORDERED | Spliterator.IMMUTABLE; + } + } + + /** + * Describe a path segment as described by {@link PathIterator#currentSegment(double[]) AWT path iteration API}. + * Basically, the awt abstraction is really poor, so we made a wrapper around it to describe segments. + */ + private static class Segment { + /** + * This segment type ({@link PathIterator#SEG_CLOSE}, etc.). + */ + final int type; + /** + * Brut points composing the segment, as returned by {@link PathIterator#currentSegment(double[])}. + */ + private final double[] points; + + Segment(int type, double[] points) { + this.type = type; + this.points = points; + } + + /** + * + * @return points composing this segment, as a contiguous set of ordinates. Points are all 2D, so every two + * elements in backed stream describe a point. Can be empty in case of {@link PathIterator#SEG_CLOSE closing segment}. + */ + DoubleStream ordinates() { + switch (type) { + case PathIterator.SEG_CLOSE: return DoubleStream.empty(); + case PathIterator.SEG_QUADTO: return Arrays.stream(points, 0, 4); + case PathIterator.SEG_CUBICTO: return Arrays.stream(points); + case PathIterator.SEG_LINETO: + case PathIterator.SEG_MOVETO: + return Arrays.stream(points, 0, 2); + default: throw new IllegalStateException("Unknown segment type: "+type); + } + } + } } diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/GeometriesTestCase.java b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/GeometriesTestCase.java index cbef788..7aa7a44 100644 --- a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/GeometriesTestCase.java +++ b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/GeometriesTestCase.java @@ -17,15 +17,32 @@ package org.apache.sis.internal.feature; import java.util.Arrays; +import java.util.EnumMap; import java.util.Iterator; -import org.apache.sis.math.Vector; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.stream.Stream; + +import org.opengis.geometry.Envelope; + import org.apache.sis.geometry.GeneralEnvelope; +import org.apache.sis.math.Vector; +import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox; +import org.apache.sis.referencing.CommonCRS; import org.apache.sis.test.DependsOnMethod; import org.apache.sis.test.TestCase; + import org.junit.Test; import static java.lang.Double.NaN; -import static org.junit.Assert.*; +import static org.apache.sis.internal.feature.WrapResolution.CONTIGUOUS; +import static org.apache.sis.internal.feature.WrapResolution.EXPAND; +import static org.apache.sis.internal.feature.WrapResolution.NONE; +import static org.apache.sis.internal.feature.WrapResolution.SPLIT; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; /** @@ -133,6 +150,79 @@ public abstract strictfp class GeometriesTestCase extends TestCase { assertWktEquals("LINESTRING (4 5, 7 9, 9 3, -1 -6)", text); } + @Test + public void testEnvelopeToLinearRing() { + // First, ensure that behavior is constant in case wrap-around is de-activated. + final GeneralEnvelope noWrapNeeded = new GeneralEnvelope(new DefaultGeographicBoundingBox(-30, 24, -60, -51)); + final double[] expectedResult = {-30, -60, -30, -51, 24, -51, 24, -60, -30, -60}; + for (WrapResolution method : WrapResolution.values()) assertConversion(noWrapNeeded, method, expectedResult); + + // Check behavior when wrap-around is on first axis. + final GeneralEnvelope wrapped = new GeneralEnvelope(CommonCRS.defaultGeographic()); + wrapped.setEnvelope(165, 32, -170, 33); + final EnumMap expected = new EnumMap(WrapResolution.class); + expected.put(NONE, new double[]{165, 32, 165, 33, -170, 33, -170, 32, 165, 32}); + expected.put(CONTIGUOUS, new double[]{165, 32, 165, 33, 190, 33, 190, 32, 165, 32}); + expected.put(EXPAND, new double[]{-180, 32, -180, 33, 180, 33, 180, 32, -180, 32}); + expected.put(SPLIT, new double[]{165, 32, 165, 33, 180, 33, 180, 32, 165, 32, -180, 32, -180, 33, -170, 33, -170, 32, -180, 32}); + assertConversion(wrapped, expected); + + wrapped.setEnvelope(177, -42, 190, 2); + expected.put(NONE, new double[]{177, -42, 177, 2, 190, 2, 190, -42, 177, -42}); + expected.put(CONTIGUOUS, new double[]{177, -42, 177, 2, 190, 2, 190, -42, 177, -42}); + expected.put(EXPAND, new double[]{-180, -42, -180, 2, 180, 2, 180, -42, -180, -42}); + expected.put(SPLIT, new double[]{177, -42, 177, 2, 180, 2, 180, -42, 177, -42, -180, -42, -180, 2, -170, 2, -170, -42, -180, -42}); + + // Check wrap-around on second axis. + wrapped.setCoordinateReferenceSystem(CommonCRS.WGS84.geographic()); + wrapped.setEnvelope(2, 89, 3, 19); + expected.put(NONE, new double[]{2, 89, 2, 19, 3, 19, 3, 89, 2, 89}); + expected.put(CONTIGUOUS, new double[]{2, 89, 2, 199, 3, 199, 3, 89, 2, 89}); + expected.put(EXPAND, new double[]{2, -180, 2, 180, 3, 180, 3, -180, 2, -180}); + expected.put(SPLIT, new double[]{2, 89, 2, 180, 3, 180, 3, 89, 2, 89, 2, -180, 2, 19, 3, 19, 3, -180, 2, -180}); + + // Ensure fail fast on dimension ambiguity + wrapped.setCoordinateReferenceSystem(null); + Stream.of(CONTIGUOUS, EXPAND, SPLIT) + .forEach(method + -> expectFailFast(() + -> factory.tryConvertToGeometry(wrapped, method) + , IllegalArgumentException.class) + ); + + final GeneralEnvelope wrapped3d = new GeneralEnvelope(3); + expectFailFast(() -> factory.tryConvertToGeometry(wrapped3d, NONE), IllegalArgumentException.class); + } + + private static void expectFailFast(Callable whichMustFail, Class<? extends Exception>... expectedErrorTypes) { + try { + final Object result = whichMustFail.call(); + fail("Fail fast expected, but successfully returned "+result); + } catch (Exception e) { + if (expectedErrorTypes == null || expectedErrorTypes.length < 1) return; // Any error accepted. + for (Class errorType : expectedErrorTypes) { + if (errorType.isInstance(e)) { + // Expected behavior + return; + } + } + // Unexpected error + fail(String.format( + "A fail fast occurred, but thrown error type is unexpected.%nError type: %s%nStack-trace: %s", + e.getClass().getCanonicalName(), e + )); + } + } + + private void assertConversion(final Envelope source, final Map<WrapResolution, double[]> expectedResults) { + expectedResults.entrySet().forEach(entry -> assertConversion(source, entry.getKey(), entry.getValue())); + } + + private void assertConversion(final Envelope source, final WrapResolution method, final double[] expectedOrdinates) { + final double[] result = factory.getPoints(factory.tryConvertToGeometry(source, method)); + assertArrayEquals("Point list for: "+method, expectedOrdinates, result, 1e-9); + } + /** * Verifies that a WKT is equal to the expected one. If the actual WKT is a multi-lines or multi-polygons, * then this method may modify the expected WKT accordingly. This adjustment is done for the ESRI case by
