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;}
 

Reply via email to