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()); }
