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

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

commit d40d2807255a170ea99724ed887c8a1336f2d8c8
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Jul 31 18:18:43 2026 +0200

    Add test case for `RenderingData`.
---
 .../sis/image/internal/shared/ReshapedImage.java   | 115 ++++++++--
 .../org/apache/sis/map/coverage/RenderingData.java |  24 +-
 .../apache/sis/map/coverage/RenderingDataTest.java | 251 +++++++++++++++++++++
 3 files changed, 356 insertions(+), 34 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ReshapedImage.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ReshapedImage.java
index 6c1469f3f0..d40d989d10 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ReshapedImage.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/ReshapedImage.java
@@ -19,6 +19,7 @@ package org.apache.sis.image.internal.shared;
 import java.util.Vector;
 import java.util.Objects;
 import java.awt.Rectangle;
+import java.awt.Image;
 import java.awt.image.Raster;
 import java.awt.image.RenderedImage;
 import java.awt.image.SampleModel;
@@ -31,7 +32,12 @@ import static java.lang.Math.subtractExact;
 import static java.lang.Math.multiplyFull;
 import static java.lang.Math.floorDiv;
 import static java.lang.Math.toIntExact;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.util.logging.Logging;
+import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.image.PlanarImage;
+import static org.apache.sis.image.ResampledImage.POSITIONAL_CONSISTENCY_KEY;
 
 
 /**
@@ -82,7 +88,8 @@ public final class ReshapedImage extends PlanarImage {
     private final int minTileX, minTileY;
 
     /**
-     * Creates an image with the following properties.
+     * Creates an image with the given properties.
+     * This constructor is invoked by the static factory methods.
      *
      * @throws ArithmeticException if image indices overflow 32-bits integer 
capacity.
      */
