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

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 9a63af427fb31f2026341883c90b2759b60ed3ce
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri May 27 16:52:43 2022 +0200

    Add a `Envelopes.transformWraparounds(…)` method for getting the individual 
envelopes before their union is computed.
    This method is useful only if the transforms chain contains at least one 
`WraparoundTransform` step.
---
 .../java/org/apache/sis/geometry/Envelopes.java    | 124 +++++++++++++++------
 .../org/apache/sis/geometry/EnvelopesTest.java     |  22 +++-
 2 files changed, 109 insertions(+), 37 deletions(-)

diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java 
b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
index f331abe68e..d7d7bd14d7 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
@@ -22,7 +22,9 @@ package org.apache.sis.geometry;
  * force installation of the Java2D module (e.g. JavaFX/SWT).
  */
 import java.util.Set;
+import java.util.List;
 import java.util.Optional;
+import java.util.ArrayList;
 import java.util.ConcurrentModificationException;
 import java.time.Instant;
 import org.opengis.geometry.Envelope;
@@ -98,7 +100,7 @@ import static 
org.apache.sis.util.StringBuilders.trimFractionalPart;
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 1.1
+ * @version 1.3
  *
  * @see org.apache.sis.metadata.iso.extent.Extents
  * @see CRS
@@ -362,49 +364,28 @@ public final class Envelopes extends Static {
     }
 
     /**
-     * Transforms an envelope using the given math transform.
-     * The transformation is only approximated: the returned envelope may be 
bigger than necessary,
-     * or smaller than required if the bounding box contains a pole.
-     * The coordinate reference system of the returned envelope will be null.
-     *
-     * <h4>Limitation</h4>
-     * This method can not handle the case where the envelope contains the 
North or South pole,
-     * or when it crosses the ±180° longitude, because {@link MathTransform} 
does not carry sufficient information.
-     * For a more robust envelope transformation, use {@link 
#transform(CoordinateOperation, Envelope)} instead.
+     * Shared implementation of {@link #transform(MathTransform, Envelope)}
+     * and {@link #transformWraparounds(MathTransform, Envelope)}.
+     * Offers also the opportunity to save the transformed center coordinates.
      *
      * @param  transform  the transform to use.
-     * @param  envelope   envelope to transform, or {@code null}. This 
envelope will not be modified.
-     * @return the transformed envelope, or {@code null} if {@code envelope} 
was null.
-     * @throws TransformException if a transform failed.
-     *
-     * @see #transform(CoordinateOperation, Envelope)
-     *
-     * @since 0.5
-     */
-    public static GeneralEnvelope transform(final MathTransform transform, 
final Envelope envelope)
-            throws TransformException
-    {
-        ArgumentChecks.ensureNonNull("transform", transform);
-        return (envelope != null) ? transform(transform, envelope, null) : 
null;
-    }
-
-    /**
-     * Implementation of {@link #transform(MathTransform, Envelope)} with the 
opportunity to
-     * save the projected center coordinate.
-     *
-     * @param  targetPt  after this method call, the center of the source 
envelope projected to the target CRS.
-     *                   The length of this array must be the number of target 
dimensions.
-     *                   May be {@code null} if this information is not needed.
+     * @param  envelope   envelope to transform. This envelope will not be 
modified.
+     * @param  targetPt   after this method call, the center of the source 
envelope transformed to the target CRS.
+     *                    The length of this array must be the number of 
target dimensions.
+     *                    May be {@code null} if this information is not 
needed.
+     * @param  results    where to store the individual results when the 
transform contains wraparound steps,
+     *                    or {@code null} for computing the union of all 
results instead.
+     * @return the transformed envelope. May be {@code null} if {@code 
results} was non-null.
      */
     @SuppressWarnings("null")
-    private static GeneralEnvelope transform(final MathTransform transform, 
final Envelope envelope, double[] targetPt)
-            throws TransformException
+    private static GeneralEnvelope transform(final MathTransform transform, 
final Envelope envelope,
+            double[] targetPt, final List<GeneralEnvelope> results) throws 
TransformException
     {
         if (transform.isIdentity()) {
             /*
              * Slight optimization: Just copy the envelope. Note that we need 
to set the CRS
              * to null because we don't know what the target CRS was supposed 
to be. Even if
-             * an identity transform often imply that the target CRS is the 
same one than the
+             * an identity transform often implies that the target CRS is the 
same one than the
              * source CRS, it is not always the case. The metadata may be 
differents, or the
              * transform may be a datum shift without Bursa-Wolf parameters, 
etc.
              */
@@ -590,6 +571,15 @@ nextPoint:  for (int pointIndex = 0;;) {                // 
Break condition at th
                 System.arraycopy(coordinates, coordinates.length - targetDim, 
targetPt, 0, targetDim);
                 targetPt = null;
             }
+            /*
+             * If the caller wants individual results, store them in the list.
+             * Do not filter empty envelopes, because some callers such as
+             * `GridExtent` have algorithms for completing empty envelopes.
+             */
+            if (results != null) {
+                results.add(transformed);
+                transformed = null;
+            }
         } while (wc.translate());
         return transformed;
     }
@@ -653,7 +643,7 @@ nextPoint:  for (int pointIndex = 0;;) {                // 
Break condition at th
         }
         final MathTransform mt = operation.getMathTransform();
         final double[] centerPt = new double[mt.getTargetDimensions()];
