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 85172cf8d6d2f48dca9d5c78a8251a71ec0d917a
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Aug 8 17:57:21 2025 +0200

    Add a clip operation in `GridCoverageProcessor` working on grid coordinates.
---
 .../coverage/grid/BandAggregateGridCoverage.java   |   2 +-
 .../sis/coverage/grid/ClippedGridCoverage.java     | 193 ++++++++++++++++++++
 .../apache/sis/coverage/grid/DefaultEvaluator.java |   3 +-
 .../sis/coverage/grid/DerivedGridCoverage.java     |   2 +-
 .../org/apache/sis/coverage/grid/GridCoverage.java |   4 +-
 .../apache/sis/coverage/grid/GridCoverage2D.java   |   2 +-
 .../sis/coverage/grid/GridCoverageProcessor.java   |  34 +++-
 .../sis/coverage/grid/TranslatedGridCoverage.java  |   9 +-
 .../sis/coverage/grid/ClippedGridCoverageTest.java | 202 +++++++++++++++++++++
 .../coverage/grid/TranslatedGridCoverageTest.java  |   4 +-
 netbeans-project/nbproject/project.xml             |   1 +
 11 files changed, 440 insertions(+), 16 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/BandAggregateGridCoverage.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/BandAggregateGridCoverage.java
index c51ab5cc97..52fbe4393e 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/BandAggregateGridCoverage.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/BandAggregateGridCoverage.java
@@ -299,7 +299,7 @@ final class BandAggregateGridCoverage extends GridCoverage {
                     offset += values.length;
                 } else {
                     for (int b : bands) {
-                        values[offset++] = values[b];
+                        aggregate[offset++] = values[b];
                     }
                 }
             }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ClippedGridCoverage.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ClippedGridCoverage.java
