You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2023/04/16 17:42:29 UTC

[sis] branch master updated (7d5dfd074e -> 0865c08dfb)

This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/sis.git


    from 7d5dfd074e Merge branch 'geoapi-3.1'
     add 6b68ae293b Remove a javadoc link to a `geoapi-conformance` interface to be removed.
     add 28032e87b4 Bug fix: value under cursor was lost after a few zooms in the JavaFX application.
     add 996e1106a9 fix(Storage): properly handle SQL timestamps to avoid ambiguity
     add da1cc1c2df Merge remote-tracking branch 'origin/fix/sql-temporal' into geoapi-4.0. The pull request has been modified for applying the mapping specified by JDBC 4.2. The specialized classes are kept as fallbacks when JDBC 4.2 is not well supported.
     add bd9026738e Filter: add CopyVisitor implementation
     add c6a2901fdd Merge remote-tracking branch 'origin/feat/filtercopy' into geoapi-4.0. Modifications to the pull request: - Add the missing parameterized types. - Register action for each filter type using the mechanism provided by `Visitor` parent class. - Reuse previously existing filter or expression instances when possible.
     add 0e0a37dc5f Minor documentation fixes in comments.
     add cbf9f26ab9 Resource : add ResourceProcessor with converted samples operation
     add 4f9dc8ee12 chore(Storage): fix a javadoc reference
     add aff9e65127 chore(Storage): add a GridCoverageProcessor attribute in resource processor
     add 5a6d0d24d0 feat(Storage): add GridCoverageResource resampling capability
     add 5a487bb198 chore(Feature): change contract of utility TiledImageMock.initializeAllTiles member function.
     add a0a748dea2 feat(Feature): add a computed image for band aggregation
     add 17a4412ed7 feat(Feature): allow user to override output color model for band aggregation
     add c5b2f14be3 feat(Feature): Add a GridCoverageResource for band aggregation
     add 211c9e7af6 feat(Feature+Storage): add a dimension selection grid coverage
     add a0722bc11b Merge branch 'geoapi-4.0' into feat/resource-processor
     add 01be36ffa4 Merge remote-tracking branch 'origin/feat/resource-processor' into geoapi-4.0. The code in the branch have been reworked for reusing more exising SIS code, for moving some aggregation methods to the dedicated `aggregate` sub-package, for resolving some limitations (e.g. band aggregation no longer requires the same tiling layout), for completing documentation and for retrofitting some operations as `GridCoverageResource.subset(…)`.
     add d485138aae Replace `PlanarImage.copyData(…)` implementation by a call to `WritableRaster.setRect(…)`. That method is optimized in various Java private subclasses of `WritableRaster`.
     add 7619fb4837 Documentation fixes and minor code formatting.
     add b56244371d Use `ArithmeticException` for errors related to an overflow of integer capacity.
     add 8f36385c7d Rename some arguments for consistency and update documentation. There is no significant code change in this commit, only renaming.
     add 763442662f Clarify in documentation the behavior of `GridCoverage.Evaluator.toGridCoordinates(DirectPosition)`.
     add e4d0c384bb Consolidation of argument checks.
     add 295965c64a Add test for `PassThroughTransform` with non-consecutive modified coordinates. Allow `DefaultPassThroughOperation` to use non-consecutive coordinates at unmarshalling time.
     add fdf44c1605 Add an `AbstractFeatureSet` constructor receiving a `Resource` argument. This is a complement to similar change in `AbstractGridCoverageResource`. This commit contains an incompatible change in the following classes:
     add 34aaff134e `CombinedImageLayout.createColorModel()` should preserve the visible band of source image if possible. This commit also remove some `java.util.Optional` from internal API because all usages of it where invoking `orElse(null)`.
     add 9627d2e9cc Initial version of an `Colorizer` interface for building the `ColorModel` of a computed image. Replacement is not yet done everywhere.
     add 14487a49ab Rename the internal `Colorizer` class as `ColorModelBuilder` for avoiding confusion with the new `Colorizer` interface.
     add 7090cb3ada Complete the migration to `Colorizer` in the `Visualization` class. Deprecate the `Map<NumberRange,Color[]>` argument in `ImageProcessor`. This is replaced by `Colorizer.forRanges(Map)`.
     add f691d87e35 Store sample dimensions in a `RenderedImage` property. Use that property instead of argument value in `ImageProcessor`.
     add 9aa776c7af Last adjustements on the `Colorizer` work and addition of a convenience `GridcoverageProcessor.visualize(GridCoverage, …)` method.
     add 4e347cda9d Tune the `Colorizer` contract for saying that a null `Color[]` array means to use default colors, which are not necessarily transparent. If the range has more than one value, that default is now grayscale.
     add d0147d6a96 When no color is specified for a category or a range of sample values, and provided that `Colorizer` is used for styling an existing image, preserve the existing colors.
     add 1b6df63689 `Colorizer.forCategories(Map)` should not keep a reference to the user-supplied map.
     add 5aebcde1a0 Allow `DimensionalityReduction` to be subclassed.
     add 03a6a48e26 `BandAggregateImage` should share references to data arrays when possible. It avoids copying the sample values.
     add da7281a6b0 Bug fix: `ColorModelBuilder` sometime created a "compact" color model when it was not desired.
     add 297e7a67fe Refactor `WritableRenderedImage`support in `BandedSampleConverter` for sharing more code with other writable images. Refactor `BandAggregateImage` by moving its inner helper class outside, and add `WritableRenderedImage`support. `BandAggregateImage` is no longer an "all or nothing" implementation: can have a mix of shared and copied arrays.
     add 65c2a49846 Make `getTileWidth()` and `getTileHeight()` methods final in `ComputedImage`. Add design notes in Javadoc for explaining some rational.
     add d3164ba70d Add a `MultiSourceImage` package-private abstract class and add support for prefetch operation.
     add 6c85b283c4 Result of "band select" operation should be writable if the image is writable.
     add ed74a09dc9 When doing an aggregation of "band select", verify if the operations cancel each other.
     add 5f36de44f4 "Band select" on a band aggregation should be able to return the original component. Aggregation of aggregations should use a flattened list or source images.
     add e172934869 Spelling fixes in documentation.
     add e9e0b2342b Improvement: `BandAggregateImage` now merges the bands of repeated sources no matter their position in the array of sources. Before this commit, the bands of repeated sources where merged only for consecutive sources (e.g. at index `i` and `i+1`). The merging of repeated sources is necessary for `BandAggregateGridResource` implementation, which relies on that. While the merging of consecutive sources was sufficient in most cases, it was a risk of causing confusing beha [...]
     add f83bef1818 Chains of operations on images need `BufferedImage` to notify when data are changed.
     add 38626a67da Simplify the way to determine if a "band select" is an identity operation.
     add 58df212721 Complete API with a few convenience methods: - `CoverageAggregator.add(GridCoverage)` method in addition to existing methods working on resources. - `GridCoverageProcessor.selectSampleDimensions(…)` in complement to `selectGridDimensions(…)`.
     add 9f8a402015 Relax the restriction that all inputs to `BandAggregateGridCoverage` have the same grid geometry. With this commit, they are allowed to have different translations but not yet more complex changes.
     add c0e6706251 More tests for `BandAggregateGridCoverage`.
     add 9c391c16b7 Remove `BandAggregateGridResource` from public API. Instead, a new method is added in `CoverageAggregator`.
     add 3a1544293a Allow `BandAggregateGridCoverage` and `BandAggregateGridResource` to unwrap the sources. It makes possible to detect when two consecutive sources are fundamentally the same source.
     add 0e2089b190 If the resources to aggregate are instances of `MemoryGridResource`, aggregate directly the underlying `GridCoverage` instances.
     add 3906ad83eb The check for `MemoryGridResource` in `BandAggregateGridResource.create(…)` should not be an "all or nothing" operation. With this commit, shortcut is used even if only some of the resources to aggregate are `MemoryGridResource` instances.
     add 175b9f0ced Fix some javadoc errors.
     add 33688738c6 Merge branch 'geoapi-4.0' into geoapi-3.1. The main work since previous merge is the integration of the "feat/resource-processor" branch. It brings "band aggregation" operations on images, grid coverages and grid resources.
     new 0865c08dfb Merge branch 'geoapi-3.1', omitting `CopyVisitor` internal class.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../apache/sis/gui/coverage/CoverageCanvas.java    |  59 +-
 .../apache/sis/gui/coverage/CoverageControls.java  |   5 +-
 .../apache/sis/gui/coverage/CoverageStyling.java   | 120 ++-
 .../org/apache/sis/gui/map/ValuesFormatter.java    |   7 +-
 .../apache/sis/internal/gui/ImageConverter.java    |  13 +-
 .../apache/sis/internal/gui/control/ColorCell.java |  33 +-
 .../internal/gui/control/ColorColumnHandler.java   |  31 +-
 .../apache/sis/internal/gui/control/ColorRamp.java |  51 +-
 .../sis/internal/gui/control/ValueColorMapper.java |  12 -
 .../sis/internal/gui/control/package-info.java     |   2 +-
 .../sis/gui/coverage/CoverageStylingApp.java       |   5 +-
 .../apache/sis/cloud/aws/s3/CachedByteChannel.java |   2 +-
 .../org/apache/sis/coverage/BandedCoverage.java    |   9 +-
 .../java/org/apache/sis/coverage/Category.java     |   4 +-
 .../org/apache/sis/coverage/SampleDimension.java   | 109 ++-
 .../coverage/grid/BandAggregateGridCoverage.java   | 323 +++++++
 .../sis/coverage/grid/BufferedGridCoverage.java    |  22 +-
 .../sis/coverage/grid/ConvertedGridCoverage.java   |   2 +-
 .../coverage/grid/CoordinateOperationFinder.java   |   5 +-
 .../apache/sis/coverage/grid/DefaultEvaluator.java |  12 +-
 .../sis/coverage/grid/DerivedGridCoverage.java     |  27 +-
 .../apache/sis/coverage/grid/DimensionReducer.java |   5 +-
 .../sis/coverage/grid/DimensionalityReduction.java | 988 +++++++++++++++++++++
 .../sis/coverage/grid/DisjointExtentException.java |   9 +-
 .../coverage/grid/FractionalGridCoordinates.java   |   6 +-
 .../sis/coverage/grid/GridCoordinatesView.java     |   4 +-
 .../org/apache/sis/coverage/grid/GridCoverage.java |  84 +-
 .../apache/sis/coverage/grid/GridCoverage2D.java   |   2 +
 .../sis/coverage/grid/GridCoverageBuilder.java     |  26 +-
 .../sis/coverage/grid/GridCoverageProcessor.java   | 451 +++++++++-
 .../org/apache/sis/coverage/grid/GridExtent.java   | 261 +++---
 .../org/apache/sis/coverage/grid/GridGeometry.java |  10 +-
 .../apache/sis/coverage/grid/GridOrientation.java  |   2 +-
 .../apache/sis/coverage/grid/ImageRenderer.java    | 103 ++-
 .../sis/coverage/grid/ReducedGridCoverage.java     | 160 ++++
 .../sis/coverage/grid/ResampledGridCoverage.java   |   3 +-
 .../apache/sis/coverage/grid/SliceGeometry.java    |  25 +-
 .../java/org/apache/sis/filter/LogicalFilter.java  |   5 +-
 .../java/org/apache/sis/image/AnnotatedImage.java  |  12 +-
 .../org/apache/sis/image/BandAggregateImage.java   | 338 +++++++
 .../java/org/apache/sis/image/BandSelectImage.java | 152 +++-
 .../org/apache/sis/image/BandSharedRaster.java     | 181 ++++
 .../java/org/apache/sis/image/BandSharing.java     | 377 ++++++++
 .../apache/sis/image/BandedSampleConverter.java    | 180 ++--
 .../main/java/org/apache/sis/image/Colorizer.java  | 348 ++++++++
 .../java/org/apache/sis/image/ComputedImage.java   |  88 +-
 .../java/org/apache/sis/image/ImageAdapter.java    |   6 +-
 .../java/org/apache/sis/image/ImageProcessor.java  | 408 ++++++++-
 .../java/org/apache/sis/image/Interpolation.java   |   6 +-
 .../main/java/org/apache/sis/image/MaskImage.java  |   2 +-
 .../org/apache/sis/image/MultiSourceImage.java     | 148 +++
 .../org/apache/sis/image/MultiSourceLayout.java    | 414 +++++++++
 .../org/apache/sis/image/MultiSourcePrefetch.java  | 178 ++++
 .../java/org/apache/sis/image/PlanarImage.java     |  95 +-
 .../java/org/apache/sis/image/RecoloredImage.java  |  47 +-
 .../java/org/apache/sis/image/ResampledImage.java  |  10 +-
 .../org/apache/sis/image/SourceAlignedImage.java   |   8 +-
 .../main/java/org/apache/sis/image/Transferer.java |  55 +-
 .../java/org/apache/sis/image/UserProperties.java  | 124 +++
 .../java/org/apache/sis/image/Visualization.java   | 244 +++--
 .../apache/sis/image/WritableComputedImage.java    | 177 ++++
 .../java/org/apache/sis/index/tree/PointTree.java  |   2 +-
 .../sis/internal/coverage/CommonDomainFinder.java  | 385 ++++++++
 .../sis/internal/coverage/MultiSourceArgument.java | 623 +++++++++++++
 .../sis/internal/coverage}/RangeArgument.java      |  46 +-
 .../sis/internal/coverage/SampleDimensions.java    |  56 +-
 .../j2d/{Colorizer.java => ColorModelBuilder.java} | 256 ++++--
 .../internal/coverage/j2d/ColorModelFactory.java   | 336 ++++---
 .../sis/internal/coverage/j2d/ColorModelType.java  |   2 +-
 .../sis/internal/coverage/j2d/ColorsForRange.java  | 166 +++-
 .../sis/internal/coverage/j2d/ImageLayout.java     |  34 +-
 .../sis/internal/coverage/j2d/ImageUtilities.java  |  78 +-
 .../coverage/j2d/MultiBandsIndexColorModel.java    |  23 +-
 .../sis/internal/coverage/j2d/ObservableImage.java | 289 ++++++
 .../sis/internal/coverage/j2d/RasterFactory.java   |  34 +-
 .../internal/coverage/j2d/SampleModelFactory.java  |   2 +-
 .../internal/coverage/j2d/ScaledColorModel.java    |  15 +-
 .../internal/coverage/j2d/ScaledColorSpace.java    |  31 +-
 .../internal/coverage/j2d/WritableTiledImage.java  |   8 +-
 .../sis/internal/coverage/j2d/WriteSupport.java    | 100 ---
 .../apache/sis/internal/coverage/package-info.java |   2 +-
 .../org/apache/sis/internal/feature/Resources.java |  40 +
 .../sis/internal/feature/Resources.properties      |   8 +
 .../sis/internal/feature/Resources_fr.properties   |   8 +
 .../apache/sis/internal/filter/package-info.java   |   2 +-
 .../sis/internal/filter/sqlmm/SpatialFunction.java |  23 +-
 .../grid/BandAggregateGridCoverageTest.java        | 183 ++++
 .../coverage/grid/ConvertedGridCoverageTest.java   |  24 +-
 .../coverage/grid/DimensionalityReductionTest.java | 194 ++++
 .../apache/sis/coverage/grid/GridGeometryTest.java |  40 +-
 .../apache/sis/image/BandAggregateImageTest.java   | 536 +++++++++++
 .../org/apache/sis/image/BandSelectImageTest.java  |  79 +-
 .../org/apache/sis/image/ImageProcessorTest.java   |  62 +-
 .../apache/sis/image/StatisticsCalculatorTest.java |   2 +-
 .../java/org/apache/sis/image/TiledImageMock.java  |  66 +-
 .../sis/internal/coverage}/RangeArgumentTest.java  |  18 +-
 ...lorizerTest.java => ColorModelBuilderTest.java} |  20 +-
 .../apache/sis/test/suite/FeatureTestSuite.java    |   8 +-
 .../apache/sis/internal/metadata/sql/Dialect.java  |  31 +-
 .../org/apache/sis/util/iso/DefaultScopedName.java |   2 +-
 .../java/org/apache/sis/test/sql/TestDatabase.java |  24 +-
 .../sis/internal/map/coverage/RenderingData.java   |  26 +-
 .../java/org/apache/sis/geometry/Envelopes.java    |   2 +-
 .../referencing/provider/DatumShiftGridLoader.java |   4 +-
 .../apache/sis/parameter/ParameterValueList.java   |   2 +-
 .../main/java/org/apache/sis/referencing/CRS.java  |  40 +-
 .../referencing/factory/sql/AuthorityCodes.java    |   2 +-
 .../operation/DefaultPassThroughOperation.java     | 141 +--
 .../operation/builder/LinearTransformBuilder.java  |   2 +-
 .../referencing/operation/projection/Mercator.java |   4 +-
 .../operation/transform/MathTransforms.java        |  37 +
 .../operation/transform/PassThroughTransform.java  | 127 ++-
 .../operation/transform/TransformSeparator.java    |  55 +-
 .../transform/PassThroughTransformTest.java        |  65 +-
 .../apache/sis/internal/system/Configuration.java  |   2 +-
 .../org/apache/sis/internal/util/Numerics.java     |  13 +
 .../java/org/apache/sis/measure/NumberRange.java   |   7 +-
 .../main/java/org/apache/sis/measure/Range.java    |  23 +-
 .../java/org/apache/sis/measure/SystemUnit.java    |   2 +-
 .../java/org/apache/sis/util/ArgumentChecks.java   | 112 ++-
 .../main/java/org/apache/sis/util/ArraysExt.java   | 140 +--
 .../src/main/java/org/apache/sis/util/Version.java |   2 +-
 .../sis/util/collection/WeakValueHashMap.java      |  89 +-
 .../java/org/apache/sis/util/package-info.java     |   2 +-
 .../org/apache/sis/util/resources/Vocabulary.java  |   5 +
 .../sis/util/resources/Vocabulary.properties       |   1 +
 .../sis/util/resources/Vocabulary_fr.properties    |   1 +
 .../java/org/apache/sis/measure/RangeTest.java     |  17 +-
 .../org/apache/sis/measure/UnitFormatTest.java     |   2 +-
 .../java/org/apache/sis/measure/UnitsTest.java     |   2 +-
 .../org/apache/sis/util/ArgumentChecksTest.java    |   6 +-
 .../java/org/apache/sis/util/ArraysExtTest.java    |  32 +-
 .../apache/sis/storage/geotiff/GeoCodesTest.java   |   1 -
 .../org/apache/sis/internal/netcdf/Convention.java |   3 +-
 .../org/apache/sis/internal/netcdf/Raster.java     |  11 +-
 .../apache/sis/internal/netcdf/RasterResource.java |   6 +-
 .../apache/sis/internal/sql/feature/Database.java  |  31 +-
 .../sis/internal/sql/feature/ValueGetter.java      | 139 ++-
 .../sis/internal/sql/feature/package-info.java     |   2 +-
 .../apache/sis/internal/sql/postgis/Postgres.java  |   9 +-
 .../sis/internal/sql/postgis/RasterReader.java     |   3 +-
 .../sis/internal/sql/postgis/package-info.java     |   2 +-
 .../sql/feature/TemporalValueGetterTest.java       | 238 +++++
 .../org/apache/sis/storage/sql/SQLStoreTest.java   | 104 +--
 .../apache/sis/storage/sql/TestOnAllDatabases.java |  99 +++
 .../org/apache/sis/test/suite/SQLTestSuite.java    |   1 +
 .../sis/internal/storage/GridResourceWrapper.java  |   4 +
 .../sis/internal/storage/MemoryFeatureSet.java     |  12 +-
 .../sis/internal/storage/MemoryGridResource.java   |  44 +-
 .../org/apache/sis/internal/storage/Resources.java |  14 +-
 .../sis/internal/storage/Resources.properties      |   2 -
 .../sis/internal/storage/Resources_fr.properties   |   2 -
 .../sis/internal/storage/TiledGridCoverage.java    |   2 +-
 .../sis/internal/storage/TiledGridResource.java    |   4 +-
 .../sis/internal/storage/esri/AsciiGridStore.java  |   2 +-
 .../sis/internal/storage/esri/RasterStore.java     |  18 +-
 .../sis/internal/storage/esri/RawRasterReader.java |   6 +-
 .../sis/internal/storage/esri/RawRasterStore.java  |   2 +-
 .../apache/sis/internal/storage/folder/Store.java  |   2 +-
 .../internal/storage/image/WorldFileResource.java  |   2 +-
 .../sis/internal/storage/io/IOUtilities.java       |   6 +-
 .../org/apache/sis/storage/AbstractFeatureSet.java |  14 +-
 .../sis/storage/AbstractGridCoverageResource.java  |  14 +-
 .../org/apache/sis/storage/AbstractResource.java   |  20 +-
 .../java/org/apache/sis/storage/CoverageQuery.java | 123 ++-
 .../org/apache/sis/storage/CoverageSubset.java     | 186 ++--
 .../java/org/apache/sis/storage/FeatureSubset.java |   4 +-
 .../apache/sis/storage/GridCoverageResource.java   |   6 +-
 .../org/apache/sis/storage/StorageConnector.java   |  44 +-
 .../storage/aggregate/AggregatedFeatureSet.java    |  13 +-
 .../sis/storage/aggregate/AggregatedResource.java  |  22 +-
 .../aggregate/BandAggregateGridResource.java       | 450 ++++++++++
 .../storage/aggregate/ConcatenatedFeatureSet.java  |   8 +-
 .../aggregate/ConcatenatedGridCoverage.java        |   2 +-
 .../aggregate/ConcatenatedGridResource.java        |  99 ++-
 .../sis/storage/aggregate/CoverageAggregator.java  | 252 +++++-
 .../apache/sis/storage/aggregate/GridSlice.java    |  79 +-
 .../sis/storage/aggregate/GridSliceLocator.java    |   3 +-
 .../org/apache/sis/storage/aggregate/Group.java    |   4 +-
 .../sis/storage/aggregate/GroupAggregate.java      |  33 +-
 .../apache/sis/storage/aggregate/GroupByCRS.java   |   4 +-
 .../sis/storage/aggregate/GroupBySample.java       |   8 +-
 .../sis/storage/aggregate/GroupByTransform.java    |   8 +-
 .../sis/storage/aggregate/JoinFeatureSet.java      |  10 +-
 .../sis/storage/aggregate/MergeStrategy.java       |   7 +-
 .../internal/storage/MemoryGridResourceTest.java   |   2 -
 .../org/apache/sis/storage/CoverageSubsetTest.java | 130 +++
 .../aggregate/BandAggregateGridResourceTest.java   | 228 +++++
 .../storage/aggregate/CoverageAggregatorTest.java  |   6 +-
 ...AggregatorTest.java => OpaqueGridResource.java} |  35 +-
 .../apache/sis/test/suite/StorageTestSuite.java    |   3 +-
 191 files changed, 11834 insertions(+), 2172 deletions(-)
 create mode 100644 core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BandAggregateGridCoverage.java
 create mode 100644 core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DimensionalityReduction.java
 create mode 100644 core/sis-feature/src/main/java/org/apache/sis/coverage/grid/ReducedGridCoverage.java
 create mode 100644 core/sis-feature/src/main/java/org/apache/sis/image/BandAggregateImage.java
 create mode 100644 core/sis-feature/src/main/java/org/apache/sis/image/BandSharedRaster.java
 create mode 100644 core/sis-feature/src/main/java/org/apache/sis/image/BandSharing.java
 create mode 100644 core/sis-feature/src/main/java/org/apache/sis/image/Colorizer.java
 create mode 100644 core/sis-feature/src/main/java/org/apache/sis/image/MultiSourceImage.java
 create mode 100644 core/sis-feature/src/main/java/org/apache/sis/image/MultiSourceLayout.java
 create mode 100644 core/sis-feature/src/main/java/org/apache/sis/image/MultiSourcePrefetch.java
 create mode 100644 core/sis-feature/src/main/java/org/apache/sis/image/UserProperties.java
 create mode 100644 core/sis-feature/src/main/java/org/apache/sis/image/WritableComputedImage.java
 create mode 100644 core/sis-feature/src/main/java/org/apache/sis/internal/coverage/CommonDomainFinder.java
 create mode 100644 core/sis-feature/src/main/java/org/apache/sis/internal/coverage/MultiSourceArgument.java
 rename {storage/sis-storage/src/main/java/org/apache/sis/internal/storage => core/sis-feature/src/main/java/org/apache/sis/internal/coverage}/RangeArgument.java (91%)
 rename core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/{Colorizer.java => ColorModelBuilder.java} (73%)
 create mode 100644 core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/ObservableImage.java
 delete mode 100644 core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/WriteSupport.java
 create mode 100644 core/sis-feature/src/test/java/org/apache/sis/coverage/grid/BandAggregateGridCoverageTest.java
 create mode 100644 core/sis-feature/src/test/java/org/apache/sis/coverage/grid/DimensionalityReductionTest.java
 create mode 100644 core/sis-feature/src/test/java/org/apache/sis/image/BandAggregateImageTest.java
 rename {storage/sis-storage/src/test/java/org/apache/sis/internal/storage => core/sis-feature/src/test/java/org/apache/sis/internal/coverage}/RangeArgumentTest.java (83%)
 rename core/sis-feature/src/test/java/org/apache/sis/internal/coverage/j2d/{ColorizerTest.java => ColorModelBuilderTest.java} (88%)
 create mode 100644 storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/feature/TemporalValueGetterTest.java
 create mode 100644 storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/TestOnAllDatabases.java
 create mode 100644 storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/BandAggregateGridResource.java
 create mode 100644 storage/sis-storage/src/test/java/org/apache/sis/storage/CoverageSubsetTest.java
 create mode 100644 storage/sis-storage/src/test/java/org/apache/sis/storage/aggregate/BandAggregateGridResourceTest.java
 copy storage/sis-storage/src/test/java/org/apache/sis/storage/aggregate/{CoverageAggregatorTest.java => OpaqueGridResource.java} (54%)