-        final GeneralEnvelope transformed = transform(mt, envelope, centerPt);
+        final GeneralEnvelope transformed = transform(mt, envelope, centerPt, 
null);
         /*
          * If the source envelope crosses the expected range of valid 
coordinates, also projects
          * the range bounds as a safety. Example: if the source envelope goes 
from 150 to 200°E,
@@ -954,6 +944,68 @@ poles:  for (int i=0; i<dimension; i++) {
         return transformed;
     }
 
+    /**
+     * Transforms an envelope using the given math transform.
+     * The transformation is only approximated: the returned envelope may be 
bigger than necessary,
+     * or smaller than required if the bounding box contains a pole.
+     * The coordinate reference system of the returned envelope will be null.
+     *
+     * <h4>Limitation</h4>
+     * This method can not handle the case where the envelope contains the 
North or South pole.
+     * Envelopes crossing the ±180° longitude are handled only if the given 
transform contains
+     * {@link 
org.apache.sis.referencing.operation.transform.WraparoundTransform} steps;
+     * this method does not add such steps itself.
+     * For a more robust envelope transformation, use {@link 
#transform(CoordinateOperation, Envelope)} instead.
+     *
+     * @param  transform  the transform to use.
+     * @param  envelope   envelope to transform, or {@code null}. This 
envelope will not be modified.
+     * @return the transformed envelope, or {@code null} if {@code envelope} 
was null.
+     * @throws TransformException if a transform failed.
+     *
+     * @see #transform(CoordinateOperation, Envelope)
+     *
+     * @since 0.5
+     */
+    public static GeneralEnvelope transform(final MathTransform transform, 
final Envelope envelope)
+            throws TransformException
+    {
+        ArgumentChecks.ensureNonNull("transform", transform);
+        return (envelope != null) ? transform(transform, envelope, null, null) 
: null;
+    }
+
+    /**
+     * Transforms potentially many times an envelope using the given math 
transform.
+     * If the given envelope is {@code null}, then this method returns an 
empty envelope.
+     * Otherwise if the transform does not contain any
+     * {@link 
org.apache.sis.referencing.operation.transform.WraparoundTransform} step,
+     * then this method is equivalent to {@link #transform(MathTransform, 
Envelope)} returned in an array of length 1.
+     * Otherwise this method returns many transformed envelopes where each 
envelope describes approximately the same region.
+     * If the envelope CRS is geographic, the many envelopes are the same 
envelope shifted by 360° of longitude.
+     * If the envelope CRS is projected, then the 360° shifts are applied 
before the map projection.
+     * It may result in very different coordinates.
+     *
+     * @param  transform  the transform to use.
+     * @param  envelope   envelope to transform, or {@code null}. This 
envelope will not be modified.
+     * @return the transformed envelopes, or an empty array if {@code 
envelope} was null.
+     * @throws TransformException if a transform failed.
+     *
+     * @since 1.3
+     */
+    public static GeneralEnvelope[] transformWraparounds(final MathTransform 
transform, final Envelope envelope)
+            throws TransformException
+    {
+        ArgumentChecks.ensureNonNull("transform", transform);
+        if (envelope == null) {
+            return new GeneralEnvelope[0];
+        }
+        final List<GeneralEnvelope> results = new ArrayList<>(4);
+        final GeneralEnvelope transformed = transform(transform, envelope, 
null, results);
+        if (results.isEmpty() && transformed != null) {
+            return new GeneralEnvelope[] {transformed};
+        }
+        return results.toArray(new GeneralEnvelope[results.size()]);
+    }
+
     /**
      * Returns the bounding box of a geometry defined in <cite>Well Known 
Text</cite> (WKT) format.
      * This method does not check the consistency of the provided WKT. For 
example it does not check
diff --git 
a/core/sis-referencing/src/test/java/org/apache/sis/geometry/EnvelopesTest.java 
b/core/sis-referencing/src/test/java/org/apache/sis/geometry/EnvelopesTest.java
index 962356e193..fe740855aa 100644
--- 
a/core/sis-referencing/src/test/java/org/apache/sis/geometry/EnvelopesTest.java
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/geometry/EnvelopesTest.java
@@ -27,6 +27,7 @@ import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.Conversion;
+import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.MathTransform2D;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.referencing.operation.transform.MathTransformWrapper;
@@ -34,6 +35,7 @@ import org.apache.sis.referencing.crs.DefaultCompoundCRS;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.operation.transform.WraparoundTransform;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.DependsOnMethod;
 import org.junit.Test;
@@ -47,7 +49,7 @@ import static org.opengis.test.Validators.validate;
  * This class inherits the test methods defined in {@link TransformTestCase}.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.1
+ * @version 1.3
  * @since   0.3
  * @module
  */
@@ -302,4 +304,22 @@ public final strictfp class EnvelopesTest extends 
TransformTestCase<GeneralEnvel
         assertEquals(Instant.parse("2019-12-23T00:00:00Z"), 
range.getMinValue());
         assertEquals(Instant.parse("2020-05-31T18:00:00Z"), 
range.getMaxValue());
     }
+
+    /**
+     * Tests {@link Envelopes#transformWraparounds(MathTransform, Envelope)}.
+     *
+     * @throws TransformException if a coordinate transformation failed.
+     */
+    @Test
+    public void testTransformWraparounds() throws TransformException {
+        final GeneralEnvelope envelope = new 
GeneralEnvelope(HardCodedCRS.WGS84);
+        envelope.setRange(0, -200, -100);
+        envelope.setRange(1, 5, 9);
+        final MathTransform tr = WraparoundTransform.create(2, 0, 360, -180, 
0);
+        assertTrue(tr instanceof WraparoundTransform);
+        final GeneralEnvelope[] results = Envelopes.transformWraparounds(tr, 
envelope);
+        assertEquals(2, results.length);
+        assertEnvelopeEquals(new GeneralEnvelope(new double[] {-200, 5}, new 
double[] {-100, 9}), results[0]);
+        assertEnvelopeEquals(new GeneralEnvelope(new double[] { 160, 5}, new 
double[] { 260, 9}), results[1]);
+    }
 }

Reply via email to