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 <martin.desruisse...@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()); } }