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 2019/10/26 15:42:10 UTC
[sis] branch geoapi-4.0 updated: Provide a default implementation
for GridCoverageResource.subset(Query). Not yet in public API;
pending the gain of more experience.
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 e4606ae Provide a default implementation for GridCoverageResource.subset(Query). Not yet in public API; pending the gain of more experience.
e4606ae is described below
commit e4606aec14700b542542503acd85fb2d9cb0c6c9
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sat Oct 26 17:35:28 2019 +0200
Provide a default implementation for GridCoverageResource.subset(Query).
Not yet in public API; pending the gain of more experience.
---
.../apache/sis/coverage/grid/GridDerivation.java | 2 +
.../referencing/provider/MapProjection.java | 2 +-
.../java/org/apache/sis/measure/AngleFormat.java | 1 +
.../apache/sis/internal/netcdf/RasterResource.java | 4 +-
.../sis/internal/storage/AbstractGridResource.java | 21 +-
.../org/apache/sis/internal/storage/Resources.java | 5 +
.../sis/internal/storage/Resources.properties | 1 +
.../sis/internal/storage/Resources_fr.properties | 1 +
.../apache/sis/internal/storage/package-info.java | 2 +-
.../sis/internal/storage/query/CoverageQuery.java | 245 ++++++++++++++++++---
.../sis/internal/storage/query/CoverageSubset.java | 135 ++++++++++++
.../sis/internal/storage/query/SimpleQuery.java | 26 ++-
.../sis/internal/storage/query/package-info.java | 2 +-
.../apache/sis/storage/GridCoverageResource.java | 21 +-
.../main/java/org/apache/sis/storage/Query.java | 4 +-
.../java/org/apache/sis/storage/package-info.java | 2 +-
16 files changed, 432 insertions(+), 42 deletions(-)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
index e19b807..5367538 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
@@ -867,6 +867,8 @@ public class GridDerivation {
* Builds a grid geometry with the configuration specified by the other methods in this {@code GridDerivation} class.
*
* @return the modified grid geometry. May be the {@link #base} grid geometry if no change apply.
+ * @throws IllegalGridGeometryException if the grid geometry can not be computed
+ * because of arguments given to a {@code subgrid(…)} or other methods.
*
* @see #getIntersection()
*/
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MapProjection.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MapProjection.java
index 3c4df75..680c2df 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MapProjection.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MapProjection.java
@@ -21,13 +21,13 @@ import java.util.HashMap;
import java.util.NoSuchElementException;
import javax.xml.bind.annotation.XmlTransient;
import javax.measure.Unit;
+import org.opengis.util.GenericName;
import org.opengis.util.FactoryException;
import org.opengis.util.InternationalString;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.citation.Citation;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.GeneralParameterDescriptor;
-import org.opengis.util.GenericName;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.ParameterNotFoundException;
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java b/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java
index 5921d6b..55902fd 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java
@@ -812,6 +812,7 @@ public class AngleFormat extends Format implements Localized {
*
* @param resolution the desired angle resolution, in decimal degrees.
* @param allowFieldChanges whether this method is allowed to change the set of fields (degrees, minutes or seconds).
+ * @throws IllegalArgumentException if the given resolution is NaN or infinite.
*
* @since 1.0
*/
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
index 9786b78..57f366e 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
@@ -660,8 +660,8 @@ public final class RasterResource extends AbstractGridResource implements Resour
} catch (DisjointExtentException e) {
throw new NoSuchDataException(canNotReadFile(), e);
} catch (RuntimeException e) { // Many exceptions thrown by RasterFactory.wrap(…).
- final Throwable cause = e.getCause();
- if (cause instanceof TransformException) {
+ final Exception cause = getReferencingCause(e);
+ if (cause != null) {
throw new DataStoreReferencingException(canNotReadFile(), cause);
} else {
throw new DataStoreContentException(canNotReadFile(), e);
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java
index 0cd9832..a803548 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java
@@ -20,7 +20,10 @@ import java.util.Arrays;
import java.util.Optional;
import org.opengis.geometry.Envelope;
import org.opengis.metadata.spatial.DimensionNameType;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.util.FactoryException;
import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.DataStoreReferencingException;
import org.apache.sis.storage.GridCoverageResource;
import org.apache.sis.coverage.grid.GridGeometry;
import org.apache.sis.coverage.grid.GridExtent;
@@ -35,7 +38,7 @@ import org.apache.sis.util.ArraysExt;
* Base class for implementations of {@link GridCoverageResource}.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 0.8
* @module
*/
@@ -294,4 +297,20 @@ public abstract class AbstractGridResource extends AbstractResource implements G
return builder;
}
}
+
+ /**
+ * If the given exception is caused by a {@link FactoryException} or {@link TransformException},
+ * returns that cause. Otherwise returns {@code null}. This is a convenience method for deciding
+ * if an exception should be rethrown as an {@link DataStoreReferencingException}.
+ *
+ * @param e the exception for which to inspect the cause.
+ * @return the cause if it is a referencing problem, or {@code null} otherwise.
+ */
+ protected static Exception getReferencingCause(final RuntimeException e) {
+ final Throwable cause = e.getCause();
+ if (cause instanceof FactoryException || cause instanceof TransformException) {
+ return (Exception) cause;
+ }
+ return null;
+ }
}
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 c062fab..7f4c1df 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
@@ -82,6 +82,11 @@ public final class Resources extends IndexedResourceBundle {
public static final short CanNotGetCommonMetadata_2 = 39;
/**
+ * Can not intersect the data with specified query.
+ */
+ public static final short CanNotIntersectDataWithQuery = 57;
+
+ /**
* Can not read the Coordinate Reference System (CRS) Well Known Text (WKT) in “{0}”.
*/
public static final short CanNotReadCRS_WKT_1 = 37;
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 39a707a..da7c771 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
@@ -23,6 +23,7 @@ AmbiguousName_4 = Name \u201c{3}\u201d is ambiguous because it
CanNotCreateFolderStore_1 = Can not create resources based on the content of \u201c{0}\u201d directory.
CanNotDeriveTypeFromFeature_1 = Can not infer the feature type resulting from \u201c{0}\u201d filtering.
CanNotGetCommonMetadata_2 = Can not get metadata common to \u201c{0}\u201d files. The reason is: {1}
+CanNotIntersectDataWithQuery = Can not intersect the data with specified query.
CanNotReadCRS_WKT_1 = Can not read the Coordinate Reference System (CRS) Well Known Text (WKT) in \u201c{0}\u201d.
CanNotReadDirectory_1 = Can not read \u201c{0}\u201d directory.
CanNotReadFile_2 = Can not read \u201c{1}\u201d as a file in the {0} format.
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 0294938..7d62c29 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
@@ -28,6 +28,7 @@ AmbiguousName_4 = Le nom \u00ab\u202f{3}\u202f\u00bb est ambig
CanNotCreateFolderStore_1 = Ne peut pas cr\u00e9er des ressources bas\u00e9es sur le contenu du r\u00e9pertoire \u00ab\u202f{0}\u202f\u00bb.
CanNotDeriveTypeFromFeature_1 = Ne peut pas d\u00e9terminer le type de donn\u00e9es r\u00e9sultant du filtrage \u00ab\u202f{0}\u202f\u00bb.
CanNotGetCommonMetadata_2 = Ne peut pas obtenir les m\u00e9ta-donn\u00e9es communes aux fichiers \u00ab\u202f{0}\u202f\u00bb. La raison est\u2008: {1}
+CanNotIntersectDataWithQuery = Ne peut pas intercepter les donn\u00e9es avec la requ\u00eate qui a \u00e9t\u00e9 sp\u00e9cifi\u00e9e.
CanNotReadCRS_WKT_1 = Ne peut pas lire le syst\u00e8me de r\u00e9f\u00e9rence spatial dans le \u00ab\u202fWell Known Text\u202f\u00bb (WKT) de \u00ab\u202f{0}\u202f\u00bb.
CanNotReadDirectory_1 = Ne peut pas lire le r\u00e9pertoire \u00ab\u202f{0}\u202f\u00bb.
CanNotReadFile_2 = Ne peut pas lire \u00ab\u202f{1}\u202f\u00bb comme un fichier au format {0}.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/package-info.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/package-info.java
index 94fad8a..b072411 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/package-info.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/package-info.java
@@ -25,7 +25,7 @@
*
* @author Martin Desruisseaux (Geomatys)
* @author Johann Sorel (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 0.4
* @module
*/
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/CoverageQuery.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/CoverageQuery.java
index 21b2897..b88a673 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/CoverageQuery.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/CoverageQuery.java
@@ -16,46 +16,67 @@
*/
package org.apache.sis.internal.storage.query;
+import java.util.Arrays;
+import java.util.Objects;
+import java.math.RoundingMode;
+import org.opengis.metadata.extent.GeographicBoundingBox;
+import org.apache.sis.measure.Angle;
+import org.apache.sis.measure.Latitude;
+import org.apache.sis.measure.Longitude;
+import org.apache.sis.measure.AngleFormat;
import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.storage.GridCoverageResource;
import org.apache.sis.storage.Query;
import org.apache.sis.util.ArgumentChecks;
+
/**
- * Define a simple query configuration for coverage resources.
+ * A simple query configuration for coverage resources for requesting a subset of the domain and the range.
+ * Experimental for now, may move to public API in a future version.
*
- * @author Johann Sorel (Geomatys)
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
* @version 1.1
* @since 1.1
* @module
*/
-public final class CoverageQuery extends Query {
+public final class CoverageQuery extends Query implements Cloneable {
+ /**
+ * Desired grid extent and resolution, or {@code null} for reading the whole domain.
+ */
+ private GridGeometry domain;
- private final GridGeometry domain;
- private final int[] range;
- private final int margin;
+ /**
+ * 0-based indices of sample dimensions to read, or {@code null} for reading them all.
+ */
+ private int[] range;
/**
- * @param domain desired grid extent and resolution, or {@code null} for reading the whole domain.
- * @param range 0-based indices of sample dimensions to read, or {@code null} or an empty sequence for reading them all.
+ * Number of additional cells to read on each border of the source grid coverage.
+ * If non-zero, this property expands the {@linkplain #domain} to be read by an amount
+ * specified in unit of cells of the image to be read. Those cells do not necessarily
+ * have the same size than the cells of <code>{@linkplain #domain}.getExtent()</code>.
*/
- public CoverageQuery(GridGeometry domain, int... range) {
- this(domain, 0, range);
+ private int sourceDomainExpansion;
+
+ /**
+ * Creates a new query performing no filtering.
+ */
+ public CoverageQuery() {
}
/**
+ * Sets the desired grid extent and resolution.
+ *
* @param domain desired grid extent and resolution, or {@code null} for reading the whole domain.
- * @param margin reading margin
- * @param range 0-based indices of sample dimensions to read, or {@code null} or an empty sequence for reading them all.
*/
- public CoverageQuery(GridGeometry domain, int margin, int... range) {
- ArgumentChecks.ensureNonNull("margin", margin);
+ public void setDomain(final GridGeometry domain) {
this.domain = domain;
- this.margin = margin;
- this.range = range;
}
/**
- * Returns desired grid extent and resolution.
+ * Returns the desired grid extent and resolution.
+ * This is the value set by the last call to {@link #setDomain(GridGeometry)}.
*
* @return desired grid extent and resolution, or {@code null} for reading the whole domain.
*/
@@ -64,25 +85,195 @@ public final class CoverageQuery extends Query {
}
/**
- * Returns indices of samples dimensions to read.
+ * Sets the indices of samples dimensions to read.
+ *
+ * @param range 0-based indices of sample dimensions to read,
+ * or {@code null} or an empty sequence for reading them all.
+ */
+ public void setRange(final int... range) {
+ this.range = (range != null && range.length != 0) ? range.clone() : null;
+ }
+
+ /**
+ * Returns the indices of samples dimensions to read.
*
- * @return 0-based indices of sample dimensions to read, or {@code null} or an empty sequence for reading them all.
+ * @return 0-based indices of sample dimensions to read, or {@code null} for reading them all.
*/
public int[] getRange() {
- return range;
+ return (range != null) ? range.clone() : null;
}
/**
- * At reading time it may be necessary to add a margin to the read extent.
+ * Converts the given range indices from the indices relative to the sub-coverage
+ * to indices relative to the source coverage.
*
- * <p>This margin is used knowing later image processing operations will use a window to
- * iterator over samples. As example Bilinear interpolation uses a 1 pixel
- * window.</p>
+ * @param caller the caller, used for error messages only.
+ * @param request the range indices to convert, or {@code null} or empty if none.
+ * @return the range of indices relative to the source coverage.
+ * @throws IllegalArgumentException if an index is invalid.
+ */
+ final int[] subrange(final CoverageSubset caller, final int[] request) {
+ if (range == null) {
+ return request;
+ }
+ if (request == null || request.length == 0) {
+ return range.clone();
+ }
+ final int[] sub = new int[request.length];
+ for (int i=0; i<request.length; i++) {
+ final int j = request[i];
+ if (j >= 0 && j < range.length) {
+ sub[i] = range[j];
+ } else {
+ throw new IllegalArgumentException(caller.invalidRange(j));
+ }
+ }
+ return sub;
+ }
+
+ /**
+ * Returns the number of range indices, or 0 if no range sub-setting has been specified.
+ */
+ final int getRangeCount() {
+ return (range != null) ? range.length : 0;
+ }
+
+ /**
+ * Set a number of additional cells to read on each border of the source grid coverage.
+ * If non-zero, this property expands the {@linkplain #getDomain() domain} to be read
+ * by an amount specified in unit of cells of the image to be read. Those cells do not
+ * necessarily have the same size than the cells of <code>domain.getExtent()</code>.
+ *
+ * <p>At reading time it may be necessary to add such margin to the coverage extent.
+ * This margin is used when the user knows that an image processing operation will
+ * need to iterate over a little bit more data than the area of interest.
+ * For example the bilinear interpolation uses a 2×2 pixels window.</p>
+ *
+ * @param margin read margin, which must be zero or positive.
+ */
+ public void setSourceDomainExpansion(final int margin) {
+ ArgumentChecks.ensurePositive("margin", margin);
+ sourceDomainExpansion = margin;
+ }
+
+ /**
+ * Returns the number of additional cells to read on each border of the source grid coverage.
+ * This is the value sets by the last call to {@link #setSourceDomainExpansion(int)}.
*
- * @return read margin, zero or positive
+ * @return read margin, zero or positive.
*/
- public int getMargin() {
- return margin;
+ public int getSourceDomainExpansion() {
+ return sourceDomainExpansion;
}
+ /**
+ * Applies this query on the given coverage.
+ *
+ * @param source the coverage resource to filter.
+ * @return a view over the given coverage resource containing only the given domain and range.
+ */
+ public GridCoverageResource execute(final GridCoverageResource source) {
+ ArgumentChecks.ensureNonNull("source", source);
+ return new CoverageSubset(source, clone());
+ }
+
+ /**
+ * Returns a clone of this query.
+ *
+ * @return a clone of this query.
+ */
+ @Override
+ public CoverageQuery clone() {
+ /*
+ * Implementation note: no need to clone the arrays. It is safe to share the same array instances
+ * because this class does not modify them and does not return them directly to the user.
+ */
+ try {
+ return (CoverageQuery) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /**
+ * Returns a hash code value for this query.
+ *
+ * @return a hash value for this query.
+ */
+ @Override
+ public int hashCode() {
+ return 59 * Objects.hashCode(domain) +
+ 37 * Arrays.hashCode(range) +
+ 31 * sourceDomainExpansion;
+ }
+
+ /**
+ * Compares this query with the given object for equality.
+ *
+ * @param obj the object to compare with this query.
+ * @return whether the two objects are equal.
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj != null && getClass() == obj.getClass()) {
+ final CoverageQuery other = (CoverageQuery) obj;
+ return sourceDomainExpansion == other.sourceDomainExpansion &&
+ Objects.equals(domain, other.domain) &&
+ Arrays.equals(range, other.range);
+ }
+ return false;
+ }
+
+ /**
+ * Returns a textual representation looking like an SQL Select query.
+ *
+ * @return textual representation of this query.
+ */
+ @Override
+ public String toString() {
+ final StringBuffer sb = new StringBuffer(80);
+ sb.append("SELECT ");
+ if (range != null) {
+ sb.append("range[");
+ for (int i=0; i<range.length; i++) {
+ if (i != 0) sb.append(", ");
+ sb.append(range[i]);
+ }
+ sb.append(']');
+ } else {
+ sb.append('*');
+ }
+ if (domain != null) {
+ sb.append(" WHERE domain ∩ [");
+ final GeographicBoundingBox box = domain.getGeographicExtent().orElse(null);
+ if (box != null) {
+ final AngleFormat f = new AngleFormat("D°");
+ append(sb, f, new Latitude (box.getSouthBoundLatitude()),
+ new Latitude (box.getNorthBoundLatitude()));
+ append(sb, f, new Longitude(box.getWestBoundLongitude()),
+ new Longitude(box.getEastBoundLongitude()));
+ } else {
+ sb.append('?'); // If the domain can not be expressed as a geographic bounding box.
+ };
+ sb.append(']');
+ if (sourceDomainExpansion != 0) {
+ sb.append(" + margin(").append(sourceDomainExpansion).append(')');
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Formats a range of longitude or latitude values in the pseudo-SQL statement.
+ * This is a helper method for {@link #toString()}.
+ */
+ private static void append(final StringBuffer sb, final AngleFormat f, final Angle start, final Angle end) {
+ final double span = Math.abs(end.degrees() - start.degrees()) / 1000;
+ f.setPrecision(span > 0 && span < Double.POSITIVE_INFINITY ? span : 0, false); // Also filter NaN values.
+ f.setRoundingMode(RoundingMode.DOWN); f.format(start, sb, null);
+ f.setRoundingMode(RoundingMode.UP); f.format(end, sb, null);
+ }
}
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/CoverageSubset.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/CoverageSubset.java
new file mode 100644
index 0000000..2765820
--- /dev/null
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/CoverageSubset.java
@@ -0,0 +1,135 @@
+/*
+ * 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.query;
+
+import java.util.List;
+import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.storage.event.StoreListeners;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.DataStoreReferencingException;
+import org.apache.sis.internal.storage.Resources;
+import org.apache.sis.internal.storage.AbstractGridResource;
+import org.apache.sis.internal.util.UnmodifiableArrayList;
+
+
+/**
+ * The result of {@link CoverageQuery#execute(GridCoverageResource)}.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+final class CoverageSubset extends AbstractGridResource {
+ /**
+ * The coverage resource instances to filter.
+ */
+ private final GridCoverageResource source;
+
+ /**
+ * The query for filtering the source coverage.
+ */
+ private final CoverageQuery query;
+
+ /**
+ * Creates a new coverage resource by filtering the given coverage using the given query.
+ */
+ CoverageSubset(final GridCoverageResource source, final CoverageQuery query) {
+ super(source instanceof StoreListeners ? (StoreListeners) source : null);
+ this.source = source;
+ this.query = query;
+ }
+
+ /**
+ * Returns the valid extent of grid coordinates clipped to the area specified in the query.
+ *
+ * @return extent of grid coordinates clipped to the query.
+ * @throws DataStoreException if an error occurred while reading definitions from the underlying data store,
+ * or if the grid geometry can not be clipped to the query area.
+ */
+ @Override
+ public GridGeometry getGridGeometry() throws DataStoreException {
+ GridGeometry domain = source.getGridGeometry();
+ final GridGeometry sub = query.getDomain();
+ if (sub != null) try {
+ domain = domain.derive().subgrid(sub).build();
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ final String msg = Resources.forLocale(getLocale()).getString(Resources.Keys.CanNotIntersectDataWithQuery);
+ final Exception cause = getReferencingCause(e);
+ if (cause != null) {
+ throw new DataStoreReferencingException(msg, cause);
+ } else {
+ throw new DataStoreException(msg, e);
+ }
+ }
+ return domain;
+ }
+
+ /**
+ * Returns the ranges of sample values together with the conversion from samples to real values.
+ * The returned list should never be empty.
+ *
+ * @return ranges of sample values together with their mapping to "real values".
+ * @throws DataStoreException if an error occurred while reading definitions from the underlying data store.
+ */
+ @Override
+ public List<SampleDimension> getSampleDimensions() throws DataStoreException {
+ final List<SampleDimension> dimensions = source.getSampleDimensions();
+ if (query.getRangeCount() == 0) {
+ return dimensions;
+ }
+ final int[] range = query.getRange();
+ final SampleDimension[] subset = new SampleDimension[range.length];
+ for (int i=0; i<range.length; i++) {
+ final int j = range[i];
+ try {
+ subset[i] = dimensions.get(j);
+ } catch (IndexOutOfBoundsException e) {
+ throw new DataStoreException(invalidRange(j), e);
+ }
+ }
+ return UnmodifiableArrayList.wrap(subset);
+ }
+
+ /**
+ * Loads a subset of the grid coverage represented by this resource.
+ *
+ * @param domain desired grid extent and resolution, or {@code null} for reading the whole domain.
+ * @param range 0-based indices of sample dimensions to read, or {@code null} or an empty sequence for reading them all.
+ * @return the grid coverage for the specified domain and range.
+ * @throws DataStoreException if an error occurred while reading the grid coverage data.
+ */
+ @Override
+ public GridCoverage read(GridGeometry domain, int... range) throws DataStoreException {
+ if (domain == null) {
+ domain = query.getDomain();
+ }
+ range = query.subrange(this, range);
+ return source.read(domain, range);
+ }
+
+ /**
+ * Creates an exception message for an invalid range index.
+ */
+ final String invalidRange(final int index) {
+ return Resources.forLocale(getLocale()).getString(
+ Resources.Keys.InvalidSampleDimensionIndex_2, query.getRangeCount() - 1, index);
+ }
+}
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/SimpleQuery.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/SimpleQuery.java
index 8752f73..cb75e22 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/SimpleQuery.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/SimpleQuery.java
@@ -48,11 +48,13 @@ import org.opengis.filter.sort.SortBy;
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 1.0
* @module
+ *
+ * @todo Rename {@code FeatureQuery}.
*/
-public class SimpleQuery extends Query {
+public class SimpleQuery extends Query implements Cloneable {
/**
* Sentinel limit value for queries of unlimited length.
* This value can be given to {@link #setLimit(long)} or retrieved from {@link #getLimit()}.
@@ -391,7 +393,7 @@ public class SimpleQuery extends Query {
*/
public FeatureSet execute(final FeatureSet source) {
ArgumentChecks.ensureNonNull("source", source);
- return new FeatureSubset(source, this);
+ return new FeatureSubset(source, clone());
}
/**
@@ -416,6 +418,24 @@ public class SimpleQuery extends Query {
}
/**
+ * Returns a clone of this query.
+ *
+ * @return a clone of this query.
+ */
+ @Override
+ public SimpleQuery clone() {
+ /*
+ * Implementation note: no need to clone the arrays. It is safe to share the same array instances
+ * because this class does not modify them and does not return them directly to the user.
+ */
+ try {
+ return (SimpleQuery) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /**
* Returns a hash code value for this query.
*
* @return a hash value for this query.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/package-info.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/package-info.java
index abcd74e..a75a1c9 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/package-info.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/package-info.java
@@ -25,7 +25,7 @@
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 1.0
* @module
*/
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/GridCoverageResource.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/GridCoverageResource.java
index dc56851..a0dfe3b 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/GridCoverageResource.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/GridCoverageResource.java
@@ -20,6 +20,7 @@ import java.util.List;
import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.coverage.grid.GridGeometry;
import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.internal.storage.query.CoverageQuery;
import org.apache.sis.util.ArgumentChecks;
@@ -37,6 +38,7 @@ import org.apache.sis.util.ArgumentChecks;
* A coverage resource may be a member of {@link Aggregate} if a single file can provide many rasters.
*
* @author Martin Desruisseaux (Geomatys)
+ * @author Johann Sorel (Geomatys)
* @version 1.1
* @since 1.0
* @module
@@ -107,6 +109,12 @@ public interface GridCoverageResource extends DataSet {
/**
* Requests a subset of the coverage.
+ * The filtering can be applied in two aspects:
+ *
+ * <ul>
+ * <li>The returned {@code GridCoverageResource} may contain a smaller domain (grid extent).</li>
+ * <li>The returned {@code GridCoverageResource} may contain a smaller range (less sample dimensions).</li>
+ * </ul>
*
* <p>The returned subset may be a <em>view</em> of this set, i.e. changes in this {@code GridCoverageResource}
* may be reflected immediately on the returned subset (and conversely), but not necessarily.
@@ -115,16 +123,21 @@ public interface GridCoverageResource extends DataSet {
*
* <p>The default implementation throws {@link UnsupportedQueryException}.</p>
*
- * @since 1.1
- * @param query definition of space and sample dimension transforms applied at reading time.
+ * @param query definition of domain (grid extent) and range (sample dimensions) filtering applied at reading time.
* @return resulting coverage resource (never {@code null}).
* @throws UnsupportedQueryException if this {@code GridCoverageResource} can not execute the given query.
* This includes query validation errors.
* @throws DataStoreException if another error occurred while processing the query.
+ *
+ * @since 1.1
*/
- default GridCoverageResource subset(Query query) throws UnsupportedQueryException, DataStoreException {
+ default GridCoverageResource subset(final Query query) throws UnsupportedQueryException, DataStoreException {
ArgumentChecks.ensureNonNull("query", query);
- throw new UnsupportedQueryException();
+ if (query instanceof CoverageQuery) {
+ return ((CoverageQuery) query).execute(this);
+ } else {
+ throw new UnsupportedQueryException();
+ }
}
/**
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/Query.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/Query.java
index 2527de7..39cf050 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/Query.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/Query.java
@@ -21,7 +21,8 @@ import org.opengis.feature.Feature;
/**
* Definition of filtering to apply for fetching a resource subset.
- * Filtering can happen in two domains:
+ * Filtering can be applied on {@link FeatureSet} or on {@link GridCoverageResource}.
+ * When applied on {@link FeatureSet}, filtering can happen in two domains:
*
* <ol>
* <li>By filtering the {@link Feature} instances.</li>
@@ -49,6 +50,7 @@ import org.opengis.feature.Feature;
* @version 1.0
*
* @see FeatureSet#subset(Query)
+ * @see GridCoverageResource#subset(Query)
*
* @since 0.8
* @module
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/package-info.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/package-info.java
index a20268b..f4ad848 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/package-info.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/package-info.java
@@ -26,7 +26,7 @@
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 0.3
* @module
*/