[sis] 01/01: Merge branch 'geoapi-3.1', omitting `CopyVisitor` internal class.

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 0865c08dfb62c88d7d7b40a3a6c8144f6b981b7f
Merge: 7d5dfd074e 33688738c6
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sun Apr 16 19:42:02 2023 +0200

    Merge branch 'geoapi-3.1', omitting `CopyVisitor` internal class.

 .../apache/sis/gui/coverage/CoverageCanvas.java    |  59 +-
 .../apache/sis/gui/coverage/CoverageControls.java  |   5 +-
 .../apache/sis/gui/coverage/CoverageStyling.java   | 120 ++-
 .../org/apache/sis/gui/map/ValuesFormatter.java    |   7 +-
 .../apache/sis/internal/gui/ImageConverter.java    |  13 +-
 .../apache/sis/internal/gui/control/ColorCell.java |  33 +-
 .../internal/gui/control/ColorColumnHandler.java   |  31 +-
 .../apache/sis/internal/gui/control/ColorRamp.java |  51 +-
 .../sis/internal/gui/control/ValueColorMapper.java |  12 -
 .../sis/internal/gui/control/package-info.java     |   2 +-
 .../sis/gui/coverage/CoverageStylingApp.java       |   5 +-
 .../apache/sis/cloud/aws/s3/CachedByteChannel.java |   2 +-
 .../org/apache/sis/coverage/BandedCoverage.java    |   9 +-
 .../java/org/apache/sis/coverage/Category.java     |   4 +-
 .../org/apache/sis/coverage/SampleDimension.java   | 109 ++-
 .../coverage/grid/BandAggregateGridCoverage.java   | 323 +++++++
 .../sis/coverage/grid/BufferedGridCoverage.java    |  22 +-
 .../sis/coverage/grid/ConvertedGridCoverage.java   |   2 +-
 .../coverage/grid/CoordinateOperationFinder.java   |   5 +-
 .../apache/sis/coverage/grid/DefaultEvaluator.java |  12 +-
 .../sis/coverage/grid/DerivedGridCoverage.java     |  27 +-
 .../apache/sis/coverage/grid/DimensionReducer.java |   5 +-
 .../sis/coverage/grid/DimensionalityReduction.java | 988 +++++++++++++++++++++
 .../sis/coverage/grid/DisjointExtentException.java |   9 +-
 .../coverage/grid/FractionalGridCoordinates.java   |   6 +-
 .../sis/coverage/grid/GridCoordinatesView.java     |   4 +-
 .../org/apache/sis/coverage/grid/GridCoverage.java |  84 +-
 .../apache/sis/coverage/grid/GridCoverage2D.java   |   2 +
 .../sis/coverage/grid/GridCoverageBuilder.java     |  26 +-
 .../sis/coverage/grid/GridCoverageProcessor.java   | 451 +++++++++-
 .../org/apache/sis/coverage/grid/GridExtent.java   | 261 +++---
 .../org/apache/sis/coverage/grid/GridGeometry.java |  10 +-
 .../apache/sis/coverage/grid/GridOrientation.java  |   2 +-
 .../apache/sis/coverage/grid/ImageRenderer.java    | 103 ++-
 .../sis/coverage/grid/ReducedGridCoverage.java     | 160 ++++
 .../sis/coverage/grid/ResampledGridCoverage.java   |   3 +-
 .../apache/sis/coverage/grid/SliceGeometry.java    |  25 +-
 .../java/org/apache/sis/filter/LogicalFilter.java  |   5 +-
 .../java/org/apache/sis/image/AnnotatedImage.java  |  12 +-
 .../org/apache/sis/image/BandAggregateImage.java   | 338 +++++++
 .../java/org/apache/sis/image/BandSelectImage.java | 152 +++-
 .../org/apache/sis/image/BandSharedRaster.java     | 181 ++++
 .../java/org/apache/sis/image/BandSharing.java     | 377 ++++++++
 .../apache/sis/image/BandedSampleConverter.java    | 180 ++--
 .../main/java/org/apache/sis/image/Colorizer.java  | 348 ++++++++
 .../java/org/apache/sis/image/ComputedImage.java   |  88 +-
 .../java/org/apache/sis/image/ImageAdapter.java    |   6 +-
 .../java/org/apache/sis/image/ImageProcessor.java  | 408 ++++++++-
 .../java/org/apache/sis/image/Interpolation.java   |   6 +-
 .../main/java/org/apache/sis/image/MaskImage.java  |   2 +-
 .../org/apache/sis/image/MultiSourceImage.java     | 148 +++
 .../org/apache/sis/image/MultiSourceLayout.java    | 414 +++++++++
 .../org/apache/sis/image/MultiSourcePrefetch.java  | 178 ++++
 .../java/org/apache/sis/image/PlanarImage.java     |  95 +-
 .../java/org/apache/sis/image/RecoloredImage.java  |  47 +-
 .../java/org/apache/sis/image/ResampledImage.java  |  10 +-
 .../org/apache/sis/image/SourceAlignedImage.java   |   8 +-
 .../main/java/org/apache/sis/image/Transferer.java |  55 +-
 .../java/org/apache/sis/image/UserProperties.java  | 124 +++
 .../java/org/apache/sis/image/Visualization.java   | 244 +++--
 .../apache/sis/image/WritableComputedImage.java    | 177 ++++
 .../java/org/apache/sis/index/tree/PointTree.java  |   2 +-
 .../sis/internal/coverage/CommonDomainFinder.java  | 385 ++++++++
 .../sis/internal/coverage/MultiSourceArgument.java | 623 +++++++++++++
 .../sis/internal/coverage}/RangeArgument.java      |  46 +-
 .../sis/internal/coverage/SampleDimensions.java    |  56 +-
 .../j2d/{Colorizer.java => ColorModelBuilder.java} | 256 ++++--
 .../internal/coverage/j2d/ColorModelFactory.java   | 336 ++++---
 .../sis/internal/coverage/j2d/ColorModelType.java  |   2 +-
 .../sis/internal/coverage/j2d/ColorsForRange.java  | 166 +++-
 .../sis/internal/coverage/j2d/ImageLayout.java     |  34 +-
 .../sis/internal/coverage/j2d/ImageUtilities.java  |  78 +-
 .../coverage/j2d/MultiBandsIndexColorModel.java    |  23 +-
 .../sis/internal/coverage/j2d/ObservableImage.java | 289 ++++++
 .../sis/internal/coverage/j2d/RasterFactory.java   |  34 +-
 .../internal/coverage/j2d/SampleModelFactory.java  |   2 +-
 .../internal/coverage/j2d/ScaledColorModel.java    |  15 +-
 .../internal/coverage/j2d/ScaledColorSpace.java    |  31 +-
 .../internal/coverage/j2d/WritableTiledImage.java  |   8 +-
 .../sis/internal/coverage/j2d/WriteSupport.java    | 100 ---
 .../apache/sis/internal/coverage/package-info.java |   2 +-
 .../org/apache/sis/internal/feature/Resources.java |  40 +
 .../sis/internal/feature/Resources.properties      |   8 +
 .../sis/internal/feature/Resources_fr.properties   |   8 +
 .../apache/sis/internal/filter/package-info.java   |   2 +-
 .../sis/internal/filter/sqlmm/SpatialFunction.java |  23 +-
 .../grid/BandAggregateGridCoverageTest.java        | 183 ++++
 .../coverage/grid/ConvertedGridCoverageTest.java   |  24 +-
 .../coverage/grid/DimensionalityReductionTest.java | 194 ++++
 .../apache/sis/coverage/grid/GridGeometryTest.java |  40 +-
 .../apache/sis/image/BandAggregateImageTest.java   | 536 +++++++++++
 .../org/apache/sis/image/BandSelectImageTest.java  |  79 +-
 .../org/apache/sis/image/ImageProcessorTest.java   |  62 +-
 .../apache/sis/image/StatisticsCalculatorTest.java |   2 +-
 .../java/org/apache/sis/image/TiledImageMock.java  |  66 +-
 .../sis/internal/coverage}/RangeArgumentTest.java  |  18 +-
 ...lorizerTest.java => ColorModelBuilderTest.java} |  20 +-
 .../apache/sis/test/suite/FeatureTestSuite.java    |   8 +-
 .../apache/sis/internal/metadata/sql/Dialect.java  |  31 +-
 .../org/apache/sis/util/iso/DefaultScopedName.java |   2 +-
 .../java/org/apache/sis/test/sql/TestDatabase.java |  24 +-
 .../sis/internal/map/coverage/RenderingData.java   |  26 +-
 .../java/org/apache/sis/geometry/Envelopes.java    |   2 +-
 .../referencing/provider/DatumShiftGridLoader.java |   4 +-
 .../apache/sis/parameter/ParameterValueList.java   |   2 +-
 .../main/java/org/apache/sis/referencing/CRS.java  |  40 +-
 .../referencing/factory/sql/AuthorityCodes.java    |   2 +-
 .../operation/DefaultPassThroughOperation.java     | 141 +--
 .../operation/builder/LinearTransformBuilder.java  |   2 +-
 .../referencing/operation/projection/Mercator.java |   4 +-
 .../operation/transform/MathTransforms.java        |  37 +
 .../operation/transform/PassThroughTransform.java  | 127 ++-
 .../operation/transform/TransformSeparator.java    |  55 +-
 .../transform/PassThroughTransformTest.java        |  65 +-
 .../apache/sis/internal/system/Configuration.java  |   2 +-
 .../org/apache/sis/internal/util/Numerics.java     |  13 +
 .../java/org/apache/sis/measure/NumberRange.java   |   7 +-
 .../main/java/org/apache/sis/measure/Range.java    |  23 +-
 .../java/org/apache/sis/measure/SystemUnit.java    |   2 +-
 .../java/org/apache/sis/util/ArgumentChecks.java   | 112 ++-
 .../main/java/org/apache/sis/util/ArraysExt.java   | 140 +--
 .../src/main/java/org/apache/sis/util/Version.java |   2 +-
 .../sis/util/collection/WeakValueHashMap.java      |  89 +-
 .../java/org/apache/sis/util/package-info.java     |   2 +-
 .../org/apache/sis/util/resources/Vocabulary.java  |   5 +
 .../sis/util/resources/Vocabulary.properties       |   1 +
 .../sis/util/resources/Vocabulary_fr.properties    |   1 +
 .../java/org/apache/sis/measure/RangeTest.java     |  17 +-
 .../org/apache/sis/measure/UnitFormatTest.java     |   2 +-
 .../java/org/apache/sis/measure/UnitsTest.java     |   2 +-
 .../org/apache/sis/util/ArgumentChecksTest.java    |   6 +-
 .../java/org/apache/sis/util/ArraysExtTest.java    |  32 +-
 .../apache/sis/storage/geotiff/GeoCodesTest.java   |   1 -
 .../org/apache/sis/internal/netcdf/Convention.java |   3 +-
 .../org/apache/sis/internal/netcdf/Raster.java     |  11 +-
 .../apache/sis/internal/netcdf/RasterResource.java |   6 +-
 .../apache/sis/internal/sql/feature/Database.java  |  31 +-
 .../sis/internal/sql/feature/ValueGetter.java      | 139 ++-
 .../sis/internal/sql/feature/package-info.java     |   2 +-
 .../apache/sis/internal/sql/postgis/Postgres.java  |   9 +-
 .../sis/internal/sql/postgis/RasterReader.java     |   3 +-
 .../sis/internal/sql/postgis/package-info.java     |   2 +-
 .../sql/feature/TemporalValueGetterTest.java       | 238 +++++
 .../org/apache/sis/storage/sql/SQLStoreTest.java   | 104 +--
 .../apache/sis/storage/sql/TestOnAllDatabases.java |  99 +++
 .../org/apache/sis/test/suite/SQLTestSuite.java    |   1 +
 .../sis/internal/storage/GridResourceWrapper.java  |   4 +
 .../sis/internal/storage/MemoryFeatureSet.java     |  12 +-
 .../sis/internal/storage/MemoryGridResource.java   |  44 +-
 .../org/apache/sis/internal/storage/Resources.java |  14 +-
 .../sis/internal/storage/Resources.properties      |   2 -
 .../sis/internal/storage/Resources_fr.properties   |   2 -
 .../sis/internal/storage/TiledGridCoverage.java    |   2 +-
 .../sis/internal/storage/TiledGridResource.java    |   4 +-
 .../sis/internal/storage/esri/AsciiGridStore.java  |   2 +-
 .../sis/internal/storage/esri/RasterStore.java     |  18 +-
 .../sis/internal/storage/esri/RawRasterReader.java |   6 +-
 .../sis/internal/storage/esri/RawRasterStore.java  |   2 +-
 .../apache/sis/internal/storage/folder/Store.java  |   2 +-
 .../internal/storage/image/WorldFileResource.java  |   2 +-
 .../sis/internal/storage/io/IOUtilities.java       |   6 +-
 .../org/apache/sis/storage/AbstractFeatureSet.java |  14 +-
 .../sis/storage/AbstractGridCoverageResource.java  |  14 +-
 .../org/apache/sis/storage/AbstractResource.java   |  20 +-
 .../java/org/apache/sis/storage/CoverageQuery.java | 123 ++-
 .../org/apache/sis/storage/CoverageSubset.java     | 186 ++--
 .../java/org/apache/sis/storage/FeatureSubset.java |   4 +-
 .../apache/sis/storage/GridCoverageResource.java   |   6 +-
 .../org/apache/sis/storage/StorageConnector.java   |  44 +-
 .../storage/aggregate/AggregatedFeatureSet.java    |  13 +-
 .../sis/storage/aggregate/AggregatedResource.java  |  22 +-
 .../aggregate/BandAggregateGridResource.java       | 450 ++++++++++
 .../storage/aggregate/ConcatenatedFeatureSet.java  |   8 +-
 .../aggregate/ConcatenatedGridCoverage.java        |   2 +-
 .../aggregate/ConcatenatedGridResource.java        |  99 ++-
 .../sis/storage/aggregate/CoverageAggregator.java  | 252 +++++-
 .../apache/sis/storage/aggregate/GridSlice.java    |  79 +-
 .../sis/storage/aggregate/GridSliceLocator.java    |   3 +-
 .../org/apache/sis/storage/aggregate/Group.java    |   4 +-
 .../sis/storage/aggregate/GroupAggregate.java      |  33 +-
 .../apache/sis/storage/aggregate/GroupByCRS.java   |   4 +-
 .../sis/storage/aggregate/GroupBySample.java       |   8 +-
 .../sis/storage/aggregate/GroupByTransform.java    |   8 +-
 .../sis/storage/aggregate/JoinFeatureSet.java      |  10 +-
 .../sis/storage/aggregate/MergeStrategy.java       |   7 +-
 .../internal/storage/MemoryGridResourceTest.java   |   2 -
 .../org/apache/sis/storage/CoverageSubsetTest.java | 130 +++
 .../aggregate/BandAggregateGridResourceTest.java   | 228 +++++
 .../storage/aggregate/CoverageAggregatorTest.java  |   6 +-
 ...AggregatorTest.java => OpaqueGridResource.java} |  35 +-
 .../apache/sis/test/suite/StorageTestSuite.java    |   3 +-
 191 files changed, 11834 insertions(+), 2172 deletions(-)

