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:36 UTC
[sis] 02/04: Remove `BandAggregateGridResource` from public API. Instead, a new method is added in `CoverageAggregator`.
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 9c391c16b70d3408d30160b56bbeb44fd1db8f38
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Apr 15 15:28:51 2023 +0200
Remove `BandAggregateGridResource` from public API.
Instead, a new method is added in `CoverageAggregator`.
---
.../sis/coverage/grid/GridCoverageProcessor.java | 2 +-
.../apache/sis/internal/storage/folder/Store.java | 2 +-
.../sis/storage/aggregate/AggregatedResource.java | 22 ++-
.../aggregate/BandAggregateGridResource.java | 59 +++---
.../aggregate/ConcatenatedGridResource.java | 29 ++-
.../sis/storage/aggregate/CoverageAggregator.java | 210 ++++++++++++++++-----
.../apache/sis/storage/aggregate/GridSlice.java | 8 +-
.../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 | 2 +-
.../sis/storage/aggregate/MergeStrategy.java | 7 +-
.../aggregate/BandAggregateGridResourceTest.java | 19 +-
.../storage/aggregate/CoverageAggregatorTest.java | 6 +-
15 files changed, 317 insertions(+), 98 deletions(-)
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 d04f92c113..d0c1ecb357 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
@@ -773,7 +773,7 @@ public class GridCoverageProcessor implements Cloneable {
* <li>All coverages shall use the same data type in their rendered image.</li>
* </ul>
*
- * Some of those restrictions may be relaxed in future versions.
+ * Some of those restrictions may be relaxed in future Apache SIS versions.
*
* @param sources coverages whose bands shall be aggregated, in order. At least one coverage must be provided.
* @param bandsPerSource bands to use for each source coverage, in order. May contain {@code null} elements.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
index 0e1d24c903..8fe4db9fae 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
@@ -414,7 +414,7 @@ class Store extends DataStore implements StoreResource, UnstructuredAggregate, D
if (structuredView == null) {
final CoverageAggregator aggregator = new CoverageAggregator(listeners);
aggregator.addComponents(this);
- structuredView = aggregator.build();
+ structuredView = aggregator.build(identifier);
}
return structuredView;
}
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/AggregatedResource.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/AggregatedResource.java
index 1683a313c1..515345c79a 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/AggregatedResource.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/AggregatedResource.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.storage.aggregate;
+import org.opengis.util.GenericName;
import org.apache.sis.storage.Resource;
@@ -23,14 +24,23 @@ import org.apache.sis.storage.Resource;
* The result of an aggregation computed by {@link CoverageAggregator}.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 1.3
*/
interface AggregatedResource {
+ /**
+ * Sets the identifier of the resource.
+ * This method is invoked by {@link CoverageAggregator#build(GenericName)} for assigning an identifier
+ * on the final result only. No identifier should be assigned on intermediate results (i.e. components).
+ *
+ * @param identifier new identifier of the resource.
+ */
+ void setIdentifier(GenericName identifier);
+
/**
* Sets the name of the resource.
* This method is invoked by {@link GroupAggregate#simplify(CoverageAggregator)} when
- * a aggregate node is excluded and we want to inherit the name of the excluded node.
+ * an aggregate node is excluded and we want to inherit the name of the excluded node.
* It should happen before the resource is published.
*
* @param name new name of the resource.
@@ -43,14 +53,16 @@ interface AggregatedResource {
* Otherwise returns a new resource. This resource is not modified by this method
* call because this method can be invoked after this resource has been published.
*
- * <div class="note"><b>API design note:</b>
- * we could try to design a common API for {@link org.apache.sis.storage.RasterLoadingStrategy}
+ * <h4>API design note</h4>
+ * We could try to design a common API for {@link org.apache.sis.storage.RasterLoadingStrategy}
* and {@link MergeStrategy}. But the former changes the state of the resource while the latter
* returns a new resource. This is because {@code RasterLoadingStrategy} does not change data,
- * while {@link MergeStrategy} can change the data obtained from the resource.</div>
+ * while {@link MergeStrategy} can change the data obtained from the resource.
*
* @param strategy the new merge strategy to apply.
* @return resource using the specified strategy (may be {@code this}).
+ *
+ * @see MergeStrategy#apply(Resource)
*/
Resource apply(MergeStrategy strategy);
}
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 d32e47d19a..5fecf76a4f 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
@@ -33,6 +33,7 @@ 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;
@@ -40,7 +41,7 @@ import org.apache.sis.util.collection.BackingStoreException;
/**
* A resource whose range is the aggregation of the ranges of a sequence of resources.
- * This class combines homogeneous {@link GridCoverageResource}s by "stacking" their bands.
+ * This class combines homogeneous {@link GridCoverageResource}s by "stacking" their sample dimensions.
* 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.
@@ -49,7 +50,7 @@ import org.apache.sis.util.collection.BackingStoreException;
* <ul>
* <li>All resources shall use the same coordinate reference system (CRS).</li>
* <li>All resources shall have the same {@linkplain GridCoverageResource#getGridGeometry() domain}, except
- * for the grid extent and the translation terms which can vary by integer amounts of grid cells.</li>
+ * for the grid extent and the translation terms which can vary by integer numbers of grid cells.</li>
* <li>All grid extents shall intersect and the intersection area shall be non-empty.</li>
* <li>If coverage data are stored in {@link java.awt.image.RenderedImage} instances,
* then all images shall use the same data type.</li>
@@ -59,17 +60,17 @@ import org.apache.sis.util.collection.BackingStoreException;
* @author Martin Desruisseaux (Geomatys)
* @version 1.4
*
- * @see GridCoverageProcessor#aggregateRanges(GridCoverage[], int[][])
+ * @see CoverageAggregator#addRangeAggregate(GridCoverageResource[], int[][])
*
* @since 1.4
*/
-public class BandAggregateGridResource extends AbstractGridCoverageResource {
+final class BandAggregateGridResource extends AbstractGridCoverageResource implements AggregatedResource {
/**
- * Identifier of this resource.
+ * Persistent identifier of this resource, or {@code null} if none.
*
* @see #getIdentifier()
*/
- private final GenericName name;
+ private GenericName identifier;
/**
* The source grid coverage resources.
@@ -113,18 +114,6 @@ public class BandAggregateGridResource extends AbstractGridCoverageResource {
*/
private final GridCoverageProcessor processor;
- /**
- * Creates a new aggregation of all sample dimensions of all specified grid coverage resources.
- * The new resource has no name and no parent, and use a default processor with default color model.
- *
- * @param sources resources whose bands shall be aggregated, in order. At least one resource must be provided.
- * @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.
- */
- public BandAggregateGridResource(final GridCoverageResource... sources) throws DataStoreException {
- this(null, null, sources, null, null);
- }
-
/**
* Creates a new range aggregation of grid coverage resources.
* The {@linkplain #getSampleDimensions() list of sample dimensions} of the aggregated resource
@@ -147,24 +136,22 @@ public class BandAggregateGridResource extends AbstractGridCoverageResource {
* The intersection of the domain of all resources shall be non-empty,
* and all resources shall use the same data type in their rendered image.
*
- * @param parent the parent resource, or {@code null} if none.
- * @param name name of the combined grid coverage 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 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.
* @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.
*/
- public BandAggregateGridResource(final Resource parent, final GenericName name,
+ BandAggregateGridResource(final StoreListeners parentListeners,
final GridCoverageResource[] sources, final int[][] bandsPerSource,
final GridCoverageProcessor processor) throws DataStoreException
{
- super(parent);
+ super(parentListeners, false);
try {
final var aggregate = new MultiSourceArgument<GridCoverageResource>(sources, bandsPerSource);
aggregate.validate(BandAggregateGridResource::range);
- this.name = name;
this.sources = aggregate.sources();
this.gridGeometry = aggregate.domain(BandAggregateGridResource::domain);
this.sampleDimensions = List.copyOf(aggregate.ranges());
@@ -199,16 +186,32 @@ public class BandAggregateGridResource extends AbstractGridCoverageResource {
}
}
+ /** Not applicable to this implementation. */
+ @Override public Resource apply(MergeStrategy strategy) {return this;}
+
+ /** Not applicable to this implementation. */
+ @Override public void setName(String name) {}
+
+ /**
+ * Sets the identifier of this resource. This is invoked by {@link CoverageAggregator} only
+ * and should not be invoked anymore after this resource has been returned to the user.
+ *
+ * @param identifier identifier of the combined grid coverage resource, or {@code null} if none.
+ */
+ @Override
+ public void setIdentifier(final GenericName identifier) {
+ this.identifier = identifier;
+ }
+
/**
* Returns the resource identifier if available.
- * This is the name specified at construction time.
*
* @return an identifier for the band aggregation.
* @throws DataStoreException if the identifier cannot be obtained.
*/
@Override
public Optional<GenericName> getIdentifier() throws DataStoreException {
- return Optional.ofNullable(name);
+ return Optional.ofNullable(identifier);
}
/**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java
index fb97d7f7c0..d6558b14f7 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/ConcatenatedGridResource.java
@@ -20,6 +20,7 @@ import java.util.List;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
+import org.opengis.util.GenericName;
import org.opengis.geometry.Envelope;
import org.opengis.metadata.Metadata;
import org.opengis.referencing.operation.TransformException;
@@ -55,7 +56,14 @@ import org.apache.sis.util.ArraysExt;
*/
final class ConcatenatedGridResource extends AbstractGridCoverageResource implements AggregatedResource {
/**
- * Name of this resource.
+ * The identifier for this aggregate, or {@code null} if none.
+ * This is optionally supplied by users for their own purposes.
+ * There is no default value.
+ */
+ private GenericName identifier;
+
+ /**
+ * Name of this resource to use in metadata.
*/
private String name;
@@ -198,6 +206,7 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem
/**
* Returns a coverage with the same data than this coverage but a different merge strategy.
+ * This is the implementation of {@link MergeStrategy#apply(Resource)} public method.
*/
@Override
public final synchronized Resource apply(final MergeStrategy s) {
@@ -214,6 +223,24 @@ final class ConcatenatedGridResource extends AbstractGridCoverageResource implem
this.name = name;
}
+ /**
+ * Sets the identifier of this resource.
+ */
+ @Override
+ public void setIdentifier(final GenericName identifier) {
+ this.identifier = identifier;
+ }
+
+
+ /**
+ * Returns the resource persistent identifier as specified by the
+ * user in {@link CoverageAggregator}. There is no default value.
+ */
+ @Override
+ public Optional<GenericName> getIdentifier() {
+ return Optional.ofNullable(identifier);
+ }
+
/**
* Creates when first requested the metadata about this resource.
*/
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 cdba29f5e0..edd4825342 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
@@ -27,7 +27,9 @@ import java.util.IdentityHashMap;
import java.util.Collections;
import java.util.Optional;
import java.util.stream.Stream;
+import org.opengis.util.GenericName;
import org.opengis.referencing.operation.NoninvertibleTransformException;
+import org.apache.sis.image.Colorizer;
import org.apache.sis.storage.Resource;
import org.apache.sis.storage.Aggregate;
import org.apache.sis.storage.DataStore;
@@ -36,6 +38,7 @@ import org.apache.sis.storage.DataStoreContentException;
import org.apache.sis.storage.GridCoverageResource;
import org.apache.sis.storage.event.StoreListeners;
import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.coverage.grid.GridCoverageProcessor;
import org.apache.sis.coverage.grid.IllegalGridGeometryException;
import org.apache.sis.coverage.SubspaceNotSpecifiedException;
import org.apache.sis.util.collection.BackingStoreException;
@@ -43,12 +46,19 @@ import org.apache.sis.util.collection.BackingStoreException;
/**
* Creates a grid coverage resource from an aggregation of an arbitrary number of other resources.
+ * This class accepts heterogeneous resources (a <cite>data lake</cite>), organizes them in a tree
+ * of resources as described in the next section, then performs different kinds of aggregation:
*
- * <div class="note"><b>Example:</b>
- * a collection of {@link GridCoverage} instances may represent the same phenomenon
- * (for example Sea Surface Temperature) over the same geographic area but at different dates and times.
- * {@link CoverageAggregator} can be used for building a single data cube with a time axis.</div>
+ * <ul class="verbose">
+ * <li><b>Creation of a data cube from a collection of slices:</b>
+ * If a collection of {@link GridCoverageResource} instances represent the same phenomenon
+ * (for example Sea Surface Temperature) over the same geographic area but at different dates and times.
+ * {@code CoverageAggregator} can be used for building a single data cube with a time axis.</li>
+ * <li><b>Aggregation of bands:</b>
+ * Resources having different sample dimensions can be combined in a single resource.</li>
+ * </ul>
*
+ * <h2>Generated resource tree</h2>
* All source coverages should share the same CRS and have the same ranges (sample dimensions).
* If this is not the case, then the source coverages will be grouped in different aggregates
* with an uniform CRS and set of ranges in each sub-aggregates.
@@ -100,7 +110,7 @@ public final class CoverageAggregator extends Group<GroupBySample> {
private final StoreListeners listeners;
/**
- * The aggregates which where the sources of components added during a call to {@link #addComponents(Aggregate)}.
+ * The aggregates which were the sources of components added during a call to {@link #addComponents(Aggregate)}.
* This is used for reusing existing aggregates instead of {@link GroupAggregate} when the content is the same.
*/
private final Map<Set<Resource>, Queue<Aggregate>> aggregates;
@@ -113,6 +123,23 @@ public final class CoverageAggregator extends Group<GroupBySample> {
*/
private volatile MergeStrategy strategy;
+ /**
+ * The processor to use for creating grid coverages. Created only when first needed.
+ * This is used for specifying the color model when creating band aggregated resources.
+ *
+ * @see #processor()
+ */
+ private GridCoverageProcessor processor;
+
+ /**
+ * Creates an initially empty aggregator with no listeners and a default grid coverage processor.
+ *
+ * @since 1.4
+ */
+ public CoverageAggregator() {
+ this(null);
+ }
+
/**
* Creates an initially empty aggregator.
*
@@ -125,8 +152,9 @@ public final class CoverageAggregator extends Group<GroupBySample> {
}
/**
- * Returns a name of the aggregate to be created.
+ * 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.
+ * In such case, this name will be the default name of the root resource.
*
* @param locale the locale for the name to return, or {@code null} for the default.
* @return a name which can be used as aggregation name, or {@code null} if none.
@@ -136,29 +164,6 @@ public final class CoverageAggregator extends Group<GroupBySample> {
return (listeners != null) ? listeners.getSourceName() : null;
}
- /**
- * Adds all grid resources provided by the given stream. This method can be invoked from any thread.
- * It delegates to {@link #add(GridCoverageResource)} for each element in the stream.
- *
- * @param resources resources to add.
- * @throws DataStoreException if a resource cannot be used.
- *
- * @see #add(GridCoverageResource)
- */
- public void addAll(final Stream<? extends GridCoverageResource> resources) throws DataStoreException {
- try {
- resources.forEach((resource) -> {
- try {
- add(resource);
- } catch (DataStoreException e) {
- throw new BackingStoreException(e);
- }
- });
- } catch (BackingStoreException e) {
- throw e.unwrapOrRethrow(DataStoreException.class);
- }
- }
-
/**
* Adds the given coverage. This method can be invoked from any thread.
*
@@ -168,7 +173,7 @@ public final class CoverageAggregator extends Group<GroupBySample> {
*/
public void add(final GridCoverage coverage) {
final GroupBySample bySample = GroupBySample.getOrAdd(members, coverage.getSampleDimensions());
- final GridSlice slice = new GridSlice(coverage);
+ final GridSlice slice = new GridSlice(listeners, coverage);
final List<GridSlice> slices;
try {
slices = slice.getList(bySample.members, strategy).members;
@@ -183,6 +188,7 @@ public final class CoverageAggregator extends Group<GroupBySample> {
/**
* Adds the given resource. This method can be invoked from any thread.
* This method does <em>not</em> recursively decomposes an {@link Aggregate} into its component.
+ * If such decomposition is desired, see {@link #addComponents(Aggregate)} instead.
*
* @param resource resource to add.
* @throws DataStoreException if the resource cannot be used.
@@ -226,12 +232,12 @@ public final class CoverageAggregator extends Group<GroupBySample> {
hasDuplicated = true; // Should never happen, but we are paranoiac.
}
}
+ /*
+ * Remember the aggregate that we just added. If after the user finished to add all components,
+ * we discover that we still have the exact same set of components than the given aggregate,
+ * then we will use `resource` instead of creating a `GroupAggregate` with the same content.
+ */
if (!(hasDuplicated || components.isEmpty())) {
- /*
- * We should not have 2 aggregates with the same components.
- * But if it happens anyway, put the aggregates in a queue.
- * Each aggregate will be used at most once.
- */
synchronized (aggregates) {
aggregates.computeIfAbsent(components, (k) -> new ArrayDeque<>(1)).add(resource);
}
@@ -260,6 +266,95 @@ public final class CoverageAggregator extends Group<GroupBySample> {
return Optional.empty();
}
+ /**
+ * Adds all grid resources provided by the given stream. This method can be invoked from any thread.
+ * It delegates to {@link #add(GridCoverageResource)} for each element in the stream.
+ * {@link Aggregate} instances are added as-is (not decomposed in their components).
+ *
+ * @param resources resources to add.
+ * @throws DataStoreException if a resource cannot be used.
+ *
+ * @see #add(GridCoverageResource)
+ */
+ public void addAll(final Stream<? extends GridCoverageResource> resources) throws DataStoreException {
+ try {
+ resources.forEach((resource) -> {
+ try {
+ add(resource);
+ } catch (DataStoreException e) {
+ throw new BackingStoreException(e);
+ }
+ });
+ } catch (BackingStoreException e) {
+ throw e.unwrapOrRethrow(DataStoreException.class);
+ }
+ }
+
+ /**
+ * Adds a resource whose range is the aggregation of the ranges of a sequence of resources.
+ * 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.
+ *
+ * <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>
+ *
+ * <h4>Restrictions</h4>
+ * <ul>
+ * <li>All resources shall use the same coordinate reference system (CRS).</li>
+ * <li>All resources shall have the same {@linkplain GridCoverageResource#getGridGeometry() domain}, except
+ * for the grid extent and the translation terms which can vary by integer numbers of grid cells.</li>
+ * <li>All grid extents shall intersect and the intersection area shall be non-empty.</li>
+ * <li>If coverage data are stored in {@link java.awt.image.RenderedImage} instances,
+ * then all images shall use the same data type.</li>
+ * </ul>
+ *
+ * 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 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.
+ * @throws IllegalArgumentException if some band indices are duplicated or outside their range of validity.
+ *
+ * @see #getColorizer()
+ * @see GridCoverageProcessor#aggregateRanges(GridCoverage[], int[][])
+ *
+ * @since 1.4
+ */
+ public void addRangeAggregate(final GridCoverageResource[] sources, final int[][] bandsPerSource) throws DataStoreException {
+ add(new BandAggregateGridResource(listeners, sources, bandsPerSource, processor()));
+ }
+
+ /**
+ * Returns the colorization algorithm to apply on computed images.
+ * This algorithm is used for all resources added by {@link #addRangeAggregate(GridCoverageResource[], int[][])},
+ *
+ * @return colorization algorithm to apply on computed image, or {@code null} for default.
+ *
+ * @since 1.4
+ */
+ public Colorizer getColorizer() {
+ return processor().getColorizer();
+ }
+
+ /**
+ * Sets the colorization algorithm to apply on computed images.
+ * This algorithm applies to all resources added by {@link #addRangeAggregate(GridCoverageResource[], int[][])},
+ * including resources already added before this method is invoked.
+ * If this method is never invoked, the default value is {@code null}.
+ *
+ * @param colorizer colorization algorithm to apply on computed image, or {@code null} for default.
+ *
+ * @since 1.4
+ */
+ public void setColorizer(final Colorizer colorizer) {
+ processor().setColorizer(colorizer);
+ }
+
/**
* Returns the algorithm to apply when more than one grid coverage can be found at the same grid index.
* This is the most recent value set by a call to {@link #setMergeStrategy(MergeStrategy)},
@@ -277,7 +372,7 @@ public final class CoverageAggregator extends Group<GroupBySample> {
* Sets the algorithm to apply when more than one grid coverage can be found at the same grid index.
* The new strategy applies to the <em>next</em> coverages to be added;
* previously added coverage may or may not be impacted by this change (see below).
- * Consequently, this method should usually be invoked before to add the first coverage.
+ * For avoiding hard-to-predict behavior, this method should be invoked before to add the first coverage.
*
* <h4>Effect on previously added coverages</h4>
* The merge strategy of previously added coverages is not modified by this method call, except
@@ -285,7 +380,7 @@ public final class CoverageAggregator extends Group<GroupBySample> {
* (data cube) than a coverage added after this method call.
* In such case, the strategy set by this call to {@code setMergeStrategy(…)} prevails.
* Said otherwise, the merge strategy of a data cube is the strategy which was active
- * at the time of the most recently added slice.
+ * at the time of the most recently added slice for that data cube.
*
* @param strategy new algorithm to apply for merging source coverages at the same grid index,
* or {@code null} if none.
@@ -294,20 +389,51 @@ public final class CoverageAggregator extends Group<GroupBySample> {
this.strategy = strategy;
}
+ /**
+ * Returns the processor to use for creating grid coverages.
+ */
+ private synchronized GridCoverageProcessor processor() {
+ if (processor == null) {
+ processor = new GridCoverageProcessor();
+ }
+ return processor;
+ }
+
/**
* Builds a resource which is the aggregation or concatenation of all components added to this aggregator.
* The returned resource will be an instance of {@link GridCoverageResource} if possible,
* or an instance of {@link Aggregate} if some heterogeneity in grid geometries or sample dimensions
- * prevent the concatenation of all coverages in a single resource.
+ * prevents the concatenation of all coverages in a single resource.
+ *
+ * <p>An identifier can optionally be specified for the resource.
+ * This identifier will be used if this method creates an aggregated or concatenated resource,
+ * but it will be ignored if this method returns directly one of the resource specified to the
+ * {@code add(…)} methods.</p>
*
- * <p>This method is not thread safe. If the {@code add(…)} and {@code addAll(…)} methods were invoked
- * in background threads, then all additions must be finished before this method is invoked.</p>
+ * <h4>Multi-threading</h4>
+ * If the {@code add(…)} and {@code addAll(…)} methods were invoked in background threads,
+ * then all additions must be finished before this method is invoked.
*
+ * @param identifier identifier to assign to the aggregated resource, or {@code null} if none.
* @return the aggregation or concatenation of all components added to this aggregator.
+ *
+ * @since 1.4
*/
- public Resource build() {
+ public synchronized Resource build(final GenericName identifier) {
final GroupAggregate aggregate = prepareAggregate(listeners);
aggregate.fillWithChildAggregates(this, GroupBySample::createComponents);
- return aggregate.simplify(this);
+ final Resource result = aggregate.simplify(this);
+ if (result instanceof AggregatedResource) {
+ ((AggregatedResource) result).setIdentifier(identifier);
+ }
+ return result;
+ }
+
+ /**
+ * @deprecated Replaced by {@link #build(GenericName)}.
+ */
+ @Deprecated
+ public Resource build() {
+ return build(null);
}
}
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GridSlice.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GridSlice.java
index 502cf6878a..09fb3c54e8 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GridSlice.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GridSlice.java
@@ -24,6 +24,7 @@ import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.apache.sis.referencing.operation.matrix.MatrixSIS;
+import org.apache.sis.storage.event.StoreListeners;
import org.apache.sis.storage.GridCoverageResource;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.coverage.grid.GridCoverage;
@@ -72,10 +73,11 @@ final class GridSlice {
/**
* Creates a new slice for the specified coverage.
*
- * @param slice coverage associated to this slice.
+ * @param parent listeners of the parent resource, or {@code null} if none.
+ * @param slice coverage associated to this slice.
*/
- GridSlice(final GridCoverage slice) {
- resource = new MemoryGridResource(null, slice);
+ GridSlice(final StoreListeners parent, final GridCoverage slice) {
+ resource = new MemoryGridResource(parent, slice);
geometry = slice.getGridGeometry();
offset = new long[geometry.getDimension()];
}
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/Group.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/Group.java
index d1ed30eb32..649eec3ae6 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/Group.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/Group.java
@@ -58,11 +58,11 @@ abstract class Group<E> {
}
/**
- * Creates a name for this group.
+ * Creates a name for this group for use in metadata (not a persistent identifier).
* This is used as the resource name if an aggregated resource needs to be created.
*
* @param locale the locale for the name to return, or {@code null} for the default.
- * @return a name which can be used as aggregation name.
+ * @return a name which can be used as aggregation name for metadata purposes.
*/
abstract String createName(Locale locale);
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupAggregate.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupAggregate.java
index e46b8b2a52..af653cb63d 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupAggregate.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupAggregate.java
@@ -20,6 +20,7 @@ import java.util.List;
import java.util.Collection;
import java.util.Optional;
import java.util.function.BiConsumer;
+import org.opengis.util.GenericName;
import org.opengis.geometry.Envelope;
import org.opengis.metadata.Metadata;
import org.opengis.referencing.operation.TransformException;
@@ -47,7 +48,7 @@ import org.apache.sis.geometry.ImmutableEnvelope;
* it would not be a persistent identifier.</p>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 1.3
*/
final class GroupAggregate extends AbstractResource implements Aggregate, AggregatedResource {
@@ -56,6 +57,15 @@ final class GroupAggregate extends AbstractResource implements Aggregate, Aggreg
*/
private static final int KEEP_ALIVE = 2;
+ /**
+ * The identifier for this aggregate, or {@code null} if none.
+ * This is optionally supplied by users for their own purposes.
+ * There is no default value.
+ *
+ * @see #getIdentifier()
+ */
+ private GenericName identifier;
+
/**
* Name of this aggregate, or {@code null} if none.
* This is <strong>not</strong> a persistent identifier.
@@ -66,6 +76,8 @@ final class GroupAggregate extends AbstractResource implements Aggregate, Aggreg
* The components of this aggregate. Array elements are initially null, but should all become non-null
* after a {@code fill(…)} method has been invoked. If the length is smaller than {@value #KEEP_ALIVE},
* then this aggregate is only a temporary object.
+ *
+ * @see #components()
*/
private final Resource[] components;
@@ -151,6 +163,7 @@ final class GroupAggregate extends AbstractResource implements Aggregate, Aggreg
/**
* Returns an aggregate with the same data than this aggregate but a different merge strategy.
+ * This is the implementation of {@link MergeStrategy#apply(Resource)} public method.
*/
@Override
public final synchronized Resource apply(final MergeStrategy strategy) {
@@ -226,6 +239,24 @@ final class GroupAggregate extends AbstractResource implements Aggregate, Aggreg
return aggregator.existingAggregate(components).orElse(this);
}
+ /**
+ * Sets the identifier of this resource.
+ */
+ @Override
+ public void setIdentifier(final GenericName identifier) {
+ this.identifier = identifier;
+ }
+
+
+ /**
+ * Returns the resource persistent identifier as specified by the
+ * user in {@link CoverageAggregator}. There is no default value.
+ */
+ @Override
+ public Optional<GenericName> getIdentifier() {
+ return Optional.ofNullable(identifier);
+ }
+
/**
* Returns the components of this aggregate.
*/
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupByCRS.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupByCRS.java
index 571c85eb54..3099885fa7 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupByCRS.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupByCRS.java
@@ -56,7 +56,9 @@ final class GroupByCRS<E> extends Group<E> {
}
/**
- * Returns a name for this group.
+ * Creates a name for this group for use in metadata (not a persistent identifier).
+ * This is used as the resource name if an aggregated resource needs to be created.
+ * The name distinguishes the group by their CRS name.
*/
@Override
final String createName(final Locale locale) {
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupBySample.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupBySample.java
index 40c4cb1805..8d97e7f033 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupBySample.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupBySample.java
@@ -31,7 +31,7 @@ import org.apache.sis.coverage.SampleDimension;
* which in turn contain an arbitrary number of {@link GridSlice} instances.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 1.3
*/
final class GroupBySample extends Group<GroupByCRS<GroupByTransform>> {
@@ -50,7 +50,9 @@ final class GroupBySample extends Group<GroupByCRS<GroupByTransform>> {
}
/**
- * Returns a name for this group.
+ * Creates a name for this group for use in metadata (not a persistent identifier).
+ * This is used as the resource name if an aggregated resource needs to be created.
+ * Current implementation tries to return a text describing sample dimensions.
*/
@Override
final String createName(final Locale locale) {
@@ -84,7 +86,7 @@ final class GroupBySample extends Group<GroupByCRS<GroupByTransform>> {
}
/**
- * Creates sub-aggregates for each member of this group and add them to the given aggregate.
+ * Creates sub-aggregates for each member of this group and adds them to the given aggregate.
*
* @param destination where to add sub-aggregates.
*/
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupByTransform.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupByTransform.java
index d8884efe15..fbf54ec8d0 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupByTransform.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GroupByTransform.java
@@ -78,7 +78,7 @@ final class GroupByTransform extends Group<GridSlice> {
}
/**
- * Returns a name for this group.
+ * Creates a name for this group for use in metadata (not a persistent identifier).
* This is used as the resource name if an aggregated resource needs to be created.
* Current implementation assumes that the main reason why many groups may exist is
* that they differ by their resolution.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/MergeStrategy.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/MergeStrategy.java
index 5cf4702cde..e149b29d59 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/MergeStrategy.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/MergeStrategy.java
@@ -32,14 +32,15 @@ import org.apache.sis.internal.util.Strings;
* A merge may happen if an aggregated coverage is created with {@link CoverageAggregator},
* and the extent of some source coverages are overlapping in the dimension to aggregate.
*
- * <div class="note"><b>Example:</b>
- * a collection of {@link GridCoverage} instances may represent the same phenomenon
+ * <h2>Example</h2>
+ * A collection of {@link GridCoverage} instances may represent the same phenomenon
* (for example Sea Surface Temperature) over the same geographic area but at different dates and times.
* {@link CoverageAggregator} can be used for building a single data cube with a time axis.
* But if two coverages have overlapping time ranges, and if a user request data in the overlapping region,
* then the aggregated coverages have more than one source coverages capable to provide the requested data.
- * This enumeration specify how to handle this multiplicity.</div>
+ * This enumeration specify how to handle this multiplicity.
*
+ * <h2>Default behavior</h2>
* If no merge strategy is specified, then the default behavior is to throw
* {@link SubspaceNotSpecifiedException} when the {@link GridCoverage#render(GridExtent)} method
* is invoked and more than one source coverage (slice) is found for a specified grid index.
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 c447fa6aca..a1669ab993 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
@@ -67,6 +67,18 @@ public final class BandAggregateGridResourceTest extends TestCase {
MathTransforms.identity(2), HardCodedCRS.WGS84);
}
+ /**
+ * Creates a new aggregation of all sample dimensions of all specified grid coverage resources.
+ * The new resource has no identifier and no parent, and uses a default processor with default color model.
+ *
+ * @param sources resources whose bands shall be aggregated, in order. At least one resource must be provided.
+ * @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);
+ }
+
/**
* Tests aggregation of two resources having one band each.
* All source coverages share the same grid geometry.
@@ -77,7 +89,7 @@ public final class BandAggregateGridResourceTest extends TestCase {
public void aggregateBandsFromSingleBandSources() throws DataStoreException {
final GridCoverageResource first = singleValuePerBand(17);
final GridCoverageResource second = singleValuePerBand(23);
- final var aggregation = new BandAggregateGridResource(first, second);
+ final var aggregation = create(first, second);
assertAllPixelsEqual(aggregation.read(null), 17, 23);
assertAllPixelsEqual(aggregation.read(null, 0), 17);
@@ -96,7 +108,7 @@ public final class BandAggregateGridResourceTest extends TestCase {
final GridCoverageResource thirdAndFourthBands = singleValuePerBand(103, 104);
final GridCoverageResource fifthAndSixthBands = singleValuePerBand(105, 106);
- var aggregation = new BandAggregateGridResource(firstAndSecondBands, thirdAndFourthBands, fifthAndSixthBands);
+ var aggregation = create(firstAndSecondBands, thirdAndFourthBands, fifthAndSixthBands);
aggregation.getIdentifier().ifPresent(name -> fail("No name provided at creation, but one was returned: " + name));
assertAllPixelsEqual(aggregation.read(null), 101, 102, 103, 104, 105, 106);
assertAllPixelsEqual(aggregation.read(null, 1, 2, 4, 5), 102, 103, 105, 106);
@@ -106,10 +118,11 @@ public final class BandAggregateGridResourceTest extends TestCase {
* In addition, band order in one of the 3 coverages is modified.
*/
final LocalName testName = Names.createLocalName(null, null, "test-name");
- aggregation = new BandAggregateGridResource(null, testName,
+ aggregation = new BandAggregateGridResource(null,
new GridCoverageResource[] {firstAndSecondBands, thirdAndFourthBands, fifthAndSixthBands},
new int[][] {null, new int[] {1, 0}, new int[] {1}}, null);
+ aggregation.setIdentifier(testName);
assertEquals(testName, aggregation.getIdentifier().orElse(null));
assertAllPixelsEqual(aggregation.read(null), 101, 102, 104, 103, 106);
assertAllPixelsEqual(aggregation.read(null, 2, 4), 104, 106);
diff --git a/storage/sis-storage/src/test/java/org/apache/sis/storage/aggregate/CoverageAggregatorTest.java b/storage/sis-storage/src/test/java/org/apache/sis/storage/aggregate/CoverageAggregatorTest.java
index 655a649a5c..fe5ca02dae 100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/storage/aggregate/CoverageAggregatorTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/storage/aggregate/CoverageAggregatorTest.java
@@ -28,7 +28,7 @@ import static org.junit.Assert.*;
* Tests {@link CoverageAggregator}.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
* @since 1.3
*/
public final class CoverageAggregatorTest extends TestCase {
@@ -39,7 +39,7 @@ public final class CoverageAggregatorTest extends TestCase {
*/
@Test
public void testEmpty() throws DataStoreException {
- final var aggregator = new CoverageAggregator(null);
- assertTrue(((Aggregate) aggregator.build()).components().isEmpty());
+ final var aggregator = new CoverageAggregator();
+ assertTrue(((Aggregate) aggregator.build(null)).components().isEmpty());
}
}