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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 575a9ed5be Add a `GridDerivation.project(Predicate)` method for 
filtering the CRS axes of a grid geometry.
575a9ed5be is described below

commit 575a9ed5be3bd8a274d2f1e9b77c5e6e8f4da3ea
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Jan 9 18:59:56 2026 +0100

    Add a `GridDerivation.project(Predicate)` method for filtering the CRS axes 
of a grid geometry.
---
 .../apache/sis/coverage/grid/DimensionReducer.java | 105 +++++++++++++++++---
 .../sis/coverage/grid/DimensionalityReduction.java |   2 +-
 .../apache/sis/coverage/grid/GridDerivation.java   | 106 +++++++++++++++------
 .../org/apache/sis/coverage/grid/GridGeometry.java |  31 +++---
 .../sis/coverage/grid/GridDerivationTest.java      |  25 +++++
 5 files changed, 213 insertions(+), 56 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionReducer.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionReducer.java
index 7f9bed10b1..daa2eebcc5 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionReducer.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionReducer.java
@@ -21,40 +21,52 @@ import org.opengis.util.FactoryException;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.internal.shared.AxisDirections;
+import org.apache.sis.referencing.operation.transform.TransformSeparator;
 import org.apache.sis.geometry.GeneralDirectPosition;
 import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.geometry.ImmutableEnvelope;
 
 
 /**
  * An helper class for reducing the number of dimensions in a grid geometry, 
an envelope or a position.
  * This is used when the Area Of Interest has more dimensions than the grid 
geometry, in which case the
- * transform from grid to AOI will fail if we do not discard the extra 
dimensions.
+ * transform from grid to <abbr>AOI</abbr> will fail if we do not discard the 
extra dimensions.
  *
- * <p>This class works on the CRS dimensions.
+ * <p>This class works on the <abbr>CRS</abbr> dimensions.
  * This is different than {@link DimensionalityReduction}, which works on grid 
dimensions.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
 final class DimensionReducer {
     /**
-     * The dimensions to keep, or {@code null} for keeping them all.
+     * The <abbr>CRS</abbr> dimensions to keep, or {@code null} for keeping 
them all.
      */
     private int[] dimensions;
 
     /**
-     * The CRS with only the {@linkplain #dimensions} to keep, or {@code null} 
if no reduction.
+     * The <abbr>CRS</abbr> with only the {@linkplain #dimensions} to keep, or 
{@code null} if no reduction.
      */
     private CoordinateReferenceSystem reducedCRS;
 
     /**
-     * Creates a helper which will retain only the {@code targetCRS} 
dimensions that are found in the base grid.
+     * Requests to retain only the axes in the specified <abbr>CRS</abbr> 
dimensions.
+     *
+     * @param  dimensions  the <abbr>CRS</abbr> dimensions to keep, or {@code 
null} for keeping them all.
+     */
+    DimensionReducer(final int... dimensions) {
+        this.dimensions = dimensions;
+    }
+
+    /**
+     * Requests to retain only the {@code targetCRS} dimensions that are found 
in the base grid.
      * This will be used by caller for creating a valid {@code sourceCRS} to 
{@code targetCRS} transform.
      *
      * @param  base       the grid geometry which will be derived. Cannot be 
null.
-     * @param  targetCRS  CRS of the area or point of interest. Cannot be null.
+     * @param  targetCRS  <abbr>CRS</abbr> of the area or point of interest. 
Cannot be null.
      */
     DimensionReducer(final GridGeometry base, final CoordinateReferenceSystem 
targetCRS) throws FactoryException {
         if (base != null && base.envelope != null) {
@@ -74,14 +86,29 @@ final class DimensionReducer {
     }
 
     /**
-     * Applies reduction on the given position.
+     * Applies dimension reduction on the given array of resolutions.
+     * If the resolution cannot be reduced, then it is returned as-is.
+     */
+    private double[] apply(final double[] target) {
+        if (target == null || dimensions == null) {
+            return target;
+        }
+        final var resolution = new double[dimensions.length];
+        for (int i=0; i < resolution.length; i++) {
+            resolution[i] = target[dimensions[i]];
+        }
+        return resolution;
+    }
+
+    /**
+     * Applies dimension reduction on the given position.
      * If the position cannot be reduced, then it is returned as-is.
      */
     final DirectPosition apply(final DirectPosition target) {
-        if (dimensions == null) {
+        if (target == null || dimensions == null) {
             return target;
         }
-        final GeneralDirectPosition position = new 
GeneralDirectPosition(reducedCRS);
+        final var position = new GeneralDirectPosition(reducedCRS);
         for (int i=0; i < dimensions.length; i++) {
             position.coordinates[i] = target.getCoordinate(dimensions[i]);
         }
@@ -89,20 +116,74 @@ final class DimensionReducer {
     }
 
     /**
-     * Applies reduction on the given envelope.
+     * Applies dimension reduction on the given envelope.
      * If the envelope cannot be reduced, then it is returned as-is.
      */
     final Envelope apply(final Envelope target) {
-        if (dimensions == null) {
+        if (target == null || dimensions == null) {
             return target;
         }
         final DirectPosition lowerCorner = target.getLowerCorner();
         final DirectPosition upperCorner = target.getUpperCorner();
-        final GeneralEnvelope envelope = new GeneralEnvelope(reducedCRS);
+        final var envelope = new GeneralEnvelope(reducedCRS);
         for (int i=0; i < dimensions.length; i++) {
             final int s = dimensions[i];
             envelope.setRange(i, lowerCorner.getCoordinate(s), 
upperCorner.getCoordinate(s));
         }
         return envelope;
     }
+
+    /**
+     * Applies dimension reduction on the given grid geometry.
+     * It may cause a reduction in the number of dimensions of the grid.
+     * If the grid cannot be reduced, then it is returned as-is.
+     */
+    final GridGeometry apply(final GridGeometry target) throws 
FactoryException {
+        if (target == null || dimensions == null) {
+            return target;
+        }
+        /*
+         * Select the dimension to keep in the "grid to CRS" transform. We 
repeat this operation for the
+         * "corner to CRS" transform rather than deriving the latter from the 
former because we don't
+         * know in advance which one of the two transforms is more accurate 
(has less NaN values).
+         * This separation determines the dimensions to keep in the grid.
+         */
+        int[] gridIndices = null;
+        MathTransform gridToCRS = target.gridToCRS;
+        if (gridToCRS != null) {
+            var ts = new TransformSeparator(gridToCRS);
+            ts.addTargetDimensions(dimensions);
+            gridToCRS = ts.separate();
+            gridIndices = ts.getSourceDimensions();
+        }
+        MathTransform cornerToCRS = target.cornerToCRS;
+        if (cornerToCRS != null) {
+            var ts = new TransformSeparator(cornerToCRS);
+            ts.addTargetDimensions(dimensions);
+            if (gridIndices != null) {
+                ts.addSourceDimensions(gridIndices);
+                cornerToCRS = ts.separate();
+            } else {
+                cornerToCRS = ts.separate();
+                gridIndices = ts.getSourceDimensions();
+            }
+        }
+        /*
+         * Reduces the number of dimensions of the extent, envelope and 
resolution.
+         */
+        GridExtent extent = target.extent;
+        if (extent != null && gridIndices != null) {
+            extent = extent.selectDimensions(gridIndices);
+        }
+        reducedCRS = 
CRS.selectDimensions(target.getCoordinateReferenceSystem(), dimensions);
+        final Envelope envelope = apply(target.envelope);
+        final double[] resolution = apply(target.resolution);
+        long nonLinears = 0;
+        for (int i=0; i < dimensions.length; i++) {
+            if ((target.nonLinears & (1L << dimensions[i])) != 0) {
+                nonLinears |= (1L << i);
+            }
+        }
+        return new GridGeometry(extent, gridToCRS, cornerToCRS, 
ImmutableEnvelope.castOrCopy(envelope), resolution, nonLinears);
+    }
 }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionalityReduction.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionalityReduction.java
index f89da839af..7b2b4969af 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionalityReduction.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DimensionalityReduction.java
@@ -681,7 +681,7 @@ public class DimensionalityReduction implements 
UnaryOperator<GridCoverage>, Ser
 
     /**
      * Returns a grid geometry on which dimensionality reduction of the grid 
extent has been applied.
-     * It usually implies a reduction in the number of dimensions of the CRS 
as well,
+     * It usually implies a reduction in the number of dimensions of the 
<abbr>CRS</abbr> as well,
      * but not necessarily in same order.
      *
      * <p>If the given source is {@code null}, then this method returns {@code 
null}.
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
index 00e8185163..e4840736a9 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
@@ -17,9 +17,12 @@
 package org.apache.sis.coverage.grid;
 
 import java.util.Map;
+import java.util.Set;
+import java.util.BitSet;
 import java.util.Arrays;
 import java.util.Locale;
 import java.util.Objects;
+import java.util.function.Predicate;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.math.RoundingMode;
@@ -30,6 +33,8 @@ import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.cs.CoordinateSystemAxis;
+import org.opengis.referencing.cs.CoordinateSystem;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.transform.LinearTransform;
@@ -76,14 +81,14 @@ import org.opengis.coverage.PointOutsideCoverageException;
  * The {@link #getIntersection()} method can also be invoked for the {@link 
GridExtent} part without subsampling.
  *
  * <h2>Multi-dimensional grids</h2>
- * All methods in this class preserve the number of dimensions. For example, 
the {@link #slice(DirectPosition)} method
- * sets the {@linkplain GridExtent#getSize(int) grid size} to 1 in all 
dimensions specified by the <i>slice point</i>,
- * but does not remove those dimensions from the grid geometry.
+ * Unless specified otherwise in Javadoc, the methods in this class preserve 
the number of dimensions.
+ * For example, the {@link #slice(DirectPosition)} method sets the {@linkplain 
GridExtent#getSize(int) grid size} to 1
+ * in all dimensions specified by the <i>slice point</i>, but does not remove 
those dimensions from the grid geometry.
  * For dimensionality reduction, see {@link 
GridGeometry#selectDimensions(int[])}.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Alexis Manin (Geomatys)
- * @version 1.5
+ * @version 1.6
  *
  * @see GridGeometry#derive()
  * @see GridGeometry#selectDimensions(int[])
@@ -224,6 +229,14 @@ public class GridDerivation {
      */
     private GeneralEnvelope intersection;
 
+    /**
+     * Indexes of <abbr>CRS</abbr> axes to keep, or {@code null} if no 
filtering will be applied.
+     * A non-null value may cause a reduction in the number of dimensions of 
the grid.
+     *
+     * @see #project(Set)
+     */
+    private BitSet dimensionsToKeepInCRS;
+
     /**
      * Creates a new builder for deriving a grid geometry from the specified 
base.
      *
@@ -1188,12 +1201,40 @@ public class GridDerivation {
         return this;
     }
 
-    /*
-     * RATIONAL FOR NOT PROVIDING reduce(int... dimensions) METHOD HERE: that 
method would need to be the last method invoked,
-     * otherwise it makes more complicated to implement other methods in this 
class.  Forcing users to invoke `build()` before
-     * (s)he can invoke GridGeometry.reduce(…) makes that clear and avoid the 
need for more flags in this GridDerivation class.
-     * Furthermore, declaring the `reduce(…)` method in GridGeometry is more 
consistent with `GridExtent.reduce(…)`.
+    /**
+     * Requests a projection where only a subset of the <abbr>CRS</abbr> 
dimensions will be kept.
+     * The real world dimensions to keep are specified by a filter applied on 
the coordinate system axes.
+     * This method may reduce the number of dimensions of the grid if, as a 
result of this filtering,
+     * some grid dimensions become unrelated to any <abbr>CRS</abbr> axis.
+     *
+     * <h4>Example</h4>
+     * The following code removes the temporal dimension of a grid geometry:
+     *
+     * {@snippet lang="java" :
+     *     gridGeometry.derive().project((axis) -> axis.getDirection() != 
AxisDirection.FUTURE).build();
+     *     }
+     *
+     * @param  filter  a predicate which returns {@code true} for coordinate 
system axes to keep.
+     * @return {@code this} for method call chaining.
+     * @throws IncompleteGridGeometryException if the base grid geometry has 
no <abbr>CRS</abbr>.
+     *
+     * @since 1.6
      */
+    public GridDerivation project(final Predicate<CoordinateSystemAxis> 
filter) {
+        final var dimensions = new BitSet();
+        final CoordinateSystem cs = 
base.getCoordinateReferenceSystem().getCoordinateSystem();
+        for (int i = cs.getDimension(); --i >= 0;) {
+            if (filter.test(cs.getAxis(i))) {
+                dimensions.set(i);
+            }
+        }
+        if (dimensionsToKeepInCRS == null) {
+            dimensionsToKeepInCRS = dimensions;
+        } else {
+            dimensionsToKeepInCRS.and(dimensions);
+        }
+        return this;
+    }
 
     /**
      * Builds a grid geometry with the configuration specified by the other 
methods in this {@code GridDerivation} class.
@@ -1229,31 +1270,37 @@ public class GridDerivation {
          * need for envelope clipping performed by GridGeometry constructor.
          */
         final GridExtent extent = (scaledExtent != null) ? scaledExtent : 
getBaseExtentExpanded(false);
+        GridGeometry grid = base;
         try {
-            if (toBase != null || extent != base.extent) {
-                return new GridGeometry(base, extent, toBase);
-            }
-            /*
-             * Intersection should be non-null only if we have not been able 
to compute more reliable properties
-             * (grid extent and "grid to CRS" transform). It should happen 
only if `gridToCRS` is null, but we
-             * nevertheless pass that transform to the constructor as a matter 
of principle.
-             */
-            if (intersection != null) {
-                return new GridGeometry(PixelInCell.CELL_CENTER, 
base.gridToCRS, intersection, rounding);
-            }
-            /*
-             * Case when the only settings were a margin or a chunk size. It 
is okay to test after `intersection`
-             * because a non-null envelope intersection would have meant that 
this `GridDerivation` does not have
-             * required information for applying a margin anyway (no 
`GridExtent` and no `gridToCRS`).
-             */
-            final GridExtent resized = getBaseExtentExpanded(false);
-            if (resized != baseExtent) {
-                return new GridGeometry(base, resized, null);
+            if (toBase != null || extent != grid.extent) {
+                grid = new GridGeometry(grid, extent, toBase);
+            } else if (intersection != null) {
+                /*
+                 * Intersection should be non-null only if we have not been 
able to compute more reliable properties
+                 * (grid extent and "grid to CRS" transform). It should happen 
only if `gridToCRS` is null, but we
+                 * nevertheless pass that transform to the constructor as a 
matter of principle.
+                 */
+                grid = new GridGeometry(PixelInCell.CELL_CENTER, 
grid.gridToCRS, intersection, rounding);
+            } else {
+                /*
+                 * Case when the only settings were a margin or a chunk size. 
It is okay to test after `intersection`
+                 * because a non-null envelope intersection would have meant 
that this `GridDerivation` does not have
+                 * required information for applying a margin anyway (no 
`GridExtent` and no `gridToCRS`).
+                 */
+                final GridExtent resized = getBaseExtentExpanded(false);
+                if (resized != baseExtent) {
+                    grid = new GridGeometry(grid, resized, null);
+                }
             }
         } catch (TransformException e) {
             throw new IllegalGridGeometryException(e, "envelope");
         }
-        return base;
+        if (dimensionsToKeepInCRS != null) try {
+            grid = new 
DimensionReducer(dimensionsToKeepInCRS.stream().toArray()).apply(grid);
+        } catch (FactoryException e) {
+            throw new IllegalGridGeometryException(e, "gridToCRS");
+        }
+        return grid;
     }
 
     /**
@@ -1315,6 +1362,7 @@ public class GridDerivation {
      * the scale factors on the diagonal are the {@linkplain #getSubsampling() 
subsampling values} and the
      * translation terms in the last column are the {@linkplain 
#getSubsamplingOffsets() subsampling offsets}.
      *
+     * @param  anchor  whether to map the center or the corner of source and 
target cells.
      * @return transform of <em>grid</em> coordinates from the derived grid to 
the {@linkplain #base} grid.
      *
      * @see #getSubsampling()
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
index 6aeef85771..3f8e687a46 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
@@ -227,6 +227,7 @@ public class GridGeometry implements LenientComparable, 
Serializable {
      * to real world coordinates, with the lower coordinates inclusive and the 
upper coordinates exclusive.
      * The Coordinate Reference System (CRS) of this envelope defines the 
"real world" CRS of this grid geometry.
      *
+     * @see #CRS
      * @see #ENVELOPE
      * @see #getEnvelope()
      */
@@ -237,7 +238,7 @@ public class GridGeometry implements LenientComparable, 
Serializable {
      * If non-null, the conversion shall map {@linkplain 
PixelInCell#CELL_CENTER cell center}.
      * This conversion is usually, but not necessarily, affine.
      *
-     * @see #CRS
+     * @see #GRID_TO_CRS
      * @see #getGridToCRS(PixelInCell)
      * @see PixelInCell#CELL_CENTER
      */
@@ -247,18 +248,21 @@ public class GridGeometry implements LenientComparable, 
Serializable {
     /**
      * Same conversion as {@link #gridToCRS} but from {@linkplain 
PixelInCell#CELL_CORNER cell corner}
      * instead of center. This transform is preferable to {@code gridToCRS} 
for transforming envelopes.
+     * This field may be more accurate than deriving this transform from 
{@link #gridToCRS}.
      *
-     * @serial This field is serialized because it may be a value specified 
explicitly at construction time,
-     *         in which case it can be more accurate than a computed value.
+     * @see #GRID_TO_CRS
+     * @see #getGridToCRS(PixelInCell)
+     * @see PixelInCell#CELL_CORNER
+     * @since 1.6
      */
     @SuppressWarnings("serial")         // Most SIS implementations are 
serializable.
-    final MathTransform cornerToCRS;
+    protected final MathTransform cornerToCRS;
 
     /**
      * An <em>estimation</em> of the grid resolution, in units of the CRS axes.
      * Computed from {@link #gridToCRS}, eventually together with {@link 
#extent}.
      * May be {@code null} if unknown. If non-null, the array length is equal 
to
-     * the number of CRS dimensions.
+     * the number of <abbr>CRS</abbr> dimensions.
      *
      * @see #RESOLUTION
      * @see #getResolution(boolean)
@@ -266,7 +270,7 @@ public class GridGeometry implements LenientComparable, 
Serializable {
     protected final double[] resolution;
 
     /**
-     * Whether the conversions from grid coordinates to the CRS are linear, 
for each target axis.
+     * Whether the conversions from grid coordinates to the <abbr>CRS</abbr> 
are linear, for each target axis.
      * The bit located at {@code 1L << dimension} is set to 1 when the 
conversion at that dimension is non-linear.
      * The dimension indices are those of the CRS, not the grid. The use of 
{@code long} type limits the capacity
      * to 64 dimensions. But actually {@code GridGeometry} can contain more 
dimensions provided that index of the
@@ -1704,22 +1708,21 @@ public class GridGeometry implements LenientComparable, 
Serializable {
 
     /**
      * Returns a grid geometry that encompass only some dimensions of this 
grid geometry.
-     * The specified dimensions will be copied into a new grid geometry if 
necessary.
-     * The selection is applied on {@linkplain #getExtent() grid extent} 
dimensions;
-     * they are not necessarily the same as the {@linkplain #getEnvelope() 
envelope} dimensions.
+     * The selection is applied on {@linkplain #getExtent() grid extent} 
dimensions,
+     * which are not necessarily the same as the {@linkplain #getEnvelope() 
envelope} dimensions.
      * The given dimensions must be in strictly ascending order without 
duplicated values.
-     * The number of dimensions of the sub grid geometry will be {@code 
dimensions.length}.
+     * The number of dimensions of the returned grid geometry will be {@code 
dimensions.length}.
      *
      * <p>This method performs a <i>dimensionality reduction</i>.
-     * This method cannot be used for changing dimension order.
-     * The converse operation is the {@linkplain #GridGeometry(GridGeometry, 
GridGeometry) concatenation}.</p>
+     * The converse of this operation is {@linkplain 
#GridGeometry(GridGeometry, GridGeometry) concatenation}.
+     * This method cannot be used for changing dimension order.</p>
      *
-     * @param  indices  the grid (not CRS) dimensions to select, in strictly 
increasing order.
+     * @param  indices  the grid (not <abbr>CRS</abbr>) dimensions to select, 
in strictly increasing order.
      * @return the sub-grid geometry, or {@code this} if the given array 
contains all dimensions of this grid geometry.
      * @throws IndexOutOfBoundsException if an index is out of bounds.
      *
-     * @see GridExtent#getSubspaceDimensions(int)
      * @see GridExtent#selectDimensions(int[])
+     * @see GridExtent#getSubspaceDimensions(int)
      * @see 
org.apache.sis.referencing.CRS#selectDimensions(CoordinateReferenceSystem, 
int[])
      *
      * @since 1.3
diff --git 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
index ecc073aad5..582e7566ad 100644
--- 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
+++ 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/GridDerivationTest.java
@@ -17,11 +17,13 @@
 package org.apache.sis.coverage.grid;
 
 import java.util.Map;
+import java.util.function.Predicate;
 import java.util.stream.DoubleStream;
 import java.util.stream.IntStream;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.Envelope;
 import org.opengis.metadata.spatial.DimensionNameType;
+import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
@@ -29,6 +31,7 @@ import org.apache.sis.geometry.Envelope2D;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.geometry.DirectPosition2D;
 import org.apache.sis.geometry.GeneralDirectPosition;
+import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.referencing.internal.shared.Formulas;
 import org.apache.sis.referencing.internal.shared.AffineTransform2D;
@@ -735,6 +738,28 @@ public final class GridDerivationTest extends TestCase {
         assertExtentEquals(expectedGridPoint, expectedGridPoint, slice.extent);
     }
 
+    /**
+     * Tests {@link GridDerivation#project(Predicate)}.
+     */
+    @Test
+    public void testProject() {
+        final var grid = new GridGeometry(
+                new GridExtent(null, new long[] {336, 20, 4}, new long[] {401, 
419, 10}, true),
+                PixelInCell.CELL_CORNER, MathTransforms.linear(new Matrix4(
+                        0,   0.5, 0,  -90,
+                        0.5, 0,   0, -180,
+                        0,   0,   2,    3,
+                        0,   0,   0,    1)), HardCodedCRS.WGS84_3D);
+
+        GridGeometry projected = grid.derive().project((axis) -> 
axis.getDirection() != AxisDirection.UP).build();
+        assertNotSame(grid, projected);
+        assertEquals(2, projected.getDimension());
+        assertTrue(CRS.equivalent(HardCodedCRS.WGS84, 
projected.getCoordinateReferenceSystem()));
+        final long[] expectedLow  = {336,  20};
+        final long[] expectedHigh = {401, 419};
+        assertExtentEquals(expectedLow, expectedHigh, projected.getExtent());
+    }
+
     /**
      * Tests deriving a grid geometry with a request crossing the antimeridian.
      * The {@link GridGeometry} crossing the anti-meridian is the one given in

Reply via email to