diff --cc core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java
index bb68262ecf,f0a24e8c9e..f563a8e813
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java
@@@ -62,10 -62,9 +62,9 @@@ import org.apache.sis.util.Debug
   *   <tr><td>[10…210]</td>     <td>Temperature to be converted into Celsius degrees through a linear equation</td></tr>
   * </table>
   * In this example, sample values in range [10…210] define a quantitative category, while all others categories are qualitative.
-  * </div>
   *
   * <h2>Relationship with metadata</h2>
 - * This class provides the same information than ISO 19115 {@link org.opengis.metadata.content.SampleDimension},
 + * This class provides the same information than ISO 19115 {@code org.opengis.metadata.content.SampleDimension},
   * but organized in a different way. The use of the same name may seem a risk, but those two types are typically
   * not used at the same time.
   *
diff --cc core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DimensionalityReduction.java
index 0000000000,517edb4390..09b7dcbf0d
mode 000000,100644..100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DimensionalityReduction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/DimensionalityReduction.java
@@@ -1,0 -1,989 +1,988 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one or more
+  * contributor license agreements.  See the NOTICE file distributed with
+  * this work for additional information regarding copyright ownership.
+  * The ASF licenses this file to You under the Apache License, Version 2.0
+  * (the "License"); you may not use this file except in compliance with
+  * the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS,
+  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  * See the License for the specific language governing permissions and
+  * limitations under the License.
+  */
+ package org.apache.sis.coverage.grid;
+ 
+ import java.util.Map;
+ import java.util.HashMap;
+ import java.util.Arrays;
+ import java.util.BitSet;
+ import java.util.function.UnaryOperator;
+ import java.io.Serializable;
+ import org.opengis.util.FactoryException;
+ import org.opengis.geometry.DirectPosition;
+ import org.opengis.geometry.MismatchedDimensionException;
+ import org.opengis.referencing.crs.CoordinateReferenceSystem;
+ import org.opengis.referencing.operation.MathTransformFactory;
+ import org.opengis.referencing.operation.MathTransform;
+ import org.opengis.referencing.datum.PixelInCell;
+ import org.apache.sis.util.Utilities;
+ import org.apache.sis.util.ArraysExt;
+ import org.apache.sis.util.ArgumentChecks;
+ import org.apache.sis.util.ComparisonMode;
+ import org.apache.sis.internal.util.Numerics;
+ import org.apache.sis.internal.util.ArgumentCheckByAssertion;
+ import org.apache.sis.internal.feature.Resources;
+ import org.apache.sis.geometry.ImmutableEnvelope;
+ import org.apache.sis.coverage.SubspaceNotSpecifiedException;
+ import org.apache.sis.geometry.GeneralDirectPosition;
+ import org.apache.sis.referencing.operation.transform.MathTransforms;
+ import org.apache.sis.referencing.operation.transform.TransformSeparator;
+ import org.apache.sis.referencing.operation.transform.PassThroughTransform;
+ import org.apache.sis.referencing.CRS;
+ 
+ // Branch-dependent imports
 -import org.opengis.coverage.PointOutsideCoverageException;
