This is an automated email from the ASF dual-hosted git repository.

amanin pushed a commit to branch fix/world-envelope-to-polygon
in repository https://gitbox.apache.org/repos/asf/sis.git

commit f5a308bb58915c894a24a31d5ada6f56a44b0bf0
Author: Alexis Manin <alexis.ma...@geomatys.com>
AuthorDate: Thu Jul 28 19:19:31 2022 +0200

    fix(Core): add control points if required when converting an envelope to a 
polygon.
    
    On axes with potential wrap-around, if the envelope is very large, the 
conversion to a rectangle without additional control points create an ambiguity.
    When trying to link two points of the rectangle using shortest path, the 
result could not follow rectangle edges, due to the wrap-around. Adding control 
points mitigate that problem.
    
    Note: the current solution is only a quick fix, surely there is room for 
improvement. For example, the current solution only consider a single 
wrap-around period.
---
 .../apache/sis/internal/feature/Geometries.java    | 44 +++++++++++++++++-----
 .../sis/internal/filter/GeometryConverter.java     | 13 +++++++
 .../sis/filter/BinarySpatialFilterTestCase.java    | 23 +++++++++++
 .../sis/internal/feature/GeometriesTestCase.java   | 36 ++++++++++++++----
 4 files changed, 100 insertions(+), 16 deletions(-)

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 4a2a9a2b91..9a14b019db 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
@@ -21,6 +21,7 @@ import java.io.Serializable;
 import java.nio.ByteBuffer;
 import java.util.Optional;
 import java.util.Iterator;
