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 58bfc8ba485ec33b6a32407b266c77eb3320d29e Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Mon Sep 30 17:04:00 2024 +0200 Better control about whether to clip the region to read to the valid area. This is required because the GeoTIFF and GDAL data stores have opposite needs. --- .../org/apache/sis/storage/geotiff/DataCube.java | 10 +++++ .../apache/sis/storage/base/TiledGridCoverage.java | 12 +++--- .../apache/sis/storage/base/TiledGridResource.java | 47 +++++++++++++++++++++- .../org/apache/sis/storage/gdal/TiledResource.java | 11 ++++- 4 files changed, 72 insertions(+), 8 deletions(-) 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 e6f123f9f7..1daafb6383 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 @@ -178,6 +178,16 @@ abstract class DataCube extends TiledGridResource implements StoreResource { */ abstract Number getReplaceableFillValue(); + /** + * Allows the reading of truncated tiles in the <var>y</var> dimension. + * Since there is no data after that dimension, it is safe to stop the + * reading process as soon as possible along that axis of each tile. + */ + @Override + protected final boolean canReadTruncatedTiles(int dim, boolean suggested) { + return suggested | (dim >= 1); // Y_DIMENSION. + } + /** * Returns {@code true} if the image can be read with the {@link DataSubset} base class, * or {@code false} if the more sophisticated {@link CompressedSubset} sub-class is needed. diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridCoverage.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridCoverage.java index 499fa4056e..d654e224a6 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridCoverage.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridCoverage.java @@ -47,6 +47,7 @@ import org.apache.sis.storage.tiling.TileMatrixSet; import org.apache.sis.storage.internal.Resources; import org.apache.sis.util.collection.WeakValueHashMap; import org.apache.sis.util.resources.Errors; +import org.apache.sis.util.privy.Numerics; import static org.apache.sis.pending.jdk.JDK18.ceilDiv; // Specific to the geoapi-3.1 and geoapi-4.0 branches: @@ -112,10 +113,11 @@ public abstract class TiledGridCoverage extends GridCoverage { * (note: this is rare. GeoTIFF for example always stores whole tiles).</li> * </ul> * - * <p>In current version this is a flag for the <var>x</var> dimension only. In a future version - * it could be flags for other dimensions as well (using bitmask) if it appears to be useful.</p> + * This a list of Boolean flags packed as a bitmask with the flag for the first dimension in the lowest bit. + * The default implementation always sets the flag of the last dimension to {@code false} (0), then sets the + * flags of other dimensions to {@code true} (1) if we are not in the case of a big untiled image. */ - private final boolean forceTileSize; + private final long forceWholeTiles; /** * Size of all tiles in the domain of this {@code TiledGridCoverage}, without clipping and subsampling. @@ -276,7 +278,7 @@ public abstract class TiledGridCoverage extends GridCoverage { this.model = model; this.colors = subset.colorsForBandSubset; this.fillValues = subset.fillValues; - forceTileSize = multiplyExact(subsampling[X_DIMENSION], subSize[X_DIMENSION]) == virtualTileSize[X_DIMENSION]; + forceWholeTiles = subset.forceWholeTiles(subSize); } /** @@ -795,7 +797,7 @@ public abstract class TiledGridCoverage extends GridCoverage { if (offset >= limit) { // Test for intersection before we adjust the limit. return false; } - if (dimension == X_DIMENSION && coverage.forceTileSize) { + if ((coverage.forceWholeTiles & Numerics.bitmask(dimension)) != 0) { limit = tileSize; } if (subsampled) { diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridResource.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridResource.java index 7f0b36e4e9..1da0df045a 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridResource.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledGridResource.java @@ -47,6 +47,7 @@ import org.apache.sis.storage.RasterLoadingStrategy; import org.apache.sis.storage.event.StoreListeners; import org.apache.sis.measure.NumberRange; import org.apache.sis.util.ArraysExt; +import org.apache.sis.util.privy.Numerics; import org.apache.sis.util.collection.WeakValueHashMap; import static org.apache.sis.storage.base.TiledGridCoverage.X_DIMENSION; import static org.apache.sis.storage.base.TiledGridCoverage.Y_DIMENSION; @@ -222,6 +223,32 @@ public abstract class TiledGridResource extends AbstractGridCoverageResource { return (dim == 0) ? TiledGridCoverage.getPixelsPerElement(getSampleModel(null)) : 1; } + /** + * Returns {@code true} if the reader can load truncated tiles. Truncated tiles may happen in the + * last row and last column of a tile matrix when the image size is not a multiple of the tile size. + * Some file formats, such as GeoTIFF, unconditionally stores full tiles, in which case this method + * should return {@code false}. At the opposite, some implementations, such as <abbr>GDAL</abbr>, + * accept only requests over the valid area, in which case this method should return {@code true}. + * + * <h4>Suggested value</h4> + * The {@code suggested} argument is a value computed by the caller based on common usages. + * The default implementation of {@link TiledGridCoverage} suggests {@code true} + * if the read operation is inside a single big tile, or {@code false} otherwise. + * The default implementation of this method returns {@code suggested} unchanged. + * + * <p>Note that even for subclasses that generally do not support the reading of truncated tiles, + * it is often safe to return {@code true} for the last dimension (for example, <var>y</var> in a + * two-dimensional image), because there is no data after that dimension. + * Therefore, it is often safe to stop the reading process in that dimension. + * + * @param dim the dimension: 0 for <var>x</var>, 1 for <var>y</var>, <i>etc.</i> + * @param suggested suggested response to return (see above heuristic rules). + * @return whether the reader can load truncated tiles along the specified dimension. + */ + protected boolean canReadTruncatedTiles(int dim, boolean suggested) { + return suggested; + } + /** * Returns {@code true} if the reader can load only the requested bands and skip the other bands, * or {@code false} if the reader must load all bands. This value controls the amount of data to @@ -373,8 +400,8 @@ check: if (dataType.isInteger()) { /** * Parameters that describe the resource subset to be accepted by the {@link TiledGridCoverage} constructor. - * Instances of this class are temporary and used only for transferring information from {@link TiledGridResource}. - * This class does not perform I/O operations. + * Instances of this class are temporary and used only for transferring information from {@link TiledGridResource} + * to {@link TiledGridCoverage}. This class does not perform I/O operations. */ public final class Subset { /** @@ -582,6 +609,22 @@ check: if (dataType.isInteger()) { cache = sharedCache ? rasters : new WeakValueHashMap<>(CacheKey.class); } + /** + * Returns flags telling, for each dimension, whether the read region should be an integer number of tiles. + * + * @param subSize tile size after subsampling. + * @return a bitmask with the flag for the first dimension in the lowest bit. + */ + final long forceWholeTiles(final int[] subSize) { + long forceWholeTiles = 0; + for (int i=0; i<subSize.length; i++) { + if (!canReadTruncatedTiles(i, Math.multiplyExact(subsampling[i], subSize[i]) != virtualTileSize[i])) { + forceWholeTiles |= Numerics.bitmask(i); + } + } + return forceWholeTiles; + } + /** * Returns {@code true} if reading data in this subset will read contiguous values on the <var>x</var> axis. * This method returns {@code true} if all following conditions are met: diff --git a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java index 1832044f7f..c72bc57fa3 100644 --- a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java +++ b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledResource.java @@ -345,7 +345,7 @@ final class TiledResource extends TiledGridResource { * The axis order used by GDAL is not the axis order in the CRS definition. * GDAL provides a separated method for specifying the axis swapping. */ - if (gridToCRS != null) { + if (gridToCRS != null && srs != null) { int dimension = (crs != null) ? crs.getCoordinateSystem().getDimension() : SpatialRef.BIDIMENSIONAL; final Matrix swap = srs.getDataToCRS(dimension); if (swap != null) { @@ -559,6 +559,15 @@ final class TiledResource extends TiledGridResource { } } + /** + * Allows the reading of truncated tiles in all dimensions. In <abbr>GDAL</abbr> case, truncating is actually + * mandatory because <abbr>GDAL</abbr> requires the read requests to be fully contained inside the valid area. + */ + @Override + protected boolean canReadTruncatedTiles(int dim, boolean suggested) { + return true; + } + /** * Returns the size of tiles (in pixels) in this resource. * The length of the returned array is the number of dimensions.