++import org.apache.sis.coverage.PointOutsideCoverageException;
+ 
+ 
+ /**
+  * Description about how to reduce the number of dimensions of the domain of a grid coverage.
+  * This is a reduction in the number of dimensions of the grid extent, which usually implies
+  * a reduction in the number of dimensions of the CRS but not necessarily at the same indices
+  * (the relationship between grid dimensions and CRS dimensions is not necessarily straightforward).
+  * The sample dimensions (coverage range) are unmodified.
+  *
+  * <p>{@code DimensionalityReduction} specifies which dimensions to keep, and which grid
+  * values to use for the omitted dimensions. This information allows the conversion from
+  * a source {@link GridGeometry} to a reduced grid geometry, and conversely.</p>
+  *
+  * <p>Instances of {@code DimensionalityReduction} are immutable and thread-safe.</p>
+  *
+  * <h2>Assumptions</h2>
+  * The current implementation assumes that removing <var>n</var> dimensions in the grid extent
+  * causes the removal of exactly <var>n</var> dimensions in the Coordinate Reference System.
+  * However, the removed dimensions do not need to be at the same indices or in same order.
+  *
+  * @author  Alexis Manin (Geomatys)
+  * @author  Martin Desruisseaux (Geomatys)
+  * @version 1.4
+  * @since   1.4
+  */
+ public class DimensionalityReduction implements UnaryOperator<GridCoverage>, Serializable {
+     /**
+      * For cross-version compatibility.
+      */
+     private static final long serialVersionUID = -6462887684250336261L;
+ 
+     /**
+      * The source grid geometry with all dimensions.
+      *
+      * @see #getSourceGridGeometry()
+      */
+     private final GridGeometry sourceGeometry;
+ 
+     /**
+      * The reduced grid geometry.
+      * The number of dimensions shall be the number of bits set in the {@link #gridAxesToPass} bitmask.
+      *
+      * @see #getReducedGridGeometry()
+      */
+     private final GridGeometry reducedGeometry;
+ 
+     /**
+      * Indices of source grid dimensions to keep in the reduced grid.
+      * This is the parameter of the "pass-through" coordinate operation.
+      * Values must be in strictly increasing order.
+      *
+      * @see #getSelectedDimensions()
+      */
+     private final int[] gridAxesToPass;
+ 
+     /**
+      * Indices of target dimensions that have been removed.
+      * Values must be in strictly increasing order.
+      */
+     private final int[] crsAxesToRemove;
+ 
+     /**
+      * Partially filled array of CRS components to use for building a compound CRS.
+      * Elements in this array are either instances of {@link CoordinateReferenceSystem} or {@link Integer}.
+      * The CRS elements are components in the dimensions that were removed, while the integer elements are
+      * slots where to insert components of a reduced CRS with the number of dimensions given by the integer.
+      * This array is {@code null} if at least one CRS component cannot be isolated.
+      */
+     @SuppressWarnings("serial")                                 // Most SIS implementations are serializable.
+     private final Object[] componentsOfCRS;
+ 
+     /**
+      * The part of the "grid to CRS" transform which has been removed in the reduced grid geometry.
+      * The number of source and target dimensions are the same than in the source grid geometry.
+      * The dimensions identified by {@link #gridAxesToPass} are pass-through dimensions.
+      *
+      * @see #getRemovedGridToCRS(PixelInCell)
+      */
+     @SuppressWarnings("serial")                                 // Most SIS implementations are serializable.
+     private final MathTransform removedGridToCRS, removedCornerToCRS;
+ 
+     /**
+      * Grid coordinates to use in {@code reverse(…)} method calls for reconstituting some removed dimensions.
+      * Keys are grid dimensions of the source that are <em>not</em> in the {@link #gridAxesToPass} array.
+      * Values are grid coordinates to assign to the source grid extent at the dimension identified by the key.
+      * This map does not need to contain an entry for all removed dimensions.
+      *
+      * @see #getSliceCoordinates()
+      */
+     @SuppressWarnings("serial")                                 // Map.of(…) are serializable.
+     private final Map<Integer,Long> sliceCoordinates;
+ 
+     /**
+      * A cache of {@link #gridAxesToPass} for all combinations of axes to retain in the first four dimensions.
+      * We use this cache because the same sequences of dimension indices will be created most of the times.
+      */
+     private static final int[][] CACHED = new int[1 << 4][];    // Length must be a power of 2.
+ 
+     /**
+      * Returns the indices for which {@code axes} contains a bit in the set state.
+      * This method may return a cached instance, <strong>do not modify.</strong>
+      * Elements in the returned array are in strictly increasing order.
+      */
+     private static int[] toArray(final BitSet axes) {
+         if (axes.length() >= CACHED.length) {
+             return axes.stream().toArray();
+         }
+         final int bitmask = (int) axes.toLongArray()[0];
+         int[] indices;
+         synchronized (CACHED) {
+             indices = CACHED[bitmask];
+             if (indices == null) {
+                 CACHED[bitmask] = indices = axes.stream().toArray();
+             }
+         }
+         return indices;
+     }
+ 
+     /**
+      * Reduces the dimension of the specified grid geometry by retaining the axes specified in the given bitset.
+      * Axes in the reduced grid geometry will be in the same order than in the source geometry:
+      *
+      * @param  source    the grid geometry on which to select a subset of its grid dimensions.
+      * @param  gridAxes  bitmask of indices of source grid dimensions to keep in the reduced grid.
+      *                   Will be modified by this constructor for internal purpose.
+      * @param  factory   the factory to use for creating new math transforms, or {@code null} if none.
+      * @throws FactoryException if the dimensions to kept cannot be separated from the dimensions to omit.
+      */
+     protected DimensionalityReduction(final GridGeometry source, final BitSet gridAxes, final MathTransformFactory factory)
+             throws FactoryException
+     {
+         gridAxesToPass   = toArray(gridAxes);
+         sliceCoordinates = Map.of();
+         sourceGeometry   = source;
+         /*
+          * Set `gridAxes` to its complement: instead of dimensions to pass, it will become
+          * the dimensions to remove. If the result is empty, we have an identity operation.
+          */
+         final int sourceDim = source.getDimension();
+         gridAxes.flip(0, sourceDim);
+         if (gridAxes.isEmpty()) {
+             reducedGeometry = source;
+             crsAxesToRemove = ArraysExt.EMPTY_INT;
+             componentsOfCRS = null;
+         } else {
+             /*
+              * The calculation of `dimSubCRS` below assumes that 1 removed grid dimension
+              * implies 1 removed CRS dimension. See "assumptions" in class javadoc.
+              */
+             final int targetDim = source.getTargetDimension();
+             final int dimSubCRS = targetDim - (sourceDim - gridAxesToPass.length);
+             final var helper    = new SliceGeometry(source, null, gridAxesToPass, factory);
+             reducedGeometry = helper.reduce(null, dimSubCRS);
+             /*
+              * Get the sequence of CRS axes to remove. The result will often be
+              * the same indices than `gridAxesToRemove`, but not necessarily.
+              */
+             final BitSet crsAxes = bitmask(helper.getTargetDimensions(), targetDim);
+             crsAxes.flip(0, targetDim);
+             crsAxesToRemove = toArray(crsAxes);
+             if (source.isDefined(GridGeometry.CRS)) {
+                 componentsOfCRS = filterCRS(source.getCoordinateReferenceSystem(), crsAxes);
+             } else {
+                 componentsOfCRS = null;
+             }
+             if (source.isDefined(GridGeometry.GRID_TO_CRS)) {
+                 final int[] gridAxesToRemove = gridAxes.stream().toArray();
+                 removedGridToCRS   = filterGridToCRS(gridAxesToRemove, gridAxes, PixelInCell.CELL_CENTER, factory);
+                 removedCornerToCRS = filterGridToCRS(gridAxesToRemove, gridAxes, PixelInCell.CELL_CORNER, factory);
+                 return;
+             }
+         }
+         removedGridToCRS   = null;
+         removedCornerToCRS = null;
+     }
+ 
+     /**
+      * Returns all CRS components for the dimensions where the bit is set.
+      * There is one CRS for each range of consecutive dimension indices.
+      * If at least one CRS cannot be fetched, then this method returns {@code null}.
+      *
+      * @param  crs   the CRS for which to get components.
+      * @param  axes  dimensions (or axis indices) of the components to get.
+      * @throws FactoryException if the geodetic factory failed to create a compound CRS.
+      * @return CRS for each range of consecutive axis indices.
+      */
+     private static Object[] filterCRS(final CoordinateReferenceSystem crs, final BitSet axes)
+             throws FactoryException
+     {
+         final int dim = crs.getCoordinateSystem().getDimension();
+         final var components = new Object[dim];
+         int count = 0;
+         int upper = 0;
+         int lower;
+         while ((lower = axes.nextSetBit(upper)) >= 0) {
+             if (lower != upper) {
+                 components[count++] = lower - upper;        // Here `upper` is not yet updated to the higher value.
+             }
+             upper = axes.nextClearBit(lower);
+             for (CoordinateReferenceSystem c : CRS.selectComponents(crs, ArraysExt.range(lower, upper))) {
+                 components[count++] = c;
+             }
+         }
+         if (upper != dim) {
+             components[count++] = dim - upper;              // Keep an empty slot for the reduced CRS component.
+         }
+         return ArraysExt.resize(components, count);
+     }
+ 
+     /**
+      * Returns a "grid to CRS" transform which will transform only the "removed" dimensions.
+      * Other dimensions are passed-through.
+      *
+      * @param  gridAxesToRemove  the dimensions on which to operate.
+      * @param  bitset   same as {@link gridAxesToRemove} but as a bit set (for efficiency).
+      * @param  anchor   whether to compute the transform for pixel corner or pixel center.
+      * @param  factory  the factory to use for creating new math transforms, or {@code null} if none.
+      */
+     private MathTransform filterGridToCRS(final int[] gridAxesToRemove, final BitSet bitset, final PixelInCell anchor,
+             final MathTransformFactory factory) throws FactoryException
+     {
+         final MathTransform gridToCRS = sourceGeometry.getGridToCRS(anchor);
+         final var sep = new TransformSeparator(gridToCRS, factory);
+         sep.addSourceDimensions(gridAxesToRemove);
+         sep.addTargetDimensions(crsAxesToRemove);
+         return PassThroughTransform.create(bitset, sep.separate(), gridToCRS.getSourceDimensions(), factory);
+     }
+ 
+     /**
+      * Creates the same dimensionality reduction as the specified {@code source}, but with different slice indices.
+      *
+      * @param  source  the dimensionality reduction to copy.
+      * @param  slice   coordinates of the slice in removed dimensions.
+      */
+     private DimensionalityReduction(final DimensionalityReduction source, final Map<Integer,Long> slice) {
+         sourceGeometry     = source.sourceGeometry;
+         reducedGeometry    = source.reducedGeometry;
+         gridAxesToPass     = source.gridAxesToPass;
+         crsAxesToRemove    = source.crsAxesToRemove;
+         componentsOfCRS    = source.componentsOfCRS;
+         removedGridToCRS   = source.removedGridToCRS;
+         removedCornerToCRS = source.removedCornerToCRS;
+         sliceCoordinates   = Map.copyOf(slice);
+     }
+ 
+     /**
+      * Returns a new bitmask of all dimension indices in the axes array.
+      * The returned object can be safely modified.
+      *
+      * @param  axes       indices of axes to pass or to remove.
+      * @param  sourceDim  maximal valid dimension index + 1.
+      * @return bitmask of dimensions in the given array.
+      * @throws IndexOutOfBoundsException if an axis index is out of bounds.
+      */
+     private static BitSet bitmask(final int[] axes, final int sourceDim) {
+         final BitSet bitmask = new BitSet(sourceDim);
+         for (final int dim : axes) {
+             ArgumentChecks.ensureValidIndex(sourceDim, dim);
+             bitmask.set(dim);
+         }
+         return bitmask;
+     }
+ 
+     /**
+      * Reduces the dimension of the specified grid geometry by retaining only the specified axes.
+      * Axes in the reduced grid geometry will be in the same order than in the source geometry:
+      * change of axis order and duplicated values in the {@code gridAxesToPass} argument are ignored.
+      *
+      * @param  source          the grid geometry to reduce.
+      * @param  gridAxesToPass  the grid axes to retain, ignoring order and duplicated values.
+      * @return reduced grid geometry together with other information.
+      * @throws IndexOutOfBoundsException if a grid axis index is out of bounds.
+      * @throws IllegalGridGeometryException if the dimensions to kept cannot be separated from the dimensions to omit.
+      */
+     public static DimensionalityReduction select(final GridGeometry source, final int... gridAxesToPass) {
+         ArgumentChecks.ensureNonNull("source", source);
+         final BitSet bitmask = bitmask(gridAxesToPass, source.getDimension());
+         try {
+             return new DimensionalityReduction(source, bitmask, null);
+         } catch (FactoryException e) {
+             throw new IllegalGridGeometryException(Resources.format(Resources.Keys.NonSeparableReducedDimensions, e));
+         }
+     }
+ 
+     /**
+      * A convenience method for selecting the two first dimensions of the specified grid geometry.
+      * This method can be used as a lambda function in resources query. Example:
+      *
+      * {@snippet lang="java" :
+      *     CoverageQuery query = new CoverageQuery();
+      *     query.setAxisSelection(DimensionalityReduction::select2D);
+      *     }
+      *
+      * @param  source  the grid geometry to reduce.
+      * @return reduced grid geometry together with other information.
+      * @throws IndexOutOfBoundsException if the grid geometry does not have at least two dimensions.
+      * @throws IllegalGridGeometryException if the dimensions to kept cannot be separated from the dimensions to omit.
+      *
+      * @see org.apache.sis.storage.CoverageQuery#setAxisSelection(Function)
+      */
+     public static DimensionalityReduction select2D(final GridGeometry source) {
+         return select(source, 0, 1);
+     }
+ 
+     /**
+      * Reduces the dimension of the specified grid geometry by removing the specified axes.
+      * Axes in the reduced grid geometry will be in the same order than in the source geometry:
+      * axis order and duplicated values in the {@code gridAxesToRemove} argument are not significant.
+      *
+      * @param  source            the grid geometry to reduce.
+      * @param  gridAxesToRemove  the grid axes to remove, ignoring order and duplicated values.
+      * @return reduced grid geometry together with other information.
+      * @throws IndexOutOfBoundsException if a grid axis index is out of bounds.
+      * @throws IllegalGridGeometryException if the dimensions to kept cannot be separated from the dimensions to omit.
+      */
+     public static DimensionalityReduction remove(final GridGeometry source, final int... gridAxesToRemove) {
+         ArgumentChecks.ensureNonNull("source", source);
+         final int sourceDim = source.getDimension();
+         final BitSet bitmask = bitmask(gridAxesToRemove, sourceDim);
+         bitmask.flip(0, sourceDim);
+         try {
+             return new DimensionalityReduction(source, bitmask, null);
+         } catch (FactoryException e) {
+             throw new IllegalGridGeometryException(Resources.format(Resources.Keys.NonSeparableReducedDimensions, e));
+         }
+     }
+ 
+     /**
+      * Automatically reduces a grid geometry by removing all grid dimensions with an extent size of 1.
+      * Axes in the reduced grid geometry will be in the same order than in the source geometry.
+      *
+      * @param  source  the grid geometry to reduce.
+      * @return reduced grid geometry together with other information.
+      * @throws IncompleteGridGeometryException if the grid geometry has no extent.
+      * @throws IllegalGridGeometryException if the dimensions to kept cannot be separated from the dimensions to omit.
+      *
+      * @see #select2D(GridGeometry)
+      */
+     public static DimensionalityReduction reduce(final GridGeometry source) {
+         ArgumentChecks.ensureNonNull("source", source);
+         final GridExtent extent = source.getExtent();
+         final int sourceDim = extent.getDimension();
+         final BitSet bitmask = new BitSet(sourceDim);
+         for (int dim=0; dim < sourceDim; dim++) {
+             if (extent.getLow(dim) != extent.getHigh(dim)) {
+                 bitmask.set(dim);
+             }
+         }
+         try {
+             return new DimensionalityReduction(source, bitmask, null);
+         } catch (FactoryException e) {
+             throw new IllegalGridGeometryException(Resources.format(Resources.Keys.NonSeparableReducedDimensions, e));
+         }
+     }
+ 
+     /**
+      * Returns {@code true} if this object does not reduce any dimension.
+      * It may happen if {@code select(…)} has been invoked with all axes to keep,
+      * or if {@code remove(…)} has been invoked with no axis to remove.
+      *
+      * @return whether this {@code DimensionalityReduction} does nothing.
+      */
+     public boolean isIdentity() {
+         return reducedGeometry == sourceGeometry;
+     }
+ 
+     /**
+      * Returns {@code true} if this dimensionality reduction is a slice in the source coverage.
+      * This is true if all removed dimensions either have a {@linkplain GridExtent#getSize(int)
+      * grid size} of one cell, or have a {@linkplain #getSliceCoordinates() slice coordinate} specified.
+      *
+      * <p>If this method returns {@code false}, then the results of {@code reverse(…)} method calls
+      * are potentially ambiguous and may cause a {@link SubspaceNotSpecifiedException} to be thrown
+      * at {@linkplain GridCoverage#render(GridExtent) rendering} time.</p>
+      *
+      * @return whether this dimensionality reduction is a slice in the source coverage.
+      *
+      * @see #getSliceCoordinates()
+      * @see #withSlicePoint(long[])
+      * @see #withSliceByRatio(double)
+      */
+     public boolean isSlice() {
+         return indexOfNonSlice() >= 0;
+     }
+ 
+     /**
+      * Ensures that {@link #isSlice()} returns {@code true}.
+      *
+      * @throws SubspaceNotSpecifiedException if this dimensionality reduction is not a slice of the source coverage.
+      */
+     final void ensureIsSlice() throws SubspaceNotSpecifiedException {
+         final int dim = indexOfNonSlice();
+         if (dim >= 0) {
+             throw new SubspaceNotSpecifiedException(Resources.format(Resources.Keys.AmbiguousGridAxisOmission_1, dim));
+         }
+     }
+ 
+     /**
+      * If {@link #isSlice()} would returns {@code false}, returns the index of the problematic dimension.
+      * Otherwise returns -1. This is used for more detailed error message.
+      */
+     private int indexOfNonSlice() {
+         int i = gridAxesToPass.length - 1;
+         final GridExtent extent = sourceGeometry.getExtent();
+         for (int dim = extent.getDimension(); --dim >= 0;) {
+             if (i >= 0 && dim == gridAxesToPass[i]) {
+                 i--;
+             } else if (!sliceCoordinates.containsKey(dim)) {
+                 if (extent.getLow(dim) != extent.getHigh(dim)) {
+                     return dim;
+                 }
+             }
+         }
+         return -1;
+     }
+ 
+     /**
+      * Returns {@code true} if the given grid geometry is likely to be already reduced.
+      * Current implementation checks only the number of dimensions.
+      *
+      * @param  candidate  the grid geometry to test.
+      * @return whether the given grid geometry is likely to be already reduced.
+      */
+     public boolean isReduced(final GridGeometry candidate) {
+         int dim;
+         if (candidate.extent == null && candidate.gridToCRS == null) {
+             dim = reducedGeometry.getTargetDimension();
+         } else {
+             dim = reducedGeometry.getDimension();
+         }
+         return candidate.getDimension() == dim;
+     }
+ 
+     /**
+      * Returns the grid geometry with only the retained grid axis dimension.
+      * The number of CRS dimensions should be reduced as well,
+      * but not necessarily in a one-to-one relationship.
+      *
+      * @return the grid geometry with retained grid dimensions.
+      */
+     public GridGeometry getReducedGridGeometry() {
+         return reducedGeometry;
+     }
+ 
+     /**
+      * Returns the grid geometry with all grid axis dimension.
+      * This is the {@code source} argument given to factory methods.
+      *
+      * @return the grid geometry with all grid dimensions.
+      */
+     public GridGeometry getSourceGridGeometry() {
+         return sourceGeometry;
+     }
+ 
+     /**
+      * Returns the part of the "grid to CRS" transform which has been removed in the reduced grid geometry.
+      * This is a pass-through transform (potentially, but not necessarily, implemented
+      * by {@link org.apache.sis.referencing.operation.transform.PassThroughTransform}).
+      * The number of source dimensions is the same than in the source grid geometry.
+      * The dimensions that are passed-through are the dimensions on which the reduced grid geometry operates.
+      *
+      * @param  anchor  the cell part to map (center or corner).
+      * @return removed part of the conversion from grid coordinates to "real world" coordinates.
+      */
+     private MathTransform getRemovedGridToCRS(final PixelInCell anchor) {
+         if (PixelInCell.CELL_CENTER.equals(anchor)) {
+             return removedGridToCRS;
+         } else if (PixelInCell.CELL_CORNER.equals(anchor)) {
+             return removedCornerToCRS;
+         }  else {
+             return PixelTranslation.translate(removedGridToCRS, PixelInCell.CELL_CENTER, anchor);
+         }
+     }
+ 
+     /**
+      * Returns the indices of the source dimensions that are kept in the reduced grid geometry.
+      *
+      * @return indices of source grid dimensions that are retained in the reduced grid geometry.
+      */
+     public int[] getSelectedDimensions() {
+         return gridAxesToPass.clone();
+     }
+ 
+     /**
+      * Returns the grid coordinates used in {@code reverse(…)} method calls for reconstituting some removed dimensions.
+      * Keys are indices of grid dimensions in the source that are <em>not</em> retained in the reduced grid geometry.
+      * Values are grid coordinates to assign to those dimensions when a {@code reverse(…)} method is executed.
+      *
+      * <p>This map does not need to contain an entry for all removed dimensions.
+      * If no slice point is specified for a given dimension, then the {@code reverse(…)} methods will use the
+      * full range of grid coordinates specified in the {@linkplain #getSourceGridGeometry() source geometry}.
+      * Often, those ranges have a {@linkplain GridExtent#getSize(int) size} of 1,
+      * in which case methods such as {@link GridCoverage#render(GridExtent)} will work anyway.
+      * If a removed source grid dimension had a size greater than 1 and no slice coordinates is specified;
+      * then the {@code reverse(…)} methods in this class will still work but an
+      * {@link SubspaceNotSpecifiedException} may be thrown later by other classes.</p>
+      *
+      * <p>This map is initially empty. Slice coordinates can be specified by calls
+      * to {@link #withSlicePoint(long[])} or {@link #withSliceByRatio(double)}.</p>
+      *
+      * @return source grid coordinates of the slice point used in {@code reverse(…)} method calls.
+      *
+      * @see #withSlicePoint(long[])
+      * @see #withSliceByRatio(double)
+      * @see GridExtent#getSliceCoordinates()
+      * @see GridCoverage.Evaluator#setDefaultSlice(Map)
+      */
+     @SuppressWarnings("ReturnOfCollectionOrArrayField")     // Map is immutable.
+     public Map<Integer,Long> getSliceCoordinates() {
+         return sliceCoordinates;
+     }
+ 
+     /**
+      * Returns the dimension in the reduced grid for the given dimension in the source grid.
+      * If the specified source grid dimension is not retained, then this method returns a negative number.
+      *
+      * @param  dim  source dimension index to map to reduced dimension index.
+      * @return reduced dimension index, or a negative value if not mapped.
+      */
+     final int toReducedDimension(final int dim) {
+         return Arrays.binarySearch(gridAxesToPass, dim);
+     }
+ 
+     /**
+      * Returns the dimension in the source grid for the given dimension in the reduced grid.
+      *
+      * @param  dim  reduced dimension index to map to source dimension index.
+      * @return source dimension index.
+      * @throws IndexOutOfBoundsException if the given dimension is invalid.
+      */
+     final int toSourceDimension(final int dim) {
+         return gridAxesToPass[dim];
+     }
+ 
+     /**
+      * Returns the dimension in the source CRS for given counter of removed dimension.
+      *
+      * @param  i  0 for the first removed CRS dimension, 1 fo the second removed CRS dimension, <i>etc.</i>
+      * @return dimension in the source CRS which has been removed, of -1 if <var>i</var> is above bounds.
+      */
+     private int toRemovedDimension(final int i) {
+         return (i < crsAxesToRemove.length) ? crsAxesToRemove[i] : -1;
+     }
+ 
+     /**
+      * Ensures that {@code source} has the same number of dimensions and the same axes than {@code expected}.
+      * Only axis names that are specified in both extents are compared.
+      * If the {@code source} to validate is null, it defaults to the {@code expected} extent.
+      *
+      * @param  expected  grid extent with expected axes, or {@code null} if none.
+      * @param  source    grid extent to validate, or {@code null} if unspecified.
+      * @return whether the two extents are equal.
+      * @throws IllegalArgumentException if the number of dimensions or at least one axis name does not match.
+      */
+     private static boolean ensureSameAxes(final GridExtent expected, final GridExtent source) {
+         if (source == null) {
+             return true;
+         }
+         if (expected != null) {
+             expected.ensureSameAxes(source, "source");
+             if (expected.equals(source, ComparisonMode.IGNORE_METADATA)) {
+                 return true;
+             }
+         }
+         return false;
+     }
+ 
+     /**
+      * Returns {@code true} if the {@code actual} CRS is equal, ignore metadata, to the one in {@code expected}.
+      * If any CRS is null, this method conservatively returns {@code true}.
+      * This is used for assertions only.
+      */
+     private static boolean assertSameCRS(final GridGeometry expected, final CoordinateReferenceSystem actual) {
+         if (actual != null && expected.isDefined(GridGeometry.CRS)) {
+             final var crs = expected.getCoordinateReferenceSystem();
+             if (crs != null) {
+                 return Utilities.deepEquals(crs, actual, ComparisonMode.DEBUG);
+             }
+         }
+         return true;
+     }
+ 
+     /**
+      * Returns a coordinate tuple on which dimensionality reduction has been applied.
+      * The coordinate reference system of the given {@code source} should be either
+      * null or equal (ignoring metadata) to the CRS of the source grid geometry.
+      * For performance reason, this is not verified unless assertions are enabled.
+      *
+      * @param  source  the source coordinate tuple, or {@code null}.
+      * @return the reduced coordinate tuple, or {@code null} if the given source was null.
+      */
+     @ArgumentCheckByAssertion
+     public DirectPosition apply(final DirectPosition source) {
+         if (source != null) {
+             ArgumentChecks.ensureDimensionMatches("source", sourceGeometry.getTargetDimension(), source);
+             assert assertSameCRS(sourceGeometry, source.getCoordinateReferenceSystem()) : source;
+             if (!isIdentity()) {
+                 final var reduced = new GeneralDirectPosition(reducedGeometry.getTargetDimension());
+                 /*
+                  * Following code is more complicated than what it could be if we stored a
+                  * `crsAxesToPass` array in this object. But it may not be worth to store
+                  * such array only for this method.
+                  */
+                 int dim = -1, remCounter = 0, removedAxis = crsAxesToRemove[0];
+                 for (int i=0; i < reduced.coordinates.length; i++) {
+                     while (++dim == removedAxis) {
+                         removedAxis = toRemovedDimension(++remCounter);
+                     }
+                     reduced.coordinates[i] = source.getOrdinate(dim);
+                 }
+                 return reduced;
+             }
+         }
+         return source;
+     }
+ 
+     /**
+      * Returns a grid extent on which dimensionality reduction has been applied.
+      * If the given source is {@code null}, then this method returns {@code null}.
+      * Nulls are accepted because they are valid argument values in calls to
+      * {@link GridCoverage#render(GridExtent)}.
+      *
+      * @param  source  the grid extent to reduce, or {@code null}.
+      * @return the reduced grid extent. May be {@code source}, which may be null.
+      * @throws MismatchedDimensionException if the given source does not have the expected number of dimensions.
+      * @throws IllegalArgumentException if axis types are specified but inconsistent in at least one dimension.
+      *
+      * @see GridExtent#selectDimensions(int...)
+      */
+     public GridExtent apply(final GridExtent source) {
+         if (source == null) return null;
+         if (ensureSameAxes(sourceGeometry.extent, source)) {
+             return reducedGeometry.extent;
+         }
+         return isIdentity() ? source : source.selectDimensions(gridAxesToPass);
+     }
+ 
+     /**
+      * Returns a grid geometry on which dimensionality reduction of the grid extent has been applied.
+      * It usually implies a reduction in the number of dimensions of the CRS as well,
+      * but not necessarily in same order.
+      *
+      * <p>If the given source is {@code null}, then this method returns {@code null}.
+      * Nulls are accepted because they are valid argument values in calls to
+      * {@link org.apache.sis.storage.GridCoverageResource#read(GridGeometry, int...)}.</p>
+      *
+      * @param  source  the grid geometry to reduce, or {@code null}.
+      * @return the reduced grid geometry. May be {@code source}, which may be null.
+      * @throws MismatchedDimensionException if the given source does not have the expected number of dimensions.
+      * @throws IllegalArgumentException if axis types are specified but inconsistent in at least one dimension.
+      *
+      * @see GridGeometry#selectDimensions(int...)
+      */
+     public GridGeometry apply(final GridGeometry source) {
+         if (source == null) return null;
+         if (ensureSameAxes(sourceGeometry.extent, source.extent)) {
+             return reducedGeometry;
+         }
+         return isIdentity() ? source : source.selectDimensions(gridAxesToPass);
+     }
+ 
+     /**
+      * Returns a grid coverage on which dimensionality reduction of the domain has been applied.
+      * This is a reduction in the number of dimensions of the grid extent. It usually implies a
+      * reduction in the number of dimensions of the CRS as well, but not necessarily in same order.
+      * The sample dimensions (coverage range) are unmodified.
+      *
+      * <p>The returned coverage is a <em>view</em>: changes in the source coverage
+      * are reflected immediately in the reduced coverage, and conversely.</p>
+      *
+      * <h4>Reversibility</h4>
+      * If {@link #isSlice()} returns {@code false},
+      * then the results of {@link #reverse(GridExtent)} are ambiguous
+      * and calls to {@link GridCoverage#render(GridExtent)} may cause
+      * an {@link SubspaceNotSpecifiedException} to be thrown.
+      * Unless the specified {@code source} grid coverage knows how to handle those cases.
+      *
+      * @param  source  the grid coverage to reduce.
+      * @return the reduced grid coverage, or {@code source} if this object {@linkplain #isIdentity() is identity}.
+      * @throws MismatchedDimensionException if the given source does not have the expected number of dimensions.
+      * @throws IllegalArgumentException if axis types are specified but inconsistent in at least one dimension.
+      *
+      * @see GridCoverageProcessor#selectGridDimensions(GridCoverage, int...)
+      */
+     @Override
+     public GridCoverage apply(final GridCoverage source) {
+         ArgumentChecks.ensureNonNull("source", source);
+         ensureSameAxes(sourceGeometry.extent, source.getGridGeometry().extent);
+         return isIdentity() ? source : new ReducedGridCoverage(source, this);
+     }
+ 
+     /**
+      * Returns a grid extent on which dimensionality reduction has been reverted.
+      * For all dimensions that were removed, grid coordinates will be set to the
+      * {@linkplain #getSliceCoordinates() slice coordinates} if specified,
+      * or to the original source grid coordinates otherwise.
+      * In the latter case, the reconstituted grid coordinates will be a single value
+      * if {@link #isSlice()} returns {@code true} (in which case the returned extent
+      * is unambiguous), or may be a (potentially ambiguous) range of values otherwise.
+      *
+      * <h4>Handling of null grid geometry</h4>
+      * If the given extent is {@code null}, then this method returns an extent
+      * with {@linkplain #getSliceCoordinates() slice coordinates} if they are known.
+      * If no slice coordinate has been specified, then this method returns {@code null}.
+      * Nulls are accepted because they are valid argument values
+      * in calls to {@link GridCoverage#render(GridExtent)}.
+      *
+      * @param  reduced  the reduced grid extent to revert, or {@code null}.
+      * @return the source grid extent. May be {@code reduced}, which may be null.
+      * @throws IncompleteGridGeometryException if the source grid geometry has no extent.
+      * @throws MismatchedDimensionException if the given extent does not have the expected number of dimensions.
+      * @throws IllegalArgumentException if axis types are specified but inconsistent in at least one dimension.
+      */
+     public GridExtent reverse(final GridExtent reduced) {
+         if (ensureSameAxes(reducedGeometry.extent, reduced)) {      // Argument validation.
+             if (sliceCoordinates.isEmpty()) {                       // Must be after argument validation.
+                 return sourceGeometry.extent;
+             }
+         }
+         if (isIdentity()) {
+             return reduced;
+         }
+         final GridExtent source = sourceGeometry.getExtent();
+         final long[] coordinates = source.getCoordinates();
+         final int m = coordinates.length >>> 1;
+         sliceCoordinates.forEach((dim, slice) -> {
+             coordinates[dim  ] = slice;
+             coordinates[dim+m] = slice;
+         });
+         if (reduced != null) {
+             for (int i=0; i < gridAxesToPass.length; i++) {
+                 final int dim = gridAxesToPass[i];
+                 coordinates[dim]   = reduced.getLow (i);
+                 coordinates[dim+m] = reduced.getHigh(i);
+             }
+         }
+         return new GridExtent(source, coordinates);
+     }
+ 
+     /**
+      * Returns a grid geometry on which dimensionality reduction has been reverted.
+      * For all dimensions that were removed, grid coordinates will be set to the
+      * {@linkplain #getSliceCoordinates() slice coordinates} if specified,
+      * or to the original source grid coordinates otherwise.
+      * In the latter case, the reconstituted dimensions will map a single coordinate value
+      * if {@link #isSlice()} returns {@code true} (in which case the returned grid geometry
+      * is unambiguous), or may map a (potentially ambiguous) range of grid coordinate values otherwise.
+      *
+      * <h4>Handling of null grid geometry</h4>
+      * If the given geometry is {@code null}, then this method returns a grid geometry
+      * with the {@linkplain #getSliceCoordinates() slice coordinates} if they are known.
+      * If no slice coordinate has been specified, then this method returns {@code null}.
+      * Nulls are accepted because they are valid argument values in calls to
+      * {@link org.apache.sis.storage.GridCoverageResource#read(GridGeometry, int...)}.
+      *
+      * @param  reduced  the reduced grid geometry to revert, or {@code null}.
+      * @return the source grid geometry. May be {@code reduced}, which may be null.
+      * @throws IncompleteGridGeometryException if the source grid geometry has no extent.
+      * @throws MismatchedDimensionException if the given geometry does not have the expected number of dimensions.
+      * @throws IllegalArgumentException if axis types are specified but inconsistent in at least one dimension.
+      */
+     public GridGeometry reverse(final GridGeometry reduced) {
+         final GridExtent extent = (reduced != null) ? reduced.extent : null;
+         if (ensureSameAxes(reducedGeometry.extent, extent)) {       // Argument validation.
+             if (sliceCoordinates.isEmpty()) {                       // Must be after argument validation.
+                 return sourceGeometry;
+             }
+         }
+         if (isIdentity()) {
+             return reduced;
+         }
+         /*
+          * Build a compound CRS on a "best effort" basis. This operation is costly
+          * if `fullCRS(…)` must be invoked, so we try to use the existing CRS first.
+          */
+         CoordinateReferenceSystem crs = null;
+         if (reduced.isDefined(GridGeometry.CRS) && sourceGeometry.isDefined(GridGeometry.CRS)) {
+             final CoordinateReferenceSystem reducedCRS = reduced.getCoordinateReferenceSystem();
+             if (Utilities.equalsIgnoreMetadata(reducedGeometry.getCoordinateReferenceSystem(), reducedCRS)) {
+                 crs = sourceGeometry.getCoordinateReferenceSystem();
+             } else {
+                 FactoryException cause = null;
+                 try {
+                     crs = fullCRS(reducedCRS);
+                 } catch (FactoryException e) {
+                     cause = e;
+                 }
+                 if (crs == null) {
+                     throw new IllegalGridGeometryException(Resources.format(Resources.Keys.NonSeparableReducedDimensions, cause));
+                 }
+             }
+         }
+         /*
+          * Build an envelope and a resolution array where values at each dimension are copied
+          * either from the source geometry or from the specified reduced geometry.
+          */
+         final int targetDim = sourceGeometry.getTargetDimension();
+         final double[] lowerCorner = new double[targetDim];
+         final double[] upperCorner = new double[targetDim];
+         final double[] resolution  = new double[targetDim];
+         long nonLinears  = 0;
+         int  reducedDim  = 0;
+         int  remCounter  = 0;
+         int  removedAxis = crsAxesToRemove[0];
+         for (int i=0; i<targetDim; i++) {
+             final GridGeometry source;
+             final int dim;
+             if (i == removedAxis) {
+                 dim = removedAxis;
+                 source = sourceGeometry;
+                 removedAxis = toRemovedDimension(++remCounter);     // Dimension of the next axis to remove.
+             } else {
+                 dim = reducedDim++;
+                 source = reduced;
+             }
+             lowerCorner[i] =  source.envelope.getLower(dim);
+             upperCorner[i] =  source.envelope.getUpper(dim);
+             resolution [i] = (source.resolution != null) ? source.resolution[dim] : Double.NaN;
+             if ((source.nonLinears & Numerics.bitmask(dim)) != 0) {
+                 nonLinears |= Numerics.bitmask(i);
+             }
+         }
+         return new GridGeometry(reverse(extent),
+                 fullGridToCRS(reduced, PixelInCell.CELL_CENTER),
+                 fullGridToCRS(reduced, PixelInCell.CELL_CORNER),
+                 new ImmutableEnvelope(lowerCorner, upperCorner, crs),
+                 ArraysExt.allEquals(resolution, Double.NaN) ? null : resolution,
+                 nonLinears);
+     }
+ 
+     /**
+      * Builds a CRS with the same number of axes than the CRS of the source geometry.
+      * Axes are copied either from the source CRS or the reduced CRS, depending on
+      * whether the corresponding dimension is present in the reduced CRS.
+      *
+      * @param  reduced  the CRS to inflate to the same number of dimensions than the source CRS.
+      * @return the "inflated" CRS.
+      */
+     private CoordinateReferenceSystem fullCRS(final CoordinateReferenceSystem reduced) throws FactoryException {
+         if (componentsOfCRS == null) {
+             return null;
+         }
+         final var components = new CoordinateReferenceSystem[componentsOfCRS.length];
+         int lower = 0;
+         for (int i=0; i<components.length; i++) {
+             final Object element = componentsOfCRS[i];
+             if (element instanceof CoordinateReferenceSystem) {
+                 components[i] = (CoordinateReferenceSystem) element;
+             } else {
+                 final int upper = lower + (Integer) element;
+                 components[i] = CRS.getComponentAt(reduced, lower, upper);
+                 lower = upper;
+             }
+         }
+         return CRS.compound(components);
+     }
+ 
+     /**
+      * Builds a transform with the same number of dimensions that the transform of the source geometry.
+      *
+      * @param  reduced  the transform to inflate to the same number of dimensions that the source geometry.
+      * @param  anchor   whether the transform map pixel centers or pixel corners.
+      * @return the "inflated" transform.
+      */
+     private MathTransform fullGridToCRS(final GridGeometry reduced, final PixelInCell anchor) {
+         final MathTransform removed = getRemovedGridToCRS(anchor);
+         if (removed == null || !reduced.isDefined(GridGeometry.GRID_TO_CRS)) {
+             return null;
+         }
+         MathTransform gridToCRS = reduced.getGridToCRS(anchor);
+         if (Utilities.equalsIgnoreMetadata(reducedGeometry.getGridToCRS(anchor), gridToCRS)) {
+             return sourceGeometry.getGridToCRS(anchor);
+         }
+         gridToCRS = MathTransforms.passThrough(gridAxesToPass, gridToCRS, removed.getTargetDimensions());
+         return MathTransforms.concatenate(removed, gridToCRS);
+     }
+ 
+     /**
+      * Returns a dimensional reduction which will use the given source grid indices for {@code reverse(…)} operations.
+      * The length of the given {@code slicePoint} array shall be the number of dimensions of the source grid geometry.
+      * All given coordinate values shall be inside the source grid extent.
+      *
+      * @param  point  grid coordinates of a point located on the slice.
+      * @return the dimensionality reduction with the given slice point used for reverse operations.
+      * @throws IncompleteGridGeometryException if the source grid geometry has no extent.
+      * @throws MismatchedDimensionException if the given point does not have the expected number of dimensions.
+      * @throws PointOutsideCoverageException if the given point is outside the source grid extent.
+      */
+     public DimensionalityReduction withSlicePoint(final long[] point) {
+         ArgumentChecks.ensureNonNull("point", point);
+         final GridExtent extent = sourceGeometry.getExtent();
+         final int sourceDim = extent.getDimension();
 -        ArgumentChecks.ensureDimensionMatches("slicePoint", sourceDim, extent);
+         final Map<Integer,Long> slices = new HashMap<>();
+         for (int dim=0; dim < sourceDim; dim++) {
+             final long low   = extent.getLow (dim);
+             final long high  = extent.getHigh(dim);
+             final long value = point[dim];
+             if (value < low || value > high) {
+                 String b = Arrays.toString(point);
+                 b = b.substring(1, b.length() - 1);   // Remove brackets.
+                 throw new PointOutsideCoverageException(Resources.format(
+                         Resources.Keys.GridCoordinateOutsideCoverage_4,
+                         extent.getAxisIdentification(dim,dim), low, high, b));
+             }
+             if (low != high && toReducedDimension(dim) < 0) {
+                 slices.put(dim, value);
+             }
+         }
+         return slices.equals(sliceCoordinates) ? this : new DimensionalityReduction(this, slices);
+     }
+ 
+     /**
+      * Returns a dimensional reduction with a relative slice position
+      * for every grid dimensions that have been removed.
+      * The relative position is specified by a ratio between 0 and 1 where
+      * 0 maps to {@linkplain GridExtent#getLow(int) low} grid coordinates,
+      * 1 maps to {@linkplain GridExtent#getHigh(int) high grid coordinates} and
+      * 0.5 maps to the median position.
+      *
+      * @param  ratio  the ratio to apply on all removed grid dimensions.
+      * @return the dimensionality reduction with the given slice ratio applied.
+      * @throws IncompleteGridGeometryException if the source grid geometry has no extent.
+      * @throws IllegalArgumentException if the given ratio is not between 0 and 1 inclusive.
+      *
+      * @see GridExtent#getRelative(int, double)
+      * @see GridDerivation#sliceByRatio(double, int...)
+      */
+     public DimensionalityReduction withSliceByRatio(final double ratio) {
+         ArgumentChecks.ensureBetween("ratio", 0, 1, ratio);
+         final GridExtent extent = sourceGeometry.getExtent();
+         final int sourceDim = extent.getDimension();
+         final Map<Integer,Long> slices = new HashMap<>();
+         for (int dim=0; dim < sourceDim; dim++) {
+             if (toReducedDimension(dim) < 0 && extent.getLow(dim) != extent.getHigh(dim)) {
+                 slices.put(dim, extent.getRelative(dim, ratio));
+             }
+         }
+         return slices.equals(sliceCoordinates) ? this : new DimensionalityReduction(this, slices);
+     }
+ }
diff --cc core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoordinatesView.java
index cf89e64a46,307800bd33..d5974b88fd
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoordinatesView.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoordinatesView.java
@@@ -24,10 -26,6 +24,10 @@@ import org.apache.sis.util.ArgumentChec
   * A view over the low or high grid envelope coordinates.
   * This is not a general-purpose grid coordinates since it assumes a {@link GridExtent} coordinates layout.
   *