@@ -94,6 +101,9 @@ public final class ReshapedImage extends PlanarImage {
     {
         while (source instanceof ReshapedImage) {
             final var r = (ReshapedImage) source;
+            if ((offsetX | offsetY) == 0 && width >= r.width && height >= 
r.height) {
+                break;      // Will be simplified by the caller through 
`simplify()`.
+            }
             offsetX = addExact(offsetX, r.offsetX);
             offsetY = addExact(offsetY, r.offsetY);
             source  = r.source;
@@ -123,15 +133,14 @@ public final class ReshapedImage extends PlanarImage {
     public static RenderedImage singleTile(final RenderedImage source, final 
int tileX, final int tileY) {
         final int tileWidth  = source.getTileWidth();
         final int tileHeight = source.getTileHeight();
-        final var image = new ReshapedImage(source,
+        return new ReshapedImage(source,
                 -(multiplyFull(tileX, tileWidth)  + 
source.getTileGridXOffset()),   // This negate cannot overflow.
                 -(multiplyFull(tileY, tileHeight) + 
source.getTileGridYOffset()),
                 0, 0,
                 tileWidth,
                 tileHeight,
                 tileX,
-                tileY);
-        return image.isIdentity() ? image.source : image;
+                tileY).simplify();
     }
 
     /**
@@ -148,7 +157,7 @@ public final class ReshapedImage extends PlanarImage {
         if ((offsetX | offsetY) == 0) {
             return source;
         }
-        final var image = new ReshapedImage(
+        return new ReshapedImage(
                 source,
                 offsetX,
                 offsetY,
@@ -157,8 +166,7 @@ public final class ReshapedImage extends PlanarImage {
                 source.getWidth(),
                 source.getHeight(),
                 source.getMinTileX(),
-                source.getMinTileY());
-        return image.isIdentity() ? image.source : image;
+                source.getMinTileY()).simplify();
     }
 
     /**
@@ -173,7 +181,10 @@ public final class ReshapedImage extends PlanarImage {
      * @return the relocated image. May be the given source or one of its 
sources.
      * @throws ArithmeticException if image indices would overflow 32-bits 
integer capacity.
      */
-    public static RenderedImage relocate(final RenderedImage source, final 
long xmin, final long ymin, final long xmax, final long ymax) {
+    public static RenderedImage relocate(final RenderedImage source,
+                                         final long xmin, final long ymin,
+                                         final long xmax, final long ymax)
+    {
         /*
          * Compute indices of all tiles to retain in this image. All local 
fields are `long` in order to force
          * 64-bits integer arithmetic, because may have temporary 32-bits 
integer overflow during intermediate
@@ -206,27 +217,41 @@ public final class ReshapedImage extends PlanarImage {
          */
         final long x = subtractExact(sx, xmin);
         final long y = subtractExact(sy, ymin);
-        final var image = new ReshapedImage(source,
+        return new ReshapedImage(
+                source,
                 x - sx,
                 y - sy,
                 x, y,
                 toIntExact(min(upperX, (maxTX + 1) * tw + xo) - sx),
                 toIntExact(min(upperY, (maxTY + 1) * th + yo) - sy),
                 toIntExact(minTX),
-                toIntExact(minTY));
-        return image.isIdentity() ? image.source : image;
+                toIntExact(minTY)).simplify();
+    }
+
+    /**
+     * Applies to the given image the same translation and clip as this image.
+     * This is used for auxiliary images such as {@value #MASK_KEY} and 
{@value #POSITIONAL_ACCURACY_KEY}.
+     */
+    private RenderedImage applySameChanges(final RenderedImage other) {
+        final Rectangle r = getBounds();
+        ImageUtilities.clipBounds(other, r);
+        return new ReshapedImage(
+                source, offsetX, offsetY,
+                r.x, r.y, r.width, r.height,
+                ImageUtilities.pixelToTileX(other, r.x),
+                ImageUtilities.pixelToTileY(other, r.y)).simplify();
     }
 
     /**
-     * Returns {@code true} if this image does not move and does not subset 
the wrapped image.
+     * Returns {@code source} if this image does not move and does not subset 
the wrapped image.
      * This is tested after construction in case that the result of unwrapping 
the source produces
      * an identity operation.
      *
-     * @return whether this image does not move and does not subset the 
wrapped image.
+     * @return a simplified version of this image, if possible.
      */
-    private boolean isIdentity() {
+    private RenderedImage simplify() {
         // The use of >= is a paranoiac check, but the > case should never 
happen actually.
-        return offsetX == 0 && offsetY == 0 && width >= source.getWidth() && 
height >= source.getHeight();
+        return (offsetX | offsetY) == 0 && width >= source.getWidth() && 
height >= source.getHeight() ? source : this;
     }
 
     /**
@@ -235,23 +260,65 @@ public final class ReshapedImage extends PlanarImage {
     @Override
     @SuppressWarnings("UseOfObsoleteCollectionType")
     public Vector<RenderedImage> getSources() {
-        final Vector<RenderedImage> sources = new Vector<>(1);
+        final var sources = new Vector<RenderedImage>(1);
         sources.add(source);
         return sources;
     }
 
     /**
-     * Delegates to the wrapped image with no change.
+     * Returns the name of all supported properties, or {@code null} if none.
+     * Current implementation inherits all properties declared by the source,
+     * but some of them will need a translation.
+     */
+    @Override
+    public String[] getPropertyNames() {
+        return source.getPropertyNames();
+    }
+
+    /**
+     * Returns the property of the given name.
+     * The property is inherited from the source image,
+     * potentially with a translation of pixel coordinates.
      *
      * @param  name  name of the property to get.
      * @return property value for the given name.
      */
-    @Override public Object      getProperty(String name) {return 
source.getProperty(name);}
-    @Override public String[]    getPropertyNames()       {return 
source.getPropertyNames();}
-    @Override public ColorModel  getColorModel()          {return 
source.getColorModel();}
-    @Override public SampleModel getSampleModel()         {return 
source.getSampleModel();}
-    @Override public int         getTileWidth()           {return 
source.getTileWidth();}
-    @Override public int         getTileHeight()          {return 
source.getTileHeight();}
+    @Override
+    public Object getProperty(final String name) {
+        final Object property = source.getProperty(name);
+        switch (name) {
+            default: {
+                return property;
+            }
+            case GRID_GEOMETRY_KEY: {
+                if (property instanceof GridGeometry) try {
+                    final var base = (GridGeometry) property;
+                    return base.shiftGrid(offsetX, offsetY).relocate(new 
GridExtent(getBounds()));
+                } catch (TransformException e) {
+                    // In most of Apache SIS, caller has a fallback for the 
case when this property is missing.
+                    Logging.recoverableException(ImageUtilities.LOGGER, 
ReshapedImage.class, "getProperty", e);
+                }
+                break;
+            }
+            case POSITIONAL_CONSISTENCY_KEY:
+            case MASK_KEY: {
+                if (property instanceof RenderedImage) {
+                    // Not cached for avoiding to retain memory.
+                    return applySameChanges((RenderedImage) property);
+                }
+                break;
+            }
+        }
+        return Image.UndefinedProperty;
+    }
+
+    /**
+     * Delegates to the wrapped image with no change.
+     */
+    @Override public ColorModel  getColorModel()  {return 
source.getColorModel();}
+    @Override public SampleModel getSampleModel() {return 
source.getSampleModel();}
+    @Override public int         getTileWidth()   {return 
source.getTileWidth();}
+    @Override public int         getTileHeight()  {return 
source.getTileHeight();}
 
     /**
      * Returns properties determined at construction time.
@@ -420,7 +487,7 @@ public final class ReshapedImage extends PlanarImage {
     @Override
     public boolean equals(final Object object) {
         if (object instanceof ReshapedImage) {
-            final ReshapedImage other = (ReshapedImage) object;
+            final var other = (ReshapedImage) object;
             return source.equals(other.source) &&
                     minX     == other.minX     &&
                     minY     == other.minY     &&
diff --git 
a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/map/coverage/RenderingData.java
 
b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/map/coverage/RenderingData.java
index 68b9c18187..c3f4ce68b4 100644
--- 
a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/map/coverage/RenderingData.java
+++ 
b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/map/coverage/RenderingData.java
@@ -49,6 +49,7 @@ import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.ImageRenderer;
 import org.apache.sis.coverage.grid.PixelInCell;
 import org.apache.sis.coverage.grid.PixelTranslation;
+import org.apache.sis.coverage.internal.shared.SampleDimensions;
 import org.apache.sis.geometry.AbstractEnvelope;
 import org.apache.sis.geometry.Envelope2D;
 import org.apache.sis.geometry.Shapes2D;
@@ -58,26 +59,25 @@ import org.apache.sis.image.ErrorHandler;
 import org.apache.sis.image.ImageProcessor;
 import org.apache.sis.image.internal.shared.ColorModelType;
 import org.apache.sis.image.internal.shared.ImageUtilities;
-import org.apache.sis.coverage.internal.shared.SampleDimensions;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.referencing.operation.transform.LinearTransform;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.referencing.operation.matrix.AffineTransforms2D;
 import org.apache.sis.referencing.internal.shared.WraparoundApplicator;
 import org.apache.sis.system.Modules;
 import org.apache.sis.util.Debug;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.internal.shared.CloneAccess;
 import org.apache.sis.util.internal.shared.Numerics;
+import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.util.logging.Logging;
 import org.apache.sis.io.TableAppender;
 import org.apache.sis.math.Statistics;
 import org.apache.sis.measure.Quantities;
 import org.apache.sis.measure.Units;
 import org.apache.sis.metadata.iso.extent.Extents;
-import org.apache.sis.referencing.operation.transform.LinearTransform;
-import org.apache.sis.referencing.operation.transform.MathTransforms;
-import org.apache.sis.referencing.operation.matrix.AffineTransforms2D;
 import org.apache.sis.map.internal.Resources;
-import org.apache.sis.util.resources.Vocabulary;
-import org.apache.sis.util.logging.Logging;
 import org.apache.sis.portrayal.PlanarCanvas;       // For javadoc.
 
 
@@ -123,7 +123,7 @@ public class RenderingData implements CloneAccess {
      *
      * @see #xyDimensions
      */
-    private static final int BIDIMENSIONAL = 2;
+    protected static final int BIDIMENSIONAL = 2;
 
     /**
      * Whether to allow the creation of {@link 
java.awt.image.IndexColorModel}. This flag may be temporarily set
@@ -367,8 +367,12 @@ public class RenderingData implements CloneAccess {
      * <p>Caller should invoke {@link #ensureImageLoaded(GridCoverage, 
GridExtent, boolean)}
      * after this method (this is not done automatically).</p>
      *
+     * <p>The {@code objectivePOI} argument should generally be non-null. But 
a null value is tolerated
+     * if the transform between the {@link GridCoverage} <abbr>CRS</abbr> and 
the argument given to the
+     * {@link #setObjectiveCRS(CoordinateReferenceSystem)} method is 
linear.</p>
+     *
      * @param  objectiveToDisplay  transform used for rendering the coverage 
on screen.
-     * @param  objectivePOI        point where to compute resolution, in 
coordinates of objective CRS.
+     * @param  objectivePOI        point where to compute resolution, in 
coordinates of objective <abbr>CRS</abbr>.
      * @return the loaded grid coverage, or {@code null} if no loading has 
been done
      *         (which means that the coverage is unchanged, not that it does 
not exist).
      * @throws TransformException if an error occurred while computing 
resolution from given transforms.
@@ -726,7 +730,7 @@ public class RenderingData implements CloneAccess {
      *
      * @param  transform     the transform to concatenate with a "wraparound" 
operation.
      * @param  sourceMedian  point of interest in the <em>source</em> CRS of 
given transform.
-     * @param  targetMedian  point of interest after wraparound.
+     * @param  targetMedian  point of interest after wraparound, or {@code 
null} if none.
      * @param  targetCRS     the target CRS of the given transform.
      */
     private static MathTransform applyWraparound(final MathTransform 
transform, DirectPosition sourceMedian,
@@ -905,7 +909,7 @@ public class RenderingData implements CloneAccess {
      * This is used for error reporting.
      */
     private String getCRSName() {
-        if (dataGeometry.isDefined(GridGeometry.CRS)) {
+        if (isDefined(GridGeometry.CRS)) {
             String name = 
IdentifiedObjects.getDisplayName(dataGeometry.getCoordinateReferenceSystem(), 
locale);
             if (name != null) {
                 return name;
diff --git 
a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/map/coverage/RenderingDataTest.java
 
b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/map/coverage/RenderingDataTest.java
new file mode 100644
index 0000000000..7cf6ac1de4
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/map/coverage/RenderingDataTest.java
@@ -0,0 +1,251 @@
+/*
+ * 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.map.coverage;
+
+import java.util.Map;
+import java.util.Arrays;
+import java.awt.Point;
+import java.awt.color.ColorSpace;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.MultiPixelPackedSampleModel;
+import java.awt.image.Raster;
+import java.awt.image.RenderedImage;
+import org.opengis.geometry.Envelope;
+import org.opengis.geometry.DirectPosition;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.coverage.grid.GridCoverage2D;
+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.ImageRenderer;
+import org.apache.sis.coverage.grid.PixelInCell;
+import org.apache.sis.geometry.DirectPosition2D;
+import org.apache.sis.geometry.GeneralDirectPosition;
+import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.image.ErrorHandler;
+import org.apache.sis.image.PlanarImage;
+import org.apache.sis.image.internal.shared.TiledImage;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.referencing.operation.transform.LinearTransform;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.referencing.internal.shared.AffineTransform2D;
+import org.apache.sis.storage.MemoryGridCoverageResource;
+
+// Test dependencies
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.apache.sis.test.Assertions.assertSingleton;
+import org.apache.sis.test.TestCase;
+
+
+/**
+ * Headless reproduction of the {@code CoverageCanvas} display chain for a 
tiled image.
+ * Compares 2D resource against the same resource augmented with a temporal 
dimension.
+ */
+public final class RenderingDataTest extends TestCase {
+    /**
+     * Width and height (in pixels) of the rendered image produced in output.
+     */
+    private static final int RENDERED_SIZE = 8;
+
+    /**
+     * Width and height (in pixels) of the resource used as input.
+     */
+    private static final int DATA_SIZE = 128;
+
+    /**
+     * Tile height in pixels. There is no tile width as this test does not 
tile the image horizontally.
+     * It is sufficient to focus the test on the tiling in only one axis, 
which is <abbr>y</abbr>.
+     */
+    private static final int TILE_HEIGHT = 16;
+
+    /**
+     * Number of meters per pixel at full resolution.
+     */
+    private static final double RESOLUTION = 10;
+
+    /**
+     * The western-most coordinate of the limit of the resource extent.
+     * Unit is meters of the <abbr>UTM</abbr> zone 31 projection.
+     */
+    private static final double WEST_BOUND = 400000;
+
+    /**
+     * The northern-most coordinate of the limit of the resource extent.
+     * Unit is meters of the <abbr>UTM</abbr> zone 31 projection.
+     */
+    private static final double NORTH_BOUND = 5000000;
+
+    /**
+     * Creates a new text case.
+     */
+    public RenderingDataTest() {
+    }
+
+    /**
+     * Simulates the {@code CoverageCanvas} worker from the <abbr>GUI</abbr> 
module.
+     * The steps include:
+     *
+     * <ul>
+     *   <li>pyramid loader,</li>
+     *   <li>{@link RenderingData#ensureCoverageLoaded(LinearTransform, 
DirectPosition)},</li>
+     *   <li>{@link RenderingData#ensureImageLoaded(GridCoverage, GridExtent, 
boolean)},</li>
+     *   <li>{@link RenderingData#resampleAndConvert(RenderedImage, 
LinearTransform, DirectPosition)}.</li>
+     * </ul>
+     *
+     * @throws Exception if a data store, a factory or a transform exception 
occurred.
+     */
+    @Test
+    public void testDisplayChain() throws Exception {
+        final GridCoverage coverage = createTiledCoverage();
+        runDisplayChain(coverage);
+        runDisplayChain(addTemporalDimension(coverage));
+    }
+
+    /**
+     * Creates a two-dimensional coverage wrapping a tiled image with an 
<var>UTM</var> projection.
+     * The pixel values do not matter and are all zeros. What matter for this 
test is the tiling.
+     * Therefore, for saving memory and <abbr>CPU</abbr>, all tiles share the 
same data buffer.
+     */
+    private static GridCoverage createTiledCoverage() {
+        final var gridToCRS  = new AffineTransform2D(RESOLUTION, 0, 0, 
-RESOLUTION, WEST_BOUND, NORTH_BOUND);
+        final var crs        = CommonCRS.WGS84.universal(45, 3);   // UTM zone 
31N.
+        final var gg         = new GridGeometry(new GridExtent(DATA_SIZE, 
DATA_SIZE), PixelInCell.CELL_CORNER, gridToCRS, crs);
+        final var properties = Map.of(TiledImage.GRID_GEOMETRY_KEY, gg);
+
+        final var colorSpace = ColorSpace.getInstance(ColorSpace.CS_GRAY);
+        final var colors     = new ComponentColorModel(colorSpace, false, 
true, ComponentColorModel.OPAQUE, DataBuffer.TYPE_BYTE);
+        final var layout     = new 
MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, DATA_SIZE, TILE_HEIGHT, 1);
+        final var buffer     = new DataBufferByte(DATA_SIZE * TILE_HEIGHT / 
Byte.SIZE, 1);
+        final var tiles      = new Raster[DATA_SIZE / TILE_HEIGHT];
+        Arrays.setAll(tiles, (i) -> Raster.createRaster(layout, buffer, new 
Point(0, i * TILE_HEIGHT)));
+        final var image      = new TiledImage(properties, colors, DATA_SIZE, 
DATA_SIZE, 0, 0, tiles);
+        return new GridCoverage2D(gg, null, image);
+    }
+
+    /**
+     * Adds a temporal dimension to the given coverage.
+     */
+    private static GridCoverage addTemporalDimension(final GridCoverage 
coverage) {
+        var timeCRS    = CommonCRS.Temporal.UNIX.crs();
+        var gridToTime = MathTransforms.linear(86400, 24*60*60);   // 1 cell = 
1 day.
+        var gridExtent = new GridExtent(null, null, new long[1], true);
+        var dimToAdd   = new GridGeometry(gridExtent, PixelInCell.CELL_CORNER, 
gridToTime, timeCRS);
+        var processor  = new GridCoverageProcessor();
+        return processor.appendDimensions(coverage, dimToAdd);
+    }
+
+    /**
+     * Simulates the {@code CoverageCanvas} worker from the <abbr>GUI</abbr> 
module.
+     * The steps include:
+     *
+     * <ul>
+     *   <li>pyramid loader,</li>
+     *   <li>{@link RenderingData#ensureCoverageLoaded(LinearTransform, 
DirectPosition)},</li>
+     *   <li>{@link RenderingData#ensureImageLoaded(GridCoverage, GridExtent, 
boolean)},</li>
+     *   <li>{@link RenderingData#resampleAndConvert(RenderedImage, 
LinearTransform, DirectPosition)}.</li>
+     * </ul>
+     */
+    private static void runDisplayChain(final GridCoverage coverage) throws 
Exception {
+        final var resource = new MemoryGridCoverageResource(coverage);
+        /*
+         * Prepare a region of interest (ROI) in the middle of the resource 
domain.
+         * For this test, it is important that the ROI does not cover all 
tiles.
+         * Because the ROI is in the middle of the data domain,
+         * the envelope center should be invariant.
+         */
+        GridGeometry domain = resource.getGridGeometry();
+        final var zoomArea = new GeneralEnvelope(domain.getEnvelope());
+        for (int i = 0; i < RenderingData.BIDIMENSIONAL; i++) {
+            final double margin = zoomArea.getSpan (i) / 4;
+            zoomArea.setRange(i,  zoomArea.getLower(i) + margin,
+                                  zoomArea.getUpper(i) - margin);
+        }
+        verifyLocationOfCenter(domain);
+        domain = domain.derive().subgrid(zoomArea, null).build();
+        verifyLocationOfCenter(domain);
+        final GridExtent sliceExtent = domain.getExtent();
+        /*
+         * Simulate the fallback in `RenderingData.ensureImageLoaded(…)` which 
recompute
+         * the grid geometry using `ImageRenderer` when that information is 
not provided
+         * as a property.
+         */
+        {
+            final var r = new ImageRenderer(coverage, sliceExtent);
+            
verifyLocationOfCenter(r.getImageGeometry(RenderingData.BIDIMENSIONAL));
+            assertArrayEquals(new int[] {0, 1}, r.getXYDimensions());
+        }
+        domain = domain.selectDimensions(0, 1);
+        final double scale = ((double) RENDERED_SIZE) / (DATA_SIZE * 
RESOLUTION);
+        final var objectiveToDisplay = new AffineTransform2D(scale, 0, 0, 
-scale, WEST_BOUND * -scale, NORTH_BOUND * scale);
+        final CoordinateReferenceSystem objectiveCRS = 
domain.getCoordinateReferenceSystem();
+        final var poi = new DirectPosition2D(objectiveCRS, WEST_BOUND + 
DATA_SIZE * RESOLUTION / 2,
+                                                          NORTH_BOUND - 
DATA_SIZE * RESOLUTION / 2);
+        /*
+         * Render the request which has been prepared above.
+         * Get the result, but also the source of the result.
+         */
+        final var render = new RenderingData(ErrorHandler.THROW);
+        render.coverageLoader = new MultiResolutionCoverageLoader(resource, 
null, null);
+        render.setImageSpace(domain, resource.getSampleDimensions(), new int[] 
{0, 1});
+        render.setObjectiveCRS(objectiveCRS);
+        
render.ensureImageLoaded(render.ensureCoverageLoaded(objectiveToDisplay, poi), 
sliceExtent, true);
+        final RenderedImage shown  = 
render.resampleAndConvert(render.getSourceImage(), objectiveToDisplay, poi);
+        final RenderedImage source = assertSingleton(shown.getSources());
+        /*
+         * Verify the source of the result before to verify the final result.
+         * The source should have the same size as the original data,
+         * but translated for having the request at coordinates (0,0).
+         */
+        assertEquals(-DATA_SIZE / 4,   source.getMinX());   // Difference 
relative to requested area.
+        assertEquals( DATA_SIZE,       source.getWidth());
+        assertEquals( 0,               source.getMinY());
+        assertEquals( DATA_SIZE / 2,   source.getHeight());
+        assertEquals(0,                 shown.getMinX());
+        assertEquals(RENDERED_SIZE,     shown.getWidth());
+        assertEquals(RENDERED_SIZE,     shown.getTileWidth());
+        assertEquals(RENDERED_SIZE / 4, shown.getMinY());
+        assertEquals(RENDERED_SIZE / 2, shown.getHeight());
+        assertEquals(RENDERED_SIZE / 2, shown.getTileHeight());
+        /*
+         * Verify georeferencing.
+         */
+        verifyLocationOfCenter(assertInstanceOf(GridGeometry.class, 
source.getProperty(PlanarImage.GRID_GEOMETRY_KEY)));
+    }
+
+    /**
+     * Verifies that the center of the given grid geometry is in the center of 
the test domain.
+     */
+    private static void verifyLocationOfCenter(final GridGeometry domain) 
throws TransformException {
+        final Envelope envelope = domain.getEnvelope();
+        assertEquals( WEST_BOUND + RESOLUTION * (DATA_SIZE / 2), 
envelope.getMedian(0));
+        assertEquals(NORTH_BOUND - RESOLUTION * (DATA_SIZE / 2), 
envelope.getMedian(1));
+
+        final GridExtent extent = domain.getExtent();
+        final var center = new GeneralDirectPosition(extent.getDimension());
+        for (int i = center.getDimension(); --i >= 0;) {
+            center.setCoordinate(i, extent.getMedian(i));
+        }
+        assertSame(center, 
(domain.getGridToCRS(PixelInCell.CELL_CORNER).transform(center, center)));
+        assertEquals( WEST_BOUND + RESOLUTION * (DATA_SIZE / 2), 
center.getCoordinate(0));
+        assertEquals(NORTH_BOUND - RESOLUTION * (DATA_SIZE / 2), 
center.getCoordinate(1));
+    }
+}

Reply via email to