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/15 15:51:38 UTC

[sis] 04/04: If the resources to aggregate are instances of `MemoryGridResource`, aggregate directly the underlying `GridCoverage` instances.

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 0e2089b1900656085bf926a2fe60ebeaf7363bc7
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Apr 15 17:50:45 2023 +0200

    If the resources to aggregate are instances of `MemoryGridResource`,
    aggregate directly the underlying `GridCoverage` instances.
---
 .../aggregate/BandAggregateGridResource.java       | 39 ++++++++++++++--
 .../sis/storage/aggregate/CoverageAggregator.java  | 52 ++++++++++++++++++----
 .../aggregate/BandAggregateGridResourceTest.java   | 34 ++++++++++++--
 3 files changed, 110 insertions(+), 15 deletions(-)

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 f7d502f9b8..301f28a259 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
@@ -28,13 +28,14 @@ import org.apache.sis.coverage.grid.GridCoverageProcessor;
 import org.apache.sis.coverage.grid.IllegalGridGeometryException;
 import org.apache.sis.internal.coverage.MultiSourceArgument;
 import org.apache.sis.internal.coverage.RangeArgument;
+import org.apache.sis.internal.storage.MetadataBuilder;
+import org.apache.sis.internal.storage.MemoryGridResource;
 import org.apache.sis.storage.Resource;
 import org.apache.sis.storage.GridCoverageResource;
 import org.apache.sis.storage.AbstractGridCoverageResource;
 import org.apache.sis.storage.RasterLoadingStrategy;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.event.StoreListeners;
-import org.apache.sis.internal.storage.MetadataBuilder;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.util.collection.BackingStoreException;
 
@@ -139,7 +140,7 @@ final class BandAggregateGridResource extends AbstractGridCoverageResource imple
      * @param  parentListeners  listeners of the parent resource, or {@code null} if none.
      * @param  sources          resources whose bands shall be aggregated, in order. At least one resource must be provided.
      * @param  bandsPerSource   sample dimensions for each source. May be {@code null} or may contain {@code null} elements.
-     * @param  processor        the processor to use for creating grid coverages, or {@code null} for a default processor.
+     * @param  processor        the processor to use for creating grid coverages.
      * @throws DataStoreException if an error occurred while fetching the grid geometry or sample dimensions from a resource.
      * @throws IllegalGridGeometryException if a grid geometry is not compatible with the others.
      * @throws IllegalArgumentException if some band indices are duplicated or outside their range of validity.
@@ -157,7 +158,7 @@ final class BandAggregateGridResource extends AbstractGridCoverageResource imple
             this.gridGeometry     = aggregate.domain(BandAggregateGridResource::domain);
             this.sampleDimensions = List.copyOf(aggregate.ranges());
             this.bandsPerSource   = aggregate.bandsPerSource(false);
-            this.processor        = (processor != null) ? processor : new GridCoverageProcessor();
+            this.processor        = processor;
         } catch (BackingStoreException e) {
             throw e.unwrapOrRethrow(DataStoreException.class);
         }
@@ -201,6 +202,38 @@ final class BandAggregateGridResource extends AbstractGridCoverageResource imple
         }
     }
 