-  * <div class="note"><b>Upcoming API generalization:</b>
++ * <h2>Upcoming API generalization</h2>
 + * this class may implement the {@code GridCoordinates} interface in a future Apache SIS version.
-  * This is pending GeoAPI update.</div>
++ * This is pending GeoAPI update.
 + *
   * @author  Martin Desruisseaux (Geomatys)
   * @version 1.1
   * @since   1.0
diff --cc core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFilter.java
index 6690fca29a,2aeb42d671..6805ddfadf
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFilter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFilter.java
@@@ -22,11 -22,11 +22,10 @@@ import java.util.LinkedHashSet
  import org.apache.sis.util.ArgumentChecks;
  import org.apache.sis.internal.util.CollectionsExt;
  import org.apache.sis.internal.util.UnmodifiableArrayList;
- import org.apache.sis.util.resources.Errors;
  
  // Branch-dependent imports
 -import org.opengis.filter.Filter;
 -import org.opengis.filter.LogicalOperator;
 -import org.opengis.filter.LogicalOperatorName;
 +import org.apache.sis.internal.geoapi.filter.LogicalOperator;
 +import org.apache.sis.internal.geoapi.filter.LogicalOperatorName;
  
  
  /**
diff --cc core/sis-feature/src/main/java/org/apache/sis/internal/coverage/CommonDomainFinder.java
index 0000000000,4fa3b4b157..8ff67d02f1
mode 000000,100644..100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/CommonDomainFinder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/CommonDomainFinder.java
@@@ -1,0 -1,376 +1,385 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one or more
+  * contributor license agreements.  See the NOTICE file distributed with
+  * this work for additional information regarding copyright ownership.
+  * The ASF licenses this file to You under the Apache License, Version 2.0
+  * (the "License"); you may not use this file except in compliance with
+  * the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS,
+  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  * See the License for the specific language governing permissions and
+  * limitations under the License.
+  */
+ package org.apache.sis.internal.coverage;
+ 
+ import java.util.Map;
+ import java.util.LinkedHashMap;
+ import java.util.NoSuchElementException;
+ import org.opengis.util.FactoryException;
+ import org.opengis.geometry.MismatchedDimensionException;
+ import org.opengis.referencing.datum.PixelInCell;
+ import org.opengis.referencing.operation.Matrix;
+ import org.opengis.referencing.operation.MathTransform;
+ import org.opengis.referencing.operation.TransformException;
+ import org.opengis.referencing.crs.CoordinateReferenceSystem;
+ import org.opengis.referencing.operation.NoninvertibleTransformException;
+ import org.apache.sis.referencing.CRS;
+ import org.apache.sis.referencing.operation.transform.MathTransforms;
+ import org.apache.sis.coverage.grid.GridExtent;
+ import org.apache.sis.coverage.grid.GridGeometry;
+ import org.apache.sis.coverage.grid.IllegalGridGeometryException;
+ import org.apache.sis.internal.referencing.ExtendedPrecisionMatrix;
+ import org.apache.sis.internal.feature.Resources;
+ import org.apache.sis.internal.util.Numerics;
+ import org.apache.sis.util.Numbers;
+ 
+ 
+ /**
+  * Helper class for building a combined domain from a list of grid geometries.
+  * After construction, one of the following methods shall be invoked exactly once.
+  *
+  * <ul>
+  *   <li>{@link #setFromGridAligned(GridGeometry...)}</li>
+  * </ul>
+  *
+  * Then, the result can be obtained by the given methods:
+  *
+  * <ul>
+  *   <li>{@link #result()}</li>
+  *   <li>{@link #gridTranslations()}</li>
+  *   <li>{@link #sourceOfGridToCRS()}</li>
+  * </ul>
+  *
+  * @author  Martin Desruisseaux (Geomatys)
+  * @version 1.4
+  * @since   1.4
+  */
+ public final class CommonDomainFinder {
+     /**
+      * Whether to compute intersection (true) or union (false) of all grid coverages.
+      * This is not yet configurable in current version.
+      *
+      * <p>We use this constant for tracking the code to update when we will want to provide an option for using
+      * union or strict equality instead (the latter would be a mode that fails if all extents are not identical).
+      * Note that in the case of unions, it would be possible to specify coverages with no intersection.
+      * Whether we should accept that or raise an exception is still an open question.</p>
+      */
+     public static final boolean INTERSECTION = true;
+ 
+     /**
+      * The grid geometry taken as the reference for computing the translations of all grid geometry items.
+      * Values of {@link #gridTranslations} and {@link #itemTranslations} are relative to that reference.
+      * At first this is an arbitrary grid geometry, for example the first encountered one.
+      * After {@code CommonDomainFinder} completed its task, this is updated to the final result.
+      *
+      * @see #result()
+      */
+     private GridGeometry reference;
+ 
+     /**
+      * Coordinate reference system of the reference, or {@code null} if not yet known.
+      * It will be set to the CRS of the first grid geometry where the CRS is defined.
+      */
+     private CoordinateReferenceSystem crs;
+ 
+     /**
+      * The inverse of the "grid to CRS" transform of the grid geometry taken as a reference.
+      */
+     private MathTransform crsToGrid;
+ 
+     /**
+      * The convention to use for fetching the "grid to CRS" transforms.
+      */
+     private final PixelInCell anchor;
+ 
+     /**
+      * The combined extent, as the union or intersection of all grid extents.
+      */
+     private GridExtent extent;
+ 
+     /**
+      * The translation in units of grid cells from the {@linkplain #reference} grid geometry
+      * to the grid geometry in the key.
+      */
+     private final Map<GridGeometry,long[]> gridTranslations;
+ 
+     /**
+      * Translations in units of grid cells from the {@linkplain #reference} grid geometry to each item.
+      * For each index <var>i</var>, {@code itemTranslations[i]} is a value from {@link #gridTranslations}
+      * map and may be reused at more than one index <var>i</var>.
+      *
+      * @see #gridTranslations()
+      */
+     private long[][] itemTranslations;
+ 
+     /**
+      * If one of the grid geometries has the same "grid to CRS" than the common grid geometry, the index.
+      * Otherwise -1.
+      */
+     private int sourceOfGridToCRS;
+ 
+     /**
+      * Creates a new common domain finder.
+      *
+      * @param  anchor  the convention to use for fetching the "grid to CRS" transforms.
+      */
+     CommonDomainFinder(final PixelInCell anchor) {
+         this.anchor = anchor;
+         gridTranslations = new LinkedHashMap<>();
+     }
+ 
+     /**
+      * Computes a common grid geometry from the given items.
+      * All items shall share be aligned on the same grid.
+      * Items may be translated relative to each other,
+      * but the translations shall be an integer number of grid cells.
+      *
+      * <h4>Coordinate reference system</h4>
+      * If the CRS of a grid geometry is undefined, it is assumed the same than other grid geometries.
+      *
+      * @param  items  the grid geometries for which to compute a common grid geometry.
+      * @throws IllegalGridGeometryException if the specified item is not compatible with the reference grid geometry.
+      */
+     final void setFromGridAligned(final GridGeometry... items) {
+         itemTranslations = new long[items.length][];
+         for (int i=0; i<items.length; i++) {
+             itemTranslations[i] = gridTranslations.computeIfAbsent(items[i], this::itemToCommon);
+         }
+         /*
+          * Change the reference grid geometry for matching more closely the desired grid extent.
+          * If one item has exactly the desired grid extent, use it. Otherwise search for an item
+          * having the same origin. This criterion is arbitrary and may change in future version.
+          */
+         GridGeometry fallback = null;
+         GridExtent   location = null;
+         for (final Map.Entry<GridGeometry,long[]> entry : gridTranslations.entrySet()) {
+             final GridGeometry item   = entry.getKey();
+             final GridExtent actual   = item.getExtent();
+             final GridExtent expected = extent.translate(entry.getValue());
+             if (actual.equals(expected)) {
+                 setGridToCRS(items, item);      // Must be before `reference` assignation.
+                 reference = item;
+                 return;
+             }
+             // Arbitrary criterion (may be revisited in any future version).
 -            if (fallback == null && expected.getLow().equals(actual.getLow())) {
++            if (fallback == null && sameLow(expected, actual)) {
+                 location = expected;
+                 fallback = item;
+             }
+         }
+         if (fallback == null) {
+             fallback = reference;
+             location = extent;
+         }
+         setGridToCRS(items, fallback);
+         try {
+             reference = fallback.relocate(location);
+         } catch (TransformException e) {
+             throw new IllegalGridGeometryException(Resources.format(Resources.Keys.IncompatibleGridGeometries), e);
+         }
+     }
+ 
++    private static boolean sameLow(final GridExtent e1, final GridExtent e2) {
++        final int dim = e1.getDimension();
++        if (dim != e2.getDimension()) return false;
++        for (int i=0; i<dim; i++) {
++            if (e1.getLow(i) != e2.getLow(i)) return false;
++        }
++        return true;
++    }
++
+     /**
+      * Given a grid geometry with the desired "grid to CRS", saves its index in {@link #sourceOfGridToCRS}.
+      * This method updates all previously computed translations for making them relative to the new reference.
+      *
+      * Note: updating values of the {@link #gridTranslations} map indirectly update all
+      * {@link #itemTranslations} array elements.
+      */
+     private void setGridToCRS(final GridGeometry[] items, final GridGeometry item) {
+         sourceOfGridToCRS = indexOf(items, item);
+         if (item == reference) {                    // Quick check for a common case.
+             return;
+         }
+         final long[] oldReference = itemTranslations[indexOf(items, reference)];
+         final long[] newReference = itemTranslations[sourceOfGridToCRS];
+         final long[] change = new long[newReference.length];
+         for (int i=0; i < newReference.length; i++) {
+             change[i] = Math.subtractExact(newReference[i], oldReference[i]);
+         }
+         for (final long[] offset : gridTranslations.values()) {
+             for (int i=0; i < offset.length; i++) {
+                 offset[i] = Math.subtractExact(offset[i], change[i]);
+             }
+         }
+     }
+ 
+     /**
+      * Returns the index of the given grid geometry.
+      * This method is invoked when the instance should always exist in the array.
+      */
+     private static int indexOf(final GridGeometry[] items, final GridGeometry item) {
+         for (int i=0; i<items.length; i++) {
+             if (items[i] == item) {
+                 return i;
+             }
+         }
+         throw new NoSuchElementException();         // Should never happen.
+     }
+ 
+     /**
+      * Returns the common grid geometry computed from all specified items.
+      *
+      * @return a grid geometry which is the union or intersection of all specified items.
+      */
+     final GridGeometry result() {
+         if (crs != null && !reference.isDefined(GridGeometry.CRS)) {
+             reference = new GridGeometry(extent, anchor, reference.getGridToCRS(anchor), crs);
+         }
+         return reference;
+     }
+ 
+     /**
+      * Returns the translations (in units of grid cells) from the common grid geometry to all items.
+      * The items are the arguments given to {@link #setFromGridAligned(GridGeometry...)}, in order.
+      * The common grid geometry is the value returned by {@link #result()}.
+      *
+      * <p>The returned array should not be modified because it is not cloned.</p>
+      *
+      * @return translation from the common grid geometry to all items. This array is not cloned.
+      */
+     @SuppressWarnings("ReturnOfCollectionOrArrayField")
+     final long[][] gridTranslations() {
+         return itemTranslations;
+     }
+ 
+     /**
+      * If one items has the same "grid to CRS" than the common grid geometry, returns its index.
+      *
+      * @return index of an items having the desired "grid to CRS", or -1 if none.
+      */
+     final int sourceOfGridToCRS() {
+         return sourceOfGridToCRS;
+     }
+ 
+     /**
+      * Computes the translation in units of grid cells from the common grid geometry to the given item.
+      * This method also opportunistically computes the union or intersection of all grid extents.
+      *
+      * @param  item  the grid geometry for which to get a translation from the common grid geometry.
+      * @return translation in unis of grid cells. Note that the caller may reuse this array for many grid geometries.
+      * @throws IllegalGridGeometryException if the specified item is not compatible with the reference grid geometry.
+      */
+     private long[] itemToCommon(final GridGeometry item) {
+         /*
+          * Compute the change ourselves instead of invoking `GridGeometry.createTransformTo(…)`
+          * because we do not want wraparound handling when we search for a simple translation.
+          */
+         MathTransform change = item.getGridToCRS(anchor);
+         try {
+             if (crsToGrid == null) {
+                 crsToGrid = change.inverse();
+                 reference = item;
+             }
+             if (item.isDefined(GridGeometry.CRS)) {
+                 final CoordinateReferenceSystem src = item.getCoordinateReferenceSystem();
+                 if (crs == null) {
+                     crs = src;
+                 } else {
+                     /*
+                      * Ask for a change of CRS without specifying an area of interest (AOI) on the assumption
+                      * that if the transformation is only a translation, the AOI would not make a difference.
+                      * It save not only the AOI computation cost, but also make easier for `findOperation(…)`
+                      * to use its cache.
+                      */
+                     change = MathTransforms.concatenate(change, CRS.findOperation(src, crs, null).getMathTransform());
+                 }
+             }
+             change = MathTransforms.concatenate(change, crsToGrid);
+         } catch (FactoryException | NoninvertibleTransformException | MismatchedDimensionException e) {
+             throw new IllegalGridGeometryException(Resources.format(Resources.Keys.IncompatibleGridGeometries), e);
+         }
+         final long[] offset = integerTranslation(MathTransforms.getMatrix(change), null);
+         if (offset == null) {
+             throw new IllegalGridGeometryException(Resources.format(Resources.Keys.IncompatibleGridGeometries));
+         }
+         /*
+          * The grid geometry has been accepted as valid. Now compute the combined extent,
+          * taking offset in account. At this point this is an offset TO the common grid geometry.
+          * It will be converted to an offset FROM the common grid geometry after the extent update.
+          */
+         final GridExtent e = item.getExtent().translate(offset);
+         if (extent == null) {
+             extent = e;
+         } else if (INTERSECTION) {
+             extent = extent.intersect(e);
+         } else if (!extent.equals(e)) {
+             throw new IllegalGridGeometryException();
+         }
+         for (int i=0; i<offset.length; i++) {
+             offset[i] = Math.negateExact(offset[i]);
+         }
+         return offset;
+     }
+ 
+     /**
+      * If the given matrix is the identity matrix except for translation terms, returns the translation.
+      * Translation terms must be integer values and will be stored in the given {@code offset} array.
+      * If the matrix is not an integer translation, this method return {@code null} without modifying
+      * the given {@code offset} array.
+      *
+      * @param  change  conversion between two grid geometries, or {@code null}.
+      * @param  offset  where to store translation terms if the change is an integer translation, or {@code null}.
+      * @return the translation terms, or {@code null} if the given matrix does not met the conditions.
+      *
+      * @see org.apache.sis.referencing.operation.matrix.Matrices#isTranslation(Matrix)
+      */
+     public static long[] integerTranslation(final Matrix change, long[] offset) {
+         if (change == null) {
+             return null;
+         }
+         final int numRows = change.getNumRow();
+         final int numCols = change.getNumCol();
+         for (int j=0; j<numRows; j++) {
+             for (int i=0; i<numCols; i++) {
+                 double tolerance = Numerics.COMPARISON_THRESHOLD;
+                 double e = change.getElement(j, i);
+                 if (i == j) {
+                     e--;
+                 } else if (i == numCols - 1) {
+                     final double a = Math.abs(e);
+                     if (a > 1) {
+                         tolerance = Math.min(tolerance*a, 0.125);
+                     }
+                     e -= Math.rint(e);
+                 }
+                 if (!(Math.abs(e) <= tolerance)) {      // Use `!` for catching NaN.
+                     return null;
+                 }
+             }
+         }
+         /*
+          * Store the translation terms after we have determined that they are integers.
+          * It must be an "all or nothing" operation (unless an exception is thrown).
+          */
+         if (offset == null) {
+             offset = new long[numRows - 1];
+         }
+         final int i = numCols - 1;
+         if (change instanceof ExtendedPrecisionMatrix) {
+             final var epm = (ExtendedPrecisionMatrix) change;
+             for (int j=0; j<offset.length; j++) {
+                 final Number e = epm.getElementOrNull(j, i);
+                 offset[j] = (e != null) ? Numbers.round(e) : 0;
+             }
+         } else {
+             for (int j=0; j<offset.length; j++) {
+                 offset[j] = Math.round(change.getElement(j, i));
+             }
+         }
+         return offset;
+     }
+ }
diff --cc core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SpatialFunction.java
index 752414f502,9d46610846..f153e9e9dd
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SpatialFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/sqlmm/SpatialFunction.java
@@@ -41,9 -43,9 +42,9 @@@ import org.apache.sis.filter.Expression
   *
   * @author  Johann Sorel (Geomatys)
   * @author  Martin Desruisseaux (Geomatys)
-  * @version 1.2
+  * @version 1.4
   *
 - * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
 + * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
   *
   * @since 1.1
   */
diff --cc core/sis-feature/src/test/java/org/apache/sis/coverage/grid/DimensionalityReductionTest.java
index 0000000000,565bc0af2d..ef4f109054
mode 000000,100644..100644
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/DimensionalityReductionTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/DimensionalityReductionTest.java
@@@ -1,0 -1,194 +1,194 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one or more
+  * contributor license agreements.  See the NOTICE file distributed with
+  * this work for additional information regarding copyright ownership.
+  * The ASF licenses this file to You under the Apache License, Version 2.0
+  * (the "License"); you may not use this file except in compliance with
+  * the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing, software
+  * distributed under the License is distributed on an "AS IS" BASIS,
+  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  * See the License for the specific language governing permissions and
+  * limitations under the License.
+  */
+ package org.apache.sis.coverage.grid;
+ 
+ import org.opengis.referencing.crs.SingleCRS;
+ import org.opengis.referencing.crs.CoordinateReferenceSystem;
+ import org.opengis.referencing.datum.PixelInCell;
+ import org.opengis.referencing.operation.Matrix;
+ import org.apache.sis.internal.referencing.DirectPositionView;
+ import org.apache.sis.referencing.operation.matrix.Matrix3;
+ import org.apache.sis.referencing.operation.matrix.Matrix4;
+ import org.apache.sis.referencing.operation.matrix.Matrices;
+ import org.apache.sis.referencing.operation.transform.MathTransforms;
+ import org.apache.sis.referencing.crs.HardCodedCRS;
+ import org.apache.sis.referencing.CRS;
+ import org.apache.sis.util.ComparisonMode;
+ import org.apache.sis.util.Utilities;
+ import org.apache.sis.test.TestCase;
+ import org.junit.Test;
+ 
 -import static org.opengis.test.Assert.*;
++import static org.apache.sis.test.Assert.*;
+ 
+ 
+ /**
+  * Tests {@link DimensionalityReduction}.
+  *
+  * @author  Martin Desruisseaux (Geomatys)
+  * @version 1.4
+  * @since   1.4
+  */
+ public final class DimensionalityReductionTest extends TestCase {
+     /**
+      * Convenience method for building a grid geometry.
+      *
+      * @param  low        low grid coordinates, inclusive.
+      * @param  high       high grid coordinates, inclusive.
+      * @param  gridToCRS  map pixel corner to geographic coordinates.
+      * @param  crs        target of {@code gridToCRS}.
+      * @return grid geometry for testing purposes.
+      */
+     private static GridGeometry createGridGeometry(long[] low, long[] high, Matrix gridToCRS, CoordinateReferenceSystem crs) {
+         return new GridGeometry(new GridExtent(null, low, high, true), PixelInCell.CELL_CORNER,
+                                 MathTransforms.linear(gridToCRS), crs);
+     }
+ 
+     /**
+      * Creates a four-dimensional grid geometry to be used for the tests in this class.
+      * The "grid to CRS" transform is a linear one.
+      */
+     private static GridGeometry linearGrid() {
+         return createGridGeometry(new long[] { 20, 136,  4, -2},
+                                   new long[] {419, 201, 10,  2},
+                                   gridToCRS(), HardCodedCRS.GEOID_4D);
+     }
+ 
+     /**
+      * Returns a "grid to CRS" transform for the full grid geometry to be used in tests.
+      */
+     private static Matrix gridToCRS() {
+         return Matrices.create(5, 5, new double[] {
+                         0.5, 0,   0,  0,  -180,
+                         0,   0.5, 0,  0,   -90,
+                         0,   0,   5,  0,    -2,
+                         0,   0,   0,  7, 60000,
+                         0,   0,   0,  0,     1});
+     }
+ 
+     /**
+      * Returns the matrix for the "grid to CRS" transform of the test grid, but without height.
+      */
+     private static Matrix4 withHeightRemoved() {
+         return new Matrix4(0.5, 0,   0,  -180,
+                            0,   0.5, 0,   -90,
+                            0,   0,   7, 60000,
+                            0,   0,   0,     1);
+     }
+ 
+     /**
+      * Returns the matrix for the "grid to CRS" transform with only the horizontal part.
+      */
+     private static Matrix3 withHorizontal() {
+         return new Matrix3(0.5, 0,  -180,
+                            0,   0.5, -90,
+                            0,   0,     1);
+     }
+ 
+     /**
+      * Asserts that the "grid to CRS" transform of the given grid geometry is equal to the specified value.
+      *
+      * @param  test      the grid geometry to verify.
+      * @param  expected  the expected "grid to CRS" transform.
+      * @return the CRS of the given grid geometry.
+      */
+     private static CoordinateReferenceSystem verifyGridToCRS(final GridGeometry test, final Matrix expected) {
+         Matrix actual = MathTransforms.getMatrix(test.getGridToCRS(PixelInCell.CELL_CORNER));
+         assertMatrixEquals("gridToCRS", expected, actual, STRICT);
+         return test.getCoordinateReferenceSystem();
+     }
+ 
+     /**
+      * Tests reduction of a direct position.
+      *
+      * @param reduction  the reduction to apply.
+      * @param source     source coordinates.
+      * @param target     expected reduced coordinates.
+      */
+     private static void testPosition(final DimensionalityReduction reduction, double[] source, double[] target) {
+         assertArrayEquals(target, reduction.apply(new DirectPositionView.Double(source)).getCoordinate(), STRICT);
+     }
+ 
+     /**
+      * Tests the removal of a single dimension in the middle of the "grid to CRS" transform.
+      * This test use the same CRS for all steps.
+      */
+     @Test
+     public void testRemoval() {
+         var reduction = DimensionalityReduction.remove(linearGrid(), 2);      // Remove height.
+         var crs = verifyGridToCRS(reduction.getReducedGridGeometry(), withHeightRemoved());
+         assertArrayEquals(new SingleCRS[] {HardCodedCRS.WGS84, HardCodedCRS.TIME},
+                           CRS.getSingleComponents(crs).toArray());
+         /*
+          * Tests the fast path in reduction and reverse operations.
+          */
+         assertSame(reduction.getReducedGridGeometry(), reduction.apply(linearGrid()));
+         assertSame(reduction.getSourceGridGeometry(),  reduction.reverse(reduction.getReducedGridGeometry()));
+         /*
+          * Test the reverse operation with a slightly different "grid to CRS".
+          * It will test the generic path, as opposed to above-cited fast path.
+          */
+         GridGeometry test = reduction.reverse(createGridGeometry(
+                 new long[] {100, 180, -5},
+                 new long[] {200, 195, -3},
+                 new Matrix4(0.5, 0,   0,  -100,
+                             0,   0.5, 0,   -80,
+                             0,   0,   7, 60002,
+                             0,   0,   0,     1), crs));
+ 
+         crs = verifyGridToCRS(test, Matrices.create(5, 5, new double[] {
+                         0.5, 0,   0,  0,  -100,
+                         0,   0.5, 0,  0,   -80,
+                         0,   0,   5,  0,    -2,
+                         0,   0,   0,  7, 60002,
+                         0,   0,   0,  0,     1}));
+ 
+         assertSame(reduction.getSourceGridGeometry().getCoordinateReferenceSystem(), crs);
+         testPosition(reduction, new double[] {100, 101, 102, 103}, new double[] {100, 101, 103});
+     }
+ 
+     /**
+      * Tests the selection of two dimensions.
+      */
+     @Test
+     public void testSelect() {
+         var reduction = DimensionalityReduction.select2D(linearGrid());
+         var gridToCRS = reduction.getReducedGridGeometry().getGridToCRS(PixelInCell.CELL_CORNER);
+         assertMatrixEquals("gridToCRS", withHorizontal(), MathTransforms.getMatrix(gridToCRS), STRICT);
+         assertSame(HardCodedCRS.WGS84, reduction.getReducedGridGeometry().getCoordinateReferenceSystem());
+ 
+         GridGeometry test = reduction.reverse(createGridGeometry(
+                 new long[] {380, 100},
+                 new long[] {400, 200},
+                 new Matrix3(0,   0.5, -80,
+                             0.5, 0,  -100,
+                             0,   0,     1), HardCodedCRS.WGS84_LATITUDE_FIRST));
+ 
+         var crs = verifyGridToCRS(test, Matrices.create(5, 5, new double[] {
+                         0,   0.5, 0,  0,   -80,
+                         0.5, 0,   0,  0,  -100,
+                         0,   0,   5,  0,    -2,
+                         0,   0,   0,  7, 60000,
+                         0,   0,   0,  0,     1}));
+         /*
+          * CRS should have different axis order.
+          */
+         var sourceCRS = reduction.getSourceGridGeometry().getCoordinateReferenceSystem();
+         assertFalse(Utilities.equalsIgnoreMetadata(sourceCRS, crs));
+         assertTrue(Utilities.deepEquals(test, test, ComparisonMode.ALLOW_VARIANT));     // Ignore axis order.
+         testPosition(reduction, new double[] {100, 101, 102, 103}, new double[] {100, 101});
+     }
+ }
diff --cc core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java
index a716370dbf,00185fec10..0b692d6e3f
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/PassThroughTransformTest.java
@@@ -285,6 -303,50 +290,50 @@@ public final class PassThroughTransform
                  1, 0, 0, 0, 0, 0,
                  0, 0, 0, 1, 0, 0,
                  0, 0, 0, 0, 1, 0,
 -                0, 0, 0, 0, 0, 1}), MathTransforms.getMatrix(steps.get(2)), null);
 +                0, 0, 0, 0, 0, 1}), MathTransforms.getMatrix(steps.get(2)), 0);
      }
