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/11/25 17:17:29 UTC
(sis) 03/03: Add in `CoverageAggregator` the same convenience methods than the ones added in `GridCoverageProcessor` in previous commit. The intend is to make easier to append a vertical or temporal dimension to two-dimensional coverages to aggregate in a cube.
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 2b6989782e5def3b42bdf670ea42d1efd672e359
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Nov 25 18:16:17 2023 +0100
Add in `CoverageAggregator` the same convenience methods than the ones added in `GridCoverageProcessor` in previous commit.
The intend is to make easier to append a vertical or temporal dimension to two-dimensional coverages to aggregate in a cube.
---
.../sis/coverage/grid/GridCoverageProcessor.java | 2 +-
.../org/apache/sis/coverage/grid/GridExtent.java | 15 ++
.../sis/storage/aggregate/CoverageAggregator.java | 149 ++++++++++++-
.../sis/storage/aggregate/DimensionAppender.java | 244 +++++++++++++++++++++
4 files changed, 408 insertions(+), 2 deletions(-)
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java
index d7194127b1..d5ce4ed858 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java
@@ -660,7 +660,7 @@ public class GridCoverageProcessor implements Cloneable {
* @param source the source on which to append a dimension.
* @param lower lower coordinate value of the slice, in units of the CRS.
* @param span size of the slice, in units of the CRS.
- * @param crs coordinate reference system of the slice, or {@code null} if unknown.
+ * @param crs one-dimensional coordinate reference system of the slice, or {@code null} if unknown.
* @return a coverage with the specified dimension added.
* @throws IllegalGridGeometryException if the compound CRS or compound extent cannot be created.
*
diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
index d996db4595..d58c9da9df 100644
--- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
+++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
@@ -35,6 +35,7 @@ import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.metadata.spatial.DimensionNameType;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.Matrix;
@@ -406,6 +407,20 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable
System.arraycopy(upper.coordinates, d2, coordinates, dim+d1, d2);
}
+ /**
+ * Suggests a grid dimension name for the given coordinate system axis.
+ * Note that grid axes are not necessarily in the same order than CRS axes.
+ * This method may be used when the caller knows which CRS axis will be associated to a grid axis.
+ *
+ * @param axis the coordinate system axis for which to get a suggested grid dimension name.
+ * @return suggested grid dimension for the given CRS axis.
+ *
+ * @since 1.5
+ */
+ public static Optional<DimensionNameType> typeFromAxis(final CoordinateSystemAxis axis) {
+ return Optional.ofNullable(AXIS_DIRECTIONS.get(AxisDirections.absolute(axis.getDirection())));
+ }
+
/**
* Infers the axis types from the given coordinate reference system.
* This method is the converse of {@link GridExtentCRS}.
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/CoverageAggregator.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/CoverageAggregator.java
index f2895a55ec..a3cff71f4d 100644
--- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/CoverageAggregator.java
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/CoverageAggregator.java
@@ -27,8 +27,17 @@ import java.util.IdentityHashMap;
import java.util.Collections;
import java.util.Optional;
import java.util.stream.Stream;
+import java.time.Instant;
+import java.time.Duration;
import org.opengis.util.GenericName;
+import org.opengis.metadata.spatial.DimensionNameType;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.datum.PixelInCell;
+import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.NoninvertibleTransformException;
+import org.apache.sis.referencing.operation.transform.MathTransforms;
+import org.apache.sis.referencing.crs.DefaultTemporalCRS;
+import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.image.Colorizer;
import org.apache.sis.storage.Resource;
import org.apache.sis.storage.Aggregate;
@@ -36,13 +45,16 @@ import org.apache.sis.storage.DataStore;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.DataStoreContentException;
import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.storage.base.MemoryGridResource;
import org.apache.sis.storage.event.StoreListeners;
import org.apache.sis.coverage.SubspaceNotSpecifiedException;
+import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.grid.GridGeometry;
import org.apache.sis.coverage.grid.GridCoverage;
import org.apache.sis.coverage.grid.GridCoverageProcessor;
import org.apache.sis.coverage.grid.IllegalGridGeometryException;
-import org.apache.sis.storage.base.MemoryGridResource;
import org.apache.sis.util.collection.BackingStoreException;
+import org.apache.sis.util.internal.Numerics;
/**
@@ -203,6 +215,63 @@ public final class CoverageAggregator extends Group<GroupBySample> {
}
}
+ /**
+ * Adds the given coverage augmented with the specified grid dimensions.
+ * The {@code dimToAdd} argument contains typically vertical or temporal axes to add to a two-dimensional coverage.
+ * All additional dimensions in {@code dimToAdd} must have a grid extent size of one cell.
+ *
+ * @param coverage coverage to add.
+ * @param dimToAdd the dimensions to append. The grid extent size must be 1 cell in all dimensions.
+ * @throws IllegalGridGeometryException if a dimension has more than one grid cell, or concatenation
+ * would result in duplicated {@linkplain GridExtent#getAxisType(int) grid axis types},
+ * or the compound CRS cannot be created.
+ *
+ * @see GridCoverageProcessor#appendDimensions(GridCoverage, GridGeometry)
+ *
+ * @since 1.5
+ */
+ public void add(GridCoverage coverage, GridGeometry dimToAdd) {
+ add(processor().appendDimensions(coverage, dimToAdd));
+ }
+
+ /**
+ * Adds the given coverage augmented with a single grid dimension.
+ * The additional dimension is typically a vertical axis to add to a two-dimensional coverage.
+ *
+ * @param coverage coverage to add.
+ * @param lower lower coordinate value of the slice, in units of the CRS.
+ * @param span size of the slice, in units of the CRS.
+ * @param crs one-dimensional coordinate reference system of the slice, or {@code null} if unknown.
+ * @throws IllegalGridGeometryException if the compound CRS or compound extent cannot be created.
+ *
+ * @see GridCoverageProcessor#appendDimension(GridCoverage, double, double, CoordinateReferenceSystem)
+ *
+ * @since 1.5
+ */
+ public void add(GridCoverage coverage, double lower, double span, CoordinateReferenceSystem crs) {
+ add(processor().appendDimension(coverage, lower, span, crs));
+ }
+
+ /**
+ * Adds the given coverage augmented with a single temporal grid dimension.
+ * This method is provided for convenience, but should be used carefully.
+ * Slice coordinates computed from calendars tend to produce slices at irregular intervals
+ * or with heterogeneous spans, which result in coverages that cannot be aggregated by this
+ * {@code CoverageAggregator} class.
+ *
+ * @param source the source on which to append a temporal dimension.
+ * @param lower start time of the slice.
+ * @param span duration of the slice.
+ * @throws IllegalGridGeometryException if the compound CRS or compound extent cannot be created.
+ *
+ * @see GridCoverageProcessor#appendDimension(GridCoverage, Instant, Duration)
+ *
+ * @since 1.5
+ */
+ public void add(GridCoverage coverage, Instant lower, Duration span) {
+ add(processor().appendDimension(coverage, lower, span));
+ }
+
/**
* 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.
@@ -225,6 +294,84 @@ public final class CoverageAggregator extends Group<GroupBySample> {
}
}
+ /**
+ * Adds the given resource augmented with the specified grid dimensions.
+ * The {@code dimToAdd} argument contains typically vertical or temporal axes to add to a two-dimensional resource.
+ * All additional dimensions in {@code dimToAdd} must have a grid extent size of one cell.
+ *
+ * @param resource resource to add.
+ * @param dimToAdd the dimensions to append. The grid extent size must be 1 cell in all dimensions.
+ * @throws IllegalGridGeometryException if the compound CRS or compound extent cannot be created.
+ * @throws DataStoreException if the resource cannot be used.
+ *
+ * @since 1.5
+ */
+ public void add(GridCoverageResource resource, GridGeometry dimToAdd) throws DataStoreException {
+ add(DimensionAppender.create(processor(), resource, dimToAdd));
+ }
+
+ /**
+ * Adds the given resource augmented with a single grid dimension.
+ * The additional dimension is typically a vertical axis to add to a two-dimensional resource.
+ *
+ * @param resource resource to add.
+ * @param lower lower coordinate value of the slice, in units of the CRS.
+ * @param span size of the slice, in units of the CRS.
+ * @param crs one-dimensional coordinate reference system of the slice, or {@code null} if unknown.
+ * @throws IllegalGridGeometryException if the compound CRS or compound extent cannot be created.
+ * @throws DataStoreException if the resource cannot be used.
+ *
+ * @since 1.5
+ */
+ public void add(final GridCoverageResource resource, final double lower, final double span,
+ final CoordinateReferenceSystem crs) throws DataStoreException
+ {
+ /*
+ * This code currently duplicates `GridCoverageProcessor.appendDimension(..., double, double, CRS)`,
+ * but a future version may use the state of this `CoverageAggregator`, for example making a better
+ * effort to align the resources on the same "gridToCRS" transform.
+ */
+ final long index = Numerics.roundAndClamp(lower / span);
+ final var indices = new long[] {index};
+ final var names = new DimensionNameType[] {
+ GridExtent.typeFromAxis(crs.getCoordinateSystem().getAxis(0)).orElse(null)
+ };
+ final GridExtent extent = new GridExtent(names, indices, indices, true);
+ final MathTransform gridToCRS = MathTransforms.linear(span, Math.fma(index, -span, lower));
+ add(resource, new GridGeometry(extent, PixelInCell.CELL_CORNER, gridToCRS, crs));
+ }
+
+ /**
+ * Adds the given resource augmented with a single temporal grid dimension.
+ * This method is provided for convenience, but should be used carefully.
+ * Slice coordinates computed from calendars tend to produce slices at irregular intervals
+ * or with heterogeneous spans, which result in coverages that cannot be aggregated by this
+ * {@code CoverageAggregator} class.
+ *
+ * @param resource resource to add.
+ * @param lower start time of the slice.
+ * @param span duration of the slice.
+ * @throws IllegalGridGeometryException if the compound CRS or compound extent cannot be created.
+ * @throws DataStoreException if the resource cannot be used.
+ *
+ * @since 1.5
+ */
+ public void add(final GridCoverageResource resource, final Instant lower, final Duration span) throws DataStoreException {
+ /*
+ * This code currently duplicates `GridCoverageProcessor.appendDimension(..., double, double, CRS)`,
+ * but a future version may use the state of this `CoverageAggregator`, for example making a better
+ * effort to align the resources on the same "gridToCRS" transform.
+ */
+ final DefaultTemporalCRS crs = DefaultTemporalCRS.castOrCopy(CommonCRS.Temporal.TRUNCATED_JULIAN.crs());
+ double scale = crs.toValue(span);
+ double offset = crs.toValue(lower);
+ long index = Numerics.roundAndClamp(offset / scale); // See comment in above method.
+ offset = crs.toValue(lower.minus(span.multipliedBy(index)));
+ final GridExtent extent = new GridExtent(DimensionNameType.TIME, index, index, true);
+ final MathTransform gridToCRS = MathTransforms.linear(scale, offset);
+ add(resource, new GridGeometry(extent, PixelInCell.CELL_CORNER, gridToCRS, crs));
+ }
+
/**
* Adds all components of the given aggregate. This method can be invoked from any thread.
* It delegates to {@link #add(GridCoverageResource)} for each component in the aggregate
diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/DimensionAppender.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/DimensionAppender.java
new file mode 100644
index 0000000000..d25ac1af63
--- /dev/null
+++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/aggregate/DimensionAppender.java
@@ -0,0 +1,244 @@
+/*
+ * 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.storage.aggregate;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import org.opengis.util.GenericName;
+import org.opengis.util.FactoryException;
+import org.opengis.metadata.Metadata;
+import org.opengis.referencing.datum.PixelInCell;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.coverage.grid.GridCoverageProcessor;
+import org.apache.sis.coverage.grid.IllegalGridGeometryException;
+import org.apache.sis.storage.Query;
+import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.storage.RasterLoadingStrategy;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.DataStoreReferencingException;
+import org.apache.sis.storage.base.StoreUtilities;
+import org.apache.sis.storage.event.StoreEvent;
+import org.apache.sis.storage.event.StoreListener;
+import org.apache.sis.storage.internal.Resources;
+import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.logging.Logging;
+
+
+/**
+ * A wrapper over an existing grid coverage resource with dimensions appended.
+ * This wrapper delegates the work to {@link GridCoverageProcessor} after a coverage has been read.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ */
+final class DimensionAppender implements GridCoverageResource {
+ /**
+ * The grid coverage processor to use for creating grid coverages with extra dimensions.
+ */
+ private final GridCoverageProcessor processor;
+
+ /**
+ * The source grid coverage resource for which to append extra dimensions.
+ */
+ private final GridCoverageResource source;
+
+ /**
+ * The dimensions added to the source grid coverage.
+ * Should have a grid size of one cell in all dimensions.
+ */
+ private final GridGeometry dimToAdd;
+
+ /**
+ * The grid geometry with dimensions appended.
+ * Created when first requested.
+ *
+ * @see #getGridGeometry()
+ */
+ private volatile GridGeometry gridGeometry;
+
+ /**
+ * Creates a new dimension appender for the given grid coverage resource.
+ * This constructor does not verify the grid geometry validity.
+ * It is caller's responsibility to verify that the size is 1 cell.
+ *
+ * @param processor the grid coverage processor to use for creating grid coverages with extra dimensions.
+ * @param source the source grid coverage for which to append extra dimensions.
+ * @param dimToAdd the dimensions to add to the source grid coverage.
+ */
+ private DimensionAppender(final GridCoverageProcessor processor, final GridCoverageResource source, final GridGeometry dimToAdd) {
+ this.processor = processor;
+ this.source = source;
+ this.dimToAdd = dimToAdd;
+ }
+
+ /**
+ * Creates a grid coverage resource augmented with the given dimensions.
+ * The grid extent of {@code dimToAdd} shall have a grid size of one cell in all dimensions.
+ *
+ * @param processor the grid coverage processor to use for creating grid coverages with extra dimensions.
+ * @param source the source grid coverage for which to append extra dimensions.
+ * @param dimToAdd the dimensions to add to the source grid coverage.
+ * @throws IllegalGridGeometryException if a dimension has more than one grid cell.
+ */
+ static GridCoverageResource create(final GridCoverageProcessor processor, GridCoverageResource source, GridGeometry dimToAdd) {
+ ArgumentChecks.ensureNonNull("source", source);
+ final GridExtent extent = dimToAdd.getExtent();
+ int i = extent.getDimension();
+ if (i == 0) {
+ return source;
+ }
+ do {
+ final long size = extent.getSize(--i);
+ if (size != 1) {
+ Object name = extent.getAxisType(i).orElse(null);
+ if (name == null) name = i;
+ throw new IllegalGridGeometryException(Resources.format(Resources.Keys.NoSliceSpecified_2, name, size));
+ }
+ } while (i != 0);
+ if (source instanceof DimensionAppender) try {
+ final var a = (DimensionAppender) source;
+ dimToAdd = new GridGeometry(a.dimToAdd, dimToAdd);
+ source = a.source;
+ } catch (FactoryException e) {
+ throw new IllegalGridGeometryException(e.getMessage(), e);
+ }
+ return new DimensionAppender(processor, source, dimToAdd);
+ }
+
+ /**
+ * Returns the identifier of the original resource.
+ */
+ @Override
+ public Optional<GenericName> getIdentifier() throws DataStoreException {
+ return source.getIdentifier();
+ }
+
+ /**
+ * Returns the metadata of the original resource.
+ */
+ @Override
+ public Metadata getMetadata() throws DataStoreException {
+ return source.getMetadata();
+ }
+
+ /**
+ * Returns the sample dimensions of the original resource.
+ * Those dimensions are not impacted by the change of domain dimensions.
+ */
+ @Override
+ public List<SampleDimension> getSampleDimensions() throws DataStoreException {
+ return source.getSampleDimensions();
+ }
+
+ /**
+ * Returns the grid geometry of the original resource augmented with the dimensions to append.
+ */
+ @Override
+ public GridGeometry getGridGeometry() throws DataStoreException {
+ GridGeometry gg = gridGeometry;
+ if (gg == null) try {
+ gg = new GridGeometry(source.getGridGeometry(), dimToAdd);
+ gridGeometry = gg;
+ } catch (FactoryException | RuntimeException e) {
+ throw new DataStoreReferencingException(e.getMessage(), e);
+ }
+ return gg;
+ }
+
+ /**
+ * Returns a subset of this grid coverage resource.
+ * The result will have the same "dimensions to add" than this resource.
+ *
+ * @param query the query to execute.
+ * @return subset of this coverage resource.
+ */
+ @Override
+ public GridCoverageResource subset(final Query query) throws DataStoreException {
+ final GridCoverageResource subset = source.subset(query);
+ if (subset == source) return this;
+ return new DimensionAppender(processor, subset, dimToAdd);
+ }
+
+ /**
+ * Reads the data and wraps the result with the dimensions to add.
+ */
+ @Override
+ public GridCoverage read(GridGeometry domain, int... ranges) throws DataStoreException {
+ return processor.appendDimensions(source.read(domain, ranges), dimToAdd);
+ }
+
+ /**
+ * Returns an indication about when the "physical" loading of raster data will happen.
+ */
+ @Override
+ public RasterLoadingStrategy getLoadingStrategy() throws DataStoreException {
+ return source.getLoadingStrategy();
+ }
+
+ /**
+ * Sets the preferred strategy about when to do the "physical" loading of raster data.
+ */
+ @Override
+ public boolean setLoadingStrategy(RasterLoadingStrategy strategy) throws DataStoreException {
+ return source.setLoadingStrategy(strategy);
+ }
+
+ /**
+ * Registers a listener to notify when the specified kind of event occurs in this resource or in children.
+ */
+ @Override
+ public <T extends StoreEvent> void addListener(Class<T> eventType, StoreListener<? super T> listener) {
+ source.addListener(eventType, listener);
+ }
+
+ /**
+ * Unregisters a listener previously added to this resource for the given type of events.
+ */
+ @Override
+ public <T extends StoreEvent> void removeListener(Class<T> eventType, StoreListener<? super T> listener) {
+ source.removeListener(eventType, listener);
+ }
+
+ /**
+ * Returns a string representation of this wrapper for debugging purposes.
+ */
+ @Override
+ public String toString() {
+ final var sb = new StringBuilder(40);
+ sb.append(source).append(" + dimensions[");
+ final GridExtent extent = dimToAdd.getExtent();
+ final double[] coordinates = ArraysExt.copyAsDoubles(extent.getLow().getCoordinateValues());
+ try {
+ dimToAdd.getGridToCRS(PixelInCell.CELL_CORNER).transform(coordinates, 0, coordinates, 0, 1);
+ } catch (RuntimeException | TransformException e) {
+ // Should never happen because the transform should be linear.
+ Logging.unexpectedException(StoreUtilities.LOGGER, DimensionAppender.class, "toString", e);
+ Arrays.fill(coordinates, Double.NaN);
+ }
+ for (int i=0; i<coordinates.length; i++) {
+ if (i != 0) sb.append(", ");
+ extent.getAxisType(i).ifPresent((type) -> sb.append(type.name()).append('='));
+ sb.append(coordinates[i]);
+ }
+ return sb.append(']').toString();
+ }
+}