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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new c69c10ddfc Provide a `MergeStrategy` option and provide more
information in the error message if
`ConcConcatenatedGridCoverage.render(GridExtent)` can not infer a slice.
c69c10ddfc is described below
commit c69c10ddfc32d25a54ce330edf0070f716cbdf67
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Sep 14 19:10:50 2022 +0200
Provide a `MergeStrategy` option and provide more information in the error
message if `ConcConcatenatedGridCoverage.render(GridExtent)` can not infer a
slice.
---
.../org/apache/sis/internal/storage/Resources.java | 10 ++++
.../sis/internal/storage/Resources.properties | 2 +
.../sis/internal/storage/Resources_fr.properties | 2 +
.../aggregate/ConcatenatedGridCoverage.java | 34 ++++++++++++-
.../aggregate/ConcatenatedGridResource.java | 9 +++-
.../storage/aggregate/CoverageAggregator.java | 59 +++++++++++++++++++++-
.../sis/internal/storage/aggregate/GridSlice.java | 8 +--
.../storage/aggregate/GridSliceLocator.java | 24 +++++++++
.../storage/aggregate/GroupByTransform.java | 14 ++++-
.../internal/storage/aggregate/MergeStrategy.java | 50 ++++++++++++++++++
10 files changed, 203 insertions(+), 9 deletions(-)
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
index 5934ebb244..448986964e 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
@@ -326,6 +326,16 @@ public final class Resources extends IndexedResourceBundle
{
*/
public static final short NoCommonFeatureType = 75;
+ /**
+ * Index {1} in dimension “{0}” maps to {2} slices.
+ */
+ public static final short NoSliceMapped_3 = 79;
+
+ /**
+ * Extent in dimension “{0}” should be a slice, but {1} cells were
specified.
+ */
+ public static final short NoSliceSpecified_2 = 80;
+
/**
* No directory of resources found at “{0}”.
*/
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
index c561b2bb2f..8e97fbf6c7 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
@@ -72,6 +72,8 @@ MarkNotSupported_1 = Marks are not supported
on \u201c{0}\u201d s
MissingResourceIdentifier_1 = Resource \u201c{0}\u201d does not have an
identifier.
MissingSchemeInURI_1 = Missing scheme in \u201c{0}\u201d URI.
NoCommonFeatureType = No feature type is common to all the
features to aggregate.
+NoSliceMapped_3 = Index {1} in dimension \u201c{0}\u201d
maps to {2} slices. This error can be avoided by specifying a merge strategy.
+NoSliceSpecified_2 = Extent in dimension \u201c{0}\u201d should
be a slice, but {1} cells were specified.
NoSuchResourceDirectory_1 = No directory of resources found at
\u201c{0}\u201d.
NoSuchResourceInAggregate_2 = Resource \u201c{1}\u201d is not part of
aggregate \u201c{0}\u201d.
NotAWritableFeatureSet_1 = Resource \u201c{0}\u201d is not a writable
feature set.
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
index 6c8d3708e5..ca2c3cc76a 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
@@ -77,6 +77,8 @@ MarkNotSupported_1 = Les marques ne sont pas
support\u00e9es sur
MissingResourceIdentifier_1 = La ressource \u00ab\u202f{0}\u202f\u00bb
n\u2019a pas d\u2019identifiant.
MissingSchemeInURI_1 = Il manque le sch\u00e9ma dans l\u2019URI
\u00ab\u202f{0}\u202f\u00bb.
NoCommonFeatureType = Il n\u2019y a pas de type commun \u00e0
toutes les entit\u00e9s \u00e0 agr\u00e9ger.
+NoSliceMapped_3 = L\u2019index {1} dans la dimension
\u00ab\u202f{0}\u202f\u00bb correspond \u00e0 {2} tranches. Cette erreur peut
\u00eatre \u00e9vit\u00e9e en sp\u00e9cifiant une strat\u00e9gie de fusion.
+NoSliceSpecified_2 = La plage dans la dimension
\u00ab\u202f{0}\u202f\u00bb devrait \u00eatre une tranche, mais {1} cellules
ont \u00e9t\u00e9 sp\u00e9cifi\u00e9es.
NoSuchResourceDirectory_1 = Aucun r\u00e9pertoire de ressources
n\u2019a \u00e9t\u00e9 trouv\u00e9 \u00e0 l\u2019emplacement
\u00ab\u202f{0}\u202f\u00bb.
NoSuchResourceInAggregate_2 = La ressource \u00ab\u202f{1}\u202f\u00bb
n\u2019est pas une partie de l\u2019agr\u00e9gat \u00ab\u202f{0}\u202f\u00bb.
NotAWritableFeatureSet_1 = La ressource \u00ab\u202f{0}\u202f\u00bb
n\u2019est pas un ensemble d\u2019entit\u00e9s accessibles en \u00e9criture.
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/ConcatenatedGridCoverage.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/ConcatenatedGridCoverage.java
index 1a5a8d280f..c47d674021 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/ConcatenatedGridCoverage.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/ConcatenatedGridCoverage.java
@@ -85,6 +85,11 @@ final class ConcatenatedGridCoverage extends GridCoverage {
*/
private final int startAt;
+ /**
+ * Algorithm to apply when more than one grid coverage can be found at the
same grid index.
+ */
+ private final MergeStrategy strategy;
+
/**
* Creates a new aggregated coverage.
*
@@ -108,6 +113,7 @@ final class ConcatenatedGridCoverage extends GridCoverage {
this.ranges = ranges;
this.isConverted = source.isConverted;
this.locator = source.locator;
+ this.strategy = source.strategy;
}
/**
@@ -124,6 +130,7 @@ final class ConcatenatedGridCoverage extends GridCoverage {
this.request = source.request;
this.ranges = source.ranges;
this.locator = source.locator;
+ this.strategy = source.strategy;
this.isConverted = converted;
}
@@ -178,9 +185,32 @@ final class ConcatenatedGridCoverage extends GridCoverage {
} else {
extent = gridGeometry.getExtent();
}
- if (upper - lower != 1) {
- throw new SubspaceNotSpecifiedException();
+ final int size = upper - lower;
+ if (size != 1) {
+ switch (strategy) {
+ default: {
+ /*
+ * Can not infer a slice. If the user specified a single
slice but that slice
+ * maps to more than one coverage, the error message tells
that this problem
+ * can be avoided by specifying a merge strategy.
+ */
+ final short message;
+ final Object[] arguments;
+ if (locator.isSlice(extent)) {
+ message = Resources.Keys.NoSliceMapped_3;
+ arguments = new Object[]
{locator.getDimensionName(extent), lower, size};
+ } else {
+ message = Resources.Keys.NoSliceSpecified_2;
+ arguments = new Object[]
{locator.getDimensionName(extent), size};
+ }
+ throw new
SubspaceNotSpecifiedException(Resources.format(message, arguments));
+ }
+ }
}
+ /*
+ * Argument have been validated and slice has been located.
+ * If the coverage has not already been loaded, load it now.
+ */
GridCoverage slice = slices[lower];
if (slice == null) try {
slice = resources[lower].read(request,
ranges).forConvertedValues(isConverted);
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/ConcatenatedGridResource.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/ConcatenatedGridResource.java
index 0b5f48fa67..0f0c6c8f4f 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/ConcatenatedGridResource.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/ConcatenatedGridResource.java
@@ -108,6 +108,11 @@ final class ConcatenatedGridResource extends
AbstractGridCoverageResource implem
*/
final GridSliceLocator locator;
+ /**
+ * Algorithm to apply when more than one grid coverage can be found at the
same grid index.
+ */
+ final MergeStrategy strategy;
+
/**
* The envelope of this aggregate, or {@code null} if not yet computed.
* May also be {@code null} if no slice declare an envelope, or if the
union can not be computed.
@@ -143,7 +148,8 @@ final class ConcatenatedGridResource extends
AbstractGridCoverageResource implem
final GridGeometry domain,
final List<SampleDimension> ranges,
final GridCoverageResource[] slices,
- final GridSliceLocator locator)
+ final GridSliceLocator locator,
+ final MergeStrategy strategy)
{
super(listeners, false);
this.name = name;
@@ -151,6 +157,7 @@ final class ConcatenatedGridResource extends
AbstractGridCoverageResource implem
this.sampleDimensions = ranges;
this.slices = slices;
this.locator = locator;
+ this.strategy = strategy;
this.deferredLoading = new long[Numerics.ceilDiv(slices.length,
Long.SIZE)];
for (final SampleDimension sd : ranges) {
if (sd.forConvertedValues(true) != sd) {
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/CoverageAggregator.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/CoverageAggregator.java
index b16486ddbe..91d4292bbc 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/CoverageAggregator.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/CoverageAggregator.java
@@ -35,12 +35,31 @@ import org.apache.sis.storage.DataStoreException;
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.util.collection.BackingStoreException;
+import org.apache.sis.util.ArgumentChecks;
/**
* Creates a grid coverage resource from an aggregation of an arbitrary amount
of other resources.
*
+ * <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>
+ *
+ * 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.
+ *
+ * <h2>Multi-threading and concurrency</h2>
+ * All {@code add(…)} methods can be invoked concurrently from arbitrary
threads.
+ * It is okay to load {@link GridCoverageResource} instances in parallel
threads
+ * and add those resources to {@code CoverageAggregator} without
synchronization.
+ * However the final {@link #build()} method is <em>not</em> thread-safe;
+ * that method shall be invoked from a single thread after all sources have
been added
+ * and no more addition are in progress.
+ *
* @author Martin Desruisseaux (Geomatys)
* @version 1.3
* @since 1.3
@@ -58,6 +77,13 @@ public final class CoverageAggregator extends
Group<GroupBySample> {
*/
private final Map<Set<Resource>, Queue<Aggregate>> aggregates;
+ /**
+ * Algorithm to apply when more than one grid coverage can be found at the
same grid index.
+ *
+ * @see #getMergeStrategy()
+ */
+ private volatile MergeStrategy strategy;
+
/**
* Creates an initially empty aggregator.
*
@@ -67,6 +93,7 @@ public final class CoverageAggregator extends
Group<GroupBySample> {
public CoverageAggregator(final StoreListeners listeners) {
this.listeners = listeners;
aggregates = new HashMap<>();
+ strategy = MergeStrategy.FAIL;
}
/**
@@ -114,7 +141,7 @@ public final class CoverageAggregator extends
Group<GroupBySample> {
final GridSlice slice = new GridSlice(resource);
final List<GridSlice> slices;
try {
- slices = slice.getList(bySample.members).members;
+ slices = slice.getList(bySample.members, strategy).members;
} catch (NoninvertibleTransformException e) {
throw new DataStoreContentException(e);
}
@@ -182,6 +209,36 @@ public final class CoverageAggregator extends
Group<GroupBySample> {
return Optional.empty();
}
+ /**
+ * 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)},
+ * or {@link MergeStrategy#FAIL} by default.
+ *
+ * @return algorithm to apply for merging source coverages at the same
grid index.
+ */
+ public MergeStrategy getMergeStrategy() {
+ return strategy;
+ }
+
+ /**
+ * 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.
+ *
+ * <h4>Effect on previously added coverages</h4>
+ * The merge strategy of previously added coverages is not modified by
this method call,
+ * except for coverages that become part of the same aggregated {@link
GridCoverageResource}
+ * than a coverage added after this method call.
+ * In such case, the strategy set by the most recent call to {@code
setMergeStrategy(…)} prevails.
+ *
+ * @param strategy new algorithm to apply for merging source coverages at
the same grid index.
+ */
+ public void setMergeStrategy(final MergeStrategy strategy) {
+ ArgumentChecks.ensureNonNull("strategy", strategy);
+ this.strategy = strategy;
+ }
+
/**
* 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,
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/GridSlice.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/GridSlice.java
index 0c3e90dc4c..e3664625a6 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/GridSlice.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/GridSlice.java
@@ -129,11 +129,12 @@ final class GridSlice {
* The CRS comparisons ignore metadata and transform comparisons ignore
integer translations.
* This method takes a synchronization lock on the given list.
*
- * @param groups the list where to search for a group.
+ * @param groups the list where to search for a group.
+ * @param strategy algorithm to apply when more than one grid coverage
can be found at the same grid index.
* @return group of objects associated to the given transform (never null).
* @throws NoninvertibleTransformException if the transform is not
invertible.
*/
- final GroupByTransform getList(final List<GroupByCRS<GroupByTransform>>
groups)
+ final GroupByTransform getList(final List<GroupByCRS<GroupByTransform>>
groups, final MergeStrategy strategy)
throws NoninvertibleTransformException
{
final MathTransform gridToCRS =
geometry.getGridToCRS(PixelInCell.CELL_CORNER);
@@ -144,10 +145,11 @@ final class GridSlice {
final Matrix groupToSlice = c.linearTransform(crsToGrid);
if (groupToSlice != null &&
isIntegerTranslation(groupToSlice)) {
setOffset(MatrixSIS.castOrCopy(groupToSlice));
+ c.strategy = strategy;
return c;
}
}
- final GroupByTransform c = new GroupByTransform(geometry,
gridToCRS);
+ final GroupByTransform c = new GroupByTransform(geometry,
gridToCRS, strategy);
transforms.add(c);
return c;
}
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/GridSliceLocator.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/GridSliceLocator.java
index 1cc91e89f2..35d061ca53 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/GridSliceLocator.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/GridSliceLocator.java
@@ -21,11 +21,13 @@ import java.util.List;
import java.util.Arrays;
import java.util.HashMap;
import java.util.function.Function;
+import org.opengis.util.InternationalString;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.metadata.spatial.DimensionNameType;
import org.apache.sis.coverage.grid.GridExtent;
import org.apache.sis.coverage.grid.GridGeometry;
import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.util.iso.Types;
/**
@@ -190,4 +192,26 @@ final class GridSliceLocator {
}
return toIndex;
}
+
+ /**
+ * Returns {@code true} if the grid extent in the search dimension is a
slice of size 1.
+ *
+ * @param sliceExtent the extent to search.
+ * @return whether the extent is a slice in the search dimension.
+ */
+ final boolean isSlice(final GridExtent sliceExtent) {
+ return sliceExtent.getLow(searchDimension) ==
sliceExtent.getHigh(searchDimension);
+ }
+
+ /**
+ * Return the name of the extent axis in the search dimension.
+ *
+ * @param extent the extent from which to get an axis label.
+ * @return label for the search axis.
+ */
+ final String getDimensionName(final GridExtent extent) {
+ return extent.getAxisType(searchDimension)
+ .map(Types::getCodeTitle).map(InternationalString::toString)
+ .orElseGet(() -> String.valueOf(searchDimension));
+ }
}
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/GroupByTransform.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/GroupByTransform.java
index 83dd19ccf5..a62efcedc7 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/GroupByTransform.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/GroupByTransform.java
@@ -57,15 +57,24 @@ final class GroupByTransform extends Group<GridSlice> {
*/
private final MathTransform gridToCRS;
+ /**
+ * Algorithm to apply when more than one grid coverage can be found at the
same grid index.
+ * This is set at construction time and usually keep the same value after
that point,
+ * unless {@link CoverageAggregator#setMergeStrategy(MergeStrategy)} is
invoked again.
+ */
+ MergeStrategy strategy;
+
/**
* Creates a new group of objects associated to the given transform.
*
* @param geometry geometry of the grid coverage or resource.
* @param gridToCRS value or {@code
geometry.getGridToCRS(PixelInCell.CELL_CORNER)}.
+ * @param strategy algorithm to apply when more than one grid coverage
can be found at the same grid index.
*/
- GroupByTransform(final GridGeometry geometry, final MathTransform
gridToCRS) {
+ GroupByTransform(final GridGeometry geometry, final MathTransform
gridToCRS, final MergeStrategy strategy) {
this.geometry = geometry;
this.gridToCRS = gridToCRS;
+ this.strategy = strategy;
}
/**
@@ -148,6 +157,7 @@ final class GroupByTransform extends Group<GridSlice> {
final GridCoverageResource[] slices = new GridCoverageResource[n];
final GridSliceLocator locator = new GridSliceLocator(members,
dimensions[0], slices);
final GridGeometry domain = locator.union(geometry,
members, GridSlice::getGridExtent);
- return new ConcatenatedGridResource(getName(parentListeners),
parentListeners, domain, ranges, slices, locator);
+ return new ConcatenatedGridResource(getName(parentListeners),
parentListeners,
+ domain, ranges, slices, locator,
strategy);
}
}
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/MergeStrategy.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/MergeStrategy.java
new file mode 100644
index 0000000000..4578097af5
--- /dev/null
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/aggregate/MergeStrategy.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.storage.aggregate;
+
+import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.coverage.SubspaceNotSpecifiedException;
+
+
+/**
+ * Algorithm to apply when more than one grid coverage can be found at the
same grid index.
+ * 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
+ * (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 an 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>
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.3
+ * @since 1.3
+ * @module
+ */
+public enum MergeStrategy {
+ /**
+ * Do not perform any merge. It will cause a {@link
SubspaceNotSpecifiedException} to be thrown by
+ * {@link GridCoverage#render(GridExtent)} if more than one source
coverage is found for a specified slice.
+ *
+ * <p>This is the default strategy.</p>
+ */
+ FAIL
+}