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 2020/09/10 14:50:02 UTC
[sis] 01/01: Merge branch 'feat/sourceDomainExpansion' into
geoapi-4.0. Reviewed some `GridExtent` and `AbstractGridResource` methods
for consistency and for making little bit easier to implement
`MemoryGridResource`.
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 e11ba912fba0636abc516da30f30bda1e38583e6
Merge: c7a721a fbd5f31
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Thu Sep 10 16:47:27 2020 +0200
Merge branch 'feat/sourceDomainExpansion' into geoapi-4.0.
Reviewed some `GridExtent` and `AbstractGridResource` methods for consistency and for making little bit easier to implement `MemoryGridResource`.
.../sis/coverage/grid/GridCoverageBuilder.java | 1 +
.../apache/sis/coverage/grid/GridDerivation.java | 8 +-
.../org/apache/sis/coverage/grid/GridExtent.java | 117 ++++++++++-----
.../apache/sis/coverage/grid/GridExtentTest.java | 19 +--
storage/sis-storage/pom.xml | 7 +
.../sis/internal/storage/AbstractGridResource.java | 54 ++++++-
.../sis/internal/storage/MemoryGridResource.java | 164 +++++++++++++++++++++
.../sis/internal/storage/query/CoverageQuery.java | 3 -
.../sis/internal/storage/query/CoverageSubset.java | 20 ++-
.../internal/storage/query/CoverageQueryTest.java | 145 ++++++++++++++++++
.../apache/sis/test/suite/StorageTestSuite.java | 1 +
11 files changed, 469 insertions(+), 70 deletions(-)
diff --cc core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
index 216837f,216837f..b446dfe
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageBuilder.java
@@@ -80,6 -80,6 +80,7 @@@ import org.apache.sis.util.resources.Er
* }
* </div>
*
++ * <h2>Limitations</h2>
* Current implementation creates only two-dimensional coverages.
* A future version may extend this builder API for creating <var>n</var>-dimensional coverages.
*
diff --cc core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridDerivation.java
index 4d197e0,2d21a2c..87ebc60
--- 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
@@@ -248,6 -248,6 +248,8 @@@ public class GridDerivation
* @throws IllegalArgumentException if a value is negative.
* @throws IllegalStateException if {@link #subgrid(Envelope, double...)} or {@link #slice(DirectPosition)}
* has already been invoked.
++ *
++ * @see GridExtent#grow(boolean, boolean, long...)
*/
public GridDerivation margin(final int... cellCounts) {
ArgumentChecks.ensureNonNull("cellCounts", cellCounts);
@@@ -373,7 -373,7 +375,8 @@@
* }
* </div>
*
-- * The following information are mandatory:
++ * If {@code gridExtent} contains only an envelope, then this method delegates to {@link #subgrid(Envelope, double...)}.
++ * Otherwise the following information are mandatory:
* <ul>
* <li>{@linkplain GridGeometry#getExtent() Extent} in {@code gridOfInterest}.</li>
* <li>{@linkplain GridGeometry#getGridToCRS(PixelInCell) Grid to CRS} conversion in {@code gridOfInterest}.</li>
@@@ -631,6 -628,6 +634,7 @@@
final int i = (modifiedDimensions != null) ? modifiedDimensions[k] : k;
m.setElement(i, i, s);
m.setElement(i, dimension, baseExtent.getLow(i) - scaledExtent.getLow(i) * s);
++ // TODO: use Math.fma with JDK9.
}
}
toBase = MathTransforms.linear(m);
@@@ -950,7 -947,7 +954,7 @@@
* required information for applying a margin anyway (no `GridExtent`, no `gridToCRS`).
*/
if (margin != null && baseExtent != null) {
-- return new GridGeometry(base, baseExtent.expand(ArraysExt.copyAsLongs(margin)), null);
++ return new GridGeometry(base, baseExtent.grow(true, true, ArraysExt.copyAsLongs(margin)), null);
}
} catch (TransformException e) {
throw new IllegalGridGeometryException(e, "envelope");
diff --cc core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
index c837147,c837147..bb4c958
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java
@@@ -27,7 -27,7 +27,6 @@@ import java.io.UncheckedIOException
import org.opengis.util.FactoryException;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.DirectPosition;
--import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.metadata.spatial.DimensionNameType;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.cs.CoordinateSystem;
@@@ -1068,31 -1068,31 +1067,71 @@@ public class GridExtent implements Grid
* @param margins amount of cells to add or subtract.
* @return a grid extent expanded by the given amount, or {@code this} if there is no change.
* @throws ArithmeticException if expanding this extent by the given margins overflows {@code long} capacity.
++ *
++ * @deprecated Replaced by {@link #grow(boolean, boolean, long...)}.
*/
++ @Deprecated
public GridExtent expand(final long... margins) {
++ return grow(true, true, margins);
++ }
++
++ /**
++ * Returns a grid extent expanded by the given amount of cells on the specified sides along each dimension.
++ * This method can modify the low coordinates, the high coordinates or both.
++ * <ul>
++ * <li>If {@code low} is true, subtracts the given margin from {@linkplain #getLow(int) low coordinates}.</li>
++ * <li>If {@code high} is true, adds the same margin to {@linkplain #getHigh(int) high coordinates}.</li>
++ * </ul>
++ * If a negative margin is supplied, the extent size decreases accordingly.
++ *
++ * <h4>Number of arguments</h4>
++ * The {@code margins} array length should be equal to the {@linkplain #getDimension() number of dimensions}.
++ * If the array is shorter, missing values default to 0 (i.e. sizes in unspecified dimensions are unchanged).
++ * If the array is longer, extraneous values are ignored.
++ *
++ * @param low whether to subtract the specified margin from low coordinates.
++ * @param high whether to add the specified margin to high coordinates.
++ * @param margins amount of cells to add or subtract on specified sides of each dimension.
++ * @return a grid extent expanded by the given amount, or {@code this} if there is no change.
++ * @throws ArithmeticException if expanding this extent by the given margin overflows {@code long} capacity.
++ *
++ * @see GridDerivation#margin(int...)
++ *
++ * @since 1.1
++ */
++ public GridExtent grow(final boolean low, final boolean high, final long... margins) {
ArgumentChecks.ensureNonNull("margins", margins);
final int m = getDimension();
final int length = Math.min(m, margins.length);
-- final GridExtent resize = new GridExtent(this);
-- final long[] c = resize.coordinates;
-- for (int i=0; i<length; i++) {
-- final long margin = margins[i];
-- c[i] = Math.subtractExact(c[i], margin);
-- c[i+m] = Math.addExact(c[i+m], margin);
++ if ((low | high) && !isZero(margins, length)) {
++ final GridExtent resized = new GridExtent(this);
++ final long[] c = resized.coordinates;
++ for (int i=0; i<length; i++) {
++ final long p = margins[i];
++ if (low) c[i] = Math.subtractExact(c[i], p);
++ if (high) c[i+m] = Math.addExact(c[i+m], p);
++ }
++ return resized;
}
-- return Arrays.equals(c, coordinates) ? this : resize;
++ return this;
}
/**
-- * Sets the size of this grid extent to the given values. This method modifies grid coordinates as if they were multiplied
-- * by (given size) / ({@linkplain #getSize(int) current size}), rounded toward zero and with the value farthest from zero
-- * adjusted by ±1 for having a size exactly equals to the specified value.
++ * Sets the size of grid extent to the given values by moving low and high coordinates.
++ * This method modifies grid coordinates as if they were multiplied by
++ *
++ * <var>(given size)</var> / <var>({@linkplain #getSize(int) current size})</var>,
++ *
++ * rounded toward zero and with the value farthest from zero adjusted by ±1 for having a size
++ * exactly equals to the specified value.
++ *
* In the common case where the {@linkplain #getLow(int) low value} is zero,
* this is equivalent to setting the {@linkplain #getHigh(int) high value} to {@code size} - 1.
*
-- * <p>The length of the given array should be equal to {@link #getDimension()}.
-- * If the array is shorter, missing dimensions have their size unchanged.
-- * If the array is longer, extra sizes are ignored.</p>
++ * <h4>Number of arguments</h4>
++ * The {@code sizes} array length should be equal to the {@linkplain #getDimension() number of dimensions}.
++ * If the array is shorter, sizes in unspecified dimensions are unchanged.
++ * If the array is longer, extraneous values are ignored.
*
* @param sizes the new grid sizes for each dimension.
* @return a grid extent having the given sizes, or {@code this} if there is no change.
@@@ -1129,8 -1129,8 +1168,8 @@@
}
/**
-- * Returns a grid envelope that encompass only some dimensions of this grid envelope.
-- * This method copies the specified dimensions of this grid envelope into a new grid envelope.
++ * Returns a grid extent that encompass only some dimensions of this grid extent.
++ * This method copies the specified dimensions of this grid extent into a new grid extent.
* The given dimensions must be in strictly ascending order without duplicated values.
* The number of dimensions of the sub grid envelope will be {@code dimensions.length}.
*
@@@ -1184,6 -1184,6 +1223,11 @@@
* This method does not reduce the number of dimensions of the grid extent.
* For dimensionality reduction, see {@link #reduce(int...)}.
*
++ * <h4>Number of arguments</h4>
++ * The {@code periods} array length should be equal to the {@linkplain #getDimension() number of dimensions}.
++ * If the array is shorter, missing values default to 1 (i.e. samplings in unspecified dimensions are unchanged).
++ * If the array is longer, extraneous values are ignored.
++ *
* @param periods the subsamplings. Length shall be equal to the number of dimension and all values shall be greater than zero.
* @return the subsampled extent, or {@code this} is subsampling results in the same extent.
* @throws IllegalArgumentException if a period is not greater than zero.
@@@ -1193,9 -1193,9 +1237,9 @@@
public GridExtent subsample(final int... periods) {
ArgumentChecks.ensureNonNull("periods", periods);
final int m = getDimension();
-- ArgumentChecks.ensureDimensionMatches("periods", m, periods);
++ final int length = Math.min(m, periods.length);
final GridExtent sub = new GridExtent(this);
-- for (int i=0; i<m; i++) {
++ for (int i=0; i<length; i++) {
final int s = periods[i];
if (s > 1) {
final int j = i + m;
@@@ -1216,7 -1216,7 +1260,7 @@@
}
/**
-- * Returns a slice of this given grid extent computed by a ratio between 0 and 1 inclusive.
++ * Returns a slice of this grid extent computed by a ratio between 0 and 1 inclusive.
* This is a helper method for {@link GridDerivation#sliceByRatio(double, int...)} implementation.
*
* @param slicePoint a pre-allocated direct position to be overwritten by this method.
@@@ -1330,11 -1330,11 +1374,14 @@@
* for an extent (x: [0…10], y: [2…4], z: [0…1]) and a translation {-2, 2},
* the resulting extent would be (x: [-2…8], y: [4…6], z: [0…1]).</div>
*
++ * <h4>Number of arguments</h4>
++ * The {@code translation} array length should be equal to the {@linkplain #getDimension() number of dimensions}.
++ * If the array is shorter, missing values default to 0 (i.e. no translation in unspecified dimensions).
++ * If the array is longer, extraneous values are ignored.
++ *
* @param translation translation to apply on each axis in order, or {@code null} if none.
* @return a grid-extent whose coordinates (both low and high ones) have been translated by given amounts.
* If the given translation is a no-op (no value or only 0 ones), then this extent is returned as is.
-- * @throws MismatchedDimensionException if given translation vector is longer than
-- * {@linkplain #getDimension() this extent dimension}.
* @throws ArithmeticException if the translation results in coordinates that overflow 64-bits integer.
*
* @see #startsAtZero()
@@@ -1342,23 -1342,23 +1389,19 @@@
* @since 1.1
*/
public GridExtent translate(final long... translation) {
-- if (translation != null) {
-- final int dimension = getDimension();
-- if (translation.length > dimension) {
-- throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3,
-- "translation", translation.length, dimension));
-- }
-- if (!isZero(translation, translation.length)) {
-- final GridExtent translated = new GridExtent(this);
-- final long[] c = translated.coordinates;
-- for (int i=0; i < translation.length; i++) {
-- final int j = i + dimension;
-- final long t = translation[i];
-- c[i] = Math.addExact(c[i], t);
-- c[j] = Math.addExact(c[j], t);
-- }
-- return translated;
++ ArgumentChecks.ensureNonNull("translation", translation);
++ final int m = getDimension();
++ final int length = Math.min(m, translation.length);
++ if (!isZero(translation, length)) {
++ final GridExtent translated = new GridExtent(this);
++ final long[] c = translated.coordinates;
++ for (int i=0; i < length; i++) {
++ final int j = i + m;
++ final long t = translation[i];
++ c[i] = Math.addExact(c[i], t);
++ c[j] = Math.addExact(c[j], t);
}
++ return translated;
}
return this;
}
diff --cc core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
index 91bde02,91bde02..c21a5d3
--- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java
@@@ -166,12 -166,12 +166,12 @@@ public final strictfp class GridExtentT
}
/**
-- * Tests {@link GridExtent#expand(long...)}.
++ * Tests {@link GridExtent#grow(boolean, boolean, long...)}.
*/
@Test
public void testExpand() {
GridExtent extent = create3D();
-- extent = extent.expand(20, -10);
++ extent = extent.grow(true, true, 20, -10);
assertExtentEquals(extent, 0, 80, 519);
assertExtentEquals(extent, 1, 210, 789);
assertExtentEquals(extent, 2, 40, 49);
@@@ -322,27 -322,27 +322,12 @@@
@Test
public void empty_translation_returns_same_extent_instance() {
final GridExtent extent = new GridExtent(10, 10);
-- assertSame("Same instance returned in case of no-op", extent, extent.translate(null));
assertSame("Same instance returned in case of no-op", extent, extent.translate());
assertSame("Same instance returned in case of no-op", extent, extent.translate(0));
assertSame("Same instance returned in case of no-op", extent, extent.translate(0, 0));
}
/**
-- * Verifies that {@link GridExtent#translate(long...)} does not accept a vector with too many dimensions.
-- */
-- @Test
-- public void translation_fails_if_given_array_contains_too_many_elements() {
-- try {
-- new GridExtent(2, 1).translate(2, 1, 2);
-- fail("A translation with too many dimensions shall fail.");
-- } catch (IllegalArgumentException e) {
-- assertTrue(e.getMessage().contains("translation"));
-- // Expected behavior.
-- }
-- }
--
-- /**
* Verifies that {@link GridExtent#translate(long...)} accepts a vector with less dimensions
* than the extent number of dimensions. No translation shall be applied in missing dimensions.
*/
diff --cc storage/sis-storage/pom.xml
index 2564055,2564055..3541089
--- a/storage/sis-storage/pom.xml
+++ b/storage/sis-storage/pom.xml
@@@ -136,6 -136,6 +136,13 @@@
<scope>test</scope>
</dependency>
<dependency>
++ <groupId>org.apache.sis.core</groupId>
++ <artifactId>sis-referencing</artifactId>
++ <version>${project.version}</version>
++ <type>test-jar</type>
++ <scope>test</scope>
++ </dependency>
++ <dependency>
<groupId>com.esri.geometry</groupId>
<artifactId>esri-geometry-api</artifactId>
<scope>test</scope>
diff --cc storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java
index 0df65f5,0df65f5..ae6e0db
--- 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
@@@ -16,6 -16,6 +16,7 @@@
*/
package org.apache.sis.internal.storage;
++import java.util.List;
import java.util.Arrays;
import java.util.Optional;
import org.opengis.geometry.Envelope;
@@@ -104,8 -104,8 +105,10 @@@ public abstract class AbstractGridResou
for (int i=1; i<numSampleDimensions; i++) {
packed[i] = (((long) i) << Integer.SIZE) | i;
}
-- return new RangeArgument(packed);
} else {
++ /*
++ * Pattern: [specified `range` value | index in `range` where the value was specified]
++ */
packed = new long[range.length];
for (int i=0; i<range.length; i++) {
final int r = range[i];
@@@ -115,9 -115,9 +118,14 @@@
}
packed[i] = (((long) r) << Integer.SIZE) | i;
}
++ /*
++ * Sort by increasing `range` value, but keep together with index in `range` where each
++ * value was specified. After sorting, it become easy to check for duplicated values.
++ */
Arrays.sort(packed);
int previous = -1;
for (int i=0; i<packed.length; i++) {
++ // Never negative because of check in previous loop.
final int r = (int) (packed[i] >>> Integer.SIZE);
if (r == previous) {
throw new IllegalArgumentException(Resources.forLocale(getLocale()).getString(
@@@ -139,8 -139,8 +147,10 @@@
private static final DimensionNameType BAND = DimensionNameType.valueOf("BAND");
/**
-- * The user-specified range indices in high bits, together with indices order in the low bits.
-- * This packing is used for making easier to sort this array in user-specified order.
++ * The range indices specified by user in high bits, together (in the low bits)
++ * with the position in the {@code ranges} array where each index was specified.
++ * This packing is used for making easier to sort this array in increasing order
++ * of user-specified range index.
*/
private final long[] packed;
@@@ -159,12 -159,12 +169,27 @@@
/**
* Encapsulates the given {@code range} argument packed in high bits.
*/
-- RangeArgument(final long[] packed) {
++ private RangeArgument(final long[] packed) {
this.packed = packed;
interval = 1;
}
/**
++ * Returns {@code true} if user specified all bands in increasing order and no subsampling is applied.
++ *
++ * @return whether user specified all bands in increasing order without subsampling.
++ */
++ public boolean isIdentity() {
++ if (interval != 1) return false;
++ for (int i=0; i<packed.length; i++) {
++ if (packed[i] != ((((long) i) << Integer.SIZE) | i)) {
++ return false;
++ }
++ }
++ return true;
++ }
++
++ /**
* Returns the number of sample dimensions. This is the length of the range array supplied by user.
*
* @return the number of sample dimensions.
@@@ -175,7 -175,7 +200,7 @@@
/**
* Returns the value of the first index specified by the user. This is not necessarily equal to
-- * {@code getBandIndex(0)} if the user specified the bands out of order.
++ * {@code getSourceIndex(0)} if the user specified the bands out of order.
*
* @return index of the first value in the user-specified {@code range} array.
*/
@@@ -201,10 -201,10 +226,10 @@@
/**
* Returns the i<sup>th</sup> band position. This is the index in the user-supplied {@code range} array
-- * where was specified the {@code getBandIndex(i)} value.
++ * where the {@code getSourceIndex(i)} value was specified.
*
* @param i index of the range index to get, from 0 inclusive to {@link #getNumBands()} exclusive.
-- * @return index in user-supplied {@code range} array where was specified the {@code getBandIndex(i)} value.
++ * @return index in user-supplied {@code range} array where was specified the {@code getSourceIndex(i)} value.
*/
public int getTargetIndex(final int i) {
return (int) packed[i];
@@@ -282,6 -282,6 +307,21 @@@
}
/**
++ * Returns sample dimensions selected by the user. This is a convenience method for situations where
++ * sample dimensions are already in memory and there is no advantage to read them in "physical" order.
++ *
++ * @param sourceBands bands in the source coverage.
++ * @return bands selected by user, in user-specified order.
++ */
++ public SampleDimension[] select(final List<? extends SampleDimension> sourceBands) {
++ final SampleDimension[] bands = new SampleDimension[getNumBands()];
++ for (int i=0; i<bands.length; i++) {
++ bands[getTargetIndex(i)] = sourceBands.get(getSourceIndex(i));
++ }
++ return bands;
++ }
++
++ /**
* Returns a builder for sample dimensions. This method recycles the same builder on every calls.
* If the builder has been returned by a previous call to this method,
* then it is {@linkplain SampleDimension.Builder#clear() cleared} before to be returned again.
diff --cc storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryGridResource.java
index 0000000,b451ca5..5ca5d2f
mode 000000,100644..100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryGridResource.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryGridResource.java
@@@ -1,0 -1,143 +1,164 @@@
+ /*
+ * 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;
+
-import java.awt.image.RenderedImage;
-import java.util.ArrayList;
+ import java.util.List;
++import java.awt.image.RenderedImage;
++import org.opengis.referencing.datum.PixelInCell;
+ import org.apache.sis.coverage.SampleDimension;
+ import org.apache.sis.coverage.grid.GridCoverage;
-import org.apache.sis.coverage.grid.GridCoverage2D;
+ import org.apache.sis.coverage.grid.GridCoverageBuilder;
-import org.apache.sis.coverage.grid.GridDerivation;
+ import org.apache.sis.coverage.grid.GridExtent;
+ import org.apache.sis.coverage.grid.GridGeometry;
+ import org.apache.sis.coverage.grid.GridRoundingMode;
+ import org.apache.sis.image.ImageProcessor;
-import org.apache.sis.storage.DataStoreException;
++import org.apache.sis.internal.util.UnmodifiableArrayList;
+ import org.apache.sis.storage.event.StoreListeners;
+ import org.apache.sis.util.ArgumentChecks;
-import org.opengis.referencing.datum.PixelInCell;
++
+
+ /**
- * A GridCoverage resource in memory. The GridCoverage is specified at construction time.
++ * A {@link org.apache.sis.storage.GridCoverageResource} in memory.
++ * This resource wraps an arbitrary {@link GridCoverage} specified at construction time.
+ * Metadata can be specified by overriding {@link #createMetadata(MetadataBuilder)}.
+ *
+ * @author Johann Sorel (Geomatys)
++ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+ public class MemoryGridResource extends AbstractGridResource {
-
- private final GridCoverage2D coverage;
++ /**
++ * The grid coverage specified at construction time.
++ */
++ private final GridCoverage coverage;
+
+ /**
+ * Creates a new coverage stored in memory.
+ *
- * @param parent listeners of the parent resource, or {@code null} if none.
- * @param coverage stored coverage, coverage will not be copied, can not be null.
++ * @param parent listeners of the parent resource, or {@code null} if none.
++ * @param coverage stored coverage retained as-is (not copied). Can not be null.
+ */
- public MemoryGridResource(final StoreListeners parent, final GridCoverage2D coverage) {
++ public MemoryGridResource(final StoreListeners parent, final GridCoverage coverage) {
+ super(parent);
+ ArgumentChecks.ensureNonNull("coverage", coverage);
+ this.coverage = coverage;
+ }
+
+ /**
- * {@inheritDoc }
++ * Returns information about the <cite>domain</cite> of wrapped grid coverage.
+ *
+ * @return extent of grid coordinates together with their mapping to "real world" coordinates.
- * @throws DataStoreException if an error occurred while reading definitions from the underlying data store.
+ */
+ @Override
- public GridGeometry getGridGeometry() throws DataStoreException {
++ public GridGeometry getGridGeometry() {
+ return coverage.getGridGeometry();
+ }
+
+ /**
- * {@inheritDoc }
++ * Returns information about the <cite>range</cite> of wrapped grid coverage.
+ *
+ * @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 {
++ public List<SampleDimension> getSampleDimensions() {
+ return coverage.getSampleDimensions();
+ }
+
+ /**
- * {@inheritDoc }
++ * Returns a subset of the wrapped grid coverage. If a non-null grid geometry is specified, then
++ * this method tries to return a grid coverage matching the given grid geometry on a best-effort basis.
++ * In current implementation this is either a {@link org.apache.sis.coverage.grid.GridCoverage2D} or
++ * the original grid coverage.
+ *
+ * @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 {
-
- //quick return of the original coverage
- if (domain == null && (range == null || range.length == 0)) {
- return coverage;
- }
-
++ public GridCoverage read(GridGeometry domain, final int... range) {
++ List<SampleDimension> bands = coverage.getSampleDimensions();
++ final RangeArgument rangeIndices = validateRangeArgument(bands.size(), range);
+ /*
- Subsampling is ignored because it is an expensive operation.
- Clipping and range selection are light and current implementation
- do not copy any data.
- */
- GridGeometry areaOfInterest = coverage.getGridGeometry();
- GridExtent intersection = areaOfInterest.getExtent();
- if (domain != null) {
- final GridDerivation derivation = getGridGeometry().derive()
++ * The given `domain` may use arbitrary `gridToCRS` and `CRS` properties.
++ * For this simple implementation we need the same `gridToCRS` and `CRS`
++ * than the wrapped coverage; only domain `extent` is allowed to differ.
++ * Subsampling is ignored for now because it is an expensive operation.
++ * Clipping and range selection are light and do not copy any data.
++ *
++ * TODO: a future implementation may apply subsampling efficiently,
++ * by adjusting the pixel stride in SampleModel.
++ */
++ GridExtent intersection = null;
++ final GridGeometry source = coverage.getGridGeometry();
++ if (domain == null) {
++ domain = source;
++ } else {
++ intersection = source.derive()
+ .rounding(GridRoundingMode.ENCLOSING)
- .subgrid(domain);
- intersection = derivation.getIntersection();
- /*
- Extent is left as null, the grid coverage builder will extract the size from the image.
- Image size and offset may not be exactly what was requested by the intersection.
- */
- areaOfInterest = new GridGeometry(null,
- PixelInCell.CELL_CENTER,
- areaOfInterest.getGridToCRS(PixelInCell.CELL_CENTER),
- areaOfInterest.getCoordinateReferenceSystem());
++ .subgrid(domain).getIntersection(); // Take in account the change of CRS if needed.
++ if (intersection.equals(source.getExtent())) {
++ intersection = null; // Will request the whole image.
++ domain = source;
++ }
++ }
++ /*
++ * Quick check before to invoke the potentially costly `coverage.render(…)` method.
++ */
++ final boolean sameBands = rangeIndices.isIdentity();
++ if (sameBands && intersection == null) {
++ return coverage;
+ }
-
- RenderedImage render = coverage.render(intersection);
-
+ /*
- Select range.
- */
- List<SampleDimension> sampleDimensions = coverage.getSampleDimensions();
- if (range != null && range.length > 0) {
- render = new ImageProcessor().selectBands(render, range);
- final List<SampleDimension> sds = new ArrayList<>();
- for (int i : range) {
- sds.add(sampleDimensions.get(i));
++ * After `render(…)` execution, the (minX, minY) image coordinates are the differences between
++ * the extent that we requested and the one that we got. If that differences is not zero, then
++ * we need to translate the `GridExtent` in order to make it matches what we got.
++ */
++ RenderedImage data = coverage.render(intersection);
++ if (intersection != null) {
++ final int[] sd = intersection.getSubspaceDimensions(2);
++ final int dimX = sd[0];
++ final int dimY = sd[1];
++ final long[] changes = new long[Math.max(dimX, dimY) + 1];
++ changes[dimX] = data.getMinX();
++ changes[dimY] = data.getMinY();
++ intersection = intersection.translate(changes);
++ changes[dimX] = Math.subtractExact(data.getWidth(), intersection.getSize(dimX));
++ changes[dimY] = Math.subtractExact(data.getHeight(), intersection.getSize(dimY));
++ intersection = intersection.grow(false, true, changes);
++ if (intersection.equals(source.getExtent())) {
++ if (sameBands) {
++ return coverage;
++ }
++ domain = source;
++ } else {
++ domain = new GridGeometry(intersection, PixelInCell.CELL_CORNER,
++ source.getGridToCRS(PixelInCell.CELL_CORNER),
++ source.getCoordinateReferenceSystem());
+ }
- sampleDimensions = sds;
+ }
-
- final GridCoverageBuilder gcb = new GridCoverageBuilder();
- gcb.setValues(render);
- gcb.setRanges(sampleDimensions);
- gcb.setDomain(areaOfInterest);
- return gcb.build();
++ if (!sameBands) {
++ data = new ImageProcessor().selectBands(data, range);
++ bands = UnmodifiableArrayList.wrap(rangeIndices.select(bands));
++ }
++ return new GridCoverageBuilder()
++ .setValues(data)
++ .setRanges(bands)
++ .setDomain(domain).build();
+ }
-
+ }
diff --cc storage/sis-storage/src/main/java/org/apache/sis/internal/storage/query/CoverageSubset.java
index 143e1c5,701b9b8..2f59592
--- 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
@@@ -20,6 -21,6 +21,7 @@@ 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.coverage.grid.GridDerivation;
import org.apache.sis.coverage.grid.GridRoundingMode;
import org.apache.sis.coverage.grid.DisjointExtentException;
import org.apache.sis.storage.GridCoverageResource;
@@@ -80,7 -81,10 +82,10 @@@ final class CoverageSubset extends Abst
*/
@Override
public GridGeometry getGridGeometry() throws DataStoreException {
- return clip(source.getGridGeometry(), query.getDomain(), GridRoundingMode.NEAREST);
+ return clip(source.getGridGeometry(),
- query.getDomain(),
- GridRoundingMode.NEAREST,
- query.getSourceDomainExpansion());
++ query.getDomain(),
++ GridRoundingMode.NEAREST,
++ query.getSourceDomainExpansion());
}
/**
@@@ -89,16 -93,23 +94,23 @@@
* @param domain the domain to clip, or {@code null}.
* @param areaOfInterest the area of interest, or {@code null}.
* @param rounding whether to clip to nearest box or an enclosing box.
- * @param expansion domain expansion, applied only if domain extent is defined.
++ * @param expansion number of additional cells to read on each border of the source grid coverage.
* @return intersection of the given grid geometry.
* @throws DataStoreException if the intersection can not be computed.
*/
private GridGeometry clip(final GridGeometry domain, final GridGeometry areaOfInterest,
- final GridRoundingMode rounding) throws DataStoreException
- final GridRoundingMode rounding, int expansion) throws DataStoreException
++ final GridRoundingMode rounding, final int expansion) throws DataStoreException
{
if (domain == null) return areaOfInterest;
if (areaOfInterest == null) return domain;
try {
- return domain.derive().rounding(rounding).subgrid(areaOfInterest).build();
- if (expansion > 0 && domain.isDefined(GridGeometry.EXTENT)) {
++ final GridDerivation derivation = domain.derive().rounding(rounding);
++ if (expansion > 0) {
+ final int[] margins = new int[domain.getDimension()];
+ Arrays.fill(margins, expansion);
- return domain.derive().rounding(rounding).margin(margins).subgrid(areaOfInterest).build();
- } else {
- return domain.derive().rounding(rounding).subgrid(areaOfInterest).build();
++ derivation.margin(margins);
+ }
++ return derivation.subgrid(areaOfInterest).build();
} catch (IllegalArgumentException | IllegalStateException e) {
final String msg = Resources.forLocale(getLocale())
.getString(Resources.Keys.CanNotIntersectDataWithQuery_1, getSourceName());
@@@ -149,7 -160,11 +161,7 @@@
*/
@Override
public GridCoverage read(GridGeometry domain, int... range) throws DataStoreException {
- domain = clip(domain, query.getDomain(), GridRoundingMode.ENCLOSING);
- domain = clip(domain, query.getDomain(), GridRoundingMode.ENCLOSING, 0);
- if (domain != null && query.getSourceDomainExpansion() > 0) {
- domain = clip(getGridGeometry(), domain, GridRoundingMode.ENCLOSING, query.getSourceDomainExpansion());
- }
-
++ domain = clip(domain, query.getDomain(), GridRoundingMode.ENCLOSING, query.getSourceDomainExpansion());
final int[] qr = query.getRange();
if (range == null) {
range = qr;
diff --cc storage/sis-storage/src/test/java/org/apache/sis/internal/storage/query/CoverageQueryTest.java
index 0000000,2c8e3fd..a02d6b4
mode 000000,100644..100644
--- a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/query/CoverageQueryTest.java
+++ b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/query/CoverageQueryTest.java
@@@ -1,0 -1,120 +1,145 @@@
+ /*
+ * 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.awt.image.BufferedImage;
++import org.opengis.metadata.spatial.DimensionNameType;
++import org.opengis.referencing.crs.CoordinateReferenceSystem;
++import org.opengis.referencing.datum.PixelInCell;
+ import org.apache.sis.coverage.grid.GridCoverage;
+ import org.apache.sis.coverage.grid.GridCoverage2D;
-import org.apache.sis.coverage.grid.GridCoverageBuilder;
+ import org.apache.sis.coverage.grid.GridExtent;
+ import org.apache.sis.coverage.grid.GridGeometry;
+ import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
+ import org.apache.sis.internal.storage.MemoryGridResource;
-import org.apache.sis.referencing.CommonCRS;
++import org.apache.sis.referencing.crs.HardCodedCRS;
+ import org.apache.sis.storage.DataStoreException;
+ import org.apache.sis.storage.GridCoverageResource;
+ import org.apache.sis.test.TestCase;
-import org.junit.Assert;
+ import org.junit.Test;
-import org.opengis.metadata.spatial.DimensionNameType;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.datum.PixelInCell;
++
++import static org.junit.Assert.*;
++
+
+ /**
++ * Tests {@link CoverageQuery} and (indirectly) {@link CoverageSubset}.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ */
+ public final strictfp class CoverageQueryTest extends TestCase {
-
+ /**
- * Verify source domain expansion parameter is used.
++ * The coordinate reference system used by the tests.
+ */
- @Test
- public void sourceExpansionTest() throws DataStoreException {
-
- final CoordinateReferenceSystem crs = CommonCRS.WGS84.normalizedGeographic();
- final AffineTransform2D gridToCrs = new AffineTransform2D(1, 0, 0, 1, 0, 0);
- final GridCoverageResource resource;
- { //create resource
- final GridGeometry grid = new GridGeometry(new GridExtent(100, 100), PixelInCell.CELL_CENTER, gridToCrs, crs);
- final BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
++ private final CoordinateReferenceSystem crs;
+
- final GridCoverageBuilder gcb = new GridCoverageBuilder();
- gcb.setDomain(grid);
- gcb.setValues(image);
- final GridCoverage coverage = gcb.build();
- resource = new MemoryGridResource(null, (GridCoverage2D) coverage);
- }
-
- { // test without any parameter
- final CoverageQuery query = new CoverageQuery();
- final GridCoverageResource subres = resource.subset(query);
- Assert.assertEquals(resource.getGridGeometry(), subres.getGridGeometry());
- }
-
- { // test with sub grid geometry
- final GridGeometry subGrid = new GridGeometry(new GridExtent(
- new DimensionNameType[]{DimensionNameType.COLUMN, DimensionNameType.ROW},
- new long[]{20, 20}, new long[]{40, 40}, true),
- PixelInCell.CELL_CENTER, gridToCrs, crs);
-
- final CoverageQuery query = new CoverageQuery();
- query.setDomain(subGrid);
-
- final GridCoverageResource subres = resource.subset(query);
- Assert.assertEquals(subGrid, subres.getGridGeometry());
- }
-
- { // test with sub grid geometry + expansion
- final GridGeometry subGrid = new GridGeometry(new GridExtent(
- new DimensionNameType[]{DimensionNameType.COLUMN, DimensionNameType.ROW},
- new long[]{20, 20}, new long[]{40, 40}, true),
- PixelInCell.CELL_CENTER, gridToCrs, crs);
-
- final CoverageQuery query = new CoverageQuery();
- query.setDomain(subGrid);
- query.setSourceDomainExpansion(3);
-
- final GridCoverageResource subres = resource.subset(query);
++ /**
++ * The conversion from pixel coordinates to geographic coordinates for the coverage in this test.
++ */
++ private final AffineTransform2D gridToCRS;
+
- final GridGeometry expGrid = new GridGeometry(new GridExtent(
- new DimensionNameType[]{DimensionNameType.COLUMN, DimensionNameType.ROW},
- new long[]{17, 17}, new long[]{43, 43}, true),
- PixelInCell.CELL_CENTER, gridToCrs, crs);
++ /**
++ * The resource to be tested by each test method.
++ */
++ private final GridCoverageResource resource;
+
- Assert.assertEquals(expGrid, subres.getGridGeometry());
++ /**
++ * Creates a new test case.
++ */
++ public CoverageQueryTest() {
++ crs = HardCodedCRS.WGS84;
++ gridToCRS = new AffineTransform2D(2, 0, 0, 3, 0, 0);
++ final int width = 50;
++ final int height = 53;
++ final GridGeometry grid = new GridGeometry(new GridExtent(width, height), PixelInCell.CELL_CENTER, gridToCRS, crs);
++ final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY);
++ resource = new MemoryGridResource(null, new GridCoverage2D(grid, null, image));
++ }
+
++ /**
++ * Tests without any parameter.
++ *
++ * @throws DataStoreException if query execution failed.
++ */
++ @Test
++ public void testNoParameters() throws DataStoreException {
++ final CoverageQuery query = new CoverageQuery();
++ final GridCoverageResource subset = resource.subset(query);
++ assertSame(resource.getGridGeometry(), subset.getGridGeometry());
++ verifyRead(subset);
++ }
+
- //read operation must add the expected margins
- //this last test is only to avoid regression
- //GridCoverage2D implementation returns a larger area then requested
- final GridGeometry readGrid = new GridGeometry(new GridExtent(
- new DimensionNameType[]{DimensionNameType.COLUMN, DimensionNameType.ROW},
- new long[]{30, 30}, new long[]{35, 35}, true),
- PixelInCell.CELL_CENTER, gridToCrs, crs);
- GridCoverage coverage = subres.read(readGrid);
++ /**
++ * Creates an arbitrary grid geometry included inside the {@linkplain #resource} extent.
++ */
++ private GridGeometry createSubGrid(final int expansion) {
++ final GridExtent extent = new GridExtent(
++ new DimensionNameType[] {DimensionNameType.COLUMN, DimensionNameType.ROW},
++ new long[] { 7 - expansion, 19 - expansion},
++ new long[] {21 + expansion, 27 + expansion}, true);
+
- final GridGeometry expReadGrid = resource.getGridGeometry();
++ return new GridGeometry(extent, PixelInCell.CELL_CENTER, gridToCRS, crs);
++ }
+
- Assert.assertEquals(expReadGrid, coverage.getGridGeometry());
++ /**
++ * Tests with a sub-grid geometry.
++ *
++ * @throws DataStoreException if query execution failed.
++ */
++ @Test
++ public void testWithSubgrid() throws DataStoreException {
++ final GridGeometry subGrid = createSubGrid(0);
++ final CoverageQuery query = new CoverageQuery();
++ query.setDomain(subGrid);
++
++ final GridCoverageResource subset = resource.subset(query);
++ assertEquals(subGrid, subset.getGridGeometry());
++ verifyRead(subset);
++ }
+
- }
++ /**
++ * Tests with a sub-grid geometry and a source domain expansion.
++ *
++ * @throws DataStoreException if query execution failed.
++ */
++ @Test
++ public void testWithExpansion() throws DataStoreException {
++ final GridGeometry subGrid = createSubGrid(0);
++ final CoverageQuery query = new CoverageQuery();
++ query.setDomain(subGrid);
++ query.setSourceDomainExpansion(3);
++
++ final GridCoverageResource subset = resource.subset(query);
++ assertEquals(createSubGrid(3), subset.getGridGeometry());
++ verifyRead(subset);
+ }
+
++ /**
++ * Verifies that the read operation adds the expected margins.
++ * This is an anti-regression test; in current implementation,
++ * {@link GridCoverage2D} returns a larger area then requested.
++ *
++ * @todo May need to be revisited after https://bugs.openjdk.java.net/browse/JDK-8166038 is fixed.
++ */
++ private void verifyRead(final GridCoverageResource subset) throws DataStoreException {
++ final GridGeometry request = createSubGrid(-4);
++ final GridCoverage coverage = subset.read(request);
++ final GridGeometry expected = resource.getGridGeometry();
++ assertEquals(expected, coverage.getGridGeometry());
++ }
+ }