This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/sis.git
commit f81f4d8c8ceb834d97c41f4722e324867fc36b96 Merge: f7bef1427d 90feafe896 Author: Martin Desruisseaux <[email protected]> AuthorDate: Tue Jun 23 17:40:41 2026 +0200 Merge branch 'geoapi-3.1'. Contains bug fixes in default implementation of `TileMatrix` interface. .../apache/sis/cloud/aws/s3/ClientFileSystem.java | 13 - .../sis/cloud/aws/s3/ClientFileSystemTest.java | 17 +- .../apache/sis/cloud/aws/s3/FileServiceTest.java | 10 +- .../main/org/apache/sis/coverage/Category.java | 87 +++-- .../main/org/apache/sis/coverage/CategoryList.java | 2 +- .../org/apache/sis/coverage/SampleDimension.java | 55 ++- .../main/org/apache/sis/coverage/package-info.java | 3 +- .../main/org/apache/sis/image/ComputedImage.java | 15 - .../main/org/apache/sis/image/PlanarImage.java | 21 ++ .../sis/image/internal/shared/ReshapedImage.java | 14 +- .../sis/image/internal/shared/TiledImage.java | 23 +- .../org/apache/sis/coverage/CategoryListTest.java | 117 +++++++ .../test/org/apache/sis/coverage/CategoryTest.java | 107 ++++++ .../apache/sis/coverage/SampleDimensionTest.java | 132 ++++++++ .../org/apache/sis/image/ImageProcessorTest.java | 45 +++ .../image/internal/shared/ReshapedImageTest.java | 25 +- .../storage/geotiff/writer/ReformattedImage.java | 4 +- .../sis/storage/geotiff/GeoTiffStoreTest.java | 128 ++++++- .../org/apache/sis/storage/geotiff/ReaderTest.java | 49 ++- .../storage/geotiff/tiled_without_compression.tiff | Bin 0 -> 2324 bytes .../geotiff/untiled_without_compression.tiff | Bin 0 -> 1240 bytes .../apache/sis/storage/tiling/ImageTileMatrix.java | 341 ++++++++++++++----- .../apache/sis/storage/tiling/IterationDomain.java | 38 +-- .../org/apache/sis/storage/tiling/TileMatrix.java | 2 + .../sis/storage/tiling/ImageTileMatrixTest.java | 371 +++++++++++++++++++++ .../main/org/apache/sis/math/NumberType.java | 47 ++- .../org/apache/sis/measure/MeasurementRange.java | 26 +- .../main/org/apache/sis/measure/Range.java | 123 ++++++- .../main/org/apache/sis/util/ComparisonMode.java | 21 +- .../main/org/apache/sis/util/Utilities.java | 15 +- .../apache/sis/util/internal/shared/Numerics.java | 11 +- .../apache/sis/measure/MeasurementRangeTest.java | 1 + .../test/org/apache/sis/measure/RangeTest.java | 24 ++ gradle/wrapper/gradle-wrapper.properties | 2 +- 34 files changed, 1630 insertions(+), 259 deletions(-) diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SampleDimension.java index 8b093dc9a9,88a1575010..5c3cc1533f --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SampleDimension.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SampleDimension.java @@@ -76,10 -82,13 +79,10 @@@ import org.apache.sis.util.iso.Names * * @author Martin Desruisseaux (IRD, Geomatys) * @author Alexis Manin (Geomatys) - * @version 1.5 + * @version 1.7 - * - * @see org.opengis.metadata.content.SampleDimension - * * @since 1.0 */ - public class SampleDimension implements Serializable { -public class SampleDimension implements IdentifiedType, LenientComparable, Serializable { ++public class SampleDimension implements LenientComparable, Serializable { /** * Serial number for inter-operability with different versions. */ diff --cc endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImageTileMatrix.java index d73349db09,3587faa0a7..3ce1b3edc0 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImageTileMatrix.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImageTileMatrix.java @@@ -43,7 -52,7 +52,6 @@@ import org.apache.sis.coverage.grid.Inc import org.apache.sis.image.ComputedImage; import org.apache.sis.image.internal.shared.ReshapedImage; import org.apache.sis.pending.jdk.JDK18; --import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.iso.Names; import org.apache.sis.util.logging.Logging; import org.apache.sis.util.resources.Errors; @@@ -353,17 -397,101 +396,96 @@@ final class ImageTileMatrix implements * @param parallel {@code true} for a parallel stream (if supported), or {@code false} for a sequential stream. * @return stream of tiles, excluding missing tiles. * @throws DataStoreException if the tiles cannot be fetched in the given ranges of tile indexes. + * @throws SubspaceNotSpecifiedException if tile size in dimensions other than X and Y is greater than 1. + * @throws ArithmeticException if some coordinates are too large. + * This exception may also happen latter, during the execution of the returned stream. */ @Override - public Stream<Tile> getTiles(GridExtent indiceRanges, final boolean parallel) throws DataStoreException { - if (indiceRanges == null) { - indiceRanges = tilingScheme.getExtent(); + public Stream<Tile> getTiles(GridExtent tileRanges, final boolean parallel) throws DataStoreException { - /* - * Argument check: "indiceRanges" is the name used in public API, but we use `tileRanges` in this - * implementation for avoiding confusion with `cellRanges`. No need to expose this change to user. - */ - ArgumentChecks.ensureDimensionMatches("indiceRanges", tilingScheme.getDimension(), tileRanges); + if (tileRanges == null) { + tileRanges = tilingScheme.getExtent(); } - try { - return StreamSupport.stream(iterator(indiceRanges).iterator(), parallel); - } catch (ArithmeticException e) { - throw new UnsupportedQueryException(e); + /* + * Gets the bounds of the images to read. If deferred reading is supported, + * we can expand to the bounds of the whole coverage in order to perform a + * read operation (deferred) only once. The bounds are intersected with the + * coverage extent. + */ + @SuppressWarnings("LocalVariableHidesMemberVariable") + final var coverage = coverage(); + final int xDimension = coverage.xDimension; + final int yDimension = coverage.yDimension; + final GridExtent cellRanges = coverage.getGridGeometry().getExtent(); + final int dimension = cellRanges.getDimension(); + final var cellLow = new long[dimension]; + final var tileLow = new long[dimension]; + final var cellHigh = new long[dimension]; + final var tileHigh = new long[dimension]; + boolean is2D = true; + for (int i=0; i<dimension; i++) { + // Intersection between request and available data, in cell coordinates. + cellLow [i] = max(cellRanges.getLow (i), tileToCell(tileRanges.getLow(i), i)); + cellHigh[i] = min(cellRanges.getHigh(i), decrementExact(tileToCell(incrementExact(tileRanges.getHigh(i)), i))); + final long span = cellHigh[i] - cellLow[i] + 1; + if (span < 0 || span > Integer.MAX_VALUE) { + throw new ArithmeticException(resource.errors().getString(Errors.Keys.IntegerOverflow_1, Integer.SIZE)); + } + // Convert back to tile indices after intersection. + tileLow [i] = cellToTile(cellLow [i], i); + tileHigh[i] = cellToTile(cellHigh[i], i); + /* + * Special cases for (X,Y) dimensions: if deferred loading is enabled, expand the request + * to the maximum size that we can handle with 32-bits integers. This is okay because the + * actual loading will not happen at `read(…)` invocation time. Note that we do not expand + * `tileLow` and `tileHigh` because they should stay bounded to user's request. + */ + if (i != xDimension && i != yDimension) { + is2D &= (cellLow[i] == cellHigh[i]); + } else if (coverage.deferredTileReading) { + final long remain = min(cellRanges.getSize(i), Integer.MAX_VALUE) - span; + final long after = min(remain / 2, cellRanges.getHigh(i) - cellHigh[i]); + final long before = min(remain - after, cellLow[i] - cellRanges.getLow(i)); + cellLow [i] -= before; + cellHigh[i] += after; + } + } + if (is2D) { + // This code should only be reached if the requested extent is 2D. + assert tileRanges.getDegreesOfFreedom() <= TiledGridCoverageResource.BIDIMENSIONAL; + return StreamSupport.stream(builder(tileLow, tileHigh, cellLow, cellHigh).iterator(), parallel); } + /* + * Splits the given extent in a lazy sequence of 2D extents. + * For each 2D slice, we query all tiles contained in the user requested tile ranges. + * + * Note: the iteration over slices is executed in sequential order even if `parallel` is true. + * Allowing parallel iteration over slices could hurt performance, because it would cause the + * loading of rendered images from different slices in parallel. As this class `image` caching + * strategy is based on 2D extents, we instead push parallelism down on tile loading level directly + * (see `flatMap` block). + */ + final long[] tileHighInExtraDimensions = tileHigh.clone(); + tileHighInExtraDimensions[xDimension] = tileLow[xDimension]; + tileHighInExtraDimensions[yDimension] = tileLow[yDimension]; + return tileRanges.reshape(tileLow, tileHighInExtraDimensions, true).latticePointStream(false) + .flatMap((final long[] sliceLow) -> { + final long[] sliceHigh = sliceLow.clone(); + sliceHigh[xDimension] = tileHigh[xDimension]; + sliceHigh[yDimension] = tileHigh[yDimension]; + for (int i=0; i<sliceLow.length; i++) { + if (i != xDimension && i != yDimension) { + cellLow [i] = tileToCell(sliceLow[i], i); + cellHigh[i] = addExact(cellLow[i], tileSize[i] - 1); // Inclusive. + } + } + final IteratorBuilder builder; + try { + builder = builder(sliceLow, sliceHigh, cellLow, cellHigh); + } catch (DataStoreException e) { + throw new BackingStoreException("Cannot load tiles for 2D extent", e); + } + return StreamSupport.stream(builder.iterator(), parallel); + }); } /**
