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

amanin pushed a commit to branch feat/resource-processor
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 211c9e7af62c33bff49a9ec6d65475a9bbc69f11
Author: Alexis Manin <alexis.ma...@geomatys.com>
AuthorDate: Mon Dec 5 18:09:20 2022 +0100

    feat(Feature+Storage): add a dimension selection grid coverage
---
 .../coverage/grid/DimensionSelectionCoverage.java  |  20 +++
 .../sis/coverage/grid/GridCoverageProcessor.java   |  49 +++++++
 .../coverage/grid/GridDimensionSelection.java      | 148 +++++++++++++++++++++
 .../sis/storage/DimensionSelectionResource.java    |  46 +++++++
 .../org/apache/sis/storage/ResourceProcessor.java  |  54 ++++++++
 .../apache/sis/storage/ResourceProcessorTest.java  |  47 +++++++
 6 files changed, 364 insertions(+)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DimensionSelectionCoverage.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DimensionSelectionCoverage.java
new file mode 100644
index 0000000000..6292b7d95d
--- /dev/null
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DimensionSelectionCoverage.java
@@ -0,0 +1,20 @@
+package org.apache.sis.coverage.grid;
+
+import java.awt.image.RenderedImage;
+import org.apache.sis.internal.coverage.grid.GridDimensionSelection;
+import org.opengis.coverage.CannotEvaluateException;
+
+class DimensionSelectionCoverage extends DerivedGridCoverage {
+    private final GridDimensionSelection.Specification spec;
+
+    DimensionSelectionCoverage(GridCoverage source, 
GridDimensionSelection.Specification spec) {
+        super(source, spec.getReducedGridGeometry());
+        this.spec = spec;
+    }
+
+    @Override
+    public RenderedImage render(GridExtent sliceExtent) throws 
CannotEvaluateException {
+        if (sliceExtent == null) return source.render(null);
+        else return source.render(spec.reverse(sliceExtent));
+    }
+}
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
index 9e207ab329..6c3b4f664d 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
@@ -26,6 +26,8 @@ import java.awt.Rectangle;
 import java.awt.image.ColorModel;
 import java.awt.image.RenderedImage;
 import javax.measure.Quantity;
+import org.apache.sis.internal.coverage.grid.GridDimensionSelection;
+import org.opengis.referencing.operation.MathTransform;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.datum.PixelInCell;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -504,6 +506,53 @@ public class GridCoverageProcessor implements Cloneable {
         return resample(source, new GridGeometry(null, 
PixelInCell.CELL_CENTER, null, target));
     }
 
