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 5431859977afae1e8d538e020aa3796af0ad369e Author: Martin Desruisseaux <[email protected]> AuthorDate: Fri Feb 13 16:56:16 2026 +0100 Adjust the API for future implementation of `TileMatrixSet`: - Simpler `TiledGridResource.read(…)` method to override. - Replace `ReshapedImage` constructors by static methods. --- .../sis/coverage/grid/ClippedGridCoverage.java | 3 +- .../apache/sis/coverage/grid/GridCoverage2D.java | 3 +- .../sis/image/internal/shared/ReshapedImage.java | 103 ++++++++++++++------- .../sis/coverage/grid/ClippedGridCoverageTest.java | 1 + .../image/internal/shared/ReshapedImageTest.java | 15 +-- .../org/apache/sis/storage/geotiff/DataCube.java | 56 +++++------ .../org/apache/sis/storage/geotiff/DataSubset.java | 11 +++ .../main/org/apache/sis/storage/Resource.java | 4 +- .../apache/sis/storage/aggregate/GridSlice.java | 12 +-- .../org/apache/sis/storage/tiling/TileMatrix.java | 11 ++- .../sis/storage/tiling/TiledGridCoverage.java | 34 +++++-- .../sis/storage/tiling/TiledGridResource.java | 97 ++++++++++++------- .../apache/sis/storage/geoheif/ImageResource.java | 22 ++--- .../org/apache/sis/storage/gdal/TiledResource.java | 29 +++--- 14 files changed, 241 insertions(+), 160 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 fe62d0d81b..0546cd204b 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,8 +131,7 @@ final class ClippedGridCoverage extends DerivedGridCoverage { } else { gridDimensions = clipped.getSubspaceDimensions(BIDIMENSIONAL); } - final var t = new ReshapedImage(image, translation[gridDimensions[0]], translation[gridDimensions[1]]); - return t.isIdentity() ? t.source : t; + 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/GridCoverage2D.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage2D.java index d40a5b56d3..72cab7f656 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 @@ -662,8 +662,7 @@ public class GridCoverage2D extends GridCoverage { * with Raster.createChild(…), but that would force us to invoke RenderedImage.getTile(…) which * may force data loading earlier than desired. */ - final var result = new ReshapedImage(data, xmin, ymin, xmax, ymax); - return result.isIdentity() ? result.source : result; + return ReshapedImage.relocate(data, xmin, ymin, xmax, ymax); } catch (ArithmeticException e) { throw new CannotEvaluateException(e.getMessage(), e); } 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 cebb3dba77..ee40136e8f 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 @@ -80,34 +80,62 @@ public final class ReshapedImage extends PlanarImage { private final int minTileX, minTileY; /** - * Creates an image with the data of the given image translated by the given amount. - * The number of tiles and their indexes are unchanged. + * Creates an image with the following properties. * - * @param source the image to translate. - * @param tx the translation to apply on <var>x</var> coordinates. - * @param ty the translation to apply on <var>y</var> coordinates. - * @throws ArithmeticException if image indices would overflow 32-bits integer capacity. + * @throws ArithmeticException if image indices overflow 32-bits integer capacity. */ - public ReshapedImage(RenderedImage source, long tx, long ty) { + private ReshapedImage(RenderedImage source, + long offsetX, long offsetY, + final long minX, final long minY, + final int width, final int height, + final int minTileX, final int minTileY) + { while (source instanceof ReshapedImage) { final var r = (ReshapedImage) source; - tx = addExact(r.offsetX, tx); - ty = addExact(r.offsetY, ty); - source = r.source; + offsetX = addExact(offsetX, r.offsetX); + offsetY = addExact(offsetY, r.offsetY); + source = r.source; } - this.source = source; - offsetX = toIntExact(tx); - offsetY = toIntExact(ty); - minX = toIntExact(tx + source.getMinX()); - minY = toIntExact(ty + source.getMinY()); - width = source.getWidth(); - height = source.getHeight(); - minTileX = source.getMinTileX(); - minTileY = source.getMinTileY(); + this.source = source; + this.offsetX = toIntExact(offsetX); + this.offsetY = toIntExact(offsetY); + this.minX = toIntExact(minX); + this.minY = toIntExact(minY); + this.width = width; + this.height = height; + this.minTileX = minTileX; + this.minTileY = minTileY; } /** - * Creates a new image with the same data as the given image but located at different coordinates. + * Returns an image with the data of the given image translated by the given amount. + * The number of tiles and their indexes are unchanged. + * + * @param source the image to translate. + * @param offsetX the translation to apply on <var>x</var> coordinates. + * @param offsetY the translation to apply on <var>y</var> coordinates. + * @return the translated image. May be the given source or one of its sources. + * @throws ArithmeticException if image indices overflow 32-bits integer capacity. + */ + public static RenderedImage translate(final RenderedImage source, final long offsetX, final long offsetY) { + if ((offsetX | offsetY) == 0) { + return source; + } + final var image = new ReshapedImage( + source, + offsetX, + offsetY, + addExact(source.getMinX(), offsetX), + addExact(source.getMinY(), offsetY), + source.getWidth(), + source.getHeight(), + source.getMinTileX(), + source.getMinTileY()); + return image.isIdentity() ? image.source : image; + } + + /** + * Returns an image with the same data as the given image but located at different coordinates. * In addition, this constructor can reduce the number of tiles. * * @param source the image to move. @@ -115,10 +143,10 @@ public final class ReshapedImage extends PlanarImage { * @param ymin minimal <var>y</var> coordinate of the requested region, inclusive. * @param xmax maximal <var>x</var> coordinate of the requested region, inclusive. * @param ymax maximal <var>y</var> coordinate of the requested region, inclusive. + * @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 ReshapedImage(final RenderedImage source, final long xmin, final long ymin, final long xmax, final long ymax) { - this.source = source; + 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 @@ -151,20 +179,25 @@ public final class ReshapedImage extends PlanarImage { */ final long x = subtractExact(sx, xmin); final long y = subtractExact(sy, ymin); - minX = toIntExact(x); - minY = toIntExact(y); - width = toIntExact(min(upperX, (maxTX + 1) * tw + xo) - sx); - height = toIntExact(min(upperY, (maxTY + 1) * th + yo) - sy); - offsetX = toIntExact(x - sx); - offsetY = toIntExact(y - sy); - minTileX = toIntExact(minTX); - minTileY = toIntExact(minTY); + final var image = 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; } /** * Returns {@code true} 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. */ - public boolean isIdentity() { + private boolean isIdentity() { // 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(); } @@ -182,6 +215,9 @@ public final class ReshapedImage extends PlanarImage { /** * Delegates to the wrapped image with no change. + * + * @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();} @@ -330,6 +366,8 @@ public final class ReshapedImage extends PlanarImage { /** * Returns a hash code value for this image. + * + * @return a hash code value for this image. */ @Override public int hashCode() { @@ -338,6 +376,9 @@ public final class ReshapedImage extends PlanarImage { /** * Compares the given object with this image for equality. + * + * @param object the object to compare with this image. + * @return whether the two object wrap equal image in the same region. */ @Override public boolean equals(final Object object) { 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 index 942559ac80..9637909e5a 100644 --- 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 @@ -37,6 +37,7 @@ import org.apache.sis.referencing.crs.HardCodedCRS; * * @author Martin Desruisseaux (Geomatys) */ +@SuppressWarnings("exports") public final class ClippedGridCoverageTest extends TestCase { /** * Size of the test image, in pixels. diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/internal/shared/ReshapedImageTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/internal/shared/ReshapedImageTest.java index f506734025..35973b6908 100644 --- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/internal/shared/ReshapedImageTest.java +++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/image/internal/shared/ReshapedImageTest.java @@ -19,6 +19,7 @@ package org.apache.sis.image.internal.shared; import java.util.Random; import java.awt.image.DataBuffer; import java.awt.image.BufferedImage; +import java.awt.image.RenderedImage; // Test dependencies import org.junit.jupiter.api.Test; @@ -96,14 +97,14 @@ public final class ReshapedImageTest extends TestCase { */ tileXOffset = minX = 1; tileYOffset = minY = 2; - verifySingleTile(new ReshapedImage(data, -1, -2, 4, 4)); + verifySingleTile(ReshapedImage.relocate(data, -1, -2, 4, 4)); /* * Tests with a request inside the image. Constructor should expand to * an integer number of tiles, which is the whole image for this test. */ tileXOffset = minX = -2; tileYOffset = minY = -1; - verifySingleTile(new ReshapedImage(data, 2, 1, 1, 1)); + verifySingleTile(ReshapedImage.relocate(data, 2, 1, 1, 1)); } /** @@ -111,7 +112,7 @@ public final class ReshapedImageTest extends TestCase { * * @param image the reshaped image. */ - private void verifySingleTile(final ReshapedImage image) { + private void verifySingleTile(final RenderedImage image) { verifyLayout(image); assertValuesEqual(image.getData(), 0, new int[][] { {1, 2, 3}, @@ -124,8 +125,7 @@ public final class ReshapedImageTest extends TestCase { * * @param image the image to verify. */ - private void verifyLayout(final ReshapedImage image) { - assertNull( image.verify()); + private void verifyLayout(final RenderedImage image) { assertEquals(minX, image.getMinX()); assertEquals(minY, image.getMinY()); assertEquals(width, image.getWidth()); @@ -138,6 +138,7 @@ public final class ReshapedImageTest extends TestCase { assertEquals(numYTiles, image.getNumYTiles()); assertEquals(tileXOffset, image.getTileGridXOffset()); assertEquals(tileYOffset, image.getTileGridYOffset()); + assertNull(assertInstanceOf(ReshapedImage.class, image).verify()); } /** @@ -165,7 +166,7 @@ public final class ReshapedImageTest extends TestCase { */ tileXOffset = (minX = 7) - minTileX * TILE_WIDTH; tileYOffset = (minY = 13) - minTileY * TILE_HEIGHT; - var image = new ReshapedImage(data, dataMinX - 7, dataMinY - 13, 100, 100); + RenderedImage image = ReshapedImage.relocate(data, dataMinX - 7, dataMinY - 13, 100, 100); verifyLayout(image); assertValuesEqual(image.getData(), 0, new int[][] { { 100, 101, 102 , 200, 201, 202 , 300, 301, 302 , 400, 401, 402 , 500, 501, 502}, @@ -188,7 +189,7 @@ public final class ReshapedImageTest extends TestCase { height = (numYTiles = 2) * TILE_HEIGHT; tileXOffset = (minX = -2) - minTileX * TILE_WIDTH; tileYOffset = (minY = -1) - minTileY * TILE_HEIGHT; - image = new ReshapedImage(data, dataMinX + 5, dataMinY + 3, dataMinX + 9, dataMinY + 5); + image = ReshapedImage.relocate(data, dataMinX + 5, dataMinY + 3, dataMinX + 9, dataMinY + 5); verifyLayout(image); assertValuesEqual(image.getData(), 0, new int[][] { { 700, 701, 702 , 800, 801, 802 , 900, 901, 902}, diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataCube.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataCube.java index d9fd5c52e5..79596fa269 100644 --- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataCube.java +++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataCube.java @@ -26,12 +26,11 @@ import org.apache.sis.storage.DataStore; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.DataStoreContentException; import org.apache.sis.storage.event.StoreListeners; -import org.apache.sis.coverage.grid.GridCoverage; -import org.apache.sis.coverage.grid.GridGeometry; import org.apache.sis.storage.geotiff.base.Tags; import org.apache.sis.storage.geotiff.base.Resources; import org.apache.sis.storage.geotiff.base.Predictor; import org.apache.sis.storage.geotiff.base.Compression; +import org.apache.sis.storage.tiling.TiledGridCoverage; import org.apache.sis.storage.tiling.TiledGridResource; import org.apache.sis.storage.base.StoreResource; import org.apache.sis.math.Vector; @@ -238,41 +237,32 @@ abstract class DataCube extends TiledGridResource implements StoreResource { } /** - * Creates a {@link GridCoverage} which will load pixel data in the given domain. + * Creates a coverage which will read the specified subset from this resource when first requested. + * Synchronization, immediate loading (if requested), logging of read time + * and handling of {@link RuntimeException} are done by the caller. * - * @param domain desired grid extent and resolution, or {@code null} for reading the whole domain. - * @param ranges 0-based index of sample dimensions to read, or an empty sequence for reading all ranges. - * @return the grid coverage for the specified domain and ranges. + * @param subset desired grid extent, resolution and sample dimensions to read. + * @return the grid coverage for the specified domain, resolution and ranges. * @throws DataStoreException if an error occurred while reading the grid coverage data. + * @throws ArithmeticException if the number of tiles overflows 32 bits integer arithmetic. + * @throws RuntimeException if the given subset is invalid. */ @Override - public final GridCoverage read(final GridGeometry domain, final int... ranges) throws DataStoreException { - final long startTime = System.nanoTime(); - GridCoverage coverage; - try { - synchronized (getSynchronizationLock()) { - final Subset subset = new Subset(domain, ranges); - final Compression compression = getCompression(); - if (compression == null) { - throw new DataStoreContentException(reader.resources().getString( - Resources.Keys.MissingValue_2, Tags.name((short) TAG_COMPRESSION))); - } - /* - * The `DataSubset` parent class is the most efficient but has many limitations - * documented in the javadoc of its `readSlice(…)` method. If any precondition - * is not met, we need to fallback on the less direct `CompressedSubset` class. - */ - if (compression == Compression.NONE && getPredictor() == Predictor.NONE && canReadDirect(subset)) { - coverage = new DataSubset(this, subset); - } else { - coverage = new CompressedSubset(this, subset); - } - coverage = preload(coverage); - } - } catch (RuntimeException e) { - throw canNotRead(filename(), domain, e); + protected final TiledGridCoverage read(final Subset subset) throws DataStoreException { + final Compression compression = getCompression(); + if (compression == null) { + throw new DataStoreContentException(reader.resources().getString( + Resources.Keys.MissingValue_2, Tags.name((short) TAG_COMPRESSION))); + } + /* + * The `DataSubset` parent class is the most efficient but has many limitations + * documented in the javadoc of its `readSlice(…)` method. If any precondition + * is not met, we need to fallback on the less direct `CompressedSubset` class. + */ + if (compression == Compression.NONE && getPredictor() == Predictor.NONE && canReadDirect(subset)) { + return new DataSubset(this, subset); + } else { + return new CompressedSubset(this, subset); } - logReadOperation(reader.store.path, coverage.getGridGeometry(), startTime); - return coverage; } } diff --git a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataSubset.java b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataSubset.java index 1923a65834..09b2d4d990 100644 --- a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataSubset.java +++ b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/DataSubset.java @@ -19,6 +19,7 @@ package org.apache.sis.storage.geotiff; import java.util.Arrays; import java.util.Locale; import java.nio.Buffer; +import java.nio.file.Path; import java.io.Closeable; import java.io.IOException; import java.awt.Point; @@ -195,6 +196,16 @@ class DataSubset extends TiledGridCoverage implements Localized { return source.listeners().getLocale(); } + /** + * Returns the path to the content of the specified data, or {@code null} if none or unknown. + * + * @param tileIndices indices of the tile, or {@code null} for the whole coverage. + */ + @Override + protected final Path getContentPath(final long... tileIndices) { + return (tileIndices == null) ? source.reader.store.path : super.getContentPath(tileIndices); + } + /** * Returns an human-readable identification of this coverage. * The namespace should be the {@linkplain #filename() filename} diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Resource.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Resource.java index 34f16f3acc..eadb094c11 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Resource.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/Resource.java @@ -197,10 +197,10 @@ public interface Resource { * Creates a new instance with the given path. * This is a convenience constructor for the common case where the data store uses exactly one file. * - * @param paths the single file to be returned by {@link #getPaths()}. + * @param path the single file to be returned by {@link #getPaths()}. */ public FileSet(final Path path) { - this.paths = List.of(path); + paths = List.of(path); } /** diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/GridSlice.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/GridSlice.java index a5e90492b1..8353fba41f 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/GridSlice.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/GridSlice.java @@ -215,14 +215,10 @@ search: synchronized (transforms) { * @return the rendered image with (0,0) locate at the beginning of the requested extent. */ final RenderedImage render(final GridCoverage coverage, final GridExtent request, final int[] subdim) { - final RenderedImage image = coverage.render(request.translate(offset)); - final long tx = Math.negateExact(offset[subdim[0]]); - final long ty = Math.negateExact(offset[subdim[1]]); - if ((tx | ty) == 0) { - return image; - } - final var translated = new ReshapedImage(image, tx, ty); - return translated.isIdentity() ? translated.source : translated; + return ReshapedImage.translate( + coverage.render(request.translate(offset)), + Math.negateExact(offset[subdim[0]]), + Math.negateExact(offset[subdim[1]])); } /** diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrix.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrix.java index dc845035a1..1ab18177a8 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrix.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrix.java @@ -211,8 +211,9 @@ public interface TileMatrix { /** * Retrieves a stream of existing tiles in the specified region. The stream contains - * the {@linkplain TileStatus#EXISTS existing} tiles that are inside the given region - * and excludes all {@linkplain TileStatus#MISSING missing} tiles. + * the {@linkplain TileStatus#EXISTS existing} tiles that are inside the given region. + * Tiles that are {@linkplain TileStatus#MISSING missing} or + * {@linkplain TileStatus#OUTSIDE_EXTENT outside} the coverage extent are excluded.. * If a tile is {@linkplain TileStatus#IN_ERROR in error}, * then the stream should nevertheless return a {@link Tile} instance * but its {@link Tile#getResource()} method should throw the exception. @@ -222,11 +223,11 @@ public interface TileMatrix { * If {@code true}, the stream may or may not be parallel; * implementations are free to ignore this argument if they do not support parallelism.</p> * - * @param indicesRanges ranges of tile indices in all dimensions, or {@code null} for all tiles. + * @param indiceRanges ranges of tile indices in all dimensions, or {@code null} for all tiles. * @param parallel {@code true} for a parallel stream (if supported), or {@code false} for a sequential stream. * @return stream of tiles, excluding {@linkplain TileStatus#MISSING missing} tiles. * Iteration order of the stream may vary from one implementation to another and from one call to another. - * @throws DataStoreException if the stream creation failed. + * @throws DataStoreException if the tiles can not be fetched in the given ranges of tile indexes. */ - Stream<Tile> getTiles(GridExtent indicesRanges, boolean parallel) throws DataStoreException; + Stream<Tile> getTiles(GridExtent indiceRanges, boolean parallel) throws DataStoreException; } 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 44f0bae9eb..40e9648e9d 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 @@ -19,6 +19,7 @@ package org.apache.sis.storage.tiling; import java.util.Map; import java.util.Locale; import java.util.Optional; +import java.nio.file.Path; import java.awt.Point; import java.awt.Rectangle; import java.awt.image.DataBuffer; @@ -43,6 +44,7 @@ import org.apache.sis.coverage.grid.DisjointExtentException; import org.apache.sis.image.internal.shared.DeferredProperty; import org.apache.sis.image.internal.shared.TiledImage; import org.apache.sis.storage.internal.Resources; +import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.collection.WeakValueHashMap; import org.apache.sis.util.resources.Errors; import org.apache.sis.util.internal.shared.Numerics; @@ -268,11 +270,11 @@ public abstract class TiledGridCoverage extends GridCoverage { /** * Creates a new tiled grid coverage. This constructor does not load any tile. - * Callers should invoke {@link TiledGridResource#preload(GridCoverage)} after - * construction for loading tiles when immediate loading was requested by user. * * @param subset description of the {@link TiledGridResource} subset to cover. * @throws ArithmeticException if the number of tiles overflows 32 bits integer arithmetic. + * + * @see TiledGridResource#read(TiledGridResource.Subset) */ protected TiledGridCoverage(final TiledGridResource.Subset subset) { super(subset.domain, subset.ranges); @@ -319,6 +321,16 @@ public abstract class TiledGridCoverage extends GridCoverage { forceWholeTiles = subset.forceWholeTiles(subSize); } + /** + * Returns the locale for error messages, or {@code null} for the default. + * The default implementation returns {@code null}. + * + * @return the locale for warning or error messages, or {@code null} if unspecified. + */ + protected Locale getLocale() { + return null; + } + /** * Returns a unique name that identifies this coverage. * The name shall be unique in the {@link TileMatrixSet}. @@ -328,17 +340,27 @@ public abstract class TiledGridCoverage extends GridCoverage { protected abstract GenericName getIdentifier(); /** - * Returns the locale for error messages, or {@code null} for the default. + * Returns the path to the content of the specified data, or {@code null} if none or unknown. + * If {@code tileIndices} is {@code null}, then this method returns a path to the content of + * the whole coverage (if known). Otherwise, this method returns a path to the content of the + * tile at the specified indices. * - * @return the locale for warning or error messages, or {@code null} if unspecified. + * <p>The default implementation returns {@code null}.</p> + * + * @param tileIndices indices of the tile, or {@code null} for the whole coverage. + * @return path to the content of the coverage (if {@code tileIndices} was null) or + * to the specified tile, or {@code null} if none or unknown. + * + * @see Tile#getContentPath() */ - protected Locale getLocale() { + protected Path getContentPath(final long... tileIndices) { + ArgumentChecks.ensureDimensionMatches("tileIndices", gridGeometry.getDimension(), tileIndices); return null; } /** * Returns the size of all tiles in the domain of this {@code TiledGridCoverage}, without sub-sampling. - * This usually the same size as the tiles in the storage which is read by {@link TiledGridResource}, + * This is usually the same size as the tiles in the storage which is read by {@link TiledGridResource}, * but not necessarily. It may be larger if the {@link TiledGridResource} subclass decided to coalesce * many real tiles into larger virtual tiles, or it may be smaller when reading a sub-region of an * effectively untiled coverage. diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridResource.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridResource.java index 245f2fa590..72e2ffc996 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridResource.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridResource.java @@ -32,6 +32,7 @@ import java.awt.image.RasterFormatException; import org.apache.sis.coverage.SampleDimension; 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.GridDerivation; import org.apache.sis.coverage.grid.GridExtent; import org.apache.sis.coverage.grid.GridGeometry; @@ -68,22 +69,9 @@ import org.opengis.coverage.CannotEvaluateException; * <li>{@link #getTileSize()}</li> * <li>{@link #getSampleModel(int[])} (optional but recommended)</li> * <li>{@link #getColorModel(int[])} (optional but recommended)</li> - * <li>{@link #read(GridGeometry, int...)}</li> + * <li>{@link #read(Subset)}</li> * </ul> * - * The read method can be implemented simply as below: - * - * {@snippet lang="java" : - * @Override - * public GridCoverage read(GridGeometry domain, int... ranges) throws DataStoreException { - * synchronized (getSynchronizationLock()) { - * var subset = new Subset(domain, ranges); - * var result = new MySubclassOfTiledGridCoverage(this, subset); - * return preload(result); - * } - * } - * } - * * @author Martin Desruisseaux (Geomatys) * @version 1.7 * @since 1.7 @@ -171,6 +159,11 @@ public abstract class TiledGridResource extends AbstractGridCoverageResource { */ private int yDimension; + /** + * The grid coverage processor to use when tiles use a subset of the bands. + */ + final GridCoverageProcessor processor; + /** * Creates a new resource. * @@ -179,6 +172,7 @@ public abstract class TiledGridResource extends AbstractGridCoverageResource { protected TiledGridResource(final Resource parent) { super(parent); yDimension = 1; + processor = new GridCoverageProcessor(); } /** @@ -755,27 +749,59 @@ check: if (dataType.isInteger()) { } /** - * If the loading strategy is to load all tiles at {@code read(…)} time, replaces the given coverage - * by a coverage will all data in memory. This method should be invoked by subclasses at the end of - * their {@link #read(GridGeometry, int...)} method implementation if the tile loading strategy is not - * {@link RasterLoadingStrategy#AT_GET_TILE_TIME}. - * - * @param coverage the {@link TiledGridCoverage} to potentially replace by a coverage with preloaded data. - * @return a coverage with preloaded data, or the given coverage if preloading is not enabled. - * @throws DataStoreException if an error occurred while preloading data. + * Creates a coverage which will read the specified subset from this resource when first requested. + * This method is invoked by the default implementation of {@link #read(GridGeometry, int...)}. + * This method creates a subclass of {@link TiledGridCoverage} which will read tiles later, when first requested. + * The implementation of this method does not need to care about synchronization, immediate (rather than deferred) + * loading of tiles, logging of loading time and handling of {@link RuntimeException}. + * Those tasks should be handled by the caller. + * + * @param subset desired grid extent, resolution and sample dimensions to read. + * @return the grid coverage for the specified domain, resolution and ranges. + * @throws DataStoreException if the coverage cannot be created. + * @throws RuntimeException if the coverage cannot be created for a reason not handled as a data store exception. + * + * @see TiledGridCoverage#TiledGridCoverage(Subset) */ - protected GridCoverage preload(final GridCoverage coverage) throws DataStoreException { - assert Thread.holdsLock(getSynchronizationLock()); - // Note: `loadingStrategy` may still be null if unitialized. - if (loadingStrategy == null || loadingStrategy == RasterLoadingStrategy.AT_READ_TIME) { - /* - * In theory the following condition is redundant with `supportImmediateLoading()`. - * We apply it anyway in case the coverage geometry is not what was announced. - * This condition is also necessary if `loadingStrategy` has not been initialized. - */ - if (coverage.getGridGeometry().getDimension() == BIDIMENSIONAL) try { + protected abstract TiledGridCoverage read(Subset subset) throws DataStoreException; + + /** + * Loads a subset of the grid coverage represented by this resource. + * While this method name suggests an immediate reading, the actual reading may be deferred. + * + * <p>This method invokes {@link #read(Subset)} inside a block synchronized + * on the {@linkplain #getSynchronizationLock() synchronization lock}. + * Then, if the {@linkplain #getLoadingStrategy() current loading strategy} + * is {@link RasterLoadingStrategy#AT_READ_TIME}, this method forces the immediate reading of tiles. + * and logs the time required for this operation.</p> + * + * @param domain desired grid extent and resolution, or {@code null} for reading the whole domain. + * @param ranges 0-based indices of sample dimensions to read, or {@code null} or an empty sequence for reading them all. + * @return the grid coverage for the specified domain and ranges. + * @throws DataStoreException if an error occurred while reading the grid coverage data. + */ + @Override + public GridCoverage read(final GridGeometry domain, final int... ranges) throws DataStoreException { + final TiledGridCoverage coverage; + final GridCoverage loaded; + final boolean preload; + final long startTime; + synchronized (getSynchronizationLock()) { + // Note: `loadingStrategy` may still be null if unitialized. + preload = (loadingStrategy == null || loadingStrategy == RasterLoadingStrategy.AT_READ_TIME); + startTime = preload ? System.nanoTime() : 0; + try { + coverage = read(new Subset(domain, ranges)); + /* + * In theory the following condition is redundant with `supportImmediateLoading()`. + * We apply it anyway in case the coverage geometry is not what was announced. + * This condition is also necessary if `loadingStrategy` has not been initialized. + */ + if (!preload || coverage.getGridGeometry().getDimension() != BIDIMENSIONAL) { + return coverage; + } final RenderedImage image = coverage.render(null); - return new GridCoverage2D(coverage.getGridGeometry(), coverage.getSampleDimensions(), image); + loaded = new GridCoverage2D(coverage.getGridGeometry(), coverage.getSampleDimensions(), image); } catch (RuntimeException e) { /* * The `coverage.render(…)` implementation may have wrapped the checked `DataStoreException` @@ -794,10 +820,11 @@ check: if (dataType.isInteger()) { if (cause == null || !(e instanceof CannotEvaluateException)) { cause = e; } - throw new DataStoreException(e.getLocalizedMessage(), cause); + throw canNotRead(listeners.getSourceName(), domain, cause); } } - return coverage; + logReadOperation(coverage.getContentPath(null), coverage.getGridGeometry(), startTime); + return loaded; } /** diff --git a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java index 8864b943c1..2f869225b6 100644 --- a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java +++ b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java @@ -35,7 +35,6 @@ import javax.imageio.spi.ImageReaderSpi; import org.opengis.metadata.Metadata; import org.opengis.util.GenericName; import org.apache.sis.coverage.SampleDimension; -import org.apache.sis.coverage.grid.GridCoverage; import org.apache.sis.coverage.grid.GridGeometry; import org.apache.sis.metadata.iso.DefaultMetadata; import org.apache.sis.storage.DataStore; @@ -244,23 +243,18 @@ final class ImageResource extends TiledGridResource implements StoreResource { } /** - * Loads a subset of the grid coverage represented by this resource. - * While this method name suggests an immediate reading, the actual reading may be deferred. + * Creates a coverage which will read the specified subset from this resource when first requested. + * Synchronization, immediate loading (if requested), logging of read time + * and handling of {@link RuntimeException} are done by the caller. * - * @param domain desired grid extent and resolution, or {@code null} for reading the whole domain. - * @param ranges 0-based indices of sample dimensions to read, or {@code null} or an empty sequence for reading them all. - * @return the grid coverage for the specified domain and ranges. + * @param subset desired grid extent, resolution and sample dimensions to read. + * @return the grid coverage for the specified domain, resolution and ranges. * @throws DataStoreException if an error occurred while reading the grid coverage data. + * @throws ArithmeticException if the number of tiles overflows 32 bits integer arithmetic. */ @Override - public final GridCoverage read(final GridGeometry domain, final int ... range) throws DataStoreException { - try { - synchronized (getSynchronizationLock()) { - return preload(new Coverage(new Subset(domain, range))); - } - } catch (RuntimeException e) { - throw new DataStoreException(e); - } + protected final TiledGridCoverage read(final Subset subset) throws DataStoreException { + return new Coverage(subset); } /** diff --git a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java index 2fd8453c29..2707239a77 100644 --- a/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java +++ b/optional/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java @@ -42,13 +42,13 @@ import org.apache.sis.image.ImageLayout; import org.apache.sis.coverage.SampleDimension; import org.apache.sis.coverage.grid.GridExtent; import org.apache.sis.coverage.grid.GridGeometry; -import org.apache.sis.coverage.grid.GridCoverage; import org.apache.sis.coverage.grid.PixelInCell; import org.apache.sis.image.internal.shared.ColorModelBuilder; import org.apache.sis.image.internal.shared.ColorModelFactory; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.DataStoreReferencingException; import org.apache.sis.storage.base.MetadataBuilder; +import org.apache.sis.storage.tiling.TiledGridCoverage; import org.apache.sis.storage.tiling.TiledGridResource; import org.apache.sis.system.Configuration; import org.apache.sis.util.ArraysExt; @@ -453,6 +453,7 @@ final class TiledResource extends TiledGridResource { * * @param bandIndices indices of the selected bands, or {@code null} for all bands. */ + @SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter") private void createColorAndSampleModel(final int[] bandIndices) throws DataStoreException { final Band[] selectedBands = bands(bandIndices); final GDAL gdal = parent.getProvider().GDAL(); @@ -615,23 +616,21 @@ final class TiledResource extends TiledGridResource { } /** - * Loads a subset of the grid coverage represented by this resource. - * The actual loading may be deferred until a tile is requested for the first time. + * Creates a coverage which will read the specified subset from this resource when first requested. + * Synchronization, immediate loading (if requested), logging of read time + * and handling of {@link RuntimeException} are done by the caller. * - * @param domain desired grid extent and resolution, or {@code null} for reading the whole domain. - * @param ranges 0-based indices of sample dimensions to read, or {@code null} or an empty sequence for reading them all. - * @return the grid coverage for the specified domain and ranges. + * @param subset desired grid extent, resolution and sample dimensions to read. + * @return the grid coverage for the specified domain, resolution and ranges. + * @throws DataStoreException if an error occurred while reading the grid coverage data. + * @throws ArithmeticException if the number of tiles overflows 32 bits integer arithmetic. */ @Override - public GridCoverage read(final GridGeometry domain, final int... ranges) throws DataStoreException { - synchronized (getSynchronizationLock()) { - try { - final var subset = new Subset(domain, ranges); - final var result = new TiledCoverage(this, subset); - return preload(result); - } finally { - ErrorHandler.report(parent, "read"); - } + protected TiledGridCoverage read(final Subset subset) throws DataStoreException { + try { + return new TiledCoverage(this, subset); + } finally { + ErrorHandler.report(parent, "read"); } } }
