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 6a8700ed3291b6a9ddb2b3c5b68485e3947a8083
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Jul 31 16:47:44 2026 +0200

    Deferred computation of `GridGeometry` image property should take in 
account the image bounds.
---
 .../sis/coverage/grid/ClippedGridCoverage.java     |  4 +-
 .../org/apache/sis/coverage/grid/GridExtent.java   | 28 ++-----------
 .../apache/sis/coverage/grid/ImageRenderer.java    |  2 +-
 .../apache/sis/coverage/grid/SliceGeometry.java    |  3 +-
 .../image/internal/shared/BatchComputedImage.java  |  2 +-
 .../image/internal/shared/DeferredProperty.java    | 47 +++++++++++++++++-----
 .../sis/image/internal/shared/TiledImage.java      |  6 +--
 .../image/internal/shared/WritableTiledImage.java  |  2 +-
 .../coverage/grid/ResampledGridCoverageTest.java   |  6 +--
 .../org/apache/sis/storage/esri/RasterStore.java   |  9 +++--
 .../sis/storage/tiling/TiledGridCoverage.java      |  2 +-
 11 files changed, 61 insertions(+), 50 deletions(-)

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
index 8d122f7690..15766ab167 100644
--- 
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
@@ -131,7 +131,9 @@ final class ClippedGridCoverage extends DerivedGridCoverage 
{
                 } else {
                     gridDimensions = 
clipped.getSubspaceDimensions(BIDIMENSIONAL);
                 }
-                return ReshapedImage.translate(image, 
translation[gridDimensions[0]], translation[gridDimensions[1]]);
+                return ReshapedImage.translate(image,
+                        translation[gridDimensions[0]],
+                        translation[gridDimensions[1]]);
             }
         }
         return image;
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
index ddafb8bddd..d990d7b7bc 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
@@ -277,7 +277,9 @@ public class GridExtent implements GridEnvelope, 
LenientComparable, Serializable
      */
     public GridExtent(final Rectangle bounds) {
         this(bounds.width, bounds.height);
-        translate2D(bounds.x, bounds.y);
+        for (int i = coordinates.length; --i >= 0;) {
+            coordinates[i] += ((i & 1) == 0) ? bounds.x : bounds.y;
+        }
     }
 
     /**
@@ -298,30 +300,6 @@ public class GridExtent implements GridEnvelope, 
LenientComparable, Serializable
         types = DEFAULT_TYPES;
     }
 
-    /**
-     * Creates a new grid extent for an image of the given size and location. 
This constructor
-     * is for internal usage: argument meanings differ from conventions in 
public constructors.
-     *
-     * @param  xmin    column index of the first cell.
-     * @param  ymin    row index of the first cell.
-     * @param  width   number of pixels in each row.
-     * @param  height  number of pixels in each column.
-     */
-    GridExtent(final int xmin, final int ymin, final int width, final int 
height) {
-        this(width, height);
-        translate2D(xmin, ymin);
-    }
-
-    /**
-     * Completes a {@link GridExtent} construction with a final translation.
-     * Shall be invoked for two-dimensional extents only.
-     */
-    private void translate2D(final long xmin, final long ymin) {
-        for (int i=coordinates.length; --i >= 0;) {
-            coordinates[i] += ((i & 1) == 0) ? xmin : ymin;
-        }
-    }
-
     /**
      * Constructs a one-dimensional grid extent set to the specified 
coordinates.
      * This convenience constructor does the same work as the constructor for 
the
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ImageRenderer.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ImageRenderer.java
index b5990beb96..89f63b141f 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ImageRenderer.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ImageRenderer.java
@@ -455,7 +455,7 @@ public class ImageRenderer {
                 ig = geometry;
             } else try {
                 ig = new SliceGeometry(geometry, sliceExtent, gridDimensions, 
mtFactory)
-                        .reduce(new GridExtent(imageX, imageY, width, height), 
dimCRS);
+                            .reduce(new GridExtent(getBounds()), dimCRS);
             } catch (FactoryException e) {
                 throw SliceGeometry.canNotCompute(e);
             }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/SliceGeometry.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/SliceGeometry.java
index 61b14a850c..44cf4fb02c 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/SliceGeometry.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/SliceGeometry.java
@@ -31,6 +31,7 @@ import 
org.apache.sis.referencing.operation.transform.LinearTransform;
 import org.apache.sis.referencing.operation.transform.TransformSeparator;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.geometry.ImmutableEnvelope;
+import org.apache.sis.image.internal.shared.ImageUtilities;
 import org.apache.sis.referencing.internal.shared.ReferencingUtilities;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.ArraysExt;
@@ -106,7 +107,7 @@ final class SliceGeometry implements 
Function<RenderedImage, GridGeometry> {
     @Override
     public GridGeometry apply(final RenderedImage image) {
         try {
-            final GridExtent extent = new GridExtent(image.getMinX(), 
image.getMinY(), image.getWidth(), image.getHeight());
+            final var extent = new GridExtent(ImageUtilities.getBounds(image));
             return reduce(extent, GridCoverage.BIDIMENSIONAL);
         } catch (FactoryException e) {
             throw canNotCompute(e);
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/BatchComputedImage.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/BatchComputedImage.java
index 5495346bce..000bcf5336 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/BatchComputedImage.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/BatchComputedImage.java
@@ -104,7 +104,7 @@ public abstract class BatchComputedImage extends 
ComputedImage {
     public Object getProperty(final String key) {
         Object value = properties.getOrDefault(key, Image.UndefinedProperty);
         if (value instanceof DeferredProperty) {
-            value = ((DeferredProperty) value).compute(this);
+            value = ((DeferredProperty) value).getOrCompute(this);
         }
         return value;
     }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/DeferredProperty.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/DeferredProperty.java
index cc26b5a19c..ee1b2338ab 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/DeferredProperty.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/DeferredProperty.java
@@ -16,11 +16,14 @@
  */
 package org.apache.sis.image.internal.shared;
 
