This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 3a1544293a15701ce444f7337c77737c12fb8391 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sat Apr 15 17:01:43 2023 +0200 Allow `BandAggregateGridCoverage` and `BandAggregateGridResource` to unwrap the sources. It makes possible to detect when two consecutive sources are fundamentally the same source. --- .../coverage/grid/BandAggregateGridCoverage.java | 14 +++++++ .../sis/coverage/grid/GridCoverageProcessor.java | 14 ++++--- .../sis/internal/coverage/MultiSourceArgument.java | 46 ++++++++++++++++++++++ .../grid/BandAggregateGridCoverageTest.java | 14 +++++++ .../aggregate/BandAggregateGridResource.java | 15 +++++++ 5 files changed, 97 insertions(+), 6 deletions(-) diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BandAggregateGridCoverage.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BandAggregateGridCoverage.java index 11a98fe7a4..eba909f842 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BandAggregateGridCoverage.java +++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/BandAggregateGridCoverage.java @@ -113,6 +113,20 @@ final class BandAggregateGridCoverage extends GridCoverage { } } + /** + * Returns potentially deeper sources than the user supplied coverage. + * This method unwraps {@link BandAggregateGridCoverage} for making possible to detect that + * two consecutive coverages are actually the same coverage, with only different bands selected. + * + * @param unwrapper a handler where to supply the result of an aggregate decomposition. + */ + static void unwrap(final MultiSourceArgument<GridCoverage>.Unwrapper unwrapper) { + if (unwrapper.source instanceof BandAggregateGridCoverage) { + final var aggregate = (BandAggregateGridCoverage) unwrapper.source; + unwrapper.applySubset(aggregate.sources, aggregate.bandsPerSource, GridCoverage::getSampleDimensions); + } + } + /** * Returns the data type identifying the primitive type used for storing sample values in each band. */ diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java index d0c1ecb357..b952560d91 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java +++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java @@ -276,23 +276,24 @@ public class GridCoverageProcessor implements Cloneable { * Allows the replacement of an operation by a more efficient one. * This optimization is enabled by default. * - * <div class="note"><b>Example:</b> - * if the {@link #resample(GridCoverage, GridGeometry) resample(…)} method is invoked with parameter values + * <h4>Example</h4> + * If the {@link #resample(GridCoverage, GridGeometry) resample(…)} method is invoked with parameter values * that cause the resampling to be a translation of the grid by an integer amount of cells, then by default * {@link GridCoverageProcessor} will use the {@link #shiftGrid(GridCoverage, long[]) shiftGrid(…)} - * algorithm instead. This option can be cleared for forcing a full resampling operation in all cases.</div> + * algorithm instead. This option can be cleared for forcing a full resampling operation in all cases. */ REPLACE_OPERATION, /** * Allows the replacement of source parameter by a more fundamental source. + * This replacement may change the results, but usually with better accuracy. * This optimization is enabled by default. * - * <div class="note"><b>Example:</b> - * if the {@link #resample(GridCoverage, GridGeometry) resample(…)} method is invoked with a source + * <h4>Example</h4> + * If the {@link #resample(GridCoverage, GridGeometry) resample(…)} method is invoked with a source * grid coverage which is itself the result of a previous resampling, then instead of resampling an * already resampled coverage, by default {@link GridCoverageProcessor} will resample the original - * coverage. This option can be cleared for disabling that replacement.</div> + * coverage. This option can be cleared for disabling that replacement. */ REPLACE_SOURCE } @@ -787,6 +788,7 @@ public class GridCoverageProcessor implements Cloneable { */ public GridCoverage aggregateRanges(GridCoverage[] sources, int[][] bandsPerSource) { final var aggregate = new MultiSourceArgument<>(sources, bandsPerSource); + aggregate.unwrap(BandAggregateGridCoverage::unwrap); aggregate.validate(GridCoverage::getSampleDimensions); if (aggregate.isIdentity()) { return aggregate.sources()[0]; diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/MultiSourceArgument.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/MultiSourceArgument.java index 3f3c1fe100..8044bd7d2c 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/MultiSourceArgument.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/MultiSourceArgument.java @@ -25,6 +25,7 @@ import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.ToIntFunction; +import java.lang.reflect.Array; import org.opengis.referencing.datum.PixelInCell; import org.apache.sis.coverage.SampleDimension; import org.apache.sis.coverage.grid.GridGeometry; @@ -243,6 +244,51 @@ public final class MultiSourceArgument<S> { this.bands = bands; } + /** + * Invoke {@code apply(…)} with components that are a subset of an existing aggregate. + * This is a helper method for decomposing an aggregate into its component. + * + * @param sources all sources of the aggregate to decompose. + * @param bandsPerSource selected bands of the aggregate to decompose. May contain null elements. + * @param getter same getter as {@link #validate(Function)}, used for getting the number of bands. + */ + public void applySubset(final S[] sources, final int[][] bandsPerSource, + final Function<S, List<SampleDimension>> getter) + { + @SuppressWarnings("unchecked") + final S[] components = (S[]) Array.newInstance(sources.getClass().getComponentType(), bands.length); + final int[][] componentBands = new int[bands.length][]; + + int sourceIndex = -1; + int[] sourceBands = null; // Value of `bandsPerSource[sourceIndex]`. + S component = null; // Value of `sources[sourceIndex]` potentially used as component. + int lower=0, upper=0; // Range of band indices in which `component` is valid. + for (int i=0; i<bands.length; i++) { + int band = bands[i]; + if (band < lower) { + lower = upper = 0; + sourceIndex = -1; + } + while (band >= upper) { + component = sources[++sourceIndex]; + sourceBands = bandsPerSource[sourceIndex]; + lower = upper; + upper += (sourceBands != null) ? sourceBands.length : getter.apply(component).size(); + } + band -= lower; + if (sourceBands != null) { + band = sourceBands[band]; + } + componentBands[i] = new int[] {band}; + components[i] = component; + } + /* + * Tne same component may be repeated many times in the `sources` array, each time with only one band specified. + * We rely on the encloding class post-processing for merging multiple references to a single one for each source. + */ + apply(components, componentBands); + } + /** * Notifies the enclosing {@code MultiSourceArgument} that the {@linkplain #source} * shall be replaced by deeper sources. The {@code componentBands} array specifies diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/BandAggregateGridCoverageTest.java b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/BandAggregateGridCoverageTest.java index d1818094e6..de3d034641 100644 --- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/BandAggregateGridCoverageTest.java +++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/BandAggregateGridCoverageTest.java @@ -119,6 +119,20 @@ public final class BandAggregateGridCoverageTest extends TestCase { 104, 204, 303, 105, 205, 304); } + /** + * Tests aggregation of two coverages where one of them is itself another aggregation. + */ + @Test + public void testNestedAggregation() { + final GridCoverage c1 = createCoverage(-2, 4, 3, -1, 100, 200); + final GridCoverage c2 = createCoverage( 0, 2, 2, +1, 300); + final GridCoverage c3 = createCoverage(-2, 4, 3, -1, 400); + final GridCoverage cr = processor.aggregateRanges( + processor.aggregateRanges(c1, c2), c3); + assertPixelsEqual(cr, 101, 201, 300, 401, 102, 202, 301, 402, + 104, 204, 303, 404, 105, 205, 304, 405); + } + /** * Returns a two-dimensional grid extents with the given bounding box. * The maximal coordinates are exclusive. diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/BandAggregateGridResource.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/BandAggregateGridResource.java index 5fecf76a4f..f7d502f9b8 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/BandAggregateGridResource.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/BandAggregateGridResource.java @@ -151,6 +151,7 @@ final class BandAggregateGridResource extends AbstractGridCoverageResource imple super(parentListeners, false); try { final var aggregate = new MultiSourceArgument<GridCoverageResource>(sources, bandsPerSource); + aggregate.unwrap(BandAggregateGridResource::unwrap); aggregate.validate(BandAggregateGridResource::range); this.sources = aggregate.sources(); this.gridGeometry = aggregate.domain(BandAggregateGridResource::domain); @@ -186,6 +187,20 @@ final class BandAggregateGridResource extends AbstractGridCoverageResource imple } } + /** + * Returns potentially deeper sources than the user supplied coverage resource. + * This method unwraps {@link BandAggregateGridResource} for making possible to detect that + * two consecutive resources are actually the same resource, with only different bands selected. + * + * @param unwrapper a handler where to supply the result of an aggregate decomposition. + */ + private static void unwrap(final MultiSourceArgument<GridCoverageResource>.Unwrapper unwrapper) { + if (unwrapper.source instanceof BandAggregateGridResource) { + final var aggregate = (BandAggregateGridResource) unwrapper.source; + unwrapper.applySubset(aggregate.sources, aggregate.bandsPerSource, BandAggregateGridResource::range); + } + } + /** Not applicable to this implementation. */ @Override public Resource apply(MergeStrategy strategy) {return this;}