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 fbde8a0a60f164a0dda0a5ecff32083d53d95884
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri May 27 18:26:48 2022 +0200

    Avoid wraparound when the result does not intersect the base grid geometry.
    The fix use `GridExtent.toEnvelopes(…)` (note the pluarl form) is applied in
    only once place for now, but we should check if it applies to more places.
    
    https://issues.apache.org/jira/browse/SIS-548
---
 .../apache/sis/coverage/grid/DomainLinearizer.java |   2 +-
 .../apache/sis/coverage/grid/GridDerivation.java   |  27 ++++-
 .../org/apache/sis/coverage/grid/GridExtent.java   | 119 ++++++++++++++++-----
 .../org/apache/sis/coverage/grid/GridGeometry.java |   9 +-
 .../sis/coverage/grid/ResampledGridCoverage.java   |   4 +-
 .../apache/sis/coverage/grid/SliceGeometry.java    |   2 +-
 .../sis/coverage/grid/GridDerivationTest.java      |  31 +++++-
 .../apache/sis/coverage/grid/GridExtentTest.java   |  29 ++++-
 8 files changed, 179 insertions(+), 44 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DomainLinearizer.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DomainLinearizer.java
index 73aa001e39..e9f74b6b0b 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DomainLinearizer.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DomainLinearizer.java
@@ -168,7 +168,7 @@ public class DomainLinearizer {
         ArgumentChecks.ensureNonNull("gg", gg);
         if (gg.nonLinears != 0) try {
             MathTransform   gridToCRS   = gg.requireGridToCRS(true);
-            GeneralEnvelope domain      = gg.extent.toCRS(null, null, null);
+            GeneralEnvelope domain      = gg.extent.toEnvelope();
             MathTransform   approximate = 
modify(LinearTransformBuilder.approximate(gridToCRS, domain));
             MathTransform   gridToGrid  = 
MathTransforms.concatenate(gridToCRS, approximate.inverse());
             domain = Envelopes.transform(gridToGrid, domain);
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
index 44772cba57..e1909ce0bc 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
@@ -79,7 +79,7 @@ import org.opengis.coverage.PointOutsideCoverageException;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Alexis Manin (Geomatys)
- * @version 1.2
+ * @version 1.3
  *
  * @see GridGeometry#derive()
  * @see GridGeometry#reduce(int...)
@@ -510,7 +510,13 @@ public class GridDerivation {
                 finder.nowraparound();
                 mapCenters = finder.gridToGrid();                              
 // We will use only the scale factors.
                 if (domain != null) {
-                    setBaseExtentClipped(domain.toCRS(mapCorners, mapCenters, 
null));
+                    final GeneralEnvelope[] envelopes;
+                    if (mapCorners != null) {
+                        envelopes = domain.toEnvelopes(mapCorners, mapCenters, 
null);
+                    } else {
+                        envelopes = new GeneralEnvelope[] 
{domain.toEnvelope()};
+                    }
+                    setBaseExtentClipped(envelopes);
                     if (baseExtent != base.extent && 
baseExtent.equals(domain)) {
                         baseExtent = domain;                                   
 // Share common instance.
                     }
@@ -805,8 +811,19 @@ public class GridDerivation {
      *
      * @see #getBaseExtentExpanded(boolean)
      */
-    private void setBaseExtentClipped(final GeneralEnvelope indices) {
-        final GridExtent sub = new GridExtent(indices, rounding, clipping, 
margin, chunkSize, baseExtent, modifiedDimensions);
+    private void setBaseExtentClipped(final GeneralEnvelope... indices) {
+        GridExtent sub = null;
+        IllegalArgumentException error = null;
+        for (final GeneralEnvelope ix : indices) try {
+            final GridExtent c = new GridExtent(ix, rounding, clipping, 
margin, chunkSize, baseExtent, modifiedDimensions);
+            sub = (sub == null) ? c : sub.union(c);
+        } catch (IllegalArgumentException e) {
+            if (error == null) error = e;
+            else error.addSuppressed(e);
+        }
+        if (sub == null) {
+            throw error;        // Should never be null because `indices` 
should never be empty.
+        }
         if (!sub.equals(baseExtent)) {
             baseExtent = sub;
         }
@@ -1100,7 +1117,7 @@ public class GridDerivation {
          *
          *   • x = (h₂ + f) × c⋅s      where        h₂ = floor(h₁/s)      and  
     f = ((h₁ mod s) + 1)/s
          *
-         * Because s ≥ 1, then f ≤ 1. But the f value actually used by 
GridExtent.toCRS(…) is hard-coded to 1
+         * Because s ≥ 1, then f ≤ 1. But the f value actually used by 
GridExtent.toEnvelope(…) is hard-coded to 1
          * since it assumes that all cells are whole, i.e. it does not take in 
account that the last cell may
          * actually be fraction of a cell. Since 1 ≥ f, the computed envelope 
may be larger. This explains the
          * need for envelope clipping performed by GridGeometry constructor.
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index 3376c00fe0..37822acd1d 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@ -392,7 +392,7 @@ public class GridExtent implements GridEnvelope, 
LenientComparable, Serializable
      *                             This argument is ignored if {@code 
enclosing} is null.
      * @throws DisjointExtentException if the given envelope does not 
intersect the enclosing grid extent.
      *
-     * @see #toCRS(MathTransform, MathTransform, Envelope)
+     * @see #toEnvelope(MathTransform, MathTransform, Envelope)
      * @see #slice(DirectPosition, int[])
      */
     GridExtent(final AbstractEnvelope envelope, final GridRoundingMode 
rounding, final GridClippingMode clipping,
@@ -420,12 +420,12 @@ public class GridExtent implements GridEnvelope, 
LenientComparable, Serializable
             final boolean isMaxValid = (max <= Long.MAX_VALUE);
             if (min > max || (enclosing == null && !(isMinValid & 
isMaxValid))) {
                 /*
-                 * We do not throw an exception if `enclosing` is non-null and 
envelope bounds are NaN
-                 * because this case occurs when the gridToCRS transform has a 
NaN scale factor.  Such
-                 * scale factor may occur with ranges like [0 … 0]. With a 
non-null `enclosing` extent,
-                 * we can still have grid coordinates: they are inherited from 
`enclosing`. We require
-                 * the two bounds to be NaN, otherwise the reason for those 
NaN envelope bounds is not
-                 * a NaN scale factor.
+                 * We do not throw an exception for NaN envelope bounds if 
`enclosing` is non-null
+                 * because this case occurs when the `gridToCRS` transform has 
a NaN scale factor.
+                 * Such scale factor may result from ranges like [0 … 0]. We 
tolerate them because
+                 * with a non-null `enclosing` extent, we can still have grid 
coordinates: they are
+                 * inherited from `enclosing`. Note that we require the two 
bounds to be NaN, because
+                 * otherwise the reason for those NaN envelope bounds is not a 
NaN scale factor.
                  */
                 throw new IllegalArgumentException(Resources.format(
                         Resources.Keys.IllegalGridEnvelope_3, 
getAxisIdentification(i,i), min, max));
@@ -999,7 +999,7 @@ public class GridExtent implements GridEnvelope, 
LenientComparable, Serializable
      */
     public GeneralEnvelope toEnvelope(final MathTransform cornerToCRS) throws 
TransformException {
         ArgumentChecks.ensureNonNull("cornerToCRS", cornerToCRS);
-        final GeneralEnvelope envelope = toCRS(cornerToCRS, cornerToCRS, null);
+        final GeneralEnvelope envelope = toEnvelope(cornerToCRS, cornerToCRS, 
null);
         final Matrix gridToCRS = MathTransforms.getMatrix(cornerToCRS);
         if (gridToCRS != null && Matrices.isAffine(gridToCRS)) try {
             
envelope.setCoordinateReferenceSystem(GridExtentCRS.build(gridToCRS, (types != 
null) ? types : DEFAULT_TYPES, null));
@@ -1014,10 +1014,10 @@ public class GridExtent implements GridEnvelope, 
LenientComparable, Serializable
      * The transform shall map <em>cell corner</em> to real world coordinates.
      * This method does not set the envelope coordinate reference system.
      *
-     * @param  cornerToCRS  a transform from <em>cell corners</em> to real 
world coordinates, or {@code null} if none.
+     * @param  cornerToCRS  a transform from <em>cell corners</em> to real 
world coordinates.
      * @param  gridToCRS    the transform specified by the user. May be the 
same as {@code cornerToCRS}.
      *                      If different, then this is assumed to map cell 
centers instead of cell corners.
-     * @param  fallback     bounds to use if some values still NaN, or {@code 
null} if none.
+     * @param  fallback     bounds to use if some values are still NaN after 
conversion, or {@code null} if none.
      * @return this grid extent in real world coordinates.
      * @throws TransformException if the envelope can not be computed with the 
given transform.
      *
@@ -1025,28 +1025,70 @@ public class GridExtent implements GridEnvelope, 
LenientComparable, Serializable
      *
      * @see GridGeometry#getEnvelope(CoordinateReferenceSystem)
      */
-    final GeneralEnvelope toCRS(final MathTransform cornerToCRS, final 
MathTransform gridToCRS, final Envelope fallback)
+    final GeneralEnvelope toEnvelope(final MathTransform cornerToCRS, final 
MathTransform gridToCRS, final Envelope fallback)
             throws TransformException
     {
+        final GeneralEnvelope envelope = Envelopes.transform(cornerToCRS, 
toEnvelope());
+        complete(envelope, gridToCRS, gridToCRS != cornerToCRS, fallback);
+        return envelope;
+    }
+
+    /**
+     * Returns the coordinates of this grid extent in an envelope.
+     * The returned envelope has no CRS.
+     */
+    final GeneralEnvelope toEnvelope() {
         final int dimension = getDimension();
-        GeneralEnvelope envelope = new GeneralEnvelope(dimension);
+        final GeneralEnvelope envelope = new GeneralEnvelope(dimension);
         for (int i=0; i<dimension; i++) {
             long high = coordinates[i + dimension];
             if (high != Long.MAX_VALUE) high++;             // Make the 
coordinate exclusive before cast.
             envelope.setRange(i, coordinates[i], high);     // Possible loss 
of precision in cast to `double` type.
         }
-        if (cornerToCRS == null) {
-            return envelope;
+        return envelope;
+    }
+
+    /**
+     * Transforms this grid extent to "real world" envelopes using the given 
transform.
+     * This method usually returns exactly one envelope, but may return more 
envelopes if the given transform
+     * contains at least one {@link 
org.apache.sis.referencing.operation.transform.WraparoundTransform} step.
+     *
+     * @param  cornerToCRS  a transform from <em>cell corners</em> to real 
world coordinates.
+     * @param  gridToCRS    the transform specified by the user. May be the 
same as {@code cornerToCRS}.
+     *                      If different, then this is assumed to map cell 
centers instead of cell corners.
+     * @param  fallback     bounds to use if some values are still NaN after 
conversion, or {@code null} if none.
+     * @return this grid extent in real world coordinates.
+     * @throws TransformException if the envelope can not be computed with the 
given transform.
+     *
+     * @see #GridExtent(AbstractEnvelope, GridRoundingMode, int[], GridExtent, 
int[])
+     *
+     * @see GridGeometry#getEnvelope(CoordinateReferenceSystem)
+     */
+    final GeneralEnvelope[] toEnvelopes(final MathTransform cornerToCRS, final 
MathTransform gridToCRS, final Envelope fallback)
+            throws TransformException
+    {
+        final GeneralEnvelope[] envelopes = 
Envelopes.transformWraparounds(cornerToCRS, toEnvelope());
+        for (final GeneralEnvelope envelope : envelopes) {
+            complete(envelope, gridToCRS, gridToCRS != cornerToCRS, fallback);
         }
-        envelope = Envelopes.transform(cornerToCRS, envelope);
+        return envelopes;
+    }
+
+    /**
+     * If the envelope contains some NaN values, tries to replace them by 
constant values inferred from the math transform.
+     * We must use the {@link MathTransform} specified by the user ({@code 
gridToCRS}), not necessarily {@code cornerToCRS},
+     * because inferring a {@code cornerToCRS} by translating a {@code 
centerToCRS} by 0.5 cell increase the amount of NaN
+     * values in the matrix. For giving a chance to {@link TransformSeparator} 
to perform its work,
+     * we need the minimal amount of NaN values.
+     *
+     * @param  envelope   the envelope to complete if empty.
+     * @param  gridToCRS  the transform specified by user.
+     * @param  isCenter   whether the "grid to CRS" transform maps cell center 
instead of cell corners.
+     * @param  fallback   bounds to use if some values are still NaN after 
conversion, or {@code null} if none.
+     */
+    private void complete(final GeneralEnvelope envelope, final MathTransform 
gridToCRS, final boolean isCenter, final Envelope fallback) {
         if (envelope.isEmpty()) try {
-            /*
-             * If the envelope contains some NaN values, try to replace them 
by constant values inferred from the math transform.
-             * We must use the MathTransform specified by the user 
(gridToCRS), not necessarily the cornerToCRS transform, because
-             * inferring a `cornerToCRS` by translating a `centerToCRS` by 0.5 
cell increase the amount of NaN values in the matrix.
-             * For giving a chance to TransformSeparator to perform its work, 
we need the minimal amount of NaN values.
-             */
-            final boolean isCenter = (gridToCRS != cornerToCRS);
+            final int dimension = getDimension();
             TransformSeparator separator = null;
             for (int srcDim=0; srcDim < dimension; srcDim++) {
                 if (coordinates[srcDim + dimension] == 0 && 
coordinates[srcDim] == 0) {
@@ -1115,7 +1157,6 @@ public class GridExtent implements GridEnvelope, 
LenientComparable, Serializable
             // "toEnvelope" is the closest public method that may invoke this 
method.
             Logging.recoverableException(getLogger(Modules.RASTER), 
GridExtent.class, "toEnvelope", e);
         }
-        return envelope;
     }
 
     /**
@@ -1588,17 +1629,45 @@ public class GridExtent implements GridEnvelope, 
LenientComparable, Serializable
      * @return the intersection result. May be one of the existing instances.
      */
     final GridExtent intersect(final GridExtent other) {
+        return combine(other, false);
+    }
+
+    /**
+     * Returns the union of this grid extent with to the given grid extent.
+     * The given extent shall have the same number of dimensions.
+     *
+     * <p>This method is not public because we do not yet have a policy
+     * about whether we should verify if axis {@link #types} match.</p>
+     *
+     * @param  other  the grid to combine with.
+     * @return the union result. May be one of the existing instances.
+     */
+    final GridExtent union(final GridExtent other) {
+        return combine(other, true);
+    }
+
+    /**
+     * Implementation of {@link #union(GridExtent)} and {@link 
#intersect(GridExtent)}
+     */
+    private GridExtent combine(final GridExtent other, final boolean union) {
         final int n = coordinates.length;
         final int m = n >>> 1;
         final long[] clipped = new long[n];
         int i = 0;
-        while (i < m) {clipped[i] = Math.max(coordinates[i], 
other.coordinates[i]); i++;}
-        while (i < n) {clipped[i] = Math.min(coordinates[i], 
other.coordinates[i]); i++;}
+        while (i < m) {clipped[i] = extremum(coordinates[i], 
other.coordinates[i], !union); i++;}
+        while (i < n) {clipped[i] = extremum(coordinates[i], 
other.coordinates[i],  union); i++;}
         if (Arrays.equals(clipped,  this.coordinates)) return this;
         if (Arrays.equals(clipped, other.coordinates)) return other;
         return new GridExtent(this, clipped);
     }
 
+    /**
+     * Returns the minimum or maximum value between the given pair of values.
+     */
+    private static long extremum(final long a, final long b, final boolean 
max) {
+        return max ? Math.max(a, b) : Math.min(a, b);
+    }
+
     /**
      * Returns a hash value for this grid extent. This value needs not to 
remain
      * consistent between different implementations of the same class.
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
index 7d7c14fe8c..42613d6ce3 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridGeometry.java
@@ -465,7 +465,7 @@ public class GridGeometry implements LenientComparable, 
Serializable {
     {
         final GeneralEnvelope env;
         if (extent != null && cornerToCRS != null) {
-            env = extent.toCRS(cornerToCRS, specified, limits);
+            env = extent.toEnvelope(cornerToCRS, specified, limits);
             env.setCoordinateReferenceSystem(crs);
             if (limits != null) {
                 env.intersect(limits);
@@ -533,7 +533,8 @@ public class GridGeometry implements LenientComparable, 
Serializable {
             try {
                 env = Envelopes.transform(cornerToCRS.inverse(), envelope);
                 extent = new GridExtent(env, rounding, 
GridClippingMode.STRICT, null, null, null, null);
-                env = extent.toCRS(cornerToCRS, gridToCRS, envelope);     // 
`gridToCRS` specified by the user, not `this.gridToCRS`.
+                env = extent.toEnvelope(cornerToCRS, gridToCRS, envelope);
+                // Use `gridToCRS` specified by the user, not `this.gridToCRS`.
             } catch (TransformException e) {
                 throw new IllegalGridGeometryException(e, "gridToCRS");
             }
@@ -934,7 +935,7 @@ public class GridGeometry implements LenientComparable, 
Serializable {
                 clip = null;
             }
             MathTransform tr = MathTransforms.concatenate(cornerToCRS, 
op.getMathTransform());
-            final GeneralEnvelope env = extent.toCRS(tr, tr, clip);
+            final GeneralEnvelope env = extent.toEnvelope(tr, tr, clip);
             env.setCoordinateReferenceSystem(op.getTargetCRS());
             env.normalize();
             if (clip != null) {
@@ -1404,7 +1405,7 @@ public class GridGeometry implements LenientComparable, 
Serializable {
         ensureDimensionMatches(getDimension(), extent);
         final ImmutableEnvelope relocated;
         if (cornerToCRS != null) {
-            final GeneralEnvelope env = extent.toCRS(cornerToCRS, gridToCRS, 
null);
+            final GeneralEnvelope env = extent.toEnvelope(cornerToCRS, 
gridToCRS, null);
             
env.setCoordinateReferenceSystem(getCoordinateReferenceSystem(envelope));
             relocated = new ImmutableEnvelope(env);
         } else {
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
index 6df6d5d925..47cae92677 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ResampledGridCoverage.java
@@ -475,7 +475,7 @@ final class ResampledGridCoverage extends GridCoverage {
             final MathTransform crsToGrid, final boolean center) throws 
TransformException
     {
         final MathTransform sourceToTarget = 
MathTransforms.concatenate(cornerToCRS, crsToGrid);
-        final GeneralEnvelope bounds = source.toCRS(sourceToTarget, 
sourceToTarget, null);
+        final GeneralEnvelope bounds = source.toEnvelope(sourceToTarget, 
sourceToTarget, null);
         if (center) {
             final double[] vector = new double[bounds.getDimension()];
             Arrays.fill(vector, 0.5);
@@ -507,7 +507,7 @@ final class ResampledGridCoverage extends GridCoverage {
              * If a dimension can not be converted (e.g. because a `gridToCRS` 
transform has a NaN
              * factor in that dimension), the corresponding source grid 
coordinates will be copied.
              */
-            final GeneralEnvelope sourceBounds = 
sliceExtent.toCRS(toSourceCorner, toSourceCenter, null);
+            final GeneralEnvelope sourceBounds = 
sliceExtent.toEnvelope(toSourceCorner, toSourceCenter, null);
             final int dimension = sourceBounds.getDimension();
             if (sourceBounds.isEmpty()) {
                 final GridExtent se = source.gridGeometry.getExtent();
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java
index 18e56c8b0e..45276f1598 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/SliceGeometry.java
@@ -181,7 +181,7 @@ final class SliceGeometry implements 
Function<RenderedImage, GridGeometry> {
         }
         GeneralEnvelope subArea = null;
         if (useSubExtent && cornerToCRS != null) try {
-            subArea = extent.toCRS(cornerToCRS, gridToCRS, null);
+            subArea = extent.toEnvelope(cornerToCRS, gridToCRS, null);
         } catch (TransformException e) {
             // GridGeometry.reduce(…) is the public method invoking indirectly 
this method.
             GridGeometry.recoverableException("reduce", e);
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
 
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
index 54d27cf404..f13e80d1c9 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridDerivationTest.java
@@ -56,7 +56,7 @@ import static 
org.apache.sis.coverage.grid.GridGeometryTest.assertExtentEquals;
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Alexis Manin (Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 1.2
+ * @version 1.3
  * @since   1.0
  * @module
  */
@@ -628,6 +628,35 @@ public final strictfp class GridDerivationTest extends 
TestCase {
      */
     @Test
     public void testAntiMeridianCrossingInSubgrid() {
+        final GridExtent be = new GridExtent(null, new long[] {0, -64800}, new 
long[] {168000, 21600}, false);
+        final GridExtent qe = new GridExtent(256, 256);
+        final double bs = 350d / 168000;                    // We will use 
[-175 …  175]° of longitude.
+        final double qs =  10d / 256;                       // We will use 
[-182 … -172]° of longitude.
+        final AffineTransform2D bt = new AffineTransform2D(bs, 0, 0, -bs, 
-175, -45);
+        final AffineTransform2D qt = new AffineTransform2D(qs, 0, 0, -qs, 
-182,  90);
+        final GridGeometry base  = new GridGeometry(be, 
PixelInCell.CELL_CORNER, bt, HardCodedCRS.WGS84);
+        final GridGeometry query = new GridGeometry(qe, 
PixelInCell.CELL_CORNER, qt, HardCodedCRS.WGS84);
+        /*
+         * The [-182 … -172]° longitude range intersects [-175 …  175]° only 
in the [-175 … -172]° part.
+         * The [-182 … -175]° part becomes [178 … 185]° after wraparound, 
which still outside the base
+         * and should not be included in the intersection result.
+         */
+        GridGeometry result = base.derive().subgrid(query).build();
+        assertExtentEquals(new long[] {0, -3410}, new long[] {75, -3158}, 
result.getExtent());
+        assertEnvelopeEquals(new Envelope2D(null,
+                -175,           // Expected minimum value.
+                  80,           // Not interresting for this test.
+                -172 - -175,    // Expected maximum value minus minimum.
+                  90 -   80),
+                result.getEnvelope(), 0.02);
+    }
+
+    /**
+     * Tests deriving a grid geometry with a request crossing the antimeridian.
+     * The request uses an envelope instead of a {@link GridGeometry}.
+     */
+    @Test
+    public void testAntiMeridianCrossingInEnvelope() {
         final GridGeometry grid = new GridGeometry(
                 new GridExtent(200, 180), PixelInCell.CELL_CORNER,
                 MathTransforms.linear(new Matrix3(
diff --git 
a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
 
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
index 040ecc36e3..5bbdbc32ff 100644
--- 
a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
+++ 
b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
@@ -245,22 +245,41 @@ public final strictfp class GridExtentTest extends 
TestCase {
         assertFalse(extent.contains(100, 200, 39));
     }
 
+    /**
+     * Creates another arbitrary extent for tests of union and intersection.
+     */
+    private static GridExtent createOther() {
+        return new GridExtent(
+                new DimensionNameType[] {DimensionNameType.COLUMN, 
DimensionNameType.ROW, DimensionNameType.TIME},
+                new long[] {150, 220, 35}, new long[] {400, 820, 47}, false);
+    }
+
     /**
      * Tests {@link GridExtent#intersect(GridExtent)}.
      */
     @Test
     public void testIntersect() {
-        final GridExtent domain = new GridExtent(
-                new DimensionNameType[] {DimensionNameType.COLUMN, 
DimensionNameType.ROW, DimensionNameType.TIME},
-                new long[] {150, 220, 35}, new long[] {400, 820, 47}, false);
-        GridExtent extent = create3D();
-        extent = extent.intersect(domain);
+        final GridExtent domain = createOther();
+        final GridExtent extent = create3D().intersect(domain);
         assertExtentEquals(extent, 0, 150, 399);
         assertExtentEquals(extent, 1, 220, 799);
         assertExtentEquals(extent, 2, 40,  46);
         assertSame(extent.intersect(domain), extent);
     }
 
+    /**
+     * Tests {@link GridExtent#union(GridExtent)}.
+     */
+    @Test
+    public void testUnion() {
+        final GridExtent domain = createOther();
+        final GridExtent extent = create3D().union(domain);
+        assertExtentEquals(extent, 0, 100, 499);
+        assertExtentEquals(extent, 1, 200, 819);
+        assertExtentEquals(extent, 2, 35,  49);
+        assertSame(extent.union(domain), extent);
+    }
+
     /**
      * Tests {@link GridExtent#slice(DirectPosition, int[])}.
      */

Reply via email to