+import java.util.Map;
 import java.util.function.Function;
 import java.awt.image.RenderedImage;
+import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.image.PlanarImage;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.util.logging.Logging;
 
 
 /**
@@ -29,6 +32,7 @@ import org.apache.sis.coverage.grid.GridGeometry;
  *
  * <ul>
  *   <li>{@link TiledImage#getProperty(String)}</li>
+ *   <li>{@link BatchComputedImage#getProperty(String)}</li>
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
@@ -60,7 +64,7 @@ public final class DeferredProperty {
      * @param  image  the image for which to compute the property value.
      * @return the property value, or {@code null} if it cannot be computed.
      */
-    final synchronized Object compute(final RenderedImage image) {
+    final synchronized Object getOrCompute(final RenderedImage image) {
         if (value == null) {
             final Function<RenderedImage, ?> p = provider;
             if (p != null) {
@@ -73,17 +77,28 @@ public final class DeferredProperty {
 
     /**
      * Creates a deferred property for computing the value of {@link 
PlanarImage#GRID_GEOMETRY_KEY}.
+     * The given grid geometry can have any size and any translation compared 
to the image.
+     * It will be clipped and translated for consistency with the image 
coordinates.
+     * The property is added to the given map.
      *
+     * <h4>Conservative usage</h4>
+     * It is not necessarily a good idea to add this property to every images 
created by Apache <abbr>SIS</abbr>.
+     * The problem is that there is a risk that some derived images blindly 
copy this property from their source,
+     * and that value may be wrong when applied to the derived image. We may 
progressively add this information
+     * to more and more images, but not necessarily to all of them.
+     *
+     * @param  properties  the map where to add the deferred image property.
      * @param  grid        the grid geometry of the grid coverage rendered as 
an image.
-     * @param  dimensions  the dimensions to keep from the coverage grid 
geometry.
-     * @return a deferred property for computing the grid geometry of an image.
+     * @param  dimensions  the dimensions to keep from the coverage grid 
geometry, or {@code null} for 0 and 1.
      */
-    public static DeferredProperty forGridGeometry(final GridGeometry grid, 
final int[] dimensions) {
-        return new DeferredProperty(new ImageGeometry(grid, dimensions));
+    public static void addGridGeometry(final Map<String, Object> properties, 
final GridGeometry grid, final int[] dimensions) {
+        properties.put(PlanarImage.GRID_GEOMETRY_KEY, new DeferredProperty(new 
ImageGeometry(grid, dimensions)));
     }
 
     /**
      * A deferred property for computing the value of {@link 
PlanarImage#GRID_GEOMETRY_KEY}.
+     * The source grid geometry can have any size and any translation compared 
to the image.
+     * It will be clipped and translated for consistency with the image 
coordinates.
      */
     private static final class ImageGeometry implements 
Function<RenderedImage, GridGeometry> {
         /** The grid geometry of the grid coverage rendered as an image. */
@@ -96,12 +111,17 @@ public final class DeferredProperty {
          * Creates a deferred property for an image grid geometry.
          *
          * @param grid        the grid geometry of the grid coverage rendered 
as an image.
-         * @param dimensions  the dimensions to keep from the coverage grid 
geometry.
+         * @param dimensions  the dimensions to keep from the coverage grid 
geometry, or {@code null} for 0 and 1.
          */
-        public ImageGeometry(final GridGeometry grid, final int[] dimensions) {
+        ImageGeometry(final GridGeometry grid, final int[] dimensions) {
             this.grid = grid;
-            this.dimX = dimensions[0];
-            this.dimY = dimensions[1];
+            if (dimensions != null) {
+                dimX = dimensions[0];
+                dimY = dimensions[1];
+            } else {
+                dimX = 0;
+                dimY = 1;
+            }
         }
 
         /**
@@ -111,13 +131,20 @@ public final class DeferredProperty {
          *
          * @param  image  the image for which to compute the property.
          * @return the grid geometry property computed for the given image.
+         * @throws ArithmeticException if the grid geometry cannot be 
translated.
          */
         @Override
         public GridGeometry apply(final RenderedImage image) {
             final GridExtent extent = grid.getExtent();
-            return grid.selectDimensions(dimX, dimY).shiftGrid(
+            GridGeometry shifted = grid.selectDimensions(dimX, dimY).shiftGrid(
                     Math.subtractExact(image.getMinX(), extent.getLow(dimX)),
                     Math.subtractExact(image.getMinY(), extent.getLow(dimY)));
+            try {
+                shifted = shifted.relocate(new 
GridExtent(ImageUtilities.getBounds(image)));
+            } catch (TransformException e) {
+                Logging.recoverableException(ImageUtilities.LOGGER, 
image.getClass(), "getProperty", e);
+            }
+            return shifted;
         }
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/TiledImage.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/TiledImage.java
index f0c3869f3d..2493d31ddd 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/TiledImage.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/TiledImage.java
@@ -60,7 +60,7 @@ public class TiledImage extends PlanarImage {
     /**
      * Image properties, or an empty map if none.
      */
-    private final Map<String,Object> properties;
+    private final Map<String, Object> properties;
 
     /**
      * Creates a new tiled image. The first tile in the given array must be the
@@ -76,7 +76,7 @@ public class TiledImage extends PlanarImage {
      * @param minTileY    minimum tile index in the Y direction.
      * @param tiles       the tiles. Must contains at least one element. This 
array is not cloned.
      */
-    public TiledImage(final Map<String, Object> properties, final ColorModel 
colorModel,
+    public TiledImage(final Map<String, ?> properties, final ColorModel 
colorModel,
                       final int width, final int height, final int minTileX, 
final int minTileY,
                       final Raster... tiles)
     {
@@ -166,7 +166,7 @@ public class TiledImage extends PlanarImage {
     public Object getProperty(final String key) {
         Object value = properties.getOrDefault(key, Image.UndefinedProperty);
         if (value instanceof DeferredProperty) {
-            value = ((DeferredProperty) value).compute(this);
+            value = ((DeferredProperty) value).getOrCompute(this);
         }
         return value;
     }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/WritableTiledImage.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/WritableTiledImage.java
index d2f87d2c12..27c3b8fa75 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/WritableTiledImage.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/internal/shared/WritableTiledImage.java
@@ -67,7 +67,7 @@ public class WritableTiledImage extends TiledImage implements 
WritableRenderedIm
      * @param tiles       the tiles. Must contains at least one element.
      *                    This array is not cloned.
      */
-    public WritableTiledImage(final Map<String,Object> properties, final 
ColorModel colorModel,
+    public WritableTiledImage(final Map<String, ?> properties, final 
ColorModel colorModel,
                               final int width, final int height, final int 
minTileX, final int minTileY,
                               final WritableRaster... tiles)
     {
diff --git 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
index 403438369e..1395d43d85 100644
--- 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
+++ 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/ResampledGridCoverageTest.java
@@ -243,12 +243,11 @@ public final class ResampledGridCoverageTest extends 
TestCase {
         final GridExtent sourceExtent = source.gridGeometry.getExtent();
         final int newWidth   = StrictMath.toIntExact(sourceExtent.getSize(0) - 
tx);
         final int newHeight  = StrictMath.toIntExact(sourceExtent.getSize(1) - 
ty);
-        GridExtent subExtent = new GridExtent(
+        final var subExtent = new GridExtent(new Rectangle(
                 StrictMath.toIntExact(sourceExtent.getLow(0) + tx),
                 StrictMath.toIntExact(sourceExtent.getLow(1) + ty),
                 newWidth,
-                newHeight
-        );
+                newHeight));
         assertPixelsEqual(source.render(null), new Rectangle(tx, ty, newWidth, 
newHeight),
                           target.render(subExtent), new Rectangle(newWidth, 
newHeight));
     }
@@ -707,6 +706,7 @@ public final class ResampledGridCoverageTest extends 
TestCase {
      *                assuming extent low coordinate matches source image 
(0,0) coordinate.
      * @return the image directly displayable through debugger.
      */
+    @SuppressWarnings("unused")
     private static BufferedImage debug(final RenderedImage source, final 
GridExtent extent) {
         Raster tile = source.getTile(source.getMinTileX(), 
source.getMinTileY());
         final int width, height;
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java
index 8a814d4bef..b12186397c 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RasterStore.java
@@ -47,6 +47,7 @@ import org.apache.sis.image.internal.shared.ColorModelFactory;
 import org.apache.sis.image.internal.shared.ColorModelBuilder;
 import org.apache.sis.image.internal.shared.WritableUntiledImage;
 import org.apache.sis.coverage.internal.shared.RangeArgument;
+import org.apache.sis.image.internal.shared.DeferredProperty;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.internal.shared.Numerics;
@@ -436,6 +437,8 @@ abstract class RasterStore extends PRJDataStore implements 
GridCoverageResource
 
     /**
      * Creates the grid coverage resulting from a {@link #read(GridGeometry, 
int...)} operation.
+     * If the read operations has opportunistically computed statistics (e.g. 
when the values are
+     * decoded from an <abbr>ASCII</abbr> file), the statistics can be saved 
as a property.
      *
      * @param  domain  the effective domain after intersection and subsampling.
      * @param  range   indices of selected bands.
@@ -448,13 +451,13 @@ abstract class RasterStore extends PRJDataStore 
implements GridCoverageResource
                                         final WritableRaster data, final 
Statistics stats)
     {
         final SampleDimension[] bands = range.select(sampleDimensions);
-        Hashtable<String,Object> properties = null;
+        final var properties = new Hashtable<String, Object>();
+        DeferredProperty.addGridGeometry(properties, domain, null);
+        properties.put(PlanarImage.SAMPLE_DIMENSIONS_KEY, bands);
         if (stats != null) {
             final var as = new Statistics[range.getNumBands()];
             Arrays.fill(as, stats);
-            properties = new Hashtable<>();
             properties.put(PlanarImage.STATISTICS_KEY, as);
-            properties.put(PlanarImage.SAMPLE_DIMENSIONS_KEY, bands);
         }
         ColorModel cm = colorModel;
         if (!range.isIdentity()) {
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverage.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverage.java
index 3f0347ec64..850b528641 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverage.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverage.java
@@ -628,7 +628,7 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
             }
             final var iterator = new TileIterator(tileLower, tileUpper, 
offsetAOI, dimension, xDimension, yDimension, eventContext);
             final var properties = new HashMap<String, Object>(4);
-            properties.put(PlanarImage.GRID_GEOMETRY_KEY, 
DeferredProperty.forGridGeometry(gridGeometry, selectedDimensions));
+            DeferredProperty.addGridGeometry(properties, gridGeometry, 
selectedDimensions);
             if (name != null) {
                 properties.put(PlanarImage.SOURCE_NAME_KEY, 
name.toFullyQualifiedName());
             }

Reply via email to