This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 93542a40c716397d65780eacbd13502c06a29d21 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Mon Oct 25 10:41:50 2021 +0200 Apply GDAL "sparse files" convention. It requires relaxing `TiledGridCoverage` tile types from `WritableRaster` to `Raster`. --- .../sis/internal/coverage/j2d/TilePlaceholder.java | 17 ++++++++ .../sis/storage/geotiff/CompressedSubset.java | 8 ++-- .../org/apache/sis/storage/geotiff/DataSubset.java | 45 ++++++++++++++++------ .../sis/internal/storage/TiledGridCoverage.java | 28 +++++++------- .../sis/internal/storage/TiledGridResource.java | 8 ++-- 5 files changed, 73 insertions(+), 33 deletions(-) diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TilePlaceholder.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TilePlaceholder.java index 23245e8..e5ad537 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TilePlaceholder.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TilePlaceholder.java @@ -131,6 +131,23 @@ public class TilePlaceholder { } /** + * Returns a provider of empty tiles filled with the given value in all bands. + * A value of {@code null} is interpreted as 0 for integer types or NaN for floating point types. + * + * @param model sample model of the empty tiles. + * @param fillValue the value to use for filling empty spaces in rasters, or {@code null} for zero. + * @return provider of filled tiles. + */ + public static TilePlaceholder filled(final SampleModel model, final Number fillValue) { + if (fillValue == null) { + return empty(model); + } + final Number[] values = new Number[model.getNumBands()]; + Arrays.fill(values, fillValue); + return filled(model, new FillValues(model, values, true)); + } + + /** * Returns a provider of empty tiles filled with the given values. * * @param model sample model of the empty tiles. diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CompressedSubset.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CompressedSubset.java index 2f2f948..3151824 100644 --- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CompressedSubset.java +++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CompressedSubset.java @@ -20,7 +20,7 @@ import java.io.Closeable; import java.io.IOException; import java.nio.Buffer; import java.awt.Point; -import java.awt.image.WritableRaster; +import java.awt.image.Raster; import org.apache.sis.internal.storage.TiledGridResource; import org.apache.sis.internal.storage.inflater.Inflater; import org.apache.sis.internal.coverage.j2d.RasterFactory; @@ -191,8 +191,8 @@ final class CompressedSubset extends DataSubset { * @return a single tile decoded from the GeoTIFF file. */ @Override - WritableRaster readSlice(final long[] offsets, final long[] byteCounts, final long[] lower, final long[] upper, - final int[] subsampling, final Point location) throws IOException, DataStoreException + Raster readSlice(final long[] offsets, final long[] byteCounts, final long[] lower, final long[] upper, + final int[] subsampling, final Point location) throws IOException, DataStoreException { final DataType dataType = getDataType(); final int width = pixelCount(lower, upper, subsampling, X_DIMENSION); @@ -250,7 +250,7 @@ final class CompressedSubset extends DataSubset { fillRemainingRows(bank.flip()); banks[b] = bank; } - return WritableRaster.createWritableRaster(model, RasterFactory.wrap(dataType, banks), location); + return Raster.createWritableRaster(model, RasterFactory.wrap(dataType, banks), location); } /** diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java index 24f969c..0bbccfb 100644 --- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java +++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java @@ -24,7 +24,7 @@ import java.io.IOException; import java.awt.Point; import java.awt.image.BandedSampleModel; import java.awt.image.DataBuffer; -import java.awt.image.WritableRaster; +import java.awt.image.Raster; import org.apache.sis.image.DataType; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.DataStoreContentException; @@ -32,6 +32,7 @@ import org.apache.sis.internal.storage.io.Region; import org.apache.sis.internal.storage.io.HyperRectangleReader; import org.apache.sis.internal.storage.TiledGridCoverage; import org.apache.sis.internal.storage.TiledGridResource; +import org.apache.sis.internal.coverage.j2d.TilePlaceholder; import org.apache.sis.internal.coverage.j2d.ImageUtilities; import org.apache.sis.internal.coverage.j2d.RasterFactory; import org.apache.sis.internal.storage.io.ChannelDataInput; @@ -64,7 +65,7 @@ import static java.lang.Math.toIntExact; * the same tile indices than {@link DataCube} in order to avoid integer overflow. * * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.2 * @since 1.1 * @module */ @@ -130,6 +131,12 @@ class DataSubset extends TiledGridCoverage implements Localized { protected final int targetPixelStride; /** + * Provider of empty tiles, created only if needed. Empty tiles are tiles with a length of 0 + * declared in the TIFF header. This interpretation is a GDAL extension, not a TIFF standard. + */ + private TilePlaceholder emptyTiles; + + /** * Creates a new data subset. All parameters should have been validated * by {@link ImageFileDirectory#validateMandatoryTags()} before this call. * This constructor should be invoked inside a synchronized block. @@ -278,7 +285,7 @@ class DataSubset extends TiledGridCoverage implements Localized { * (0,0) is the tile in the upper-left corner of this {@code DataSubset} (not necessarily the upper-left * corner of the image stored in the TIFF file). * - * The {@link WritableRaster#getMinX()} and {@code getMinY()} coordinates of returned rasters + * The {@link Raster#getMinX()} and {@code getMinY()} coordinates of returned rasters * will start at the given {@code offsetAOI} values. * * <p>This method is thread-safe.</p> @@ -291,7 +298,7 @@ class DataSubset extends TiledGridCoverage implements Localized { * (too many exception types to list them all). */ @Override - protected final WritableRaster[] readTiles(final AOI iterator) throws IOException, DataStoreException { + protected final Raster[] readTiles(final AOI 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. @@ -299,13 +306,13 @@ class DataSubset extends TiledGridCoverage implements Localized { * (`sourcePixelStride` > 1) or use one separated bank per band (`sourcePixelStride` == 1). */ final int[] includedBanks = (sourcePixelStride == 1) ? includedBands : null; - final WritableRaster[] result = new WritableRaster[iterator.tileCountInQuery]; + final Raster[] result = new Raster[iterator.tileCountInQuery]; final Tile[] missings = new Tile[iterator.tileCountInQuery]; int numMissings = 0; boolean needsCompaction = false; synchronized (source.getSynchronizationLock()) { do { - final WritableRaster tile = iterator.getCachedTile(); + final Raster tile = iterator.getCachedTile(); if (tile != null) { result[iterator.getIndexInResultArray()] = tile; } else { @@ -337,10 +344,26 @@ class DataSubset extends TiledGridCoverage implements Localized { origin.y = tile.originY; tile.copyTileInfo(tileOffsets, offsets, includedBanks, numTiles); tile.copyTileInfo(tileByteCounts, byteCounts, includedBanks, numTiles); + boolean isEmpty = true; for (int b=0; b<offsets.length; b++) { + isEmpty &= (byteCounts[b] == 0); offsets[b] = addExact(offsets[b], source.reader.origin); } - WritableRaster r = readSlice(offsets, byteCounts, lower, upper, subsampling, origin); + /* + * If the length if zero for all bands, the GDAL "sparse files" convention said + * that pixel values are not stored in the file and are assumed zero for all pixels. + * This is a GDAL-specific convention but seems reasonable. Note that the default + * fill value zero is different than `TilePlaceholder` default, which can be NaN. + */ + final Raster r; + if (isEmpty) { + if (emptyTiles == null) { + emptyTiles = TilePlaceholder.filled(model, (fillValue != null) ? fillValue : 0); + } + r = emptyTiles.create(origin); + } else { + r = readSlice(offsets, byteCounts, lower, upper, subsampling, origin); + } result[tile.indexInResultArray] = tile.cache(r); } else { needsCompaction = true; @@ -356,7 +379,7 @@ class DataSubset extends TiledGridCoverage implements Localized { */ if (needsCompaction) { int n = 0; - for (final WritableRaster tile : result) { + for (final Raster tile : result) { if (tile != null) result[n++] = tile; } return Arrays.copyOf(result, n); @@ -417,8 +440,8 @@ class DataSubset extends TiledGridCoverage implements Localized { * * @see DataCube#canReadDirect(TiledGridResource.Subset) */ - WritableRaster readSlice(final long[] offsets, final long[] byteCounts, final long[] lower, final long[] upper, - final int[] subsampling, final Point location) throws IOException, DataStoreException + Raster readSlice(final long[] offsets, final long[] byteCounts, final long[] lower, final long[] upper, + final int[] subsampling, final Point location) throws IOException, DataStoreException { final DataType type = getDataType(); final int sampleSize = type.size(); // Assumed same as `SampleModel.getSampleSize(…)` by pre-conditions. @@ -462,7 +485,7 @@ class DataSubset extends TiledGridCoverage implements Localized { banks[b] = bank; } final DataBuffer buffer = RasterFactory.wrap(type, banks); - return WritableRaster.createWritableRaster(model, buffer, location); + return Raster.createWritableRaster(model, buffer, location); } /** diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java index 3980f13..b5ffb7b 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java @@ -25,7 +25,7 @@ import java.awt.image.ColorModel; import java.awt.image.SampleModel; import java.awt.image.MultiPixelPackedSampleModel; import java.awt.image.RenderedImage; -import java.awt.image.WritableRaster; +import java.awt.image.Raster; import org.opengis.coverage.CannotEvaluateException; import org.opengis.geometry.MismatchedDimensionException; import org.apache.sis.coverage.grid.GridCoverage; @@ -92,8 +92,8 @@ public abstract class TiledGridCoverage extends GridCoverage { * This is relevant only for the last column of tile matrix, because those tiles may be truncated * if the image size is not a multiple of tile size. It is usually necessary to read those tiles * fully anyway because otherwise, the pixels read from the storage would not be aligned with the - * pixels stored in the {@link WritableRaster}. However there is a few exceptions where the read - * extent should not be forced to the tile size: + * pixels stored in the {@link Raster}. However there is a few exceptions where the read extent + * should not be forced to the tile size: * * <ul> * <li>If the image is untiled, then the {@link org.apache.sis.internal.storage.TiledGridResource.Subset} @@ -176,7 +176,7 @@ public abstract class TiledGridCoverage extends GridCoverage { * @see AOI#getCachedTile() * @see #createCacheKey(int) */ - private final Map<TiledGridResource.CacheKey, WritableRaster> rasters; + private final Map<TiledGridResource.CacheKey, Raster> rasters; /** * The sample model for all rasters. The size of this sample model is the values of two elements @@ -422,7 +422,7 @@ public abstract class TiledGridCoverage extends GridCoverage { /* * Get all tiles in the specified region. I/O operations, if needed, happen here. */ - final WritableRaster[] result = readTiles(new AOI(tileLower, tileUpper, offsetAOI, dimension)); + final Raster[] result = readTiles(new AOI(tileLower, tileUpper, offsetAOI, dimension)); /* * Wraps in an image all the tiles that we just read, together with the following properties: * - Two-dimensional conversion from pixel coordinates to "real world" coordinates. @@ -554,10 +554,10 @@ public abstract class TiledGridCoverage extends GridCoverage { * * @return cached tile at current iterator position, or {@code null} if none. * - * @see Snapshot#cache(WritableRaster) + * @see Snapshot#cache(Raster) */ - public WritableRaster getCachedTile() { - WritableRaster tile = rasters.get(createCacheKey(indexInTileVector)); + public Raster getCachedTile() { + 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. @@ -566,9 +566,9 @@ public abstract class TiledGridCoverage extends GridCoverage { final int x = getTileOrigin(X_DIMENSION); final int y = getTileOrigin(Y_DIMENSION); if (!model.equals(tile.getSampleModel())) { - tile = WritableRaster.createWritableRaster(model, tile.getDataBuffer(), new Point(x, y)); + tile = Raster.createRaster(model, tile.getDataBuffer(), new Point(x, y)); } else if (tile.getMinX() != x || tile.getMinY() != y) { - tile = tile.createWritableTranslatedChild(x, y); + tile = tile.createTranslatedChild(x, y); } } return tile; @@ -765,8 +765,8 @@ public abstract class TiledGridCoverage extends GridCoverage { * * @see AOI#getCachedTile() */ - public WritableRaster cache(final WritableRaster raster) { - final WritableRaster existing = coverage.rasters.putIfAbsent( + public Raster cache(final Raster raster) { + final Raster existing = coverage.rasters.putIfAbsent( coverage.createCacheKey(indexInTileVector), raster); return (existing != null) ? existing : raster; } @@ -784,7 +784,7 @@ public abstract class TiledGridCoverage extends GridCoverage { * (0,0) is the tile in the upper-left corner of this {@code TiledGridCoverage} (not necessarily the upper-left * corner of the image in the {@link TiledGridResource}). * - * The {@link WritableRaster#getMinX()} and {@code getMinY()} coordinates of returned rasters + * The {@link Raster#getMinX()} and {@code getMinY()} coordinates of returned rasters * shall start at the given {@code iterator.offsetAOI} values. * * <p>This method must be thread-safe.</p> @@ -796,5 +796,5 @@ public abstract class TiledGridCoverage extends GridCoverage { * @throws RuntimeException if the Java2D image can not be created for another reason * (too many exception types to list them all). */ - protected abstract WritableRaster[] readTiles(AOI iterator) throws IOException, DataStoreException; + protected abstract Raster[] readTiles(AOI iterator) throws IOException, DataStoreException; } diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java index bb67d36..498abe4 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java @@ -23,8 +23,8 @@ import java.awt.image.ColorModel; import java.awt.image.SampleModel; import java.awt.image.BandedSampleModel; import java.awt.image.ComponentSampleModel; +import java.awt.image.Raster; import java.awt.image.RenderedImage; -import java.awt.image.WritableRaster; import java.awt.image.RasterFormatException; import org.opengis.coverage.CannotEvaluateException; import org.apache.sis.coverage.SampleDimension; @@ -100,13 +100,13 @@ public abstract class TiledGridResource extends AbstractGridResource { /** * All tiles loaded by any {@link TiledGridCoverage} created from this resource. * Keys contains tile indices in a row-major array of tiles. - * For each value, the {@link WritableRaster#getMinX()} and {@code minY} values + * For each value, the {@link Raster#getMinX()} and {@code minY} values * can be anything, depending which {@link TiledGridResource} was first to load the tile. * * @see TiledGridCoverage#rasters * @see TiledGridCoverage.AOI#getCachedTile() */ - private final WeakValueHashMap<CacheKey, WritableRaster> rasters; + private final WeakValueHashMap<CacheKey, Raster> rasters; /** * Whether all tiles should be loaded at {@code read(…)} method call or deferred to a later time. @@ -322,7 +322,7 @@ public abstract class TiledGridResource extends AbstractGridResource { * Cache to use for tiles loaded by the {@link TiledGridCoverage}. * It is a reference to {@link TiledGridResource#rasters} if shareable. */ - final WeakValueHashMap<CacheKey, WritableRaster> cache; + final WeakValueHashMap<CacheKey, Raster> cache; /** * Creates parameters for the given domain and range.