+    /**
+     * Remove all "flat" grid dimensions from input data. Flat dimensions are 
dimensions with a single grid cell.
+     * @param source The coverage we want to reduce to lower dimension.
+     * @return Either input coverage if no dimension can be removed, or a view 
of the coverage with less dimensions.
+     */
+    public GridCoverage squeeze(GridCoverage source) {
+        return GridDimensionSelection.squeeze(source.getGridGeometry())
+                .<GridCoverage>map(spec -> new 
DimensionSelectionCoverage(source, spec))
+                .orElse(source);
+    }
+
+    /**
+     * Create a coverage containing only specified dimensions.
+     *
+     * Constraints:
+     * <ul>
+     *     <li>Removed dimensions must have only one degree of liberty.</li>
+     *     <li>Output dimension order is the same as in source coverage, 
whatever order axes are given as input.</li>
+     *     <li>If input dataset contains dimensions that are not separable, 
but only part of them are selected, this code will fail.</li>
+     * </ul>
+     *
+     * @param source The coverage to reduce to lower dimension.
+     * @param gridAxesToPreserve Index of each grid dimension to maintain in 
result. Must contain at least one element.
+     */
+    public GridCoverage selectDimensions(GridCoverage source, int... 
gridAxesToPreserve) {
+        final GridDimensionSelection.Specification spec = 
GridDimensionSelection.preserve(source.getGridGeometry(), gridAxesToPreserve);
+        return new DimensionSelectionCoverage(source, spec);
+    }
+
+    /**
+     * Create a coverage trimmed from specified <em>grid</em> dimensions.
+     *
+     * Constraints:
+     * <ul>
+     *     <li>Removed dimensions must have only one degree of liberty.</li>
+     *     <li>Output dimension order is the same as in source coverage.</li>
+     *     <li>If input dataset contains dimensions that are not separable, 
but only part of them are selected for removal, this code will fail.</li>
+     * </ul>
+     *
+     * @param source Dataset to reduce.
+     * @param gridAxesToRemove Index of each grid dimension to strip from 
result. Must contain at least one element.
+     */
+    public GridCoverage removeDimensions(GridCoverage source, int... 
gridAxesToRemove) {
+        final GridDimensionSelection.Specification spec = 
GridDimensionSelection.remove(source.getGridGeometry(), gridAxesToRemove);
+        return new DimensionSelectionCoverage(source, spec);
+    }
+
     /**
      * Invoked when an ignorable exception occurred.
      *
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/grid/GridDimensionSelection.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/grid/GridDimensionSelection.java
new file mode 100644
index 0000000000..00f3f5fe84
--- /dev/null
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/grid/GridDimensionSelection.java
@@ -0,0 +1,148 @@
+package org.apache.sis.internal.coverage.grid;
+
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.stream.IntStream;
+import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.referencing.operation.matrix.Matrices;
+import org.apache.sis.referencing.operation.matrix.MatrixSIS;
+import org.apache.sis.referencing.operation.transform.LinearTransform;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.Static;
+import org.apache.sis.util.Utilities;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
+
+import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
+import static org.opengis.referencing.datum.PixelInCell.CELL_CENTER;
+
+/**
+ * Provide utility methods to reduce/select dimensions of a grid-geometry, and 
provide an object holding information
+ * needed to travel between source to reduced space.
+ */
+public final class GridDimensionSelection extends Static {
+    private GridDimensionSelection() {}
+
+    public static Optional<Specification> squeeze(GridGeometry source) {
+        final int[] axesToPreserve = findUnsqueezableDimensions(source);
+        if (axesToPreserve.length == source.getExtent().getDimension()) return 
Optional.empty();
+        else if (axesToPreserve.length == 0) throw new 
IllegalArgumentException("All input grid dimensions are squeezable. Squeezing 
it would degenerate to a 0 dimension grid.");
+        else return Optional.of(preserve(source, axesToPreserve));
+    }
+
+    public static Specification remove(GridGeometry source, int... 
gridAxesToRemove) {
+        return preserve(source, reverse(source, gridAxesToRemove));
+    }
+
+    public static Specification preserve(GridGeometry source, int... 
gridAxesToPreserve) {
+        ensureNonNull("Source", source);
+        final GridExtent extent = source.getExtent();
+        ArgumentChecks.ensureNonEmpty("Grid axes to preserve", 
gridAxesToPreserve, 0, extent.getDimension(), true);
+        Arrays.sort(gridAxesToPreserve);
+        final GridGeometry reducedGeom = 
source.selectDimensions(gridAxesToPreserve);
+
+        final int sourceDim = extent.getDimension();
+        final int targetDim = gridAxesToPreserve.length;
+        int newSpaceIdx = 0;
+        final MatrixSIS mat = Matrices.create(sourceDim + 1, targetDim + 1, 
new double[Math.multiplyExact(sourceDim + 1, targetDim + 1)]);
+        mat.setElement(sourceDim, targetDim, 1.0);
+        for (int row = 0 ; row < sourceDim ; row++) {
+            if (Arrays.binarySearch(gridAxesToPreserve, row) >= 0) {
+                mat.setElement(row, newSpaceIdx++, 1.0);
+            } else {
+                mat.setElement(row, targetDim, extent.getLow(row));
+            }
+        }
+        final LinearTransform reducedToOrigin = MathTransforms.linear(mat);
+        return new Specification(reducedGeom, gridAxesToPreserve, 
reducedToOrigin, source);
+    }
+
+    public static class Specification {
+        private final GridGeometry reducedGridGeometry;
+        private final int[] gridAxesToPreserve;
+        private final LinearTransform rollbackAxes;
+        private final GridGeometry sourceGeometry;
+
+        public Specification(GridGeometry reducedGridGeometry, int[] 
gridAxesToPreserve, LinearTransform rollbackAxes, GridGeometry sourceGeometry) {
+            this.reducedGridGeometry = reducedGridGeometry;
+            this.gridAxesToPreserve = gridAxesToPreserve;
+            this.rollbackAxes = rollbackAxes;
+            this.sourceGeometry = sourceGeometry;
+        }
+
+        public GridGeometry getReducedGridGeometry() {
+            return reducedGridGeometry;
+        }
+
+        public int[] getGridAxesToPreserve() {
+            return gridAxesToPreserve.clone();
+        }
+
+        public LinearTransform getRollbackAxes() {
+            return rollbackAxes;
+        }
+
+        public GridGeometry getSourceGeometry() {
+            return sourceGeometry;
+        }
+
+        public GridExtent reverse(GridExtent extent) {
+            final GridExtent sourceExtent = sourceGeometry.getExtent();
+            final long[] newLow  = sourceExtent.getLow().getCoordinateValues();
+            final long[] newHigh = 
sourceExtent.getHigh().getCoordinateValues();
+            for (int i = 0 ; i < gridAxesToPreserve.length ; i++) {
+                int j = gridAxesToPreserve[i];
+                newLow[j] = extent.getLow(i);
+                newHigh[j] = extent.getHigh(i);
+            }
+            return new GridExtent(null, newLow, newHigh, true);
+        }
+
+        public GridGeometry reverse(GridGeometry domain) throws 
NoninvertibleTransformException {
+            if (domain.isDefined(GridGeometry.CRS) && 
!Utilities.equalsIgnoreMetadata(reducedGridGeometry.getCoordinateReferenceSystem(),
 domain.getCoordinateReferenceSystem())) {
+                throw new IllegalArgumentException("Input geometry CRS must 
match this specification CRS");
+            }
+
+            final MathTransform inflatedGridToCrs;
+            if (domain.isDefined(GridGeometry.GRID_TO_CRS)) {
+                inflatedGridToCrs = null;
+            } else if 
(Utilities.equalsIgnoreMetadata(domain.getGridToCRS(CELL_CENTER), 
reducedGridGeometry.getGridToCRS(CELL_CENTER))) {
+                inflatedGridToCrs = sourceGeometry.getGridToCRS(CELL_CENTER);
+            } else {
+                final MathTransform reducedToSource = 
MathTransforms.concatenate(
+                        
reducedGridGeometry.getGridToCRS(CELL_CENTER).inverse(),
+                        rollbackAxes,
+                        sourceGeometry.getGridToCRS(CELL_CENTER)
+                );
+
+                inflatedGridToCrs = MathTransforms.concatenate(
+                        rollbackAxes.inverse(),
+                        domain.getGridToCRS(CELL_CENTER),
+                        reducedToSource
+                );
+            }
+
+            final CoordinateReferenceSystem inflatedCrs = 
sourceGeometry.isDefined(GridGeometry.CRS) ? 
sourceGeometry.getCoordinateReferenceSystem() : null;
+            return new GridGeometry(reverse(domain.getExtent()), CELL_CENTER, 
inflatedGridToCrs, inflatedCrs);
+        }
+    }
+
+    private static int[] findUnsqueezableDimensions(GridGeometry sourceGeom) {
+        final GridExtent extent = sourceGeom.getExtent();
+        return IntStream.range(0, extent.getDimension())
+                .filter(i -> extent.getSize(i) > 1)
+                .toArray();
+    }
+
+    private static int[] reverse(GridGeometry source, int[] axes) {
+        final int[] sorted = axes.clone();
+        Arrays.sort(sorted);
+
+        return IntStream.range(0, source.getExtent().getDimension())
+                .filter(i -> Arrays.binarySearch(sorted, i) < 0)
+                .toArray();
+    }
+}
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/DimensionSelectionResource.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/DimensionSelectionResource.java
new file mode 100644
index 0000000000..d94691849c
--- /dev/null
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/DimensionSelectionResource.java
@@ -0,0 +1,46 @@
+package org.apache.sis.storage;
+
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.coverage.grid.GridCoverageProcessor;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.internal.coverage.grid.GridDimensionSelection;
+import org.apache.sis.internal.storage.DerivedGridCoverageResource;
+import org.apache.sis.util.collection.BackingStoreException;
+import org.opengis.referencing.operation.NoninvertibleTransformException;
+import org.opengis.util.GenericName;
+
+import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
+
+class DimensionSelectionResource extends DerivedGridCoverageResource {
+
+    private final GridCoverageProcessor processor;
+    private final GridDimensionSelection.Specification spec;
+
+    protected DimensionSelectionResource(GenericName name, 
GridCoverageResource source, GridDimensionSelection.Specification spec, 
GridCoverageProcessor processor) {
+        super(name, source);
+        ensureNonNull("Specification", spec);
+        this.spec = spec;
+        this.processor = processor;
+    }
+
+    @Override
+    public GridGeometry getGridGeometry() throws DataStoreException {
+        return spec.getReducedGridGeometry();
+    }
+
+    @Override
+    public GridCoverage read(GridGeometry domain, int... ranges) throws 
DataStoreException {
+        if (domain == null) domain = spec.getSourceGeometry();
+        else {
+            domain = 
spec.getReducedGridGeometry().derive().subgrid(domain).build();
+            try {
+                domain = spec.reverse(domain);
+            } catch (NoninvertibleTransformException e) {
+                throw new BackingStoreException("Cannot determine source 
geometry from reduced one", e);
+            }
+        }
+
+        final GridCoverage sourceData = source.read(domain, ranges);
+        return processor.selectDimensions(sourceData, 
spec.getGridAxesToPreserve());
+    }
+}
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/ResourceProcessor.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/ResourceProcessor.java
index bce74739e5..23a9a2219e 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/storage/ResourceProcessor.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/storage/ResourceProcessor.java
@@ -33,6 +33,7 @@ import org.apache.sis.coverage.grid.GridRoundingMode;
 import org.apache.sis.coverage.grid.IncompleteGridGeometryException;
 import org.apache.sis.image.DataType;
 import org.apache.sis.image.ImageProcessor;
+import org.apache.sis.internal.coverage.grid.GridDimensionSelection;
 import org.apache.sis.internal.storage.ConvertedCoverageResource;
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.measure.NumberRange;
@@ -171,6 +172,59 @@ public class ResourceProcessor implements Cloneable {
         return new BandAggregateGridResource(name, selections, userColors);
     }
 
+    /**
+     * Remove all "flat" grid dimensions from input data. Flat dimensions are 
dimensions with a single grid cell.
+     * @param resultName A name to affect to output coverage resource. If 
null, result will not have any identifier.
+     * @param source The coverage we want to reduce to lower dimension.
+     * @return Either input coverage if no dimension can be removed, or a view 
of the coverage with fewer dimensions.
+     * @see GridCoverageProcessor#squeeze(GridCoverage)
+     */
+    public GridCoverageResource squeeze(GenericName resultName, 
GridCoverageResource source) throws DataStoreException {
+        return GridDimensionSelection.squeeze(source.getGridGeometry())
+                .<GridCoverageResource>map(spec -> new 
DimensionSelectionResource(resultName, source, spec, processor))
+                .orElse(source);
+    }
+
+    /**
+     * Create a coverage containing only specified dimensions.
+     *
+     * Constraints:
+     * <ul>
+     *     <li>Removed dimensions must have only one degree of liberty.</li>
+     *     <li>Output dimension order is the same as in source coverage, 
whatever order axes are given as input.</li>
+     *     <li>If input dataset contains dimensions that are not separable, 
but only part of them are selected, this code will fail.</li>
+     * </ul>
+     *
+     * @param resultName A name to affect to output coverage resource. If 
null, result will not have any identifier.
+     * @param source The coverage to reduce to lower dimension.
+     * @param gridAxesToPreserve Index of each grid dimension to maintain in 
result. Must contain at least one element.
+     * @see GridCoverageProcessor#selectDimensions(GridCoverage, int...)
+     */
+    public GridCoverageResource selectDimensions(GenericName resultName, 
GridCoverageResource source, int... gridAxesToPreserve) throws 
DataStoreException {
+        final GridDimensionSelection.Specification spec = 
GridDimensionSelection.preserve(source.getGridGeometry(), gridAxesToPreserve);
+        return new DimensionSelectionResource(resultName, source, spec, 
processor);
+    }
+
+    /**
+     * Create a coverage trimmed from specified <em>grid</em> dimensions.
+     *
+     * Constraints:
+     * <ul>
+     *     <li>Removed dimensions must have only one degree of liberty.</li>
+     *     <li>Output dimension order is the same as in source coverage.</li>
+     *     <li>If input dataset contains dimensions that are not separable, 
but only part of them are selected for removal, this code will fail.</li>
+     * </ul>
+     *
+     * @param resultName A name to affect to output coverage resource. If 
null, result will not have any identifier.
+     * @param source Dataset to reduce.
+     * @param gridAxesToRemove Index of each grid dimension to strip from 
result. Must contain at least one element.
+     * @see GridCoverageProcessor#removeDimensions(GridCoverage, int...)
+     */
+    public GridCoverageResource removeDimensions(GenericName resultName, 
GridCoverageResource source, int... gridAxesToRemove) throws DataStoreException 
{
+        final GridDimensionSelection.Specification spec = 
GridDimensionSelection.remove(source.getGridGeometry(), gridAxesToRemove);
+        return new DimensionSelectionResource(resultName, source, spec, 
processor);
+    }
+
     private static Optional<GeographicBoundingBox> 
searchGeographicExtent(GridCoverageResource source) throws DataStoreException {
         final Optional<GeographicBoundingBox> bbox = 
source.getMetadata().getIdentificationInfo().stream()
                 .flatMap(it -> it.getExtents().stream())
diff --git 
a/storage/sis-storage/src/test/java/org/apache/sis/storage/ResourceProcessorTest.java
 
b/storage/sis-storage/src/test/java/org/apache/sis/storage/ResourceProcessorTest.java
index 59b4a4ef9d..95cb05aac3 100644
--- 
a/storage/sis-storage/src/test/java/org/apache/sis/storage/ResourceProcessorTest.java
+++ 
b/storage/sis-storage/src/test/java/org/apache/sis/storage/ResourceProcessorTest.java
@@ -15,12 +15,14 @@ import org.apache.sis.coverage.grid.GridCoverageProcessor;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridOrientation;
+import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.image.ImageProcessor;
 import org.apache.sis.image.Interpolation;
 import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
 import org.apache.sis.internal.storage.MemoryGridResource;
 import org.apache.sis.measure.Units;
 import org.apache.sis.referencing.crs.HardCodedCRS;
+import org.apache.sis.test.Assert;
 import org.apache.sis.test.TestCase;
 import org.apache.sis.util.iso.Names;
 import org.junit.Test;
@@ -174,6 +176,42 @@ public class ResourceProcessorTest extends TestCase {
         );
     }
 
+    @Test
+    public void testDimensionSelection() throws Exception {
+        final GridExtent extent4d = new GridExtent(null, new long[4], new 
long[]{2, 2, 1, 1}, false);
+        final GeneralEnvelope env4d = new 
GeneralEnvelope(HardCodedCRS.WGS84_4D);
+        env4d.setEnvelope(0, 1, 2, 3, 4, 5, 6, 7);
+        final GridGeometry domain4d = new GridGeometry(extent4d, env4d, 
GridOrientation.HOMOTHETY);
+        final GridCoverageResource data4d = grid1234(domain4d);
+
+        final GridCoverageResource squeezed = nearestInterpol().squeeze(null, 
data4d);
+        final GridGeometry squeezedDomain = squeezed.getGridGeometry();
+        assertEquals("Only 2 dimensions should remain", 2, 
squeezedDomain.getDimension());
+        Assert.assertEqualsIgnoreMetadata(HardCodedCRS.WGS84, 
squeezedDomain.getCoordinateReferenceSystem());
+        GridCoverage loaded = squeezed.read(null);
+        assertEquals(squeezedDomain, loaded.getGridGeometry());
+        int[] values = loaded.render(null).getData().getPixels(0, 0, 2, 2, 
(int[]) null);
+        assertArrayEquals(new int[] { 1, 2, 3, 4 }, values);
+
+        final GridCoverageResource selected = 
nearestInterpol().selectDimensions(null, data4d, 0, 1, 3);
+        final GridGeometry selectedDomain = selected.getGridGeometry();
+        assertEquals("Only 3 dimensions should remain", 3, 
selectedDomain.getDimension());
+        Assert.assertEqualsIgnoreMetadata(HardCodedCRS.WGS84_WITH_TIME, 
selectedDomain.getCoordinateReferenceSystem());
+        loaded = selected.read(null);
+        assertEquals(selectedDomain, loaded.getGridGeometry());
+        values = loaded.render(null).getData().getPixels(0, 0, 2, 2, (int[]) 
null);
+        assertArrayEquals(new int[] { 1, 2, 3, 4 }, values);
+
+        final GridCoverageResource removed = 
nearestInterpol().removeDimensions(null, data4d, 3);
+        final GridGeometry removedDomain = removed.getGridGeometry();
+        assertEquals("Only 3 dimensions should remain", 3, 
removedDomain.getDimension());
+        Assert.assertEqualsIgnoreMetadata(HardCodedCRS.WGS84_3D, 
removedDomain.getCoordinateReferenceSystem());
+        loaded = removed.read(null);
+        assertEquals(removedDomain, loaded.getGridGeometry());
+        values = loaded.render(null).getData().getPixels(0, 0, 2, 2, (int[]) 
null);
+        assertArrayEquals(new int[] { 1, 2, 3, 4 }, values);
+    }
+
     private static GridCoverageResource singleValuePerBand(int... bandValues) {
         GridGeometry domain = new GridGeometry(new GridExtent(2, 2), 
PixelInCell.CELL_CENTER, identity(2), HardCodedCRS.WGS84);
         final List<SampleDimension> samples = IntStream.of(bandValues)
@@ -195,6 +233,15 @@ public class ResourceProcessorTest extends TestCase {
      */
     private static GridCoverageResource grid1234() {
         GridGeometry domain = new GridGeometry(new GridExtent(2, 2), 
PixelInCell.CELL_CENTER, identity(2), HardCodedCRS.WGS84);
+        return grid1234(domain);
+    }
+
+    /**
+     * Same as {@link #grid1234()}, but allow to override output domain, 
mostly to allow additional flat dimensions.
+     *
+     * @param domain A 2D+ domain whose x and y axes (rendering axes) are 2 
cells each.
+     */
+    private static GridCoverageResource grid1234(GridGeometry domain) {
         SampleDimension band = new SampleDimension.Builder()
                 .setBackground(0)
                 .addQuantitative("1-based row-major order pixel number", 1, 5, 
1, 0, Units.UNITY)

Reply via email to