+import org.apache.sis.internal.referencing.WraparoundAxesFinder;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.Envelope;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -388,19 +389,39 @@ public abstract class Geometries<G> implements 
Serializable {
      * Creates a polyline made of points describing a rectangle whose start 
point is the lower left corner.
      * The sequence of points describes each corner, going in clockwise 
direction and repeating the starting
      * point to properly close the ring.
-     *
-     * @param  xd  dimension of first axis.
-     * @param  yd  dimension of second axis.
+     * In case a wrap-around ambiguity resides, control points are also added 
in the middle of the rectangle edges.
+     *
+     * @param xd dimension of first axis.
+     * @param yd dimension of second axis.
+     * @param xPeriod Maximum span on <em>first</em> axis before triggering a 
wrap-around.
+     *                If no wrap-around is possible, please set it to {@link 
Double#POSITIVE_INFINITY}.
+     * @param yPeriod Maximum span on <em>second</em> axis before triggering a 
wrap-around.
+     *                If no wrap-around is possible, please set it to {@link 
Double#POSITIVE_INFINITY}.
      * @return a polyline made of a sequence of 5 points describing the given 
rectangle.
      */
-    private GeometryWrapper<G> createGeometry2D(final Envelope envelope, final 
int xd, final int yd) {
+    private GeometryWrapper<G> createGeometry2D(final Envelope envelope, final 
int xd, final int yd, double xPeriod, double yPeriod) {
         final DirectPosition lc = envelope.getLowerCorner();
         final DirectPosition uc = envelope.getUpperCorner();
         final double xmin = lc.getOrdinate(xd);
         final double ymin = lc.getOrdinate(yd);
         final double xmax = uc.getOrdinate(xd);
         final double ymax = uc.getOrdinate(yd);
-        return createWrapper(createPolyline(true, BIDIMENSIONAL, 
Vector.create(new double[] {
+        final boolean applyXWrapAround = xPeriod / 2 < xmax - xmin;
+        final boolean applyYWrapAround = yPeriod / 2 < ymax - ymin;
+        if (applyXWrapAround && applyYWrapAround) {
+            final double xmid = (xmin + xmax) / 2;
+            final double ymid = (ymin + ymax) / 2;
+            return createWrapper(createPolyline(true, BIDIMENSIONAL, 
Vector.create(new double[] {
+                    xmin, ymin,  xmin, ymid,  xmin, ymax,  xmid, ymax,  xmax, 
ymid,  xmax, ymax,  xmax, ymid,  xmax, ymin,  xmid, ymin,  xmin, ymin})));
+        } else if (applyXWrapAround) {
+            final double xmid = (xmin + xmax) / 2;
+            return createWrapper(createPolyline(true, BIDIMENSIONAL, 
Vector.create(new double[] {
+                    xmin, ymin,  xmin, ymax,  xmid, ymax,  xmax, ymax,  xmax, 
ymin,  xmid, ymin,  xmin, ymin})));
+        } else if (applyYWrapAround) {
+            final double ymid = (ymin + ymax) / 2;
+            return createWrapper(createPolyline(true, BIDIMENSIONAL, 
Vector.create(new double[] {
+                    xmin, ymin,  xmin, ymid,  xmin, ymax,  xmax, ymax,  xmax, 
ymid,  xmax, ymin,  xmin, ymin})));
+        } else return createWrapper(createPolyline(true, BIDIMENSIONAL, 
Vector.create(new double[] {
                              xmin, ymin,  xmin, ymax,  xmax, ymax,  xmax, 
ymin,  xmin, ymin})));
     }
 
@@ -441,32 +462,37 @@ public abstract class Geometries<G> implements 
Serializable {
                  */
             }
         }
+
+        final double[] periods = crs == null ? null : new 
WraparoundAxesFinder(crs).periods();
+        double xPeriod = periods != null && periods.length > 0 && periods[0] > 
0 ? periods[0] : Double.POSITIVE_INFINITY;
+        double yPeriod = periods != null && periods.length > 1 && periods[1] > 
0 ? periods[1] : Double.POSITIVE_INFINITY;
+
         final GeometryWrapper<G> result;
         switch (strategy) {
             case NORMALIZE: {
                 throw new IllegalArgumentException();
             }
             case NONE: {
-                result = createGeometry2D(envelope, xd, yd);
+                result = createGeometry2D(envelope, xd, yd, xPeriod, yPeriod);
                 break;
             }
             default: {
                 final GeneralEnvelope ge = new GeneralEnvelope(envelope);
                 ge.normalize();
                 ge.wraparound(strategy);
-                result = createGeometry2D(ge, xd, yd);
+                result = createGeometry2D(ge, xd, yd, xPeriod, yPeriod);
                 break;
             }
             case SPLIT: {
                 final Envelope[] parts = 
AbstractEnvelope.castOrCopy(envelope).toSimpleEnvelopes();
                 if (parts.length == 1) {
-                    result = createGeometry2D(parts[0], xd, yd);
+                    result = createGeometry2D(parts[0], xd, yd, xPeriod, 
yPeriod);
                     break;
                 }
                 @SuppressWarnings({"unchecked", "rawtypes"})
                 final GeometryWrapper<G>[] polygons = new 
GeometryWrapper[parts.length];
                 for (int i=0; i<parts.length; i++) {
-                    polygons[i] = createGeometry2D(parts[i], xd, yd);
+                    polygons[i] = createGeometry2D(parts[i], xd, yd, xPeriod, 
yPeriod);
                     polygons[i].setCoordinateReferenceSystem(crs);
                 }
                 result = createMultiPolygon(polygons);
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java
index 2d362d29b5..1fdfc0ca4c 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java
@@ -19,6 +19,7 @@ package org.apache.sis.internal.filter;
 import java.util.List;
 import java.util.Collection;
 import java.util.Collections;
+import org.opengis.geometry.DirectPosition;
 import org.opengis.util.ScopedName;
 import org.opengis.geometry.Envelope;
 import org.opengis.metadata.extent.GeographicBoundingBox;
@@ -149,6 +150,18 @@ final class GeometryConverter<R,G> extends Node implements 
Optimization.OnExpres
             envelope = new ImmutableEnvelope((GeographicBoundingBox) value);
         } else if (value instanceof Envelope) {
             envelope = (Envelope) value;
+        } else if (value instanceof DirectPosition) {
+            final DirectPosition pt = (DirectPosition) value;
+            final Object geometry;
+            if (pt.getDimension() == 2) {
+                geometry = library.createPoint(pt.getOrdinate(0), 
pt.getOrdinate(1));
+            } else if (pt.getDimension() == 3) {
+                geometry = library.createPoint(pt.getOrdinate(0), 
pt.getOrdinate(1), pt.getOrdinate(2));
+            } else throw new InvalidFilterValueException(Errors.format(
+                        Errors.Keys.IllegalClass_2, library.rootClass, 
Classes.getClass(value)));
+            final GeometryWrapper<G> wrapper = library.castOrWrap(geometry);
+            if (pt.getCoordinateReferenceSystem() != null) 
wrapper.setCoordinateReferenceSystem(pt.getCoordinateReferenceSystem());
+            return wrapper;
         } else try {
             return library.castOrWrap(value);
         } catch (ClassCastException e) {
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterTestCase.java
 
b/core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterTestCase.java
index 5879bc6d82..be5f44f78e 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterTestCase.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterTestCase.java
@@ -18,6 +18,11 @@ package org.apache.sis.filter;
 
 import javax.measure.Quantity;
 import javax.measure.quantity.Length;
+import org.apache.sis.geometry.DirectPosition2D;
+import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.setup.GeometryLibrary;
+import org.apache.sis.test.Assume;
+import org.opengis.filter.DistanceOperatorName;
 import org.opengis.geometry.Envelope;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.geometry.Envelope2D;
@@ -347,4 +352,22 @@ public abstract strictfp class 
BinarySpatialFilterTestCase<G> extends TestCase {
         BinarySpatialOperator<Feature> overlaps = 
factory.overlaps(literal(Polygon.CONTAINS), right);
         assertSerializedEquals(overlaps);
     }
+
+    /**
+     * Ensures that a world geographic envelope, once converted to a polygon 
and reprojected, remain coherent.
+     * This is a regression test. In the past, the operation pipeline 
[envelope -> polygon -> reprojected polygon]
+     * caused the result to degenerate to single line following the 
anti-meridian.
+     */
+    @Test
+    public void testSpatialContextDoesNotDegenerateEnvelope() throws Exception 
{
+        Assume.assumeTrue("Require reprojection. Only supported for JTS for 
now", library.library == GeometryLibrary.JTS);
+        final Envelope e1 = new Envelope2D(HardCodedCRS.WGS84, -180, -90, 360, 
180);
+        final DistanceFilter<?, G> within = new 
DistanceFilter<>(DistanceOperatorName.WITHIN,
+                library, factory.literal(e1),
+                factory.literal(new DirectPosition2D(HardCodedCRS.WGS84, 44, 
2)),
+                Quantities.create(1.0, Units.METRE));
+
+        final GeneralEnvelope envInCtx = 
within.context.transform(within.expression1.apply(null)).getEnvelope();
+        assertNotEquals(envInCtx.getMinimum(0), envInCtx.getMaximum(0), 1000);
+    }
 }
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 73a9d0c714..a0633569e2 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
@@ -18,6 +18,7 @@ package org.apache.sis.internal.feature;
 
 import java.util.Arrays;
 import java.util.Iterator;
+import org.apache.sis.geometry.Envelope2D;
 import org.opengis.geometry.Envelope;
 import org.apache.sis.setup.GeometryLibrary;
 import org.apache.sis.geometry.GeneralEnvelope;
@@ -193,7 +194,9 @@ public abstract strictfp class GeometriesTestCase extends 
TestCase {
         assertToGeometryEquals(e, WraparoundMethod.CONTIGUOUS,        165, 32, 
 165, 33,  190, 33,  190, 32,  165, 32);
         assertToGeometryEquals(e, WraparoundMethod.CONTIGUOUS_LOWER, -195, 32, 
-195, 33, -170, 33, -170, 32, -195, 32);
         assertToGeometryEquals(e, WraparoundMethod.CONTIGUOUS_UPPER,  165, 32, 
 165, 33,  190, 33,  190, 32,  165, 32);
-        assertToGeometryEquals(e, WraparoundMethod.EXPAND,           -180, 32, 
-180, 33,  180, 33,  180, 32, -180, 32);
+
+        assertToGeometryEquals(e, WraparoundMethod.EXPAND,           -180, 32, 
 -180, 33,  0, 33,  180, 33,  180, 32,  0, 32,  -180, 32);
+
         assertToGeometryEquals(e, WraparoundMethod.SPLIT,             165, 32, 
 165, 33,  180, 33,  180, 32,  165, 32,
                                                                      -180, 32, 
-180, 33, -170, 33, -170, 32, -180, 32);
         e.setRange(0, 177, -170);
@@ -202,7 +205,9 @@ public abstract strictfp class GeometriesTestCase extends 
TestCase {
         assertToGeometryEquals(e, WraparoundMethod.CONTIGUOUS,       -183, 
-42, -183, 2, -170, 2, -170, -42, -183, -42);
         assertToGeometryEquals(e, WraparoundMethod.CONTIGUOUS_UPPER,  177, 
-42,  177, 2,  190, 2,  190, -42,  177, -42);
         assertToGeometryEquals(e, WraparoundMethod.CONTIGUOUS_LOWER, -183, 
-42, -183, 2, -170, 2, -170, -42, -183, -42);
-        assertToGeometryEquals(e, WraparoundMethod.EXPAND,           -180, 
-42, -180, 2,  180, 2,  180, -42, -180, -42);
+
+        assertToGeometryEquals(e, WraparoundMethod.EXPAND,           -180, 
-42,  -180, 2,  0, 2,  180, 2,  180, -42,  0, -42,  -180, -42);
+
         assertToGeometryEquals(e, WraparoundMethod.SPLIT,             177, 
-42,  177, 2,  180, 2,  180, -42,  177, -42,
                                                                      -180, 
-42, -180, 2, -170, 2, -170, -42, -180, -42);
     }
@@ -218,11 +223,28 @@ public abstract strictfp class GeometriesTestCase extends 
TestCase {
         e.setRange(1, 1000, 1007);      // Time
         e.setRange(2,    2,    3);      // Latitude
         e.setRange(3,   89,   19);      // Longitude (span anti-meridian).
-        assertToGeometryEquals(e, WraparoundMethod.NONE,       2,   89, 2,  
19, 3,  19, 3,   89, 2,   89);
-        assertToGeometryEquals(e, WraparoundMethod.CONTIGUOUS, 2, -271, 2,  
19, 3,  19, 3, -271, 2, -271);
-        assertToGeometryEquals(e, WraparoundMethod.EXPAND,     2, -180, 2, 
180, 3, 180, 3, -180, 2, -180);
-        assertToGeometryEquals(e, WraparoundMethod.SPLIT,      2,   89, 2, 
180, 3, 180, 3,   89, 2,   89,
-                                                               2, -180, 2,  
19, 3,  19, 3, -180, 2, -180);
+
+        assertToGeometryEquals(e, WraparoundMethod.NONE,       2, 89,  2, 19,  
3, 19,  3, 89,  2, 89);
+
+        assertToGeometryEquals(e, WraparoundMethod.CONTIGUOUS, 2, -271,  2, 
-126,  2, 19,  3, 19,  3, -126,  3, -271, 2, -271);
+
+        assertToGeometryEquals(e, WraparoundMethod.EXPAND,     2, -180,  2, 0, 
 2, 180,  3, 180,  3, 0,  3, -180,  2, -180);
+
+        assertToGeometryEquals(e, WraparoundMethod.SPLIT,      2, 89,  2, 180, 
 3, 180,  3, 89,  2, 89,
+                                                               2, -180,  2, 
-80.5,  2, 19,  3, 19, 3,  -80.5,  3, -180,  2, -180);
+    }
+
+    @Test
+    public void testWorldWGS84ToGeometry2D() {
+        Envelope2D env2d = new Envelope2D(HardCodedCRS.WGS84, -180, -90, 360, 
180);
+        for (WraparoundMethod method : new WraparoundMethod[] { 
WraparoundMethod.NONE, WraparoundMethod.CONTIGUOUS, WraparoundMethod.EXPAND, 
WraparoundMethod.SPLIT}) {
+            assertToGeometryEquals(env2d, method, -180, -90,  -180, 90,  0, 
90,  180, 90,  180, -90,  0, -90,  -180, -90);
+        }
+
+        env2d = new Envelope2D(HardCodedCRS.WGS84_LATITUDE_FIRST, -90, -180, 
180, 360);
+        for (WraparoundMethod method : new WraparoundMethod[] { 
WraparoundMethod.NONE, WraparoundMethod.CONTIGUOUS, WraparoundMethod.EXPAND, 
WraparoundMethod.SPLIT}) {
+            assertToGeometryEquals(env2d, method, -90, -180,  -90, 0,  -90, 
180,  90, 180,  90, 0,  90, -180,  -90, -180);
+        }
     }
 
     /**

Reply via email to