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 e39706a9de2075642d46fd69c9692d452e1128b5 Author: Alexis Manin <[email protected]> AuthorDate: Mon Oct 7 18:16:03 2019 +0200 wip(Feature): envelope to geometry --- .../java/org/apache/sis/internal/feature/ESRI.java | 17 ++- .../apache/sis/internal/feature/Geometries.java | 153 +++++++++++++++------ .../java/org/apache/sis/internal/feature/JTS.java | 44 +++++- .../org/apache/sis/internal/feature/Java2D.java | 10 ++ .../sis/internal/feature/GeometriesTestCase.java | 43 +++++- 5 files changed, 208 insertions(+), 59 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..05be9ff 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,8 +18,6 @@ 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; @@ -88,11 +86,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 +196,16 @@ add: for (;;) { return path; } + @Override + double[] getPoints(Object geometry) { + throw new UnsupportedOperationException("Not supported yet"); // "Alexis Manin (Geomatys)" on 07/10/2019 + } + + @Override + Object createMultiPolygonImpl(Object... polygonsOrLinearRings) { + throw new UnsupportedOperationException("Not supported yet"); // "Alexis Manin (Geomatys)" on 07/10/2019 + } + /** * 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..3fd582b 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 @@ -24,10 +24,15 @@ import java.util.logging.LogRecord; 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; @@ -156,8 +161,6 @@ public abstract class Geometries<G> { .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 +346,124 @@ 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) - }; + private 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; + + double minX = env.getMinimum(x); + double minY = env.getMinimum(y); + double maxX = env.getMaximum(x); + double maxY = env.getMaximum(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.getMinimum(i) > fixedEnv.getMaximum(i)) wrapAxis = i; + } + if (wrapAxis >= x) { + final CoordinateReferenceSystem crs = env.getCoordinateReferenceSystem(); + if (crs == null) throw new IllegalStateException("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(); + //TODO + 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 createMultiPolygon(mainRect, secondRect); } + 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..58c7b29 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 @@ -219,6 +219,16 @@ add: for (;;) { return ShapeUtilities.toPrimitive(path); } + @Override + double[] getPoints(Object geometry) { + throw new UnsupportedOperationException("Not supported yet"); // "Alexis Manin (Geomatys)" on 07/10/2019 + } + + @Override + Object createMultiPolygonImpl(Object... polygonsOrLinearRings) { + throw new UnsupportedOperationException("Not supported yet"); // "Alexis Manin (Geomatys)" on 07/10/2019 + } + /** * 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 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..9d02c74 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,25 @@ 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 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.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; /** @@ -133,6 +143,35 @@ public abstract strictfp class GeometriesTestCase extends TestCase { assertWktEquals("LINESTRING (4 5, 7 9, 9 3, -1 -6)", text); } + @Test + public void testEnvelopeToLinearRing() { + 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); + + final GeneralEnvelope basicWrap = new GeneralEnvelope(CommonCRS.defaultGeographic()); + basicWrap.setEnvelope(165, 32, -170, 33); + final EnumMap expected = new EnumMap(WrapResolution.class); + expected.put(WrapResolution.NONE, new double[]{165, 32, 165, 33, -170, 33, -170, 32, 165, 32}); + expected.put(WrapResolution.CONTIGUOUS, new double[]{165, 32, 165, 33, 190, 33, 190, 32, 165, 32}); + expected.put(WrapResolution.EXPAND, new double[]{-180, 32, -180, 33, 180, 33, 180, 32, -180, 32}); + expected.put(WrapResolution.SPLIT, new double[]{165, 32, 165, 33, 180, 33, 180, 32, 165, 32, -180, 32, -180, 33, -170, 33, -170, 32, -180, 32}); + assertConversion(basicWrap, expected); + + + } + + private static void assertConversion(final Envelope source, final Map<WrapResolution, double[]> expectedResults) { + expectedResults.entrySet().forEach(entry -> assertConversion(source, entry.getKey(), entry.getValue())); + } + + private static void assertConversion(final Envelope source, final WrapResolution method, final double[] expectedOrdinates) { + final double[] result = Geometries.toGeometry(source, method) + .flatMap(Geometries::getOrdinates) + .orElseThrow(() -> new AssertionError("No geometry returned for envelope conversion")); + 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
