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:01 UTC
[sis] branch geoapi-4.0 updated (c7a721a -> e11ba91)
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git.
from c7a721a Apply wraparound on geographic coordinates shown in status bar. Prepare `RenderedData` for wraparound axes (not yet completed). Convenience method for fetching DefaultMathTransformFactory.
add 6e8b8eb Best effort implementation of coverage query source domain expansion parameter
add c0e5729 Merge branch 'geoapi-4.0' into feat/sourceDomainExpansion
add fbd5f31 Add coverage query expansion test cases, add readonly memory grid resource
new e11ba91 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`.
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../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(-)
create mode 100644 storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MemoryGridResource.java
create mode 100644 storage/sis-storage/src/test/java/org/apache/sis/internal/storage/query/CoverageQueryTest.java
[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`.
Posted by de...@apache.org.
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());
++ }
+ }