+ 
+     /**
+      * Tests the creation of a pass-through transform with modified coordinates that are not consecutive.
+      * This is a test of {@link PassThroughTransform#create(BitSet, MathTransform, int, MathTransformFactory)}
+      * factory method.
+      *
+      * @throws FactoryException if an error occurred while combining the transforms.
+      * @throws TransformException if a test coordinate tuple cannot be transformed.
+      */
+     @Test
+     public void testNonConsecutiveModifiedCoordinates() throws FactoryException, TransformException {
+         random = TestUtilities.createRandomNumberGenerator();
+         /*
+          * First, create a pass-through transform from an inseparable `PseudoTransform`.
+          * Because `PseudoTransform` is inseparable, the modified coordinates must be consecutive.
+          * However the `PassThroughTransform` result is partially separable and used in next step.
+          */
+         final var bitset = new BitSet();
+         bitset.set(1, 3, true);                                 // Modified coordinates = {1, 2}.
+         MathTransform subTransform = new PseudoTransform(2, 2);
+         transform = PassThroughTransform.create(bitset, subTransform, 5, null);
+         assertEquals(5, transform.getSourceDimensions());
+         assertEquals(5, transform.getTargetDimensions());
+         assertEquals(1, ((PassThroughTransform) transform).firstAffectedCoordinate);
+         assertEquals(2, ((PassThroughTransform) transform).numTrailingCoordinates);
+         isInverseTransformSupported = false;
+         verifyTransform(subTransform, 1);
+         /*
+          * Now test with non-consecutive coordinates, except for the `PseudoTransform` part
+          * which must still be in consecutive coordinates. We add a linear transform before
+          * for making the work a little bit harder.
+          */
+         bitset.clear();
+         bitset.set(1, true);
+         bitset.set(3, 5, true);     // The inseparable `PseudoTransform` part.
+         bitset.set(6, true);
+         bitset.set(9, true);
+         subTransform = MathTransforms.concatenate(MathTransforms.scale(4, 3, 7, 5, -6), transform);
+         transform = PassThroughTransform.create(bitset, subTransform, 10, null);
+         verifyTransform(
+             // _____________[0]_________[1]-____[2]____[3]_______[4]     Dimension index in sub-transform.
+             new double[] {2, 1, -1,    0.2,    0.1, 9,  2, 8, 4, -1},
+             new double[] {2, 4, -1, 1600.6, 2700.7, 9, 10, 8, 4,  6});
+     }
  }
