You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2026/02/13 17:07:58 UTC
(sis) 03/03: Adjust the API for future implementation of `TileMatrixSet`: - Simpler `TiledGridResource.read(…)` method to override. - Replace `ReshapedImage` constructors by static methods.
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 <ma...@geomatys.com>
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");
}
}
}