sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [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`.
Date Thu, 10 Sep 2020 14:50:02 GMT
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 <martin.desruisseaux@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());
++    }
+ }


Mime
View raw message