+    /**
+     * Creates a new range aggregation of grid coverage resources,
+     * potentially unwrapping in-memory resources for efficiency.
+     *
+     * @param  parentListeners  listeners of the parent resource, or {@code null} if none.
+     * @param  sources          resources whose bands shall be aggregated, in order. At least one resource must be provided.
+     * @param  bandsPerSource   sample dimensions for each source. May be {@code null} or may contain {@code null} elements.
+     * @param  processor        the processor to use for creating grid coverages.
+     * @return the band aggregated grid resource.
+     * @throws DataStoreException if an error occurred while fetching the grid geometry or sample dimensions from a resource.
+     * @throws IllegalGridGeometryException if a grid geometry is not compatible with the others.
+     * @throws IllegalArgumentException if some band indices are duplicated or outside their range of validity.
+     */
+    static GridCoverageResource create(final StoreListeners parentListeners,
+            final GridCoverageResource[] sources, final int[][] bandsPerSource,
+            final GridCoverageProcessor processor) throws DataStoreException
+    {
+        GridCoverageResource source = sources[0];
+        if (source instanceof MemoryGridResource) {
+            final var coverages = new GridCoverage[sources.length];
+            int i = 0;
+            do {
+                coverages[i] = ((MemoryGridResource) source).coverage;
+                if (++i >= sources.length) {
+                    GridCoverage coverage = processor.aggregateRanges(coverages, bandsPerSource);
+                    return new MemoryGridResource(parentListeners, coverage);
+                }
+            } while ((source = sources[i]) instanceof MemoryGridResource);
+        }
+        return new BandAggregateGridResource(parentListeners, sources, bandsPerSource, processor);
+    }
+
     /** Not applicable to this implementation. */
     @Override public Resource apply(MergeStrategy strategy) {return this;}
 
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/CoverageAggregator.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/CoverageAggregator.java
index edd4825342..828c045584 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/CoverageAggregator.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/CoverageAggregator.java
@@ -132,7 +132,7 @@ public final class CoverageAggregator extends Group<GroupBySample> {
     private GridCoverageProcessor processor;
 
     /**
-     * Creates an initially empty aggregator with no listeners and a default grid coverage processor.
+     * Creates an initially empty aggregator with no listeners.
      *
      * @since 1.4
      */
@@ -141,7 +141,7 @@ public final class CoverageAggregator extends Group<GroupBySample> {
     }
 
     /**
-     * Creates an initially empty aggregator.
+     * Creates an initially empty aggregator with the given listeners.
      *
      * @param listeners  listeners of the parent resource, or {@code null} if none.
      *        This is usually the listeners of the {@link org.apache.sis.storage.DataStore}.
@@ -151,6 +151,18 @@ public final class CoverageAggregator extends Group<GroupBySample> {
         aggregates = new HashMap<>();
     }
 
+    /**
+     * Creates an initially empty aggregator with the given listeners and coverage processor.
+     *
+     * @param listeners  listeners of the parent resource, or {@code null} if none.
+     * @param processor  the processor to use if an operation needs to be applied on coverages,
+     *                   or {@code null} for a default processor.
+     */
+    CoverageAggregator(final StoreListeners listeners, final GridCoverageProcessor processor) {
+        this(listeners);
+        this.processor = processor;
+    }
+
     /**
      * Creates a name for this group for use in metadata (not a persistent identifier).
      * This is used only if this aggregator find resources having different sample dimensions.
@@ -295,12 +307,32 @@ public final class CoverageAggregator extends Group<GroupBySample> {
      * This method combines homogeneous grid coverage resources by "stacking" their sample dimensions (bands).
      * The grid geometry is typically the same for all resources, but some variations described below are allowed.
      * The number of sample dimensions in the aggregated coverage is the sum of the number of sample dimensions in
-     * each individual resource, unless a subset of sample dimensions is specified.
+     * each individual resource.
+     *
+     * <p>This convenience method delegates to {@link #addRangeAggregate(GridCoverageResource[], int[][])}.
+     * See that method for more information on restrictions.</p>
+     *
+     * @param  sources  resources whose bands shall be aggregated, in order.
+     * @throws DataStoreException if an error occurred while fetching the grid geometry or sample dimensions from a resource.
+     * @throws IllegalGridGeometryException if a grid geometry is not compatible with the others.
      *
-     * <p>The {@code bandsPerSource} argument specifies the bands to select in each resource.
-     * That array can be {@code null} for selecting all bands in all resources,
-     * or may contain {@code null} elements for selecting all bands of the corresponding resource.
-     * An empty array element (i.e. zero band to select) discards the corresponding resource.</p>
+     * @see #getColorizer()
+     * @see GridCoverageProcessor#aggregateRanges(GridCoverage...)
+     *
+     * @since 1.4
+     */
+    public void addRangeAggregate(final GridCoverageResource... sources) throws DataStoreException {
+        addRangeAggregate(sources, (int[][]) null);
+    }
+
+    /**
+     * Adds a resource whose range is the aggregation of the specified bands of a sequence of resources.
+     * This method performs the same work than {@link #addRangeAggregate(GridCoverageResource...)},
+     * but with the possibility to specify the sample dimensions to retain in each source coverage.
+     * The {@code bandsPerSource} argument specifies the sample dimensions to keep, in order.
+     * That array can be {@code null} for selecting all sample dimensions in all source coverages,
+     * or may contain {@code null} elements for selecting all sample dimensions of the corresponding coverage.
+     * An empty array element (i.e. zero sample dimension to select) discards the corresponding source coverage.
      *
      * <h4>Restrictions</h4>
      * <ul>
@@ -314,7 +346,7 @@ public final class CoverageAggregator extends Group<GroupBySample> {
      *
      * Some of those restrictions may be relaxed in future Apache SIS versions.
      *
-     * @param  sources         resources whose bands shall be aggregated, in order. At least one resource must be provided.
+     * @param  sources         resources whose bands shall be aggregated, in order.
      * @param  bandsPerSource  sample dimensions for each source. May be {@code null} or may contain {@code null} elements.
      * @throws DataStoreException if an error occurred while fetching the grid geometry or sample dimensions from a resource.
      * @throws IllegalGridGeometryException if a grid geometry is not compatible with the others.
@@ -326,7 +358,9 @@ public final class CoverageAggregator extends Group<GroupBySample> {
      * @since 1.4
      */
     public void addRangeAggregate(final GridCoverageResource[] sources, final int[][] bandsPerSource) throws DataStoreException {
-        add(new BandAggregateGridResource(listeners, sources, bandsPerSource, processor()));
+        if (sources.length != 0) {
+            add(BandAggregateGridResource.create(listeners, sources, bandsPerSource, processor()));
+        }
     }
 
     /**
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/storage/aggregate/BandAggregateGridResourceTest.java b/storage/sis-storage/src/test/java/org/apache/sis/storage/aggregate/BandAggregateGridResourceTest.java
index a1669ab993..9af75669e8 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/storage/aggregate/BandAggregateGridResourceTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/storage/aggregate/BandAggregateGridResourceTest.java
@@ -30,6 +30,7 @@ import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.BufferedGridCoverage;
+import org.apache.sis.coverage.grid.GridCoverageProcessor;
 import org.apache.sis.internal.storage.MemoryGridResource;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.GridCoverageResource;
@@ -59,10 +60,16 @@ public final class BandAggregateGridResourceTest extends TestCase {
      */
     private final GridGeometry domain;
 
+    /**
+     * The processor to give to {@link BandAggregateGridResource} constructor.
+     */
+    private final GridCoverageProcessor processor;
+
     /**
      * Creates a new test case.
      */
     public BandAggregateGridResourceTest() {
+        processor = new GridCoverageProcessor();
         domain = new GridGeometry(new GridExtent(WIDTH, HEIGHT), PixelInCell.CELL_CENTER,
                                   MathTransforms.identity(2), HardCodedCRS.WGS84);
     }
@@ -75,8 +82,8 @@ public final class BandAggregateGridResourceTest extends TestCase {
      * @throws DataStoreException if an error occurred while fetching the grid geometry or sample dimensions from a resource.
      * @throws IllegalGridGeometryException if a grid geometry is not compatible with the others.
      */
-    private static BandAggregateGridResource create(final GridCoverageResource... sources) throws DataStoreException {
-        return new BandAggregateGridResource(null, sources, null, null);
+    private BandAggregateGridResource create(final GridCoverageResource... sources) throws DataStoreException {
+        return new BandAggregateGridResource(null, sources, null, processor);
     }
 
     /**
@@ -120,7 +127,7 @@ public final class BandAggregateGridResourceTest extends TestCase {
         final LocalName testName = Names.createLocalName(null, null, "test-name");
         aggregation = new BandAggregateGridResource(null,
                 new GridCoverageResource[] {firstAndSecondBands, thirdAndFourthBands, fifthAndSixthBands},
-                new int[][] {null, new int[] {1, 0}, new int[] {1}}, null);
+                new int[][] {null, new int[] {1, 0}, new int[] {1}}, processor);
 
         aggregation.setIdentifier(testName);
         assertEquals(testName, aggregation.getIdentifier().orElse(null));
@@ -128,6 +135,27 @@ public final class BandAggregateGridResourceTest extends TestCase {
         assertAllPixelsEqual(aggregation.read(null, 2, 4), 104, 106);
     }
 
+    /**
+     * Tests aggregation of resources using {@link CoverageAggregator}.
+     *
+     * @throws DataStoreException if an error occurred while reading a resource.
+     */
+    @Test
+    public void usingCoverageAggregator() throws DataStoreException {
+        final GridCoverageResource first  = singleValuePerBand(17);
+        final GridCoverageResource second = singleValuePerBand(23);
+        final var aggregator = new CoverageAggregator(null, processor);
+        aggregator.addRangeAggregate(first, second);
+        /*
+         * If the result is not an instance of `MemoryGridResource`,
+         * this is a bug in `BandAggregateGridResource.create(…)`.
+         */
+        final var aggregation = (MemoryGridResource) aggregator.build(null);
+        assertAllPixelsEqual(aggregation.read(null), 17, 23);
+        assertAllPixelsEqual(aggregation.read(null, 0), 17);
+        assertAllPixelsEqual(aggregation.read(null, 1), 23);
+    }
+
     /**
      * Creates a new grid coverage resource with bands having the given values.
      * The length of the {@code bandValues} array is the number of bands to create.