diff --cc storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryFeatureSet.java
index b100426b1c,4c3a19aae4..10f05d2c7e
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryFeatureSet.java
@@@ -51,17 -51,15 +51,15 @@@ public class MemoryFeatureSet extends A
  
      /**
       * Creates a new set of features stored in memory. It is caller responsibility to ensure that
 -     * <code>{@linkplain Feature#getType()} == type</code> for all elements in the given collection
 +     * <code>{@linkplain AbstractFeature#getType()} == type</code> for all elements in the given collection
       * (this is not verified).
       *
-      * @param parent     listeners of the parent resource, or {@code null} if none.
+      * @param parent     the parent resource, or {@code null} if none.
       * @param type       the type of all features in the given collection.
       * @param features   collection of stored features. This collection will not be copied.
       */
-     public MemoryFeatureSet(final StoreListeners parent,
-                             final DefaultFeatureType type, final Collection<AbstractFeature> features)
-     {
-         super(parent, false);
 -    public MemoryFeatureSet(final Resource parent, final FeatureType type, final Collection<Feature> features) {
++    public MemoryFeatureSet(final Resource parent, final DefaultFeatureType type, final Collection<AbstractFeature> features) {
+         super(parent);
          ArgumentChecks.ensureNonNull("type",     type);
          ArgumentChecks.ensureNonNull("features", features);
          this.type     = type;
diff --cc storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java
index b8af67c437,b065ce9b73..a6e50e038f
--- 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
@@@ -33,7 -34,7 +33,8 @@@ import org.apache.sis.coverage.grid.Gri
  import org.apache.sis.coverage.grid.GridExtent;
  import org.apache.sis.coverage.grid.GridGeometry;
  import org.apache.sis.coverage.grid.GridRoundingMode;
 +import org.apache.sis.coverage.CannotEvaluateException;
+ import org.apache.sis.internal.coverage.RangeArgument;
  import org.apache.sis.storage.AbstractGridCoverageResource;
  import org.apache.sis.storage.DataStoreException;
  import org.apache.sis.storage.RasterLoadingStrategy;
diff --cc storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedFeatureSet.java
index da537fb5a9,cc9f56d6e3..39f5f760d5
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedFeatureSet.java
@@@ -32,12 -32,12 +32,12 @@@ import org.apache.sis.internal.util.Col
  import org.apache.sis.internal.util.UnmodifiableArrayList;
  import org.apache.sis.internal.storage.Resources;
  import org.apache.sis.storage.AbstractFeatureSet;
- import org.apache.sis.storage.event.StoreListeners;
  import org.apache.sis.storage.Query;
+ import org.apache.sis.storage.Resource;
  
  // Branch-dependent imports
 -import org.opengis.feature.Feature;
 -import org.opengis.feature.FeatureType;
 +import org.apache.sis.feature.AbstractFeature;
 +import org.apache.sis.feature.DefaultFeatureType;
  
  
  /**
diff --cc storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/JoinFeatureSet.java
index ef38d87834,898e02089e..61931e80bf
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/JoinFeatureSet.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/JoinFeatureSet.java
@@@ -200,13 -202,13 +200,13 @@@ public class JoinFeatureSet extends Agg
       * @param  rightAlias   name of the associations to the {@code right} features, or {@code null} for a default name.
       * @param  joinType     whether values on both sides are required (inner join), or only one side (outer join).
       * @param  condition    join condition as <var>property from left feature</var> = <var>property from right feature</var>.
 -     * @param  featureInfo  information about the {@link FeatureType} of this feature set.
 +     * @param  featureInfo  information about the {@code FeatureType} of this feature set.
       * @throws DataStoreException if an error occurred while creating the feature set.
       */
-     public JoinFeatureSet(final StoreListeners parent,
+     public JoinFeatureSet(final Resource parent,
                            final FeatureSet left,  String leftAlias,
                            final FeatureSet right, String rightAlias,
 -                          final Type joinType, final BinaryComparisonOperator<? super Feature> condition,
 +                          final Type joinType, final BinaryComparisonOperator<? super AbstractFeature> condition,
                            Map<String,?> featureInfo)
              throws DataStoreException
      {