new file mode 100644
index 0000000000..a81b2c6286
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ClippedGridCoverage.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.coverage.grid;
+
+import java.util.Map;
+import java.awt.image.RenderedImage;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.util.CorruptedObjectException;
+import org.apache.sis.image.PlanarImage;
+import org.apache.sis.image.privy.ReshapedImage;
+
+
+/**
+ * A grid coverage for a subset of the data as the source coverage.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+final class ClippedGridCoverage extends DerivedGridCoverage {
+    /**
+     * The coordinates of the slice, or {@code null} if same as the source 
coverage.
+     *
+     * @see #evaluator()
+     */
+    private final Map<Integer,Long> defaultSlice;
+
+    /**
+     * Constructs a new grid coverage which will delegate the rendering 
operation to the given source.
+     * This coverage will take the same sample dimensions as the source.
+     *
+     * @param  source  the source to which to delegate rendering operations.
+     * @param  domain  the grid extent, CRS and conversion from cell indices 
to CRS.
+     */
+    private ClippedGridCoverage(final GridCoverage source, final GridGeometry 
domain) {
+        super(source, domain);
+        final Map<Integer,Long> c = domain.getExtent().getSliceCoordinates();
+        if 
(c.equals(source.getGridGeometry().getExtent().getSliceCoordinates())) {
+            defaultSlice = null;
+        } else {
+            defaultSlice = Map.copyOf(c);
+        }
+    }
+
+    /**
+     * Returns a grid coverage clipped to the given extent.
+     *
+     * @param  source  the source to which to delegate rendering operations.
+     * @param  clip    the clip to apply in units of source grid coordinates.
+     * @return the clipped coverage. May be the {@code source} returned as-is.
+     * @throws IncompleteGridGeometryException if the given coverage has no 
grid extent.
+     * @throws DisjointExtentException if the given extent does not intersect 
the given coverage.
+     * @throws IllegalArgumentException if axes of the given extent are 
inconsistent with the axes of the grid of the given coverage.
+     */
+    static GridCoverage create(GridCoverage source, final GridExtent clip, 
final boolean allowSourceReplacement) {
+        GridGeometry gridGeometry = source.getGridGeometry();
+        GridExtent extent = gridGeometry.getExtent();
+        if (extent == (extent = extent.intersect(clip))) {
+            return source;
+        }
+        if (allowSourceReplacement) {
+            while (source instanceof ClippedGridCoverage) {
+                source = ((ClippedGridCoverage) source).source;
+                if (extent.equals(source.gridGeometry.extent)) {
+                    return source;
+                }
+            }
+        }
+        try {
+            gridGeometry = new GridGeometry(gridGeometry, extent, null);
+        } catch (TransformException e) {
+            // Unable to transform an envelope which was successfully 
transformed before.
+            // If it happens, assume that something wrong happened with the 
objects.
+            throw new CorruptedObjectException(e);
+        }
+        return new ClippedGridCoverage(source, gridGeometry);
+    }
+
+    /**
+     * Returns a grid coverage that contains real values or sample values, 
depending if {@code converted}
+     * is {@code true} or {@code false} respectively. This method delegates to 
the source and wraps the
+     * result in a {@link ClippedGridCoverage} with the same extent.
+     */
+    @Override
+    protected final GridCoverage createConvertedValues(final boolean 
converted) {
+        final GridCoverage c = source.forConvertedValues(converted);
+        return (c == source) ? this : new ClippedGridCoverage(c, gridGeometry);
+    }
+
+    /**
+     * Returns a two-dimensional slice of grid data as a rendered image.
+     */
+    @Override
+    public RenderedImage render(GridExtent sliceExtent) {
+        final GridExtent clipped;
+        if (sliceExtent == null) {
+            clipped = sliceExtent = gridGeometry.extent;
+        } else {
+            clipped = sliceExtent.intersect(gridGeometry.extent);
+        }
+        final RenderedImage image = source.render(clipped);
+        /*
+         * After `render(…)` execution, the (minX, minY) image coordinates are 
the differences
+         * between the extent that we requested and what we got. If the 
clipped extent that we
+         * specified in above method call has an origin different than the 
user-supplied extent,
+         * we need to adjust.
+         */
+        if (clipped != sliceExtent) {       // Slight optimization for a 
common case.
+            long any = 0;
+            final long[] translation = new long[clipped.getDimension()];
+            for (int i=0; i<translation.length; i++) {
+                any |= (translation[i] = Math.subtractExact(clipped.getLow(i), 
sliceExtent.getLow(i)));
+            }
+            if (any != 0) {
+                final Object property = 
image.getProperty(PlanarImage.XY_DIMENSIONS_KEY);
+                final int[] gridDimensions;
+                if (property instanceof int[]) {
+                    gridDimensions = (int[]) property;
+                } else {
+                    gridDimensions = 
clipped.getSubspaceDimensions(GridCoverage2D.BIDIMENSIONAL);
+                }
+                final var t = new ReshapedImage(image, 
translation[gridDimensions[0]], translation[gridDimensions[1]]);
+                return t.isIdentity() ? t.source : t;
+            }
+        }
+        return image;
+    }
+
+    /**
+     * Creates a new function for computing or interpolating sample values at 
given locations.
+     * That function accepts {@link DirectPosition} in arbitrary Coordinate 
Reference System.
+     * If a given position contains coordinate values for dimensions that were 
sliced by this
+     * {@code ClippedGridCoverage}, the coordinates of the specified position 
will have precedence
+     * over the grid extent that were specified in the {@link 
ClippedGridCoverage} constructor.
+     */
+    @Override
+    public Evaluator evaluator() {
+        Evaluator evaluator = source.evaluator();
+        if (defaultSlice != null) {
+            evaluator = new SliceEvaluator(evaluator);
+        }
+        return evaluator;
+    }
+
+    /**
+     * Implementation of the evaluator returned by {@link #evaluator()}.
+     * This evaluator delegates to the source evaluator with default slices
+     * initialized to the values specified by {@link ClippedGridCoverage}.
+     */
+    private final class SliceEvaluator extends EvaluatorWrapper {
+        /**
+         * Creates a new evaluator wrapping the given source coverage 
evaluator.
+         *
+         * @param  source  an evaluator newly created from the source coverage.
+         */
+        SliceEvaluator(final GridCoverage.Evaluator source) {
+            super(source);
+            super.setDefaultSlice(defaultSlice);
+        }
+
+        /**
+         * Returns the coverage from which this evaluator is fetching sample 
values.
+         * This is the coverage on which the {@link 
ClippedGridCoverage#evaluator()}
+         * method has been invoked.
+         */
+        @Override
+        public GridCoverage getCoverage() {
+            return ClippedGridCoverage.this;
+        }
+
+        /**
+         * Sets the default slice where to perform evaluation when the points 
do not have enough dimensions.
+         *
+         * @throws IllegalArgumentException if the map contains an illegal 
dimension or grid coordinate value.
+         */
+        @Override
+        public void setDefaultSlice(final Map<Integer,Long> slice) {
+            super.setDefaultSlice(slice != null ? slice : defaultSlice);
+        }
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java
index 6a7eef4a17..29a51992ba 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java
@@ -575,10 +575,11 @@ class DefaultEvaluator implements GridCoverage.Evaluator {
                 /*
                  * Above block tried to compute a "CRS to grid" transform in 
the most direct way.
                  * It covers the usual case where the point has the required 
number of dimensions,
-                 * and works better if the point has more dimensions (extra 
dimensions are ignored).
+                 * and fixes the case when the point has more dimensions 
(extra dimensions are ignored).
                  * The following block covers the opposite case, where the 
point does not have enough
                  * dimensions. We try to fill missing dimensions with the help 
of the `slice` map.
                  */
+                @SuppressWarnings("LocalVariableHidesMemberVariable")
                 final Map<Integer,Long> slice = getDefaultSlice();
                 try {
                     CoordinateOperation op = CRS.findOperation(stepCRS, crs, 
areaOfInterest);
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DerivedGridCoverage.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DerivedGridCoverage.java
index dc4a52512a..43261c24d6 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DerivedGridCoverage.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DerivedGridCoverage.java
@@ -93,7 +93,7 @@ abstract class DerivedGridCoverage extends GridCoverage {
      *
      * <h4>Differences with usual behavior</h4>
      * The evaluator returned by the default implementation has two methods 
with a behavior different
-     * than the intuitively expected ones. Howerver those differences are 
allowed by methods contract.
+     * than the intuitively expected ones. However, those differences are 
allowed by methods contract.
      *
      * <ul>
      *   <li>{@link GridCoverage.Evaluator#getCoverage()} returns an instance 
which is not {@code this}.</li>
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java
index c224df7107..29671ec2fe 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java
@@ -329,7 +329,7 @@ public abstract class GridCoverage extends BandedCoverage {
      * <h4>Multi-threading</h4>
      * {@code Evaluator}s are not thread-safe. For computing sample values 
concurrently,
      * a new {@code Evaluator} instance should be created for each thread by 
invoking this
-     * method multiply times.
+     * method multiple times.
      *
      * @return a new function for computing or interpolating sample values.
      *
@@ -382,7 +382,7 @@ public abstract class GridCoverage extends BandedCoverage {
          * Keys are dimensions from 0 inclusive to {@link 
GridGeometry#getDimension()} exclusive,
          * and values are the grid coordinates of the slice in the dimension 
specified by the key.
          *
-         * <p>This information allows to invoke {@link #apply(DirectPosition)} 
with for example
+         * <p>This information allows to invoke {@link #apply(DirectPosition)} 
with, for example,
          * two-dimensional points even if the underlying coverage is 
three-dimensional.
          * The missing coordinate values are replaced by the values provided 
in the map.</p>
          *
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage2D.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage2D.java
index 86dd521f02..38d60c852f 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage2D.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage2D.java
@@ -647,7 +647,7 @@ public class GridCoverage2D extends GridCoverage {
              * may force data loading earlier than desired.
              */
             final var result = new ReshapedImage(data, xmin, ymin, xmax, ymax);
-            return result.isIdentity() ? data : result;
+            return result.isIdentity() ? result.source : result;
         } catch (ArithmeticException e) {
             throw new CannotEvaluateException(e.getMessage(), e);
         }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java
index 9d8a33a514..47c44ab22d 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java
@@ -497,6 +497,34 @@ public class GridCoverageProcessor implements Cloneable {
         return TranslatedGridCoverage.create(source, null, translation, 
allowSourceReplacement);
     }
 
+    /**
+     * Returns the intersection of the given coverage with the given extent.
+     * The extent shall have the same number of dimensions than the coverage.
+     * The "grid to <abbr>CRS</abbr>" transform is unchanged.
+     *
+     * <p>This method is useful for taking a slice of a multi-dimensional grid.
+     * Having a slice allows to invoke {@link GridCoverage#render(GridExtent)}
+     * with a null argument value.</p>
+     *
+     * @param  source  the grid coverage to clip.
+     * @param  clip    the clip to apply in units of source grid coordinates.
+     * @return a coverage with grid coordinates contained inside the given 
clip.
+     * @throws IncompleteGridGeometryException if the given coverage has no 
grid extent.
+     * @throws DisjointExtentException if the given extent does not intersect 
the given coverage.
+     * @throws IllegalArgumentException if axes of the given extent are 
inconsistent with the axes of the grid of the given coverage.
+     *
+     * @since 1.5
+     */
+    public GridCoverage clip(final GridCoverage source, final GridExtent clip) 
{
+        ArgumentChecks.ensureNonNull("source", source);
+        ArgumentChecks.ensureNonNull("clip",   clip);
+        final boolean allowSourceReplacement;
+        synchronized (this) {
+            allowSourceReplacement = 
optimizations.contains(Optimization.REPLACE_SOURCE);
+        }
+        return ClippedGridCoverage.create(source, clip, 
allowSourceReplacement);
+    }
+
     /**
      * Creates a new coverage with a different grid extent, resolution or 
coordinate reference system.
      * The desired properties are specified by the {@link GridGeometry} 
argument, which may be incomplete.
@@ -967,10 +995,12 @@ public class GridCoverageProcessor implements Cloneable {
     @Override
     public boolean equals(final Object object) {
         if (object != null && object.getClass() == getClass()) {
-            final GridCoverageProcessor other = (GridCoverageProcessor) object;
+            final var other = (GridCoverageProcessor) object;
             if (imageProcessor.equals(other.imageProcessor)) {
+                @SuppressWarnings("LocalVariableHidesMemberVariable")
                 final EnumSet<?> optimizations;
                 synchronized (this) {
+                    // Clone for allowing comparison outside the synchronized 
block.
                     optimizations = (EnumSet<?>) this.optimizations.clone();
                 }
                 synchronized (other) {
@@ -999,7 +1029,7 @@ public class GridCoverageProcessor implements Cloneable {
     @Override
     public GridCoverageProcessor clone() {
         try {
-            final GridCoverageProcessor clone = (GridCoverageProcessor) 
super.clone();
+            final var clone = (GridCoverageProcessor) super.clone();
             final Field f = 
GridCoverageProcessor.class.getDeclaredField("imageProcessor");
             f.setAccessible(true);      // Caller sensitive: must be invoked 
in same module.
             f.set(clone, imageProcessor.clone());
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/TranslatedGridCoverage.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/TranslatedGridCoverage.java
index 26225bd368..8f70ffa212 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/TranslatedGridCoverage.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/TranslatedGridCoverage.java
@@ -18,9 +18,6 @@ package org.apache.sis.coverage.grid;
 
 import java.awt.image.RenderedImage;
 
-// Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import org.opengis.coverage.CannotEvaluateException;
-
 
 /**
  * A grid coverage with the same data as the source coverage,
@@ -68,7 +65,7 @@ final class TranslatedGridCoverage extends 
DerivedGridCoverage {
     {
         if (allowSourceReplacement) {
             while (source instanceof TranslatedGridCoverage) {
-                final TranslatedGridCoverage tc = (TranslatedGridCoverage) 
source;
+                final var tc = (TranslatedGridCoverage) source;
                 final long[] shifted = tc.translation.clone();
                 long tm = 0;
                 for (int i = Math.min(shifted.length, translation.length); --i 
>= 0;) {
@@ -83,7 +80,7 @@ final class TranslatedGridCoverage extends 
DerivedGridCoverage {
         final GridGeometry gridGeometry = source.getGridGeometry();
         if (domain == null) {
             domain = gridGeometry.shiftGrid(translation);
-        } else if (!domain.extent.isSameSize(gridGeometry.extent)) {
+        } else if (!domain.getExtent().isSameSize(gridGeometry.extent)) {
             return null;
         }
         if (domain.equals(gridGeometry)) {
@@ -110,7 +107,7 @@ final class TranslatedGridCoverage extends 
DerivedGridCoverage {
      * the rendered image shall not be translated.
      */
     @Override
-    public RenderedImage render(GridExtent sliceExtent) throws 
CannotEvaluateException {
+    public RenderedImage render(GridExtent sliceExtent) {
         if (sliceExtent == null) {
             sliceExtent = gridGeometry.extent;
         }
diff --git 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/ClippedGridCoverageTest.java
 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/ClippedGridCoverageTest.java
new file mode 100644
index 0000000000..cc80efe1dd
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/ClippedGridCoverageTest.java
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.coverage.grid;
+
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+import java.awt.image.RenderedImage;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.image.privy.RasterFactory;
+import org.apache.sis.geometry.DirectPosition2D;
+
+// Test dependencies
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+import org.apache.sis.test.TestCase;
+import org.apache.sis.referencing.crs.HardCodedCRS;
+
+
+/**
+ * Tests {@link ClippedGridCoverage}.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+public final class ClippedGridCoverageTest extends TestCase {
+    /**
+     * Size of the test image, in pixels.
+     */
+    private static final int WIDTH = 7, HEIGHT = 9;
+
+    /**
+     * Origin of the grid extent used in the test.
+     */
+    private static final int OX = 2, OY = 5;
+
+    /**
+     * Creates a new test case.
+     */
+    public ClippedGridCoverageTest() {
+    }
+
+    /**
+     * Creates a test coverage.
+     *
+     * @param  processor  non-null for hiding the {@link BufferedImage} 
implementation class.
+     */
+    private static GridCoverage createCoverage(final GridCoverageProcessor 
processor) {
+        final GridExtent     extent = new GridExtent(WIDTH, 
HEIGHT).translate(OX, OY);
+        final GridGeometry   domain = new GridGeometry(extent, 
PixelInCell.CELL_CORNER, MathTransforms.scale(4, 2), HardCodedCRS.WGS84);
+        final BufferedImage  data   = 
RasterFactory.createGrayScaleImage(DataBuffer.TYPE_BYTE, WIDTH, HEIGHT, 1, 0, 
0, 100);
+        final WritableRaster raster = data.getRaster();
+        for (int y=0; y<HEIGHT; y++) {
+            for (int x=0; x<WIDTH; x++) {
+                raster.setSample(x, y, 0, 10*y+x);
+            }
+        }
+        RenderedImage image = data;
+        if (processor != null) {
+            // We are not really interrested in statistics, we just want to 
get a different implementation class.
+            image = processor.imageProcessor.statistics(image, null, null);
+        }
+        return new 
GridCoverageBuilder().setDomain(domain).setValues(image).build();
+    }
+
+    /**
+     * Verifies that the given two-dimensional extent has the given low 
coordinates and size.
+     */
+    private static void assertExtentStarts(final GridExtent extent, final long 
low0, final long low1, final long size0, final long size1) {
+        assertEquals(2, extent.getDimension());
+        assertEquals(low0,  extent.getLow (0));
+        assertEquals(low1,  extent.getLow (1));
+        assertEquals(size0, extent.getSize(0));
+        assertEquals(size1, extent.getSize(1));
+    }
+
+    /**
+     * Asserts that the value of the first pixel of the given image is equal 
to the given value.
+     */
+    private static void assertFirstPixelEquals(final int expected, final 
RenderedImage image) {
+        final Raster tile = image.getTile(0, 0);
+        assertEquals(expected, tile.getSample(0, 0, 0));
+    }
+
+    /**
+     * Tests the clipping of a coverage backed by a {@link BufferedImage}.
+     */
+    @Test
+    public void testWithBufferedImage() {
+        test(false);
+    }
+
+    /**
+     * Tests the clipping of a coverage backed by something else than {@link 
BufferedImage}.
+     */
+    @Test
+    public void testWithRenderedImage() {
+        test(true);
+    }
+
+    /**
+     * Shared implementation of {@link #testWithBufferedImage()} and {@link 
#testWithRenderedImage()}.
+     *
+     * @param  hide  whether to hide the {@link BufferedImage} implementation.
+     */
+    private static void test(final boolean hide) {
+        final var clip = new GridExtent(WIDTH - 3, HEIGHT - 2).translate(OX - 
1, OY + 1);
+        final var processor = new GridCoverageProcessor();
+        final GridCoverage source = createCoverage(hide ? processor : null);
+        final GridCoverage target = processor.clip(source, clip);
+        assertExtentStarts(source.getGridGeometry().getExtent(), OX, OY,   
WIDTH,   HEIGHT);
+        assertExtentStarts(target.getGridGeometry().getExtent(), OX, OY+1, 
WIDTH-4, HEIGHT-2);
+        /*
+         * Verifications on the source as a matter of principle.
+         * This is for making sure that the verifications on the target are 
okay.
+         */
+        RenderedImage image = source.render(null);
+        assertEquals(WIDTH,  image.getWidth());
+        assertEquals(HEIGHT, image.getHeight());
+        assertEquals(0,      image.getMinX());      // The returned image 
match exactly the request.
+        assertEquals(0,      image.getMinY());
+        assertFirstPixelEquals(0, image);
+        /*
+         * Verification on the target. In the general case, the image is 
translated instead of clipped
+         * for having at the (0,0) coordinates the pixel that we would have if 
the image was clipped.
+         * This is because `GridCoverage2D` does not know how to clip an 
arbitrary rendered image.
+         * Only in the particular case of `BufferedImage`, a real clip is 
expected.
+         */
+        image = target.render(null);
+        if (hide) {
+            assertEquals(WIDTH,  image.getWidth());
+            assertEquals(HEIGHT, image.getHeight());
+            assertEquals( 0,     image.getMinX());
+            assertEquals(-1,     image.getMinY());
+        } else {
+            assertEquals(WIDTH  - 4, image.getWidth());
+            assertEquals(HEIGHT - 2, image.getHeight());
+            assertEquals(0,          image.getMinX());
+            assertEquals(0,          image.getMinY());
+        }
+        assertFirstPixelEquals(10, image);
+        /*
+         * The result for identical "real world" coordinates should be the 
same for both coverages.
+         */
+        final var p = new DirectPosition2D(HardCodedCRS.WGS84, 15, 20);
+        assertArrayEquals(source.evaluator().apply(p),
+                          target.evaluator().apply(p));
+        /*
+         * Test again with a sub-region having its origin inside the clipped 
area. Because of that,
+         * `ClippedGridCoverage` does not apply any additional translation on 
the rendered image.
+         */
+        image = target.render(new GridExtent(WIDTH, HEIGHT).translate(OX+1, 
OY+2));
+        if (hide) {
+            assertEquals(WIDTH,  image.getWidth());
+            assertEquals(HEIGHT, image.getHeight());
+            assertEquals(-1,     image.getMinX());
+            assertEquals(-2,     image.getMinY());
+        } else {
+            assertEquals(WIDTH  - 5, image.getWidth());
+            assertEquals(HEIGHT - 3, image.getHeight());
+            assertEquals(0,          image.getMinX());
+            assertEquals(0,          image.getMinY());
+        }
+        assertFirstPixelEquals(21, image);
+        assertArrayEquals(source.evaluator().apply(p),
+                          target.evaluator().apply(p));
+        /*
+         * Test again with a sub-region having its origin outside the clipped 
area.
+         * `ClippedGridCoverage` must add a translation for the difference 
between
+         * the request and what we got.
+         */
+        image = target.render(new GridExtent(WIDTH, HEIGHT).translate(OX+1, 
OY-1));
+        if (hide) {
+            assertEquals(WIDTH,  image.getWidth());
+            assertEquals(HEIGHT, image.getHeight());
+            assertEquals(-1,     image.getMinX());
+            assertEquals(+1,     image.getMinY());
+        } else {
+            assertEquals(WIDTH  - 5, image.getWidth());
+            assertEquals(HEIGHT - 2, image.getHeight());
+            assertEquals(0,          image.getMinX());
+            assertEquals(2,          image.getMinY());
+        }
+        // Do not test pixel at (0,0) because it does not exist.
+        assertArrayEquals(source.evaluator().apply(p),
+                          target.evaluator().apply(p));
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/TranslatedGridCoverageTest.java
 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/TranslatedGridCoverageTest.java
index ce5645ebca..37aae6af4f 100644
--- 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/TranslatedGridCoverageTest.java
+++ 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/TranslatedGridCoverageTest.java
@@ -73,7 +73,7 @@ public final class TranslatedGridCoverageTest extends 
TestCase {
      */
     @Test
     public void testUsingProcessor() {
-        final GridCoverageProcessor processor = new GridCoverageProcessor();
+        final var processor = new GridCoverageProcessor();
         final GridCoverage source = createCoverage();
         final GridCoverage target = processor.shiftGrid(source, 30, -5);
         assertExtentStarts(source.getGridGeometry().getExtent(), -20, -10);
@@ -81,7 +81,7 @@ public final class TranslatedGridCoverageTest extends 
TestCase {
         /*
          * The result for identical "real world" coordinates should be the 
same for both coverages.
          */
-        final DirectPosition2D p = new DirectPosition2D(HardCodedCRS.WGS84, 
-75, -18);
+        final var p = new DirectPosition2D(HardCodedCRS.WGS84, -75, -18);
         assertArrayEquals(source.evaluator().apply(p),
                           target.evaluator().apply(p));
     }
diff --git a/netbeans-project/nbproject/project.xml 
b/netbeans-project/nbproject/project.xml
index 8ae7f076ad..f578f8c466 100644
--- a/netbeans-project/nbproject/project.xml
+++ b/netbeans-project/nbproject/project.xml
@@ -32,6 +32,7 @@
             <word>denormalization</word>
             <word>genericity</word>
             <word>Geopackage</word>
+            <word>geospatial</word>
             <word>Molodensky</word>
             <word>transformative</word>
             <word>untiled</word>

Reply via email to