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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push: new 7913e12a60 Notify GDAL in advance of the region which will be read (all tiles). This commit contains again renaming of some `TiledGridCoverage` methods in an attempt to make them less ambiguous. 7913e12a60 is described below commit 7913e12a603d9ff74c668f40efd8725553f898b0 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Wed Sep 18 17:10:40 2024 +0200 Notify GDAL in advance of the region which will be read (all tiles). This commit contains again renaming of some `TiledGridCoverage` methods in an attempt to make them less ambiguous. --- .../org/apache/sis/storage/geotiff/DataSubset.java | 2 +- .../apache/sis/storage/base/TiledGridCoverage.java | 197 ++++++++++++++------- .../main/org/apache/sis/storage/gdal/Band.java | 69 ++++++-- .../main/org/apache/sis/storage/gdal/GDAL.java | 22 ++- .../org/apache/sis/storage/gdal/TiledCoverage.java | 36 +++- 5 files changed, 236 insertions(+), 90 deletions(-) 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 6140124f95..b64a104605 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 @@ -61,7 +61,7 @@ import static org.apache.sis.pending.jdk.JDK18.ceilDiv; * <h2>Cell Coordinates</h2> * When there is no subsampling, {@code DataSubset} uses the same cell coordinates as {@link DataCube}. * When there is a subsampling, cell coordinates in this subset are divided by the subsampling factors. - * Conversion is done by {@link #pixelToResourceCoordinate(long, int)}. + * Conversion is done by {@link #coverageToResourceCoordinate(long, int)}. * * <h2>Tile Matrix Coordinates</h2> * In each {@code DataSubset}, indices of tiles starts at (0, 0, …). This class does not use 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 dab74aaff7..499fa4056e 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 @@ -64,7 +64,7 @@ import org.opengis.coordinate.MismatchedDimensionException; * <h2>Cell coordinates</h2> * When there is no subsampling, this coverage uses the same cell coordinates as the originating resource. * When there is a subsampling, cell coordinates in this coverage are divided by the subsampling factors. - * Conversions are done by {@link #pixelToResourceCoordinates(Rectangle)}. + * Conversions are done by {@link #coverageToResourceCoordinate(long, int)}. * * <p><b>DEsign note:</b> {@code TiledGridCoverage} use the same cell coordinates as the originating * {@link TiledGridResource} (when no subsampling) because those two classes use {@code long} integers. @@ -95,7 +95,7 @@ public abstract class TiledGridCoverage extends GridCoverage { * This is the intersection between user-specified domain and the source * {@link TiledGridResource} domain, expanded to an integer number of tiles. */ - private final GridExtent readExtent; + protected final GridExtent readExtent; /** * Whether to force the {@link #readExtent} tile intersection to the {@link #virtualTileSize}. @@ -148,7 +148,7 @@ public abstract class TiledGridCoverage extends GridCoverage { /** * The Tile Matrix Coordinates (<abbr>TMC</abbr>) that tile (0,0) of this coverage * would have in the originating {@code TiledGridResource}. - * This is the value to subtract from tile indices computed from pixel coordinates. + * This is the value to subtract from tile indices computed from cell coordinates. * * <p>The current implementation assumes that the tile (0,0) in the resource starts * at cell coordinates (0,0) of the resource.</p> @@ -159,7 +159,7 @@ public abstract class TiledGridCoverage extends GridCoverage { private final long[] tmcOfFirstTile; /** - * Conversion from pixel coordinates in this (potentially subsampled) coverage + * Conversion from cell coordinates in this (potentially subsampled) coverage * to cell coordinates in the originating resource coverage at full resolution. * The conversion from (<var>x</var>, <var>y</var>) to (<var>x′</var>, <var>y′</var>) is as below, * where <var>s</var> are subsampling factors and <var>t</var> are subsampling offsets: @@ -172,7 +172,7 @@ public abstract class TiledGridCoverage extends GridCoverage { * This transform maps {@linkplain org.apache.sis.coverage.grid.PixelInCell#CELL_CORNER pixel corners}. * * @see #getSubsampling(int) - * @see #pixelToResourceCoordinate(long, int) + * @see #coverageToResourceCoordinate(long, int) */ private final long[] subsampling, subsamplingOffsets; @@ -321,50 +321,58 @@ public abstract class TiledGridCoverage extends GridCoverage { /** * Converts a cell coordinate from this coverage to the {@code TiledGridResource} coordinate space. * This method removes the subsampling effect, i.e. returns the coordinate that we would have if this - * coverage was at full resolution. Such unsampled {@code TiledGridCoverage} uses the same coordinates - * as the originating {@link TiledGridResource}. - * - * <p>This method uses the "pixel" word for simplicity and because this method is used mostly - * for the first two dimensions, but "pixel" should be understood as "grid coverage cell".</p> + * coverage was at full resolution. When there is no subsampling, {@code TiledGridCoverage} uses the + * same coordinates as the originating {@link TiledGridResource}. * * @param coordinate coordinate in this {@code TiledGridCoverage} domain. - * @param dimension dimension of the coordinate. + * @param dimension the dimension of the coordinate to convert. * @return coordinate in this {@code TiledGridResource} with no subsampling applied. * @throws ArithmeticException if the coordinate cannot be represented as a long integer. - * - * @see #pixelToResourceCoordinates(Rectangle) */ - private long pixelToResourceCoordinate(final long coordinate, final int dimension) { + protected final long coverageToResourceCoordinate(final long coordinate, final int dimension) { return addExact(multiplyExact(coordinate, subsampling[dimension]), subsamplingOffsets[dimension]); } /** * Converts a cell coordinate from {@link TiledGridResource} space to {@code TiledGridCoverage} coordinate. - * This is the converse of {@link #pixelToResourceCoordinate(long, int)}. + * This method is the converse of {@link #coverageToResourceCoordinate(long, int)}. * Note that there is a possible accuracy lost. * * @param coordinate coordinate in the {@code TiledGridResource} domain. - * @param dimension dimension of the coordinate. + * @param dimension the dimension of the coordinate to convert. * @return coordinates in this subsampled {@code TiledGridCoverage} domain. * @throws ArithmeticException if the coordinate cannot be represented as a long integer. */ - private long resourceToPixelCoordinate(final long coordinate, final int dimension) { + private long resourceToCoverageCoordinate(final long coordinate, final int dimension) { return floorDiv(subtractExact(coordinate, subsamplingOffsets[dimension]), subsampling[dimension]); } + /** + * Converts a tile index from the <abbr>TMC</abbr> of this coverage to a cell coordinate in the originating resource. + * Note that the computation (like all methods in this class) uses the <em>virtual</em> tile size. + * This is usually the same as the real tile size, but not always. + * + * @param tileIndex tile index from the <abbr>TMC</abbr> of this coverage. + * @param dimension the dimension of the coordinate to convert. + * @return cell coordinate of the tile lower coordinate in the originating resource. + */ + final long coverageTileToResourceCell(final long tileIndex, final int dimension) { + return multiplyExact(addExact(tileIndex, tmcOfFirstTile[dimension]), virtualTileSize[dimension]); + } + /** * Converts a cell coordinate from this {@code TiledGridCoverage} coordinate space to * the Tile Matrix Coordinate (<abbr>TMC</abbr>) of the tile which contains that cell. - * The <abbr>TMC</abbr> is relative to the full {@link TiledGridResource}, + * The returned <abbr>TMC</abbr> is relative to the full {@link TiledGridResource}, * i.e. without subtraction of {@link #tmcOfFirstTile}. * * @param coordinate coordinates in this {@code TiledGridCoverage} domain. - * @param dimension dimension of the coordinate. + * @param dimension the dimension of the coordinate to convert. * @return Tile Matrix Coordinate (TMC) of the tile which contains the specified cell. * @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), virtualTileSize[dimension]); + private long coverageCellToResourceTile(final long coordinate, final int dimension) { + return floorDiv(coverageToResourceCoordinate(coordinate, dimension), virtualTileSize[dimension]); } /** @@ -411,6 +419,25 @@ public abstract class TiledGridCoverage extends GridCoverage { return 1; } + /** + * Returns the two-dimensional slice of the given grid extent, converted to 32-bits integers. + * By default, the <var>x</var> axis is the grid dimension at index 0 and the <var>y</var> axis + * is the grid dimension at index 1. Other dimensions are ignored. + * + * @param extent the grid extent to slice, or {@code null}. + * @return two-dimensional slice of the given extent, or {@code null} if the given extent was null. + * @throws ArithmeticException if the extent exceeds the capacity of 32-bits integers. + */ + protected final Rectangle bidimensional(final GridExtent extent) { + if (extent == null) { + return null; + } + return new Rectangle(toIntExact(extent.getLow (X_DIMENSION)), + toIntExact(extent.getLow (Y_DIMENSION)), + toIntExact(extent.getSize(X_DIMENSION)), + toIntExact(extent.getSize(Y_DIMENSION))); + } + /** * Returns a two-dimensional slice of grid data as a rendered image. * @@ -446,8 +473,8 @@ public abstract class TiledGridCoverage extends GridCoverage { final long max = available .getHigh(i); // Highest valid coordinate, inclusive. final long aoiMin = sliceExtent.getLow (i); // Requested coordinate in subsampled image. final long aoiMax = sliceExtent.getHigh(i); - final long tileUp = incrementExact(toResourceTileMatrixCoordinate(Math.min(aoiMax, max), i)); - final long tileLo = toResourceTileMatrixCoordinate(Math.max(aoiMin, min), i); + final long tileUp = incrementExact(coverageCellToResourceTile(Math.min(aoiMax, max), i)); + final long tileLo = coverageCellToResourceTile(Math.max(aoiMin, min), i); if (tileUp <= tileLo) { final String message = Errors.forLocale(getLocale()) .getString(Errors.Keys.IllegalRange_2, aoiMin, aoiMax); @@ -458,8 +485,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, virtualTileSize[i]), i), min); - final long upper = incrementExact(Math.min(resourceToPixelCoordinate(decrementExact(multiplyExact(tileUp, virtualTileSize[i])), i), max)); + final long lower = /* inclusive */Math.max(resourceToCoverageCoordinate(/* inclusive */multiplyExact(tileLo, virtualTileSize[i]), i), min); + final long upper = incrementExact(Math.min(resourceToCoverageCoordinate(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])); @@ -812,12 +839,13 @@ public abstract class TiledGridCoverage extends GridCoverage { /** * Pixel coordinates to assign to the upper-left corner of the region to render, with subsampling applied. * This is the difference between the region requested by user and the region which will be rendered. + * This is often 0 or negative. May be positive if the image has been clipped. */ private final int[] offsetAOI; /** * Cell coordinates of current iterator position relative to the Area Of Interest specified by user. - * Those coordinates are in units of the coverage at full resolution. + * Those coordinates are in units of the coverage at full resolution (except for the translation). * Initial position is {@link #offsetAOI} multiplied by {@link #subsampling}. * This array is modified by calls to {@link #next()}. */ @@ -903,31 +931,93 @@ public abstract class TiledGridCoverage extends GridCoverage { } /** - * Returns the extent of the current tile in units of the full coverage resource (without subsampling). - * This method is a generalization to <var>n</var> dimensions of the rectangle computed by the - * following code: - * - * {@snippet lang="java" : - * WritableRaster tile = createRaster(); - * Rectangle target = tile.getBounds(); - * Rectangle source = pixelToResourceCoordinates(bounds); - * } + * Returns the extent of the full region to read (all tiles) in units of the originating resource. + * The returned extent does not change during the iteration process (this method is not available + * in {@link Snapshot} for that reason). This method is typically invoked only at the beginning of + * the iteration process, when knowing in advance the full read region allows some optimizations. * - * @return extent of this tile in units of the full coverage resource. + * <p>The returned region is based on the user's request in her/his call to {@link #render(GridExtent)}, + * but is not necessarily identical. The render method may have expanded or clipped the user's request.</p> * - * @see #pixelToResourceCoordinates(Rectangle) + * @return extent of all tiles to be traversed by this iterator, in units of the originating resource. */ - public final GridExtent getTileExtentInResource() { - final int dimension = tileOffsetFull.length; + public GridExtent getFullRegionInResourceCoordinates() { + final int dimension = tileLower.length; final var axes = new DimensionNameType[dimension]; final long[] lower = new long[dimension]; final long[] upper = new long[dimension]; for (int i=0; i<dimension; i++) { - lower[i] = pixelToResourceCoordinate(getTileOrigin(i), i); - upper[i] = addExact(lower[i], getTileSize(i)); axes [i] = readExtent.getAxisType(i).orElse(null); + lower[i] = Math.max(coverageTileToResourceCell(tileLower[i], i), readExtent.getLow(i)); + upper[i] = Math.min(coverageTileToResourceCell(tileUpper[i], i)-1, readExtent.getHigh(i)); } - return new GridExtent(axes, lower, upper, false); + return new GridExtent(axes, lower, upper, true); + } + + /** + * Converts the given cell coordinate from the originating resource to the pixel coordinate. + * The destination coordinate system is the coordinate system of the {@link RenderedImage} + * to be rendered. Note that the top-left corner of the image is not necessarily (0,0). + * + * @param coordinate pixel coordinate in the {@link RenderedImage} coordinate system. + * @param dimension the dimension of the coordinate to convert. + * @return cell coordinate in the {@link TiledGridResource} coordinate system. + */ + public long resourceToImage(long coordinate, final int dimension) { + coordinate = Math.subtractExact(coordinate, coverageTileToResourceCell(tileLower[dimension], dimension)); + coordinate = Math.floorDiv(coordinate, getSubsampling(dimension)); + coordinate = Math.addExact(coordinate, offsetAOI[dimension]); + return coordinate; + } + + /** + * Converts the given pixel coordinate to the coordinate in the originating resource. + * The source coordinate system is the coordinate system of the {@link RenderedImage} + * to be rendered. Note that the top-left corner of the image is not necessarily (0,0). + * + * @param coordinate pixel coordinate in the {@link RenderedImage} coordinate system. + * @param dimension the dimension of the coordinate to convert. + * @return cell coordinate in the {@link TiledGridResource} coordinate system. + */ + public long imageToResource(long coordinate, final int dimension) { + coordinate = subtractExact(coordinate, offsetAOI[dimension]); // (0,0) at image origin instead of AOI. + coordinate = multiplyExact(coordinate, getSubsampling(dimension)); // Full resolution, like in the resource. + coordinate = addExact(coordinate, coverageTileToResourceCell(tileLower[dimension], dimension)); + return coordinate; + } + + /** + * Converts cell coordinates from the originating resource to pixel coordinates. + * + * @param bounds the coordinates to convert. + * @return the converted coordinates. + * @throws ArithmeticException if the result cannot be expressed as 32-bits integers. + */ + public Rectangle resourceToImage(final Rectangle bounds) { + long x, y; // Convenience for casting `int` to `long`. + final var r = new Rectangle(); + r.x = toIntExact(resourceToImage(x = bounds.x, X_DIMENSION)); + r.y = toIntExact(resourceToImage(y = bounds.y, Y_DIMENSION)); + r.width = toIntExact(resourceToImage(x + bounds.width, X_DIMENSION) - r.x); + r.height = toIntExact(resourceToImage(y + bounds.height, Y_DIMENSION) - r.y); + return r; + } + + /** + * Converts cell coordinates from pixel coordinates to the originating resource. + * + * @param bounds the coordinates to convert. + * @return the converted coordinates. + * @throws ArithmeticException if the result cannot be expressed as 32-bits integers. + */ + public Rectangle imageToResource(final Rectangle bounds) { + long x, y; // Convenience for casting `int` to `long`. + final var r = new Rectangle(); + r.x = toIntExact(imageToResource(x = bounds.x, X_DIMENSION)); + r.y = toIntExact(imageToResource(y = bounds.y, Y_DIMENSION)); + r.width = toIntExact(imageToResource(x + bounds.width, X_DIMENSION) - r.x); + r.height = toIntExact(imageToResource(y + bounds.height, Y_DIMENSION) - r.y); + return r; } /** @@ -1120,27 +1210,4 @@ public abstract class TiledGridCoverage extends GridCoverage { * (too many exception types to list them all). */ protected abstract Raster[] readTiles(TileIterator iterator) throws IOException, DataStoreException; - - /** - * Converts raster coordinate from this coverage to {@code TiledGridResource} coordinate space. - * This method removes the subsampling effect, i.e. returns the coordinates that we would have if this - * coverage was at full resolution. Such unsampled {@code TiledGridCoverage} uses the same coordinates - * as the originating {@link TiledGridResource}. - * - * <p>This method uses the "pixel" word for simplicity and because this method is used for - * the two-dimensional case, but "pixel" should be understood as "grid coverage cell".</p> - * - * @param bounds the rectangle to convert. - * @return the converted rectangle. - * @throws ArithmeticException if the coordinate cannot be represented as an integer. - * - * @see TileIterator#getTileExtentInResource() - */ - protected final Rectangle pixelToResourceCoordinates(final Rectangle bounds) { - return new Rectangle( - toIntExact(pixelToResourceCoordinate(bounds.x, X_DIMENSION)), - toIntExact(pixelToResourceCoordinate(bounds.y, Y_DIMENSION)), - toIntExact(multiplyExact(subsampling[X_DIMENSION], bounds.width)), - toIntExact(multiplyExact(subsampling[Y_DIMENSION], bounds.height))); - } } diff --git a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/Band.java b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/Band.java index c8f06f505c..9e46ccdac1 100644 --- a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/Band.java +++ b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/Band.java @@ -266,7 +266,7 @@ final class Band { * @param raster the Java2D raster where to store of fetch the values to read or write. * @param rasterBounds region to write or read in raster coordinates. * @param transferBuffer a temporary buffer used for copying data. - * @return whether the operation was successful according <abbr>GDAL</abbr>. + * @return whether the operation was successful according <abbr>GDAL</abbr>. * @throws ClassCastException if an above-documented prerequisite is not true. * @throws DataStoreException if <var>GDAL</var> reported a warning or fatal error. */ @@ -283,10 +283,13 @@ final class Band { if (readWriteFlags == OpenFlag.READ && !(raster instanceof WritableRaster)) { throw new ClassCastException(); } - final var sampleModel = (ComponentSampleModel) raster.getSampleModel(); // See prerequisites in Javadoc. - final var dataBuffer = raster.getDataBuffer(); - final int dataSize = DataBuffer.getDataTypeSize(dataBuffer.getDataType()) / Byte.SIZE; - final int[] bankIndices = sampleModel.getBankIndices(); + final var dataBuffer = raster.getDataBuffer(); + final var rasterType = resourceType.forDataBufferType(dataBuffer.getDataType()); + final int dataSize = DataBuffer.getDataTypeSize(dataBuffer.getDataType()) / Byte.SIZE; + final var sampleModel = (ComponentSampleModel) raster.getSampleModel(); // See prerequisites in Javadoc. + final int pixelStride = Math.multiplyExact(dataSize, sampleModel.getPixelStride()); + final int scanlineStride = Math.multiplyExact(dataSize, sampleModel.getScanlineStride()); + final int[] bankIndices = sampleModel.getBankIndices(); /* * The following assertions are critical: if those conditions are not true, it may crash the JVM. * For that reason, we test them unconditionally instead of using the `assert` statement. @@ -304,14 +307,19 @@ final class Band { rasterBounds.y - raster.getSampleModelTranslateY(), i)); final int err; try { - err = (int) gdal.rasterIO.invokeExact(selectedBands[i].handle, readWriteFlags, - resourceBounds.x, resourceBounds.y, resourceBounds.width, resourceBounds.height, - transferBuffer, - rasterBounds.width, - rasterBounds.height, - resourceType.forDataBufferType(dataBuffer.getDataType()).ordinal(), - Math.multiplyExact(dataSize, sampleModel.getPixelStride()), - Math.multiplyExact(dataSize, sampleModel.getScanlineStride())); + err = (int) gdal.rasterIO.invokeExact( + selectedBands[i].handle, + readWriteFlags, // Either GF_Read to read a region of data, or GF_Write to write a region of data. + resourceBounds.x, // First column of the region to be accessed. Zero to start from the left side. + resourceBounds.y, // First row of the region to be accessed. Zero to start from the top. + resourceBounds.width, // The width of the region of the band to be accessed in pixels. + resourceBounds.height, // The height of the region of the band to be accessed in lines. + transferBuffer, // The buffer into which the data is read, or from which it is written. + rasterBounds.width, // The width of the region of the Java2D raster to be accessed. + rasterBounds.height, // The height of the region of the Java2D raster to be accessed. + rasterType.ordinal(), // The type of the pixel values in the destinaton image. + pixelStride, // The byte offset from the start of one pixel to the start of the next pixel. + scanlineStride); // The byte offset from the start of one scanline to the start of the next. } catch (Throwable e) { throw GDAL.propagate(e); } @@ -322,4 +330,39 @@ final class Band { } return true; } + + /** + * Advise driver of upcoming read requests. Contrarily to the above {@code read(…)} method which receives + * a rectangle for one tile at a time, the rectangle received by this method is for all tiles to be read. + * + * @param gdal set of handles for invoking <abbr>GDAL</abbr> functions. + * @param resourceBounds region to read in resource coordinates. (0,0) is the upper-left pixel. + * @param imageBounds region to write in image coordinates (may cover more than one tile). + * @param imageType the <abbr>GDAL</abbr> data type of the destination raster. + * @return whether the operation was successful according <abbr>GDAL</abbr>. + * @throws DataStoreException if <var>GDAL</var> reported a warning or fatal error. + */ + final boolean adviseRead(final GDAL gdal, + final Rectangle resourceBounds, + final Rectangle imageBounds, // Not the same as `rasterBounds`. + final DataType imageType) + throws DataStoreException + { + final int err; + try { + err = (int) gdal.adviseRead.invokeExact( + handle, + resourceBounds.x, // First column of the region to be accessed. Zero to start from the left side. + resourceBounds.y, // First row of the region to be accessed. Zero to start from the top. + resourceBounds.width, // The width of the region of the band to be accessed in pixels. + resourceBounds.height, // The height of the region of the band to be accessed in lines. + imageBounds.width, // The width of the destination image. + imageBounds.height, // The height of the destination image. + imageType.ordinal(), // The type of the pixel values in the destinaton image. + MemorySegment.NULL); // A list of name=value strings with special control options. + } catch (Throwable e) { + throw GDAL.propagate(e); + } + return ErrorHandler.checkCPLErr(err); + } } diff --git a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDAL.java b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDAL.java index 1857780d47..246aeb96b9 100644 --- a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDAL.java +++ b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/GDAL.java @@ -21,20 +21,20 @@ import java.util.List; import java.util.ArrayList; import java.util.Optional; import java.util.NoSuchElementException; -import java.lang.foreign.Arena; import java.util.logging.Level; import java.util.logging.LogRecord; +import java.lang.foreign.Arena; import java.lang.foreign.Linker; import java.lang.foreign.ValueLayout; import java.lang.foreign.SymbolLookup; import java.lang.foreign.MemorySegment; import java.lang.foreign.FunctionDescriptor; import java.lang.invoke.MethodHandle; +import org.apache.sis.util.logging.Logging; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.panama.LibraryLoader; import org.apache.sis.storage.panama.LibraryStatus; import org.apache.sis.storage.panama.NativeFunctions; -import org.apache.sis.util.logging.Logging; /** @@ -267,6 +267,12 @@ final class GDAL extends NativeFunctions { */ final MethodHandle rasterIO; + /** + * <abbr>GDAL</abbr> {@code CPLErr GDALRasterAdviseRead(GDALRasterBandH hRBand, ...)}. + * Advise driver of upcoming read requests. + */ + final MethodHandle adviseRead; + /** * Creates the handles for all <abbr>GDAL</abbr> functions which will be needed. * @@ -376,6 +382,18 @@ final class GDAL extends NativeFunctions { ValueLayout.JAVA_INT, // int nPixelSpace ValueLayout.JAVA_INT)); // int nLineSpace + adviseRead = lookup(linker, "GDALRasterAdviseRead", FunctionDescriptor.of( + ValueLayout.JAVA_INT, // CPLErr error code (return value) + ValueLayout.ADDRESS, // GDALRasterBandH hRBand + ValueLayout.JAVA_INT, // int nDSXOff + ValueLayout.JAVA_INT, // int nDSYOff + ValueLayout.JAVA_INT, // int nDSXSize + ValueLayout.JAVA_INT, // int nDSYSize + ValueLayout.JAVA_INT, // int nBXSize + ValueLayout.JAVA_INT, // int nBYSize + ValueLayout.JAVA_INT, // GDALDataType eBDataType + ValueLayout.ADDRESS)); // CSLConstList papszOptions + // Set error handling first in order to redirect initialization warnings. setErrorHandler(linker, null); diff --git a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledCoverage.java b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledCoverage.java index 733e42d4aa..e7b58649e9 100644 --- a/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledCoverage.java +++ b/incubator/src/org.apache.sis.storage.gdal/main/org/apache/sis/storage/gdal/TiledCoverage.java @@ -23,6 +23,7 @@ import java.awt.image.WritableRaster; import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; import org.opengis.util.GenericName; +import org.apache.sis.util.logging.Logging; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.base.TiledGridCoverage; import org.apache.sis.storage.base.TiledGridResource; @@ -78,20 +79,37 @@ final class TiledCoverage extends TiledGridCoverage { */ @Override protected Raster[] readTiles(final TileIterator iterator) throws IOException, DataStoreException { + Rectangle resourceBounds = bidimensional(iterator.getFullRegionInResourceCoordinates()); + Rectangle imageBounds; + try { + imageBounds = iterator.resourceToImage(resourceBounds); + } catch (ArithmeticException e) { + // Ignore, this is used only as a hint. + Logging.ignorableException(GDALStoreProvider.LOGGER, GDALStore.class, "read", e); + imageBounds = null; + } synchronized (owner.getSynchronizationLock()) { - final Band[] bands = owner.bands(includedBands); - final GDAL gdal = owner.parent.getProvider().GDAL(); - final var result = new WritableRaster[iterator.tileCountInQuery]; + final Band[] bands = owner.bands(includedBands); + final GDAL gdal = owner.parent.getProvider().GDAL(); + final var result = new WritableRaster[iterator.tileCountInQuery]; + final DataType rasterType = owner.dataType.forDataBufferType(model.getDataType()); + if (imageBounds != null) { + // Give a chance to the GDAL driver to prepare itself for the reading of all tiles in the AOI. + for (final Band band : bands) { + if (!band.adviseRead(gdal, resourceBounds, imageBounds, rasterType)) break; + } + } try (Arena arena = Arena.ofConfined()) { final MemorySegment transferBuffer = arena.allocate(getTileLength()); do { final WritableRaster tile = iterator.createRaster(); - final Rectangle target = iterator.getRegionInsideTile(true); - if (target != null) { - target.x = Math.addExact(target.x, tile.getMinX()); - target.y = Math.addExact(target.y, tile.getMinY()); - final Rectangle source = pixelToResourceCoordinates(target); - if (!Band.transfer(gdal, OpenFlag.READ, bands, owner.dataType, source, tile, target, transferBuffer)) { + final Rectangle rasterBounds = iterator.getRegionInsideTile(true); + if (rasterBounds != null) { + rasterBounds.x += tile.getMinX(); + rasterBounds.y += tile.getMinY(); + resourceBounds = iterator.imageToResource(rasterBounds); + assert (imageBounds == null) || imageBounds.contains(rasterBounds) : rasterBounds + " not in " + imageBounds; + if (!Band.transfer(gdal, OpenFlag.READ, bands, owner.dataType, resourceBounds, tile, rasterBounds, transferBuffer)) { break; // Exception will be thrown by `throwOnFailure(…)` } }