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 d0ccef4330 Reorganize the `TiledGridCoverage` base class in support
for GDAL data store:
d0ccef4330 is described below
commit d0ccef43303f6bb1763c3bcf5a24eb690b2647e9
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sun Sep 15 01:09:30 2024 +0200
Reorganize the `TiledGridCoverage` base class in support for GDAL data
store:
* Rename `TiledGridCoverage.AOI` as `TileIterator` and define `AOI` as the
parent class of `TileIterator` and `Snapshot`.
* Rename some `TiledGridCoverage` methods for better clarity, but with few
significant changes in the code.
It allows the reuse of existing code for requestion to GDAL only the valid
area of a tile.
This commit also creates the temporary transfer buffer only once per read
operation.
---
.../org/apache/sis/storage/geotiff/DataSubset.java | 16 +-
.../sis/storage/base/TiledDeferredImage.java | 6 +-
.../apache/sis/storage/base/TiledGridCoverage.java | 692 ++++++++++++---------
.../main/org/apache/sis/storage/gdal/Band.java | 99 +--
.../org/apache/sis/storage/gdal/TiledCoverage.java | 33 +-
.../org/apache/sis/storage/gdal/TiledResource.java | 63 +-
.../storage/gimi/internal/MatrixGridRessource.java | 9 +-
7 files changed, 533 insertions(+), 385 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 5210fcb925..b1e5ca3be0 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
@@ -62,7 +62,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 #toFullResolution(long, int)}.
+ * Conversion is done by {@link #pixelToResourceCoordinate(long, int)}.
*
* <h2>Tile Matrix Coordinates</h2>
* In each {@code DataSubset}, indices of tiles starts at (0, 0, …). This
class does not use
@@ -237,7 +237,7 @@ class DataSubset extends TiledGridCoverage implements
Localized {
*/
private static final class Tile extends Snapshot implements
Comparable<Tile> {
/**
- * Value of {@link DataSubset#tileOffsets} at index {@link
#indexInTileVector}.
+ * Value of {@link DataSubset#tileOffsets} at index {@link
#getTileIndexInResource()}.
* If pixel data are stored in different planes ("banks" in Java2D
terminology),
* then current implementation takes only the offset of the first bank
to read.
* This field contains the value that we want in increasing order.
@@ -256,7 +256,7 @@ class DataSubset extends TiledGridCoverage implements
Localized {
*/
Tile(final AOI domain, final Vector tileOffsets, final int[]
includedBanks, final int numTiles) {
super(domain);
- int p = indexInTileVector;
+ int p = getTileIndexInResource();
if (includedBanks != null) {
p += includedBanks[0] * numTiles;
}
@@ -275,7 +275,7 @@ class DataSubset extends TiledGridCoverage implements
Localized {
final void notifyInputChannel(final Vector tileOffsets, final Vector
tileByteCounts,
int b, final int numTiles, final
ChannelDataInput input)
{
- b = indexInTileVector + b * numTiles;
+ b = getTileIndexInResource() + b * numTiles;
final long offset = tileOffsets.longValue(b);
final long length = tileByteCounts.longValue(b);
input.rangeOfInterest(offset, Numerics.saturatingAdd(offset,
length));
@@ -292,7 +292,7 @@ class DataSubset extends TiledGridCoverage implements
Localized {
*/
final void copyTileInfo(final Vector source, final long[] target,
final int[] includedBanks, final int numTiles) {
for (int j=0; j<target.length; j++) {
- final int i = indexInTileVector + numTiles * (includedBanks !=
null ? includedBanks[j] : j);
+ final int i = getTileIndexInResource() + numTiles *
(includedBanks != null ? includedBanks[j] : j);
target[j] = source.longValue(i);
}
}
@@ -324,7 +324,7 @@ class DataSubset extends TiledGridCoverage implements
Localized {
*/
@Override
@SuppressWarnings("try")
- protected final Raster[] readTiles(final AOI iterator) throws IOException,
DataStoreException {
+ protected final Raster[] readTiles(final TileIterator iterator) throws
IOException, DataStoreException {
/*
* Prepare an array for all tiles to be returned. Tiles that are
already in memory will be stored
* in this array directly. Other tiles will be declared in the
`missings` array and loaded later.
@@ -341,7 +341,7 @@ class DataSubset extends TiledGridCoverage implements
Localized {
do {
final Raster tile = iterator.getCachedTile();
if (tile != null) {
- result[iterator.getIndexInResultArray()] = tile;
+ result[iterator.getTileIndexInResultArray()] = tile;
} else {
/*
* Tile not yet loaded. Add to a queue of tiles to load
later.
@@ -407,7 +407,7 @@ class DataSubset extends TiledGridCoverage implements
Localized {
} else {
r = readSlice(offsets, byteCounts, lower,
upper, subsampling, origin);
}
- result[tile.indexInResultArray] = tile.cache(r);
+ result[tile.getTileIndexInResultArray()] =
tile.cache(r);
} else {
needsCompaction = true;
}
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledDeferredImage.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledDeferredImage.java
index 149ca9872d..f81ca880b9 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledDeferredImage.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/TiledDeferredImage.java
@@ -45,7 +45,7 @@ final class TiledDeferredImage extends BatchComputedImage {
* Iterator over tiles. The iterator position should not be modified;
* instead subsets of this iterator will be created when needed.
*/
- private final TiledGridCoverage.AOI iterator;
+ private final TiledGridCoverage.TileIterator iterator;
/**
* Creates a new tiled image.
@@ -55,7 +55,7 @@ final class TiledDeferredImage extends BatchComputedImage {
* @param properties image properties, or {@code null} if none.
*/
TiledDeferredImage(final int[] imageSize, final int[] tileLower,
- final Map<String,Object> properties, final
TiledGridCoverage.AOI iterator)
+ final Map<String,Object> properties, final
TiledGridCoverage.TileIterator iterator)
{
super(iterator.getCoverage().model, properties);
this.width = imageSize[TiledGridCoverage.X_DIMENSION];
@@ -100,7 +100,7 @@ final class TiledDeferredImage extends BatchComputedImage {
*/
@Override
protected Raster[] computeTiles(final Rectangle tiles) throws Exception {
- final TiledGridCoverage.AOI aoi = iterator.subset(
+ final TiledGridCoverage.TileIterator aoi = iterator.subset(
new int[] {
tiles.x,
tiles.y
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 91731a7f96..c54f888654 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
@@ -62,15 +62,19 @@ import org.opengis.coordinate.MismatchedDimensionException;
* This grid coverage may represent only a subset of the coverage resource.
* Tiles are read from the storage only when first needed.
*
- * <h2>Cell Coordinates</h2>
+ * <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 #toFullResolution(long, int)}.
+ * Conversions are done by {@link #pixelToResourceCoordinates(Rectangle)}.
*
- * <h2>Tile coordinate matrix</h2>
+ * <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.
+ * There is no integer overflow to avoid.</p>
+ *
+ * <h2>Tile matrix coordinate (<abbr>TMC</abbr>)</h2>
* In each {@code TiledGridCoverage}, indices of tiles starts at (0, 0, …).
- * This class does not use the same tile indices as the coverage resource
- * in order to avoid integer overflow.
+ * This class does not use the same tile indices as the coverage resource in
order to avoid integer overflow.
+ * Each {@code TiledGridCoverage} instance uses its own, independent, Tile
Matrix Coordinates (<abbr>TMC</abbr>).
*
* @author Martin Desruisseaux (Geomatys)
*/
@@ -119,6 +123,8 @@ public abstract class TiledGridCoverage extends
GridCoverage {
* All coverages created from the same {@link TiledGridResource} shall
have the same tile size values.
* 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.
+ *
+ * @see #getTileSize(int)
*/
private final int[] tileSize;
@@ -136,16 +142,21 @@ public abstract class TiledGridCoverage extends
GridCoverage {
private final int indexOfFirstTile;
/**
- * The Tile Matrix Coordinates (TMC) of the first tile.
+ * 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.
*
+ * <p>The current implementation assumes that the tile (0,0) in the
resource starts
+ * at cell coordinates (0,0) of the resource.</p>
+ *
* @see #indexOfFirstTile
+ * @see AOI#getTileCoordinatesInResource()
*/
private final long[] tmcOfFirstTile;
/**
* Conversion from pixel coordinates in this (potentially subsampled)
coverage
- * to pixel coordinates in the resource coverage at full resolution.
+ * 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:
*
@@ -157,7 +168,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 #toFullResolution(long, int)
+ * @see #pixelToResourceCoordinate(long, int)
*/
private final int[] subsampling, subsamplingOffsets;
@@ -302,39 +313,43 @@ public abstract class TiledGridCoverage extends
GridCoverage {
}
/**
- * Converts a cell coordinate from this {@code TiledGridCoverage}
coordinate space to full resolution.
- * This method removes the subsampling effect. Note that since this {@code
TiledGridCoverage} uses the
- * same coordinate space as {@link TiledGridResource}, the converted
coordinates should be valid in
- * the full resource as well.
+ * 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>
*
* @param coordinate coordinate in this {@code TiledGridCoverage} domain.
* @param dimension dimension of the coordinate.
- * @return coordinate in this {@code TiledGridCoverage} as if no
subsampling was applied.
+ * @return coordinate in this {@code TiledGridResource} with no
subsampling applied.
* @throws ArithmeticException if the coordinate cannot be represented as
a long integer.
*
- * @see #toFullResolution(Rectangle)
+ * @see #pixelToResourceCoordinates(Rectangle)
*/
- private long toFullResolution(final long coordinate, final int dimension) {
+ private long pixelToResourceCoordinate(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 #toFullResolution(long, int)}. Note that
there is a possible accuracy lost.
+ * This is the converse of {@link #pixelToResourceCoordinate(long, int)}.
+ * Note that there is a possible accuracy lost.
*
* @param coordinate coordinate in the {@code TiledGridResource} domain.
* @param dimension dimension of the coordinate.
* @return coordinates in this subsampled {@code TiledGridCoverage} domain.
* @throws ArithmeticException if the coordinate cannot be represented as
a long integer.
*/
- private long toSubsampledPixel(final long coordinate, final int dimension)
{
+ private long resourceToPixelCoordinate(final long coordinate, final int
dimension) {
return floorDiv(subtractExact(coordinate,
subsamplingOffsets[dimension]), subsampling[dimension]);
}
/**
- * Converts a cell coordinate from this {@code TiledGridCoverage}
coordinate space
- * to the Tile Matrix Coordinate (TMC) of the tile which contains that
cell.
- * The TMC is relative to the full {@link TiledGridResource},
+ * 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},
* i.e. without subtraction of {@link #tmcOfFirstTile}.
*
* @param coordinate coordinates in this {@code TiledGridCoverage}
domain.
@@ -342,8 +357,8 @@ public abstract class TiledGridCoverage extends
GridCoverage {
* @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 toTileMatrixCoordinate(final long coordinate, final int
dimension) {
- return floorDiv(toFullResolution(coordinate, dimension),
tileSize[dimension]);
+ private long toResourceTileMatrixCoordinate(final long coordinate, final
int dimension) {
+ return floorDiv(pixelToResourceCoordinate(coordinate, dimension),
tileSize[dimension]);
}
/**
@@ -425,8 +440,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(toTileMatrixCoordinate(Math.min(aoiMax, max), i));
- final long tileLo =
toTileMatrixCoordinate(Math.max(aoiMin, min), i);
+ final long tileUp =
incrementExact(toResourceTileMatrixCoordinate(Math.min(aoiMax, max), i));
+ final long tileLo =
toResourceTileMatrixCoordinate(Math.max(aoiMin, min), i);
if (tileUp <= tileLo) {
final String message = Errors.forLocale(getLocale())
.getString(Errors.Keys.IllegalRange_2, aoiMin,
aoiMax);
@@ -437,8 +452,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(toSubsampledPixel(/* inclusive */multiplyExact(tileLo, tileSize[i]),
i), min);
- final long upper =
incrementExact(Math.min(toSubsampledPixel(decrementExact(multiplyExact(tileUp,
tileSize[i])), i), max));
+ 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));
imageSize[i] = toIntExact(subtractExact(upper, lower));
offsetAOI[i] = toIntExact(subtractExact(lower, aoiMin));
tileLower[i] = toIntExact(subtractExact(tileLo,
tmcOfFirstTile[i]));
@@ -448,7 +463,7 @@ public abstract class TiledGridCoverage extends
GridCoverage {
* Prepare an iterator over all tiles to read, together with the
following properties:
* - Two-dimensional conversion from pixel coordinates to "real
world" coordinates.
*/
- final AOI iterator = new AOI(tileLower, tileUpper, offsetAOI,
dimension);
+ final var iterator = new TileIterator(tileLower, tileUpper,
offsetAOI, dimension);
final Map<String,Object> properties =
DeferredProperty.forGridGeometry(gridGeometry, selectedDimensions);
if (deferredTileReading) {
image = new TiledDeferredImage(imageSize, tileLower,
properties, iterator);
@@ -469,14 +484,289 @@ public abstract class TiledGridCoverage extends
GridCoverage {
return image;
}
+
+
+
/**
- * The Area Of Interest specified by user in a call to {@link
#render(GridExtent)}.
- * This class is also an iterator over tiles in the region of interest.
+ * An Area Of Interest (<abbr>AOI</abbr>) describing a tile area or
sub-area to read in response to a user's request.
+ * {@code AOI} can be a mutable iterator over all the tiles to read
({@link TileIterator}) or an immutable snapshot
+ * of the iterator position as an instant ({@link Snapshot}).
*/
- protected final class AOI {
+ protected static abstract class AOI {
+ /**
+ * Tile Matrix Coordinates (TMC) relative to the enclosing {@link
TiledGridCoverage}.
+ * Tile (0,0) is the tile in the upper-left corner of this {@link
TiledGridCoverage},
+ * not necessarily the tile in the upper-left corner of the image in
the resource.
+ *
+ * <p>In the case of {@link Snapshot}, this array shall be considered
unmodifiable.
+ * In the case of {@link TileIterator}, this array is initialized to a
clone of
+ * {@link #tileLower} and is modified by calls to {@link
TileIterator#next()}.</p>
+ */
+ final int[] tmcInSubset;
+
+ /**
+ * Current iterator position as an index in the array of tiles to be
returned by {@link #readTiles(TileIterator)}.
+ * The initial position is zero. This field is incremented by calls to
{@link TileIterator#next()}.
+ *
+ * @see #getTileIndexInResultArray()
+ */
+ int indexInResultArray;
+
+ /**
+ * Current iterator position as an index in the vector of tiles in the
{@link TiledGridResource}.
+ * Tiles are assumed stored in a row-major fashion. This field is
incremented by calls to {@link #next()}.
+ * This index is also used as key in the {@link
TiledGridCoverage#rasters} map.
+ *
+ * <h4>Example</h4>
+ * In a GeoTIFF image, this is the index of the tile in the {@code
tileOffsets}
+ * and {@code tileByteCounts} vectors.
+ */
+ int indexInTileVector;
+
+ /**
+ * Creates a new area of interest.
+ */
+ AOI(final int[] tmcInSubset) {
+ this.tmcInSubset = tmcInSubset;
+ }
+
+ /**
+ * Returns the enclosing coverage.
+ */
+ abstract TiledGridCoverage getCoverage();
+
+ /**
+ * Returns the current <abbr>AOI</abbr> position in the tile matrix of
the original resource.
+ * This method assumes that the upper-left corner of tile (0,0) in the
resource starts at cell
+ * coordinates (0,0) of the resource.
+ *
+ * @return current <abbr>AOI</abbr> tile coordinates in original
coverage resource.
+ */
+ public final long[] getTileCoordinatesInResource() {
+ final long[] tmcOfFirstTile = getCoverage().tmcOfFirstTile;
+ final long[] coordinate = new long[tmcOfFirstTile.length];
+ for (int i = 0; i < coordinate.length; i++) {
+ coordinate[i] = addExact(tmcOfFirstTile[i], tmcInSubset[i]);
+ }
+ return coordinate;
+ }
+
+ /**
+ * Returns the current <abbr>AOI</abbr> position as an index in the
vector of tiles of the original resource.
+ * Tiles are assumed stored in a row-major fashion. with the first
tiles starting at index 0.
+ *
+ * @return current <abbr>AOI</abbr> tile index in original coverage
resource.
+ */
+ public final int getTileIndexInResource() {
+ return indexInTileVector;
+ }
+
+ /**
+ * Returns the current <abbr>AOI</abbr> position as an index in the
array of tiles to be returned
+ * by {@code TiledGridCoverage.readTiles(…)}. If this <abbr>AOI</abbr>
is an iterator, the initial
+ * position is zero and is incremented by 1 in each call to {@link
TileIterator#next()}.
+ *
+ * @return current <abbr>AOI</abbr> tile index in the result array of
tiles.
+ *
+ * @see #readTiles(TileIterator)
+ */
+ public final int getTileIndexInResultArray() {
+ return indexInResultArray;
+ }
+
+ /**
+ * Returns the origin to assign to the tile at the current iterator
position.
+ * See {@link TileIterator#getTileOrigin(int)} for more explanation.
+ *
+ * @see TileIterator#getTileOrigin(int)
+ */
+ abstract int getTileOrigin(final int dimension);
+
+ /**
+ * Returns the cached tile for current <abbr>AOI</abbr> position.
+ *
+ * @return cached tile at current <abbr>AOI</abbr> position, or {@code
null} if none.
+ *
+ * @see #cache(Raster)
+ */
+ public Raster getCachedTile() {
+ final TiledGridCoverage coverage = getCoverage();
+ final Raster tile = coverage.getCachedTile(indexInTileVector);
+ if (tile != null) {
+ /*
+ * Found a tile, but the sample model may be different because
band order may be different.
+ * In any cases, we need to make sure that the raster starts
at the expected coordinates.
+ */
+ final int x = getTileOrigin(X_DIMENSION);
+ final int y = getTileOrigin(Y_DIMENSION);
+ final SampleModel model = coverage.model;
+ if (model.equals(tile.getSampleModel())) {
+ if (tile.getMinX() == x && tile.getMinY() == y) {
+ return tile;
+ }
+ return tile.createTranslatedChild(x, y);
+ }
+ /*
+ * If the sample model is not the same (e.g. different bands),
it must at least have the same size.
+ * Having a sample model of different size would probably be a
bug, but we check anyway for safety.
+ * Note that the tile size is not necessarily equals to the
sample model size.
+ */
+ final SampleModel sm = tile.getSampleModel();
+ if (sm.getWidth() == model.getWidth() && sm.getHeight() ==
model.getHeight()) {
+ final int width = tile.getWidth(); // May be smaller
than sample model width.
+ final int height = tile.getHeight(); // Idem.
+ /*
+ * It is okay to have a different number of bands if the
sample model is
+ * a view created by
`SampleModel.createSubsetSampleModel(int[] bands)`.
+ * Bands can also be in a different order and still share
the same buffer.
+ */
+ Raster r = Raster.createRaster(model,
tile.getDataBuffer(), new Point(x, y));
+ if (r.getWidth() != width || r.getHeight() != height) {
+ r = r.createChild(x, y, width, height, x, y, null);
+ }
+ return r;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Stores the given raster in the cache for the current
<abbr>AOI</abbr> position.
+ * If another raster existed previously in the cache, the old raster
will be reused if
+ * it has the same size and model, or discarded otherwise. The latter
case may happen if
+ * {@link #getCachedTile()} determined that a cached raster exists but
cannot be reused.
+ *
+ * @param tile the raster to cache.
+ * @return the cached raster. Should be the given {@code raster}
instance,
+ * but this method check for concurrent caching as a paranoiac
check.
+ *
+ * @see #getCachedTile()
+ */
+ public Raster cache(final Raster tile) {
+ return getCoverage().cacheTile(indexInTileVector, tile);
+ }
+
+ /**
+ * Creates an initially empty raster for the tile at the current
<abbr>AOI</abbr> position.
+ * The sample model is {@link #model} and the minimum <var>x</var> and
<var>y</var> position
+ * are set the the pixel coordinates in the two first dimensions of
the <abbr>AOI</abbr>.
+ *
+ * <p>The raster is <em>not</em> filled with {@link #fillValues}.
+ * Filling, if needed, should be done by the caller.</p>
+ *
+ * @return a newly created, initially empty raster.
+ */
+ public WritableRaster createRaster() {
+ final int x = getTileOrigin(X_DIMENSION);
+ final int y = getTileOrigin(Y_DIMENSION);
+ return Raster.createWritableRaster(getCoverage().model, new
Point(x, y));
+ }
+
+ /**
+ * Returns the coordinates of the pixels to read <em>inside</em> the
tile, ignoring subsampling.
+ * The tile upper-left corner is assumed (0,0). Therefore, the lower
coordinates computed by this
+ * method are usually (0,0) and the rectangle size is usually the tile
size, but those values may
+ * be different if the enclosing {@link TiledGridCoverage} contains
only one (potentially big) tile.
+ * The rectangle may also be smaller when reading tiles on the last
row or column of the tile matrix.
+ *
+ * @return pixel to read inside the tile, or {@code null} if the
region is empty.
+ * @throws ArithmeticException if the tile coordinates overflow 32
bits integer capacity.
+ */
+ public Rectangle getRegionInsideTile() {
+ final long[] lower = new long[BIDIMENSIONAL];
+ final long[] upper = new long[BIDIMENSIONAL];
+ if (getRegionInsideTile(lower, upper, null, BIDIMENSIONAL)) {
+ return new Rectangle(
+ toIntExact(lower[X_DIMENSION]),
+ toIntExact(lower[Y_DIMENSION]),
+ toIntExact(subtractExact(upper[X_DIMENSION],
lower[X_DIMENSION])),
+ toIntExact(subtractExact(upper[Y_DIMENSION],
lower[Y_DIMENSION])));
+ }
+ return null;
+ }
+
+ /**
+ * Returns the coordinates of the pixels to read <em>inside</em> the
tile, ignoring subsampling.
+ * The tile upper-left corner is assumed (0,0). Therefore, the lower
coordinates computed by this
+ * method are usually (0,0) and the upper coordinates are usually the
tile size, but those values
+ * may be different if the enclosing {@link TiledGridCoverage}
contains only one (potentially big) tile.
+ * In the latter case, the reading process is more like untiled image
reading.
+ * The rectangle may also be smaller when reading tiles on the last
row or column of the tile matrix.
+ *
+ * <p>The {@link TiledGridCoverage} subsampling is provided for
convenience,
+ * but is constant for all tiles regardless the subregion to read.
+ * The same values can be obtained by {@link #getSubsampling(int)}.</p>
+ *
+ * <p>This method is a generalization of {@link
#getRegionInsideTile()} to any number of dimensions.</p>
+ *
+ * @param lower a pre-allocated array where to store relative
coordinates of the first pixel.
+ * @param upper a pre-allocated array where to store relative
coordinates after the last pixel.
+ * @param subsampling a pre-allocated array where to store
subsampling, or {@code null} if not needed.
+ * @param dimension number of elements to write in the {@code
lower} and {@code upper} arrays.
+ * @return {@code true} on success, or {@code false} if the tile is
empty.
+ */
+ public boolean getRegionInsideTile(final long[] lower, final long[]
upper, final int[] subsampling, int dimension) {
+ final TiledGridCoverage coverage = getCoverage();
+ if (subsampling != null) {
+ System.arraycopy(coverage.subsampling, 0, subsampling, 0,
dimension);
+ }
+ while (--dimension >= 0) {
+ final int tileSize = coverage.getTileSize(dimension);
+ final long tileIndex =
addExact(coverage.tmcOfFirstTile[dimension], tmcInSubset[dimension]);
+ final long tileBase = multiplyExact(tileIndex, tileSize);
+ /*
+ * The `offset` value is usually zero or negative because the
tile to read should be inside the AOI,
+ * e.g. at the right of the AOI left border. It may be
positive if the `TiledGridCoverage` contains
+ * only one (potentially big) tile, so the tile reading
process become a reading of untiled data.
+ */
+ long offset =
subtractExact(coverage.readExtent.getLow(dimension), tileBase);
+ long limit = Math.min(addExact(offset,
coverage.readExtent.getSize(dimension)), tileSize);
+ if (offset < 0) {
+ /*
+ * Example: for `tileSize` = 10 pixels and `subsampling` =
3,
+ * the pixels to read are represented by black small
squares below:
+ *
+ * -10 0 10 20 30
+ * ┼──────────╫──────────┼──────────┼──────────╫
+ * │▪▫▫▪▫▫▪▫▫▪║▫▫▪▫▫▪▫▫▪▫│▫▪▫▫▪▫▫▪▫▫│▪▫▫▪▫▫▪▫▫▪║
+ * ┼──────────╫──────────┼──────────┼──────────╫
+ *
+ * If reading the second tile, then `tileBase` = 10 and
`offset` = -10.
+ * The first pixel to read in the second tile has a
subsampling offset.
+ * We usually try to avoid this situation because it
causes a variable
+ * number of white squares in tiles (4,3,3,4 in the above
example),
+ * except when there is only 1 tile to read in which case
offset is tolerated.
+ */
+ final int s = coverage.getSubsampling(dimension);
+ offset %= s;
+ if (offset != 0) {
+ offset += s;
+ }
+ }
+ if (offset >= limit) { // Test for intersection
before we adjust the limit.
+ return false;
+ }
+ if (dimension == X_DIMENSION && coverage.forceTileSize) {
+ limit = tileSize;
+ }
+ lower[dimension] = offset;
+ upper[dimension] = limit;
+ }
+ return true;
+ }
+ }
+
+
+
+
+ /**
+ * An iterator over the tiles to read. Instances of this class are
computed by {@link #render(GridExtent)}
+ * and given to {@link #readTiles(TileIterator)}. The latter is the method
that subclasses need to override.
+ */
+ protected final class TileIterator extends AOI {
/**
* Total number of tiles in the AOI, from {@link #tileLower} inclusive
to {@link #tileUpper} exclusive.
- * This is the length of the array to be returned by {@link
#readTiles(AOI)}.
+ * This is the length of the array to be returned by {@link
#readTiles(TileIterator)}.
*/
public final int tileCountInQuery;
@@ -498,12 +788,6 @@ public abstract class TiledGridCoverage extends
GridCoverage {
*/
private final int[] offsetAOI;
- /**
- * Tile Matrix Coordinates (TMC) of current iterator position relative
to enclosing {@code TiledGridCoverage}.
- * Initial position is a clone of {@link #tileLower}. This array is
modified by calls to {@link #next()}.
- */
- private final int[] tmcInSubset;
-
/**
* Pixel coordinates of current iterator position relative to the Area
Of Interest specified by user.
* Those coordinates are in units of the full resolution image.
@@ -512,18 +796,6 @@ public abstract class TiledGridCoverage extends
GridCoverage {
*/
private final long[] tileOffsetFull;
- /**
- * Current iterator position as an index in the array of tiles to be
returned by {@link #readTiles(AOI)}.
- * The initial position is zero. This field is incremented by calls to
{@link #next()}.
- */
- private int indexInResultArray;
-
- /**
- * Current iterator position as an index in the vector of tiles in the
{@link TiledGridResource}.
- * Tiles are assumed stored in a row-major fashion. This field is
incremented by calls to {@link #next()}.
- */
- private int indexInTileVector;
-
/**
* Creates a new Area Of Interest for the given tile indices.
*
@@ -532,7 +804,8 @@ public abstract class TiledGridCoverage extends
GridCoverage {
* @param offsetAOI pixel coordinates to assign to the upper-left
corner of the subsampled region to render.
* @param dimension number of dimension of the {@code
TiledGridCoverage} grid extent.
*/
- AOI(final int[] tileLower, final int[] tileUpper, final int[]
offsetAOI, final int dimension) {
+ TileIterator(final int[] tileLower, final int[] tileUpper, final int[]
offsetAOI, final int dimension) {
+ super(tileLower.clone());
this.tileLower = tileLower;
this.tileUpper = tileUpper;
this.offsetAOI = offsetAOI;
@@ -560,11 +833,10 @@ public abstract class TiledGridCoverage extends
GridCoverage {
assert max > Math.max(offsetAOI[i], 0) : max;
}
this.tileCountInQuery = tileCountInQuery;
- this.tmcInSubset = tileLower.clone();
}
/**
- * Returns a new {@code AOI} instance over a sub-region of this Area
Of Interest.
+ * Returns a new {@code TileIterator} instance over a sub-region of
this Area Of Interest.
* The region is specified by tile indices, with (0,0) being the first
tile of the enclosing grid coverage.
* The given region is intersected with the region of this {@code AOI}.
* The {@code tileLower} and {@code tileUpper} array can have any
length;
@@ -573,9 +845,9 @@ public abstract class TiledGridCoverage extends
GridCoverage {
*
* @param tileLower indices (relative to enclosing {@code
TiledGridCoverage}) of the upper-left tile to read.
* @param tileUpper indices (relative to enclosing {@code
TiledGridCoverage}) after the bottom-right tile to read.
- * @return a new {@code AOI} instance for the specified sub-region.
+ * @return a new {@code TileIterator} instance for the specified
sub-region.
*/
- public AOI subset(final int[] tileLower, final int[] tileUpper) {
+ public TileIterator subset(final int[] tileLower, final int[]
tileUpper) {
final int[] offset = this.offsetAOI.clone();
final int[] lower = this.tileLower.clone();
for (int i = Math.min(tileLower.length, lower.length); --i >= 0;) {
@@ -591,112 +863,43 @@ public abstract class TiledGridCoverage extends
GridCoverage {
for (int i = Math.min(tileUpper.length, upper.length); --i >= 0;) {
upper[i] = Math.max(lower[i], Math.min(upper[i],
tileUpper[i]));
}
- return new AOI(lower, upper, offset, offset.length);
+ return new TileIterator(lower, upper, offset, offset.length);
}
/**
* Returns the enclosing coverage.
*/
+ @Override
final TiledGridCoverage getCoverage() {
return TiledGridCoverage.this;
}
/**
- * Returns the current iterator tile position in the original coverage
resource.
- *
- * @return current iterator tile position in original coverage
resource.
- */
- public final long[] getTileCoordinatesInSource() {
- final long[] coordinate = new long[tmcOfFirstTile.length];
- for (int i = 0; i < coordinate.length; i++) {
- coordinate[i] = Math.addExact(tmcOfFirstTile[i],
tmcInSubset[i]);
- }
- return coordinate;
- }
-
- /**
- * Returns the extent of this tile in units of the full coverage
resource (without subsampling).
+ * 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 bounds = tile.getBounds();
- * toFullResolution(bounds); // Convert in-place.
+ * Rectangle target = tile.getBounds();
+ * Rectangle source = pixelToResourceCoordinates(bounds);
* }
*
* @return extent of this tile in units of the full coverage resource.
*
- * @see #toFullResolution(Rectangle)
+ * @see #pixelToResourceCoordinates(Rectangle)
*/
- public final GridExtent getExtentInSource() {
+ public final GridExtent getTileExtentInResource() {
final int dimension = tileOffsetFull.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] = toFullResolution(getTileOrigin(i), i);
- upper[i] = Math.addExact(lower[i], getTileSize(i));
+ lower[i] = pixelToResourceCoordinate(getTileOrigin(i), i);
+ upper[i] = addExact(lower[i], getTileSize(i));
axes [i] = readExtent.getAxisType(i).orElse(null);
}
- return new GridExtent(axes, lower, upper, true);
- }
-
- /**
- * Returns the current iterator position as an index in the array of
tiles to be returned
- * by {@link #readTiles(AOI)}. The initial position is zero.
- * The position is incremented by 1 in each call to {@link #next()}.
- *
- * @return current iterator position in result array.
- */
- public final int getIndexInResultArray() {
- return indexInResultArray;
- }
-
- /**
- * Returns the cached tile for current iterator position.
- *
- * @return cached tile at current iterator position, or {@code null}
if none.
- *
- * @see Snapshot#cache(Raster)
- */
- public Raster getCachedTile() {
- final Raster tile = rasters.get(createCacheKey(indexInTileVector));
- if (tile != null) {
- /*
- * Found a tile, but the sample model may be different because
band order may be different.
- * In both cases, we need to make sure that the raster starts
at the expected coordinates.
- */
- final int x = getTileOrigin(X_DIMENSION);
- final int y = getTileOrigin(Y_DIMENSION);
- if (model.equals(tile.getSampleModel())) {
- if (tile.getMinX() == x && tile.getMinY() == y) {
- return tile;
- }
- return tile.createTranslatedChild(x, y);
- }
- /*
- * If the sample model is not the same (e.g. different bands),
it must at least have the same size.
- * Having a sample model of different size would probably be a
bug, but we check anyway for safety.
- * Note that the tile size is not necessarily equals to the
sample model size.
- */
- final SampleModel sm = tile.getSampleModel();
- if (sm.getWidth() == model.getWidth() && sm.getHeight() ==
model.getHeight()) {
- final int width = tile.getWidth(); // May be smaller
than sample model width.
- final int height = tile.getHeight(); // Idem.
- /*
- * It is okay to have a different number of bands if the
sample model is
- * a view created by
`SampleModel.createSubsetSampleModel(int[] bands)`.
- * Bands can also be in a different order and still share
the same buffer.
- */
- Raster r = Raster.createRaster(model,
tile.getDataBuffer(), new Point(x, y));
- if (r.getWidth() != width || r.getHeight() != height) {
- r = r.createChild(x, y, width, height, x, y, null);
- }
- return r;
- }
- }
- return null;
+ return new GridExtent(axes, lower, upper, false);
}
/**
@@ -711,6 +914,7 @@ public abstract class TiledGridCoverage extends
GridCoverage {
* <li>If subsampling is larger than tile size.</li>
* </ul>
*/
+ @Override
final int getTileOrigin(final int dimension) {
/*
* We really need `ceilDiv(…)` below, not `floorDiv(…)`. It makes
no difference in the usual
@@ -726,23 +930,6 @@ public abstract class TiledGridCoverage extends
GridCoverage {
return toIntExact(ceilDiv(tileOffsetFull[dimension],
getSubsampling(dimension)));
}
- /**
- * Creates an initially empty raster for the tile at the current
iterator position.
- * The sample model is {@link #model} and the minimum <var>x</var> and
<var>y</var>
- * coordinates are the values returned by {@link #getTileOrigin(int)}
for dimensions
- * of two-dimensional slices.
- *
- * <p>The raster is <em>not</em> filled with {@link #fillValues}.
- * Filling, if needed, should be done by the caller.</p>
- *
- * @return a newly created, initially empty raster.
- */
- public WritableRaster createRaster() {
- final int x = getTileOrigin(X_DIMENSION);
- final int y = getTileOrigin(Y_DIMENSION);
- return Raster.createWritableRaster(model, new Point(x, y));
- }
-
/**
* Moves the iterator position to next tile. This method should be
invoked in a loop as below:
*
@@ -784,37 +971,21 @@ public abstract class TiledGridCoverage extends
GridCoverage {
}
}
+
+
+
/**
- * Snapshot of a {@link AOI} iterator position. Those snapshots can be
created during an iteration
- * for processing a tile later. For example, a {@link #readTiles(AOI)}
method implementation may want
- * to create a list of all tiles to load before to start the actual
reading process in order to read
- * the tiles in some optimal order, or for combining multiple read
operations in a single operation.
+ * Snapshot of a {@link TileIterator} position. Those snapshots can be
created during an iteration
+ * for processing a tile later. For example, a {@link
#readTiles(TileIterator)} method implementation
+ * may want to create a list of all tiles to load before to start the
actual reading process in order
+ * to read the tiles in some optimal order, or for combining multiple read
operations in a single operation.
*/
- protected static class Snapshot {
+ protected static class Snapshot extends AOI {
/**
* The source coverage.
*/
private final TiledGridCoverage coverage;
- /**
- * Tile Matrix Coordinates (TMC) relative to the enclosing {@link
TiledGridCoverage}.
- * Tile (0,0) is the tile in the upper-left corner of this {@link
TiledGridCoverage},
- * not necessarily the tile in the upper-left corner of the image in
the resource.
- */
- private final int[] tmcInSubset;
-
- /**
- * Index of this tile in the array of tiles returned by {@link
#readTiles(AOI)}.
- */
- public final int indexInResultArray;
-
- /**
- * Index of this tile in the {@link TiledGridResource}. In a GeoTIFF
image, this is
- * the index of the tile in the {@code tileOffsets} and {@code
tileByteCounts} vectors.
- * This index is also used as key in the {@link
TiledGridCoverage#rasters} map.
- */
- public final int indexInTileVector;
-
/**
* Pixel coordinates of the upper-left corner of the tile.
*/
@@ -826,8 +997,8 @@ public abstract class TiledGridCoverage extends
GridCoverage {
* @param iterator the iterator for which to create a snapshot of its
current position.
*/
public Snapshot(final AOI iterator) {
+ super(iterator.tmcInSubset.clone());
coverage = iterator.getCoverage();
- tmcInSubset = iterator.tmcInSubset.clone();
indexInResultArray = iterator.indexInResultArray;
indexInTileVector = iterator.indexInTileVector;
originX = iterator.getTileOrigin(X_DIMENSION);
@@ -835,105 +1006,26 @@ public abstract class TiledGridCoverage extends
GridCoverage {
}
/**
- * Returns the coordinates of the pixel to read <em>inside</em> the
tile, ignoring subsampling.
- * The tile upper-left corner is assumed (0,0). Consequently, the
lower coordinates are usually
- * (0,0) and the upper coordinates are usually the tile size, but
those values may be different
- * if the enclosing {@link TiledGridCoverage} contains only one
(potentially big) tile.
- * In that case, the reading process is more like untiled image
reading.
- *
- * <p>The {@link TiledGridCoverage} subsampling is provided for
convenience,
- * but is constant for all tiles regardless the subregion to read.
- * The same values can be obtained by {@link #getSubsampling(int)}.</p>
- *
- * @param lower a pre-allocated array where to store relative
coordinates of the first pixel.
- * @param upper a pre-allocated array where to store relative
coordinates after the last pixel.
- * @param subsampling a pre-allocated array where to store
subsampling.
- * @param dimension number of elements to write in the {@code
lower} and {@code upper} arrays.
- * @return {@code true} on success, or {@code false} if the tile is
empty.
+ * Returns the enclosing coverage.
*/
- public boolean getRegionInsideTile(final long[] lower, final long[]
upper, final int[] subsampling, int dimension) {
- System.arraycopy(coverage.subsampling, 0, subsampling, 0,
dimension);
- while (--dimension >= 0) {
- final int tileSize = coverage.getTileSize(dimension);
- final long tileIndex =
addExact(coverage.tmcOfFirstTile[dimension], tmcInSubset[dimension]);
- final long tileBase = multiplyExact(tileIndex, tileSize);
- /*
- * The `offset` value is usually zero or negative because the
tile to read should be inside the AOI,
- * e.g. at the right of the AOI left border. It may be
positive if the `TiledGridCoverage` contains
- * only one (potentially big) tile, so the tile reading
process become a reading of untiled data.
- */
- long offset =
subtractExact(coverage.readExtent.getLow(dimension), tileBase);
- long limit = Math.min(addExact(offset,
coverage.readExtent.getSize(dimension)), tileSize);
- if (offset < 0) {
- /*
- * Example: for `tileSize` = 10 pixels and `subsampling` =
3,
- * the pixels to read are represented by black small
squares below:
- *
- * -10 0 10 20 30
- * ┼──────────╫──────────┼──────────┼──────────╫
- * │▪▫▫▪▫▫▪▫▫▪║▫▫▪▫▫▪▫▫▪▫│▫▪▫▫▪▫▫▪▫▫│▪▫▫▪▫▫▪▫▫▪║
- * ┼──────────╫──────────┼──────────┼──────────╫
- *
- * If reading the second tile, then `tileBase` = 10 and
`offset` = -10.
- * The first pixel to read in the second tile has a
subsampling offset.
- * We usually try to avoid this situation because it
causes a variable
- * number of white squares in tiles (4,3,3,4 in the above
example),
- * except when there is only 1 tile to read in which case
offset is tolerated.
- */
- final int s = coverage.subsampling[dimension];
- offset %= s;
- if (offset != 0) {
- offset += s;
- }
- }
- if (offset >= limit) { // Test for intersection
before we adjust the limit.
- return false;
- }
- if (dimension == X_DIMENSION && coverage.forceTileSize) {
- limit = tileSize;
- }
- lower[dimension] = offset;
- upper[dimension] = limit;
- }
- return true;
+ @Override
+ final TiledGridCoverage getCoverage() {
+ return coverage;
}
/**
- * Stores the given raster in the cache. If another raster existed
previously in the cache,
- * the old raster will be reused if it has the same size and model, or
discarded otherwise.
- * The latter case may happen if {@link AOI#getCachedTile()}
determined that a cached raster
- * exists but cannot be reused.
+ * Returns the origin to assign to the tile at the current iterator
position.
+ * This is needed by the parent class only for the two first
dimensions.
*
- * @param tile the raster to cache.
- * @return the cached raster. Should be the given {@code raster}
instance,
- * but this method check for concurrent caching as a paranoiac
check.
- *
- * @see AOI#getCachedTile()
+ * @see TileIterator#getTileOrigin(int)
*/
- public Raster cache(final Raster tile) {
- final TiledGridResource.CacheKey key =
coverage.createCacheKey(indexInTileVector);
- Raster existing = coverage.rasters.put(key, tile);
- /*
- * If a tile already exists, verify if its layout is compatible
with the given tile.
- * If yes, we assume that the two tiles have the same content. We
do this check as a
- * safety but it should not happen if the caller synchronized the
tile read actions.
- */
- if (existing != null
- && existing.getSampleModel().equals(tile.getSampleModel())
- && existing.getWidth() == tile.getWidth()
- && existing.getHeight() == tile.getHeight())
- {
- // Restore the existing tile in the cache, with its original
position.
- if (coverage.rasters.replace(key, tile, existing)) {
- final int x = tile.getMinX();
- final int y = tile.getMinY();
- if (existing.getMinX() != x || existing.getMinY() != y) {
- existing = existing.createTranslatedChild(x, y);
- }
- return existing;
- }
+ @Override
+ final int getTileOrigin(final int dimension) {
+ switch (dimension) {
+ case X_DIMENSION: return originX;
+ case Y_DIMENSION: return originY;
+ default: throw new AssertionError(dimension);
}
- return tile;
}
}
@@ -944,6 +1036,43 @@ public abstract class TiledGridCoverage extends
GridCoverage {
return new TiledGridResource.CacheKey(indexInTileVector,
includedBands, subsampling, subsamplingOffsets);
}
+ /**
+ * Returns a raster in the cache, or {@code null} if none.
+ * See {@link AOI#getCachedTile()} for more information.
+ */
+ private Raster getCachedTile(final int indexInTileVector) {
+ return rasters.get(createCacheKey(indexInTileVector));
+ }
+
+ /**
+ * Caches the given raster. See {@link AOI#cache(Raster)} for more
information.
+ */
+ private Raster cacheTile(final int indexInTileVector, final Raster tile) {
+ final TiledGridResource.CacheKey key =
createCacheKey(indexInTileVector);
+ Raster existing = rasters.put(key, tile);
+ /*
+ * If a tile already exists, verify if its layout is compatible with
the given tile.
+ * If yes, we assume that the two tiles have the same content. We do
this check as a
+ * safety but it should not happen if the caller synchronized the tile
read actions.
+ */
+ if (existing != null
+ && existing.getSampleModel().equals(tile.getSampleModel())
+ && existing.getWidth() == tile.getWidth()
+ && existing.getHeight() == tile.getHeight())
+ {
+ // Restore the existing tile in the cache, with its original
position.
+ if (rasters.replace(key, tile, existing)) {
+ final int x = tile.getMinX();
+ final int y = tile.getMinY();
+ if (existing.getMinX() != x || existing.getMinY() != y) {
+ existing = existing.createTranslatedChild(x, y);
+ }
+ return existing;
+ }
+ }
+ return tile;
+ }
+
/**
* Returns all tiles in the given area of interest. Tile indices are
relative to this {@code TiledGridCoverage}:
* (0,0) is the tile in the upper-left corner of this {@code
TiledGridCoverage} (not necessarily the upper-left
@@ -962,23 +1091,28 @@ public abstract class TiledGridCoverage extends
GridCoverage {
* @throws RuntimeException if the Java2D image cannot be created for
another reason
* (too many exception types to list them all).
*/
- protected abstract Raster[] readTiles(AOI iterator) throws IOException,
DataStoreException;
+ protected abstract Raster[] readTiles(TileIterator iterator) throws
IOException, DataStoreException;
/**
- * Converts raster coordinate from this {@code TiledGridCoverage}
coordinate space to full resolution.
- * This method removes the subsampling effect. Note that since this {@code
TiledGridCoverage} uses the
- * same coordinate space as {@link TiledGridResource}, the converted
coordinates should be valid in
- * the full resource as well.
+ * 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. Will be modified in-place.
+ * @param bounds the rectangle to convert.
+ * @return the converted rectangle.
* @throws ArithmeticException if the coordinate cannot be represented as
an integer.
*
- * @see AOI#getExtentInSource()
+ * @see TileIterator#getTileExtentInResource()
*/
- protected final void toFullResolution(final Rectangle bounds) {
- bounds.x = Math.toIntExact(toFullResolution(bounds.x,
X_DIMENSION));
- bounds.y = Math.toIntExact(toFullResolution(bounds.y,
Y_DIMENSION));
- bounds.width = Math.multiplyExact(bounds.width,
subsampling[X_DIMENSION]);
- bounds.height = Math.multiplyExact(bounds.height,
subsampling[Y_DIMENSION]);
+ protected final Rectangle pixelToResourceCoordinates(final Rectangle
bounds) {
+ return new Rectangle(
+ toIntExact(pixelToResourceCoordinate(bounds.x, X_DIMENSION)),
+ toIntExact(pixelToResourceCoordinate(bounds.y, Y_DIMENSION)),
+ multiplyExact(bounds.width, subsampling[X_DIMENSION]),
+ multiplyExact(bounds.height, subsampling[Y_DIMENSION]));
}
}
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 ec1c0e1bf4..6da315a03f 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
@@ -17,6 +17,7 @@
package org.apache.sis.storage.gdal;
import java.util.List;
+import java.nio.Buffer;
import java.awt.Rectangle;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
@@ -234,7 +235,7 @@ final class Band {
}
/**
- * Transfers (reads or writes) sample values between <abbr>GDAL</abbr>
raster and Java2D raster for one band.
+ * Transfers (reads or writes) sample values between <abbr>GDAL</abbr>
raster and Java2D raster.
* The full area of the Java2D raster is transferred. It may corresponds
to a sub-area of the GDAL raster.
*
* <h4>Prerequisites</h4>
@@ -243,52 +244,70 @@ final class Band {
* <li>In read mode, the given raster shall be an instance of {@link
WritableRaster}.</li>
* </ul>
*
- * @param gdal set of handles for invoking <abbr>GDAL</abbr> functions.
- * @param rwFlag {@link OpenFlag#READ} or {@link OpenFlag#WRITE}.
- * @param image the <abbr>GDAL</abbr> raster which contains the band to
read or write.
- * @param aoi region of the image to read or write. (0,0) is the
upper-left pixel.
- * @param raster the Java2D raster where to store of fetch the values to
read or write.
- * @param band band of sample values in the Java2D raster.
- * @return whether the operation was successful according
<abbr>GDAL</abbr>.
+ * <h4>Alternatives</h4>
+ * {@code GDALReadBlock} would have been a more efficient method, but we
do not use it because the actual
+ * tile size given to this method is sometime different than the natural
block size of the data set.
+ * This difference happens when <abbr>GDAL</abbr> uses block size as width
as the image and 1 row in height.
+ * Such block sizes are inefficient for Apache <abbr>SIS</abbr>, therefore
we request a different size.
+ *
+ * <p>A yet more efficient approach would be to use {@code
GDALRasterBlock::GetLockedBlockRef(…)}
+ * for copying the data from the cache without intermediate buffer. But
the latter is C++ API.
+ * We cannot use it as of Java 22.</p>
+ *
+ * @param gdal set of handles for invoking <abbr>GDAL</abbr>
functions.
+ * @param readWriteFlags {@link OpenFlag#READ} or {@link
OpenFlag#WRITE}.
+ * @param selectedBands all bands to read, in the same order as they
appear in the given Java2D raster.
+ * @param resourceType the <abbr>GDAL</abbr> data type of all
specified bands, as stored in the resource.
+ * @param resourceBounds region to read or write in resource
coordinates. (0,0) is the upper-left pixel.
+ * @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>.
* @throws ClassCastException if an above-documented prerequisite is not
true.
* @throws DataStoreException if <var>GDAL</var> reported a warning or
fatal error.
*/
- final boolean transfer(final GDAL gdal, final int rwFlag,
- final TiledResource image, final Rectangle aoi,
// GDAL model
- final Raster raster, final int band)
// Java2D model
+ static boolean transfer(final GDAL gdal,
+ final int readWriteFlags,
+ final Band[] selectedBands,
+ final DataType resourceType,
+ final Rectangle resourceBounds,
+ final Raster raster,
+ final Rectangle rasterBounds,
+ final MemorySegment transferBuffer)
throws DataStoreException
{
- if (rwFlag == OpenFlag.READ && !(raster instanceof WritableRaster)) {
+ if (readWriteFlags == OpenFlag.READ && !(raster instanceof
WritableRaster)) {
throw new ClassCastException();
}
- final var model = (ComponentSampleModel) raster.getSampleModel();
// See prerequisites in Javadoc.
- final var data = raster.getDataBuffer();
- final int dataSize = DataBuffer.getDataTypeSize(data.getDataType()) /
Byte.SIZE;
- final var buffer = RasterFactory.wrapAsBuffer(data,
model.getBankIndices()[band]);
- buffer.position(model.getOffset(raster.getMinX() -
raster.getSampleModelTranslateX(),
- raster.getMinY() -
raster.getSampleModelTranslateY(), band));
- final int err;
- try (Arena arena = Arena.ofConfined()) {
- /*
- * TODO: we wanted to use `MemorySegment.ofBuffer` but it does not
work.
- * We get an "IllegalArgumentException: Heap segment not allowed"
error.
- * For now we copy in a temporary array as a workaround, but it
needs to
- * be replaced by a call to GetLockedBlockRef.
- */
- MemorySegment tmp =
arena.allocate(Math.multiplyFull(buffer.remaining(), dataSize));
- err = (int) gdal.rasterIO.invokeExact(handle, rwFlag,
- aoi.x, aoi.y, aoi.width, aoi.height,
- tmp,
- raster.getWidth(),
- raster.getHeight(),
-
image.dataType.forDataBufferType(data.getDataType()).ordinal(),
- Math.multiplyExact(dataSize, model.getPixelStride()),
- Math.multiplyExact(dataSize, model.getScanlineStride()));
-
- MemorySegment.ofBuffer(buffer).copyFrom(tmp);
- } catch (Throwable e) {
- throw GDAL.propagate(e);
+ 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();
+ for (int i=0; i < selectedBands.length; i++) {
+ assert raster.getBounds().contains(rasterBounds) : rasterBounds;
+ final Buffer buffer = RasterFactory.wrapAsBuffer(dataBuffer,
bankIndices[i])
+ .position(sampleModel.getOffset(
+ rasterBounds.x - raster.getSampleModelTranslateX(),
+ rasterBounds.y -
raster.getSampleModelTranslateY(), i));
+ final int err;
+ try {
+ assert transferBuffer.byteSize() >=
Math.multiplyFull(rasterBounds.width, rasterBounds.height) * dataSize;
+ 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()));
+ } catch (Throwable e) {
+ throw GDAL.propagate(e);
+ }
+ if (!ErrorHandler.checkCPLErr(err)) {
+ return false;
+ }
+ MemorySegment.ofBuffer(buffer).copyFrom(transferBuffer);
}
- return ErrorHandler.checkCPLErr(err);
+ return true;
}
}
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 b68297fff0..e4eccc4ec2 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
@@ -20,6 +20,8 @@ import java.io.IOException;
import java.awt.Rectangle;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemorySegment;
import org.opengis.util.GenericName;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.base.TiledGridCoverage;
@@ -57,6 +59,13 @@ final class TiledCoverage extends TiledGridCoverage {
return owner.getIdentifier().orElse(null);
}
+ /**
+ * Returns the length of tiles in bytes.
+ */
+ private long getTileLength() {
+ return Math.ceilDiv(Math.multiplyFull(model.getWidth(),
model.getHeight()) * owner.dataType.numBits, Byte.SIZE);
+ }
+
/**
* Returns all tiles in the given area of interest. Tile indices (0,0)
locates the tile in the upper-left corner
* of this {@code TiledGridCoverage} (not necessarily the upper-left
corner of the {@link TiledGridResource}).
@@ -65,21 +74,29 @@ final class TiledCoverage extends TiledGridCoverage {
*
* @param iterator an iterator over the tiles that intersect the Area Of
Interest specified by user.
* @return tiles decoded from the {@link TiledGridResource}.
+ * @throws ArithmeticException if an integer overflow occurred.
*/
@Override
- protected Raster[] readTiles(final AOI iterator) throws IOException,
DataStoreException {
+ protected Raster[] readTiles(final TileIterator iterator) throws
IOException, DataStoreException {
synchronized (owner.getSynchronizationLock()) {
- final var result = new Raster[iterator.tileCountInQuery];
- try {
+ final Band[] bands = owner.bands(includedBands);
+ final GDAL gdal = owner.parent.getProvider().GDAL();
+ final var result = new
WritableRaster[iterator.tileCountInQuery];
+ try (Arena arena = Arena.ofConfined()) {
+ final MemorySegment transferBuffer =
arena.allocate(getTileLength());
do {
final WritableRaster tile = iterator.createRaster();
- final Rectangle bounds = tile.getBounds();
- toFullResolution(bounds);
- owner.transfer(OpenFlag.READ, bounds, tile, includedBands);
- result[iterator.getIndexInResultArray()] = tile;
+ final Rectangle target = iterator.getRegionInsideTile();
+ 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)) {
+ break; // Exception will be thrown by
`throwOnFailure(…)`
+ }
+ result[iterator.getTileIndexInResultArray()] = tile;
} while (iterator.next());
} finally {
- ErrorHandler.report(owner.parent, "read"); // Public
caller of this method.
+ ErrorHandler.throwOnFailure(owner.parent, "read"); //
Public caller of this method.
}
return result;
}
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 3cfb673508..0c9d7fa60f 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
@@ -23,11 +23,9 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Optional;
import java.awt.Dimension;
-import java.awt.Rectangle;
import java.awt.image.ColorModel;
import java.awt.image.SampleModel;
import java.awt.image.BandedSampleModel;
-import java.awt.image.Raster;
import java.lang.foreign.Arena;
import java.lang.foreign.ValueLayout;
import java.lang.foreign.MemorySegment;
@@ -363,6 +361,24 @@ final class TiledResource extends TiledGridResource {
return 1;
}
+ /**
+ * Returns the bands in the given indices.
+ *
+ * @param bandIndices indices of the selected bands, or {@code null} for
all bands.
+ * @return specified bands. May be a reference to internal array: do not
modify.
+ */
+ @SuppressWarnings("ReturnOfCollectionOrArrayField")
+ final Band[] bands(final int[] bandIndices) {
+ if (bandIndices == null) {
+ return bands;
+ }
+ var selectedBands = new Band[bandIndices.length];
+ for (int i=0; i<bandIndices.length; i++) {
+ selectedBands[i] = bands[bandIndices[i]];
+ }
+ return selectedBands;
+ }
+
/**
* Creates the color model and sample model.
* This method stores the results in {@link #sampleModel} and {@link
#colorModel},
@@ -373,18 +389,10 @@ final class TiledResource extends TiledGridResource {
* through the {@link Subset} constructor. Therefore, this method relies
on the
* error handling setup by {@code read(…)}.
*
- * @param bandIndices indices of the selected bands.
+ * @param bandIndices indices of the selected bands, or {@code null} for
all bands.
*/
private void createColorAndSampleModel(final int[] bandIndices) throws
DataStoreException {
- final Band[] selectedBands;
- if (bandIndices == null) {
- selectedBands = bands;
- } else {
- selectedBands = new Band[bandIndices.length];
- for (int i=0; i<bandIndices.length; i++) {
- selectedBands[i] = bands[bandIndices[i]];
- }
- }
+ final Band[] selectedBands = bands(bandIndices);
final GDAL gdal = parent.getProvider().GDAL();
int[] palette = null;
int paletteIndex = 0;
@@ -508,37 +516,6 @@ final class TiledResource extends TiledGridResource {
return new int[] {tileWidth, tileHeight};
}
- /**
- * Transfers (reads or writes) sample values between <abbr>GDAL</abbr>
raster and Java2D raster.
- * The full area of the Java2D raster is transferred. It may corresponds
to a sub-area of the GDAL raster.
- *
- * <h4>Prerequisites</h4>
- * <ul>
- * <li>The Java2D raster shall use a {@link ComponentSampleModel}.</li>
- * <li>In read mode, the given raster shall be an instance of {@link
WritableRaster}.</li>
- * </ul>
- *
- * @param rwFlag {@link OpenFlag#READ} or {@link OpenFlag#WRITE}.
- * @param aoi region of the image to read or write. (0,0) is the
upper-left pixel.
- * @param raster the Java2D raster where to store of fetch the
values to read or write.
- * @param bandIndices bands of sample values in the Java2D raster, or
{@code null} for all.
- * @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.
- */
- final boolean transfer(final int rwFlag, final Rectangle aoi, final Raster
raster, final int[] bandIndices)
- throws DataStoreException
- {
- final GDAL gdal = parent.getProvider().GDAL();
- final int n = (bandIndices != null) ? bandIndices.length :
bands.length;
- boolean success = true;
- for (int i=0; i<n; i++) {
- final Band band = bands[(bandIndices != null) ? bandIndices[i] :
i];
- success &= band.transfer(gdal, rwFlag, this, aoi, raster, i);
- }
- return success;
- }
-
/**
* 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.
diff --git
a/incubator/src/org.apache.sis.storage.gimi/main/org/apache/sis/storage/gimi/internal/MatrixGridRessource.java
b/incubator/src/org.apache.sis.storage.gimi/main/org/apache/sis/storage/gimi/internal/MatrixGridRessource.java
index 61f5328da3..6cbf6d0d35 100644
---
a/incubator/src/org.apache.sis.storage.gimi/main/org/apache/sis/storage/gimi/internal/MatrixGridRessource.java
+++
b/incubator/src/org.apache.sis.storage.gimi/main/org/apache/sis/storage/gimi/internal/MatrixGridRessource.java
@@ -113,17 +113,18 @@ public abstract class MatrixGridRessource extends
TiledGridResource {
}
@Override
- protected Raster[] readTiles(AOI iterator) throws IOException,
DataStoreException {
+ protected Raster[] readTiles(final TileIterator iterator) throws
IOException, DataStoreException {
final Raster[] result = new Raster[iterator.tileCountInQuery];
synchronized (MatrixGridRessource.this.getSynchronizationLock()) {
do {
final Raster tile = iterator.getCachedTile();
if (tile != null) {
- result[iterator.getIndexInResultArray()] = tile;
+ result[iterator.getTileIndexInResultArray()] = tile;
} else {
- long[] tileCoord =
iterator.getTileCoordinatesInSource();
+ long[] tileCoord =
iterator.getTileCoordinatesInResource();
final RenderedImage image = getTileImage(tileCoord);
- result[iterator.getIndexInResultArray()] = image
instanceof BufferedImage ? ((BufferedImage)image).getRaster() : image.getData();
+ result[iterator.getTileIndexInResultArray()] =
+ (image instanceof BufferedImage) ?
((BufferedImage)image).getRaster() : image.getData();
}
} while (iterator.next());
}