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 ddc4420c59b9df5d03af9b59dbe92e3f73ef7806
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Tue Sep 17 12:55:04 2024 +0200

    Allow to read more tiles in one GDAL read operation.
---
 .../sis/storage/geotiff/CompressedSubset.java      | 24 ++++++++----
 .../org/apache/sis/storage/geotiff/DataSubset.java |  6 ++-
 .../apache/sis/storage/base/TiledGridCoverage.java | 44 +++++++++++++---------
 .../apache/sis/storage/base/TiledGridResource.java | 42 +++++++++++++++++++--
 .../org/apache/sis/storage/gdal/TiledResource.java | 15 ++++++++
 5 files changed, 99 insertions(+), 32 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/CompressedSubset.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/CompressedSubset.java
index 5ae22e39b6..5d418d27be 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/CompressedSubset.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/CompressedSubset.java
@@ -22,7 +22,6 @@ import java.nio.Buffer;
 import java.awt.Point;
 import java.awt.image.Raster;
 import static java.lang.Math.toIntExact;
-import static java.lang.Math.multiplyFull;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.base.TiledGridResource;
 import org.apache.sis.storage.geotiff.inflater.Inflater;
@@ -53,7 +52,7 @@ final class CompressedSubset extends DataSubset {
      * <em>first</em> pixel in a row. For computing the actual number of 
sample values to skip,
      * the number of sample values read or skipped before the last pixel must 
be subtracted.
      */
-    private final int afterLastBand;
+    private final long afterLastBand;
 
     /**
      * Number of sample values to skip after a chunk has been read, or {@code 
null} if none.
@@ -108,9 +107,9 @@ final class CompressedSubset extends DataSubset {
     @SuppressWarnings("LocalVariableHidesMemberVariable")
     CompressedSubset(final DataCube source, final TiledGridResource.Subset 
subset) throws DataStoreException {
         super(source, subset);
-        scanlineStride    = multiplyFull(getTileSize(X_DIMENSION), 
sourcePixelStride);
-        final int between = sourcePixelStride * (getSubsampling(X_DIMENSION) - 
1);
-        int afterLastBand = sourcePixelStride * (getTileSize(X_DIMENSION) - 1);
+        scanlineStride     = sourcePixelStride * getTileSize(X_DIMENSION);
+        final int between  = sourcePixelStride * (getSubsampling(X_DIMENSION) 
- 1);
+        long afterLastBand = scanlineStride - sourcePixelStride;
         if (includedBands != null && sourcePixelStride > 1) {
             final int[] skips = new int[includedBands.length];
             final int m = skips.length - 1;
@@ -213,9 +212,18 @@ final class CompressedSubset extends DataSubset {
         final int pixelsPerElement = getPixelsPerElement();                 // 
Always ≥ 1 and usually = 1.
         assert (head % pixelsPerElement) == 0 : head;
         if (inflater == null) {
-            inflater = Inflater.create(source.listeners(), input(), 
source.getCompression(), source.getPredictor(),
-                        sourcePixelStride, getTileSize(X_DIMENSION), 
chunksPerRow, samplesPerChunk, skipAfterChunks,
-                        pixelsPerElement, dataType);
+            inflater = Inflater.create(
+                    source.listeners(),                   // Object where to 
report warnings.
+                    input(),                              // The source of 
data to decompress.
+                    source.getCompression(),              // The compression 
method.
+                    source.getPredictor(),                // The mathematical 
operator to apply after decompression.
+                    sourcePixelStride,                    // Number of sample 
values per pixel in the source image.
+                    toIntExact(getTileSize(X_DIMENSION)), // Number of pixels 
in a row of source image.
+                    chunksPerRow,                         // Number of pixels 
per row in target image.
+                    samplesPerChunk,                      // Number of sample 
values per pixel.
+                    skipAfterChunks,                      // Number of sample 
values to skip between pixels.
+                    pixelsPerElement,                     // Number of pixels 
per primitive element (for packed.
+                    dataType);                            // Primitive type 
used for storing data elements in the bank.
         }
         @SuppressWarnings("LocalVariableHidesMemberVariable")
         final Inflater inflater = this.inflater;
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 668cb11692..0ed305eedb 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
@@ -29,7 +29,6 @@ import java.awt.image.DataBufferDouble;
 import java.awt.image.Raster;
 import static java.lang.Math.subtractExact;
 import static java.lang.Math.multiplyExact;
-import static java.lang.Math.multiplyFull;
 import static java.lang.Math.toIntExact;
 import org.opengis.util.GenericName;
 import org.apache.sis.image.DataType;
@@ -497,7 +496,10 @@ class DataSubset extends TiledGridCoverage implements 
Localized {
          * This length is used only for verification purpose so it does not 
need to be exact.
          */
         final long length = ceilDiv(width * height * sourcePixelStride * 
sampleSize, Byte.SIZE);
-        final long[] size = new long[] {multiplyFull(sourcePixelStride, 
getTileSize(X_DIMENSION)), getTileSize(Y_DIMENSION)};
+        final long[] size = new long[] {
+            multiplyExact(getTileSize(X_DIMENSION), sourcePixelStride),
+                          getTileSize(Y_DIMENSION)
+        };
         /*
          * If we use an interleaved sample model, each "element" from 
`HyperRectangleReader` perspective is actually a
          * group of `sourcePixelStride` values. Note that in such case, we 
cannot handle subsampling on the first axis.
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 eccfd9f5c5..684d9a5226 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
@@ -99,7 +99,7 @@ public abstract class TiledGridCoverage extends GridCoverage {
     private final GridExtent readExtent;
 
     /**
-     * Whether to force the {@link #readExtent} tile intersection to the 
{@link #tileSize}.
+     * Whether to force the {@link #readExtent} tile intersection to the 
{@link #virtualTileSize}.
      * This is relevant only for the last column of tile matrix, because those 
tiles may be truncated
      * if the image size is not a multiple of tile size. It is usually 
necessary to read those tiles
      * fully anyway because otherwise, the pixels read from the storage would 
not be aligned with the
@@ -124,14 +124,19 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
      * The length of this array is the number of dimensions in the source 
{@link GridExtent}.
      * This is often {@value #BIDIMENSIONAL} but can also be more.
      *
+     * <p>The tile size may be virtual if the {@link TiledGridResource} 
subclass decided to coalesce
+     * many real tiles in bigger virtual tiles. This is sometime useful when a 
subsampling is applied,
+     * for avoiding that the subsampled tiles become too small.
+     * This strategy may be convenient when coalescing is easy.</p>
+     *
      * @see #getTileSize(int)
      */
-    private final int[] tileSize;
+    private final long[] virtualTileSize;
 
     /**
      * Values by which to multiply each tile coordinates for obtaining the 
index in the tile vector.
-     * The length of this array is the same as {@link #tileSize}. All 
coverages created from the same
-     * {@link TiledGridResource} have the same stride values.
+     * The length of this array is the same as {@link #virtualTileSize}. All 
coverages created from
+     * the same {@link TiledGridResource} have the same stride values.
      */
     private final int[] tileStrides;
 
@@ -198,8 +203,8 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
 
     /**
      * The sample model for all rasters. The width and height of this sample 
model are the two first elements
-     * of {@link #tileSize} divided by subsampling and clipped to the domain. 
If user requested to read only
-     * a subset of the bands, then this sample model is already the subset.
+     * of {@link #virtualTileSize} divided by subsampling and clipped to the 
domain.
+     * If user requested to read only a subset of the bands, then this sample 
model is already the subset.
      *
      * @see TiledGridResource#getSampleModel(int[])
      */
@@ -241,7 +246,7 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
         subsamplingOffsets  = subset.subsamplingOffsets;
         includedBands       = subset.includedBands;
         rasters             = subset.cache;
-        tileSize            = subset.tileSize;
+        virtualTileSize     = subset.virtualTileSize;
         tmcOfFirstTile      = new long[dimension];
         tileStrides         = new int [dimension];
         final int[] subSize = new int [dimension];
@@ -250,10 +255,10 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
         long indexOfFirstTile = 0;
         int  tileStride       = 1;
         for (int i=0; i<dimension; i++) {
-            final int ts      = tileSize[i];
+            final long ts     = virtualTileSize[i];
             tmcOfFirstTile[i] = floorDiv(readExtent.getLow(i), ts);
             tileStrides[i]    = tileStride;
-            subSize[i]        = (int) Math.min(((ts-1) / subsampling[i]) + 1, 
extent.getSize(i));
+            subSize[i]        = toIntExact(Math.min(((ts-1) / subsampling[i]) 
+ 1, extent.getSize(i)));
             indexOfFirstTile  = addExact(indexOfFirstTile, 
multiplyExact(tmcOfFirstTile[i], tileStride));
             int tileCount     = 
toIntExact(ceilDiv(subset.sourceExtent.getSize(i), ts));
             tileStride        = multiplyExact(tileCount, tileStride);
@@ -272,7 +277,7 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
         this.model      = model;
         this.colors     = subset.colorsForBandSubset;
         this.fillValues = subset.fillValues;
-        forceTileSize   = subSize[X_DIMENSION] * subsampling[X_DIMENSION] == 
tileSize[X_DIMENSION];
+        forceTileSize   = multiplyFull(subSize[X_DIMENSION], 
subsampling[X_DIMENSION]) == virtualTileSize[X_DIMENSION];
     }
 
     /**
@@ -294,12 +299,14 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
 
     /**
      * Returns the size of all tiles in the domain of this {@code 
TiledGridCoverage}, without clipping and subsampling.
+     * It may be a virtual tile size if the {@link TiledGridResource} subclass 
decided to coalesce many real tiles into
+     * fewer bigger virtual tiles.
      *
      * @param  dimension  dimension for which to get tile size.
      * @return tile size in the given dimension, without clipping and 
subsampling.
      */
-    protected final int getTileSize(final int dimension) {
-        return tileSize[dimension];
+    protected final long getTileSize(final int dimension) {
+        return virtualTileSize[dimension];
     }
 
     /**
@@ -358,7 +365,7 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
      * @throws ArithmeticException if the coordinate cannot be represented as 
an integer.
      */
     private long toResourceTileMatrixCoordinate(final long coordinate, final 
int dimension) {
-        return floorDiv(pixelToResourceCoordinate(coordinate, dimension), 
tileSize[dimension]);
+        return floorDiv(pixelToResourceCoordinate(coordinate, dimension), 
virtualTileSize[dimension]);
     }
 
     /**
@@ -452,8 +459,8 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
                     }
                 }
                 // Lower and upper coordinates in subsampled image, rounded to 
integer number of tiles and clipped to available data.
-                final long lower = /* inclusive 
*/Math.max(resourceToPixelCoordinate(/* inclusive */multiplyExact(tileLo, 
tileSize[i]),  i), min);
-                final long upper = 
incrementExact(Math.min(resourceToPixelCoordinate(decrementExact(multiplyExact(tileUp,
 tileSize[i])), i), max));
+                final long lower = /* inclusive 
*/Math.max(resourceToPixelCoordinate(/* inclusive */multiplyExact(tileLo, 
virtualTileSize[i]),  i), min);
+                final long upper = 
incrementExact(Math.min(resourceToPixelCoordinate(decrementExact(multiplyExact(tileUp,
 virtualTileSize[i])), i), max));
                 imageSize[i] = toIntExact(subtractExact(upper, lower));
                 offsetAOI[i] = toIntExact(subtractExact(lower, aoiMin));
                 tileLower[i] = toIntExact(subtractExact(tileLo, 
tmcOfFirstTile[i]));
@@ -727,7 +734,7 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
                 System.arraycopy(coverage.subsampling, 0, subsampling, 0, 
dimension);
             }
             while (--dimension >= 0) {
-                final int  tileSize  = coverage.getTileSize(dimension);
+                final long tileSize  = coverage.getTileSize(dimension);
                 final long tileIndex = 
addExact(coverage.tmcOfFirstTile[dimension], tmcInSubset[dimension]);
                 final long tileBase  = multiplyExact(tileIndex, tileSize);
                 /*
@@ -850,7 +857,7 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
                  * now instead of in a call to `next()` during iteration. A 
negative value
                  * would mean that the AOI does not intersect the region 
requested by user.
                  */
-                final int max = addExact(offsetAOI[i], 
multiplyExact(getTileSize(i), count));
+                final long max = addExact(offsetAOI[i], 
multiplyExact(getTileSize(i), count));
                 assert max > Math.max(offsetAOI[i], 0) : max;
             }
             this.tileCountInQuery = tileCountInQuery;
@@ -877,7 +884,8 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
                 if (s > base) {
                     lower[i] = s;
                     // Use of `ceilDiv(…)` is for consistency with 
`getTileOrigin(int)`.
-                    offset[i] = addExact(offset[i], ceilDiv(multiplyExact(s - 
base, getTileSize(i)), getSubsampling(i)));
+                    long origin = ceilDiv(multiplyExact(s - base, 
getTileSize(i)), getSubsampling(i));
+                    offset[i] = toIntExact(addExact(offset[i], origin));
                 }
             }
             final int[] upper = this.tileUpper.clone();
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 f4322a3daf..b4bff677b3 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
@@ -171,13 +171,36 @@ public abstract class TiledGridResource extends 
AbstractGridCoverageResource {
 
     /**
      * Returns the size of tiles in this resource.
-     * The length of the returned array is the number of dimensions, which 
must be 2 or more.
+     * The length of the returned array is the number of dimensions,
+     * which must be {@value TiledGridCoverage#BIDIMENSIONAL} or more.
      *
      * @return the size of tiles (in pixels) in this resource.
      * @throws DataStoreException if an error occurred while fetching the tile 
size.
      */
     protected abstract int[] getTileSize() throws DataStoreException;
 
+    /**
+     * Returns the tile size to use for a read operation using the given 
subsampling.
+     * The default implementation returns the real tile size, as returned by 
{@link #getTileSize()}.
+     * Subclasses may override if it is easy for them to read many tiles as if 
they were a single tile.
+     * Such coalescing can be useful for avoiding that a read operation 
produces tiles that become too
+     * small after the subsampling.
+     *
+     * <p>Note that {@link TiledGridCoverage} aligns its {@link GridExtent} on 
the boundaries of "real" tiles
+     * (i.e. on tiles of the size returned by {@link #getTileSize()}), not on 
the boundaries of virtual tiles.
+     * Therefore, subclasses should override this method only if they are 
prepared to read regions covering a
+     * fraction of the virtual tiles. A simple and efficient strategy is to 
simply multiply {@code tileSize[i]}
+     * by {@code subsampling[i]} for each dimension <var>i</var> so that each 
virtual tile contains only whole
+     * real tiles.
+     *
+     * @param  subsampling  the subsampling which will be applied in a read 
operation.
+     * @return the size of tiles (in pixels) in this resource.
+     * @throws DataStoreException if an error occurred while fetching the tile 
size.
+     */
+    protected long[] getVirtualTileSize(int[] subsampling) throws 
DataStoreException {
+        return ArraysExt.copyAsLongs(getTileSize());
+    }
+
     /**
      * Returns the number of sample values in an indivisible element of a tile.
      * An element is a primitive type such as {@code byte}, {@code int} or 
{@code float}.
@@ -411,8 +434,12 @@ check:  if (dataType.isInteger()) {
 
         /**
          * Size of tiles (or chunks) in the resource, without clipping and 
subsampling.
+         * May be a virtual tile size (i.e., tiles larger than the real tiles) 
if the
+         * resource can easily coalesce many tiles in a single read operation.
+         *
+         * @see #getVirtualTileSize(int[])
          */
-        final int[] tileSize;
+        final long[] virtualTileSize;
 
         /**
          * The sample model for the bands to read (not the full set of bands 
in the resource).
@@ -456,7 +483,7 @@ check:  if (dataType.isInteger()) {
             final RangeArgument   rangeIndices = 
RangeArgument.validate(bands.size(), range, listeners);
             final GridGeometry    gridGeometry = getGridGeometry();
             sourceExtent = gridGeometry.getExtent();
-            tileSize = getTileSize();
+            final int[] tileSize = getTileSize();
             boolean sharedCache = true;
             if (domain == null) {
                 domain             = gridGeometry;
@@ -507,6 +534,13 @@ check:  if (dataType.isInteger()) {
                 subsampling        = target.getSubsampling();
                 subsamplingOffsets = target.getSubsamplingOffsets();
             }
+            /*
+             * Virtual tile size is usually the same as the real tile size.
+             */
+            virtualTileSize = getVirtualTileSize(subsampling);
+            for (int i=0; i < virtualTileSize.length; i++) {
+                virtualTileSize[i] = Math.min(sourceExtent.getSize(i), 
Math.max(tileSize[i], virtualTileSize[i]));
+            }
             /*
              * Get the bands selected by user in strictly increasing order of 
source band index.
              * If user has specified bands in a different order, that change 
of band order will
@@ -580,7 +614,7 @@ check:  if (dataType.isInteger()) {
                 return false;
             }
             for (int i = subsampling.length; --i >= 0;) {
-                if (subsampling[i] >= tileSize[i]) {
+                if (subsampling[i] >= virtualTileSize[i]) {
                     return false;
                 }
             }
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 45e42ee8f6..1a71900078 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
@@ -551,6 +551,21 @@ final class TiledResource extends TiledGridResource {
         return new int[] {tileWidth, tileHeight};
     }
 
+    /**
+     * Returns virtual tile size to use during read operations. From the point 
of view of this class,
+     * pretending that tiles have a size different than their real size is 
easy because <abbr>GDAL</abbr>
+     * does the hard work of reading only the relevant parts of the real 
tiles. Therefore, we compensate
+     * subsampling with larger virtual size for avoiding that subsampled tiles 
become too small.
+     * This is useful in particular with pyramided images read with 
potentially high subsampling values.
+     */
+    @Override
+    protected long[] getVirtualTileSize(final int[] subsampling) {
+        return new long[] {
+            Math.multiplyFull(subsampling[0], tileWidth),
+            Math.multiplyFull(subsampling[1], tileHeight)
+        };
+    }
+
     /**
      * 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.

Reply via email to