This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new e16859b  Read all tiles that are inside the requested domain at 
`GridCoverageResource.read(…)` invocation time by default, but provide a 
`RasterLoadingStrategy` enumeration for allowing developers to keep the 
previous mode if desired. This enumeration is not in public API for now; we 
will see how it works in practice first.
e16859b is described below

commit e16859b2c048227c88a16a045f63aba23d663d0d
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Fri Jul 30 18:26:49 2021 +0200

    Read all tiles that are inside the requested domain at 
`GridCoverageResource.read(…)` invocation time by default,
    but provide a `RasterLoadingStrategy` enumeration for allowing developers 
to keep the previous mode if desired.
    This enumeration is not in public API for now; we will see how it works in 
practice first.
---
 .../apache/sis/coverage/grid/GridCoverage2D.java   |   1 -
 .../org/apache/sis/storage/geotiff/DataCube.java   |  14 ++-
 .../org/apache/sis/storage/geotiff/DataSubset.java |   2 +-
 .../sis/storage/geotiff/ImageFileDirectory.java    |   4 +-
 .../sis/internal/storage/AbstractGridResource.java |  28 +++++
 .../internal/storage/RasterLoadingStrategy.java    | 112 +++++++++++++++++++
 .../sis/internal/storage/TiledGridResource.java    | 121 ++++++++++++++++++++-
 .../sis/test/storage/CoverageReadConsistency.java  |  13 +++
 8 files changed, 287 insertions(+), 8 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
index e1bc00f..837a475 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
@@ -608,7 +608,6 @@ public class GridCoverage2D extends GridCoverage {
              * may force data loading earlier than desired.
              */
             final ReshapedImage result = new ReshapedImage(data, xmin, ymin, 
xmax, ymax);
-            String error; assert (error = result.verify()) == null : error;
             return result.isIdentity() ? data : result;
         } catch (ArithmeticException e) {
             throw new CannotEvaluateException(e.getMessage(), e);
diff --git 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
index 23f2e66..1ecb856 100644
--- 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
+++ 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java
@@ -19,6 +19,7 @@ package org.apache.sis.storage.geotiff;
 import java.nio.file.Path;
 import java.awt.image.SampleModel;
 import java.awt.image.BandedSampleModel;
+import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.coverage.grid.GridCoverage;
@@ -59,6 +60,14 @@ abstract class DataCube extends TiledGridResource implements 
ResourceOnFileSyste
     }
 
     /**
+     * Returns the object on which to perform all synchronizations for 
thread-safety.
+     */
+    @Override
+    protected final DataStore getSynchronizationLock() {
+        return reader.store;
+    }
+
+    /**
      * Shortcut for a frequently requested information.
      */
     final String filename() {
@@ -122,9 +131,9 @@ abstract class DataCube extends TiledGridResource 
implements ResourceOnFileSyste
     @Override
     public final GridCoverage read(final GridGeometry domain, final int... 
range) throws DataStoreException {
         final long startTime = System.nanoTime();
-        final GridCoverage coverage;
+        GridCoverage coverage;
         try {
-            synchronized (reader.store) {
+            synchronized (getSynchronizationLock()) {
                 final Subset subset = new Subset(domain, range, false);
                 final Compression compression = getCompression();
                 if (compression == null) {
@@ -145,6 +154,7 @@ abstract class DataCube extends TiledGridResource 
implements ResourceOnFileSyste
                                 Resources.Keys.UnsupportedCompressionMethod_1, 
compression));
                     }
                 }
+                coverage = preload(coverage);
             }
         } catch (RuntimeException e) {
             throw new 
DataStoreException(reader.errors().getString(Errors.Keys.CanNotRead_1, 
filename()), e);
diff --git 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java
 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java
index 313b262..d0a7574 100644
--- 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java
+++ 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java
@@ -283,7 +283,7 @@ class DataSubset extends TiledGridCoverage implements 
Localized {
         final Tile[] missings = new Tile[iterator.tileCountInQuery];
         int numMissings = 0;
         boolean needsCompaction = false;
-        synchronized (reader().store) {
+        synchronized (source.getSynchronizationLock()) {
             do {
                 final WritableRaster tile = iterator.getCachedTile();
                 if (tile != null) {
diff --git 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index 778c3f5..a14997b 100644
--- 
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++ 
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -1297,7 +1297,7 @@ final class ImageFileDirectory extends DataCube {
      */
     @Override
     public GridGeometry getGridGeometry() throws DataStoreContentException {
-        synchronized (reader.store) {
+        synchronized (getSynchronizationLock()) {
             if (referencing != null) {
                 GridGeometry gridGeometry = referencing.gridGeometry;
                 if (gridGeometry == null) try {
@@ -1321,7 +1321,7 @@ final class ImageFileDirectory extends DataCube {
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
     public List<SampleDimension> getSampleDimensions() throws 
DataStoreContentException {
-        synchronized (reader.store) {
+        synchronized (getSynchronizationLock()) {
             if (sampleDimensions == null) {
                 final SampleDimension[] dimensions = new 
SampleDimension[samplesPerPixel];
                 final SampleDimension.Builder builder = new 
SampleDimension.Builder();
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java
index d502e8c..fe5b1ce 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/AbstractGridResource.java
@@ -482,6 +482,34 @@ public abstract class AbstractGridResource extends 
AbstractResource implements G
     }
 
     /**
+     * Returns an indication about when the "physical" loading of raster data 
will happen.
+     * This is the strategy actually applied by this resource implementation, 
not necessarily
+     * the strategy given in the last call to {@link 
#setLoadingStrategy(RasterLoadingStrategy)
+     * setLoadingStrategy(…)}.
+     *
+     * <p>The default strategy is to load raster data at {@link #read read(…)} 
method invocation time.</p>
+     *
+     * @return current raster data loading strategy for this resource.
+     */
+    public RasterLoadingStrategy getLoadingStrategy() {
+        return RasterLoadingStrategy.AT_READ_TIME;
+    }
+
+    /**
+     * Sets the preferred strategy about when to do the "physical" loading of 
raster data.
+     * Implementations are free to ignore this parameter or to replace the 
given strategy
+     * by the closest alternative that this resource can support.
+     *
+     * @param  strategy  the desired strategy for loading raster data.
+     * @return {@code true} if the given strategy has been accepted, or {@code 
false}
+     *         if this implementation replaced the given strategy by an 
alternative.
+     */
+    public boolean setLoadingStrategy(final RasterLoadingStrategy strategy) {
+        ArgumentChecks.ensureNonNull("strategy", strategy);
+        return strategy == getLoadingStrategy();
+    }
+
+    /**
      * Logs the execution of a {@link #read(GridGeometry, int...)} operation.
      * The log level will be {@link Level#FINE} if the operation was quick 
enough,
      * or {@link PerformanceLevel#SLOW} or higher level otherwise.
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/RasterLoadingStrategy.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/RasterLoadingStrategy.java
new file mode 100644
index 0000000..7d674c2
--- /dev/null
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/RasterLoadingStrategy.java
@@ -0,0 +1,112 @@
+/*
+ * 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.lang.ref.SoftReference;
+import java.awt.image.RenderedImage;
+import java.awt.image.ImagingOpException;
+import org.opengis.coverage.CannotEvaluateException;
+import org.apache.sis.coverage.grid.GridExtent;
+import org.apache.sis.coverage.grid.GridGeometry;
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.storage.DataStoreException;
+
+
+/**
+ * Time when the "physical" loading of raster data should happen. Some 
resource implementations may
+ * not load data immediately when the {@linkplain GridCoverageResource#read 
read method} is invoked,
+ * but instead defer the actual loading until the {@linkplain 
GridCoverage#render image is rendered}.
+ * This enumeration gives some control over the time when data loading happens.
+ * The different strategies are compromises between memory consumption,
+ * redundant loading of same data and early error detection.
+ *
+ * <p>Enumeration values are ordered from the most eager strategy to the 
laziest strategy.
+ * The eager strategy is fastest when all pixels are used, at the cost of 
largest memory consumption.
+ * The lazy strategy is more efficient when only a few tiles will be used and 
those tiles are not known in advance.
+ * The lazy strategy is also the only applicable one if the image is too large 
for holding in memory.</p>
+ *
+ * @todo This enumeration is internal for now, but may move to public API in a 
future version.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public enum RasterLoadingStrategy {
+    /**
+     * Raster data are loaded at {@link 
GridCoverageResource#read(GridGeometry, int...)} invocation time.
+     * This is the most eager loading strategy.
+     *
+     * <p><b>Advantages:</b></p>
+     * <ul>
+     *   <li>Fastest loading strategy if all pixels in the returned {@link 
GridCoverage} will be used.</li>
+     *   <li>No redundant data loading: all data stay in memory until the 
resource is garbage-collected.</li>
+     *   <li>Immediate error notification with a checked {@link 
DataStoreException}.</li>
+     * </ul>
+     *
+     * <p><b>Disadvantages:</b></p>
+     * <ul>
+     *   <li>Slower than other strategies if only a subset of the returned 
{@link GridCoverage} will be used.</li>
+     *   <li>Consume memory for the full {@link GridCoverage} as long as the 
resource is referenced.</li>
+     * </ul>
+     */
+    AT_READ_TIME,
+
+    /**
+     * Raster data are loaded at {@link GridCoverage#render(GridExtent)} 
invocation time.
+     * Speed and memory usage are at an intermediate level between {@link 
#AT_READ_TIME} and {@link #AT_GET_TILE_TIME}.
+     *
+     * <p><b>Advantages:</b></p>
+     * <ul>
+     *   <li>Faster than {@link #AT_GET_TILE_TIME} if all pixels in the 
returned {@link RenderedImage} will be used.</li>
+     *   <li>Load less data than {@link #AT_READ_TIME} if the {@link 
GridExtent} is a sub-region of the coverage.</li>
+     *   <li>Memory is released sooner (when the {@link RenderedImage} is 
garbage-collected) than above mode.</li>
+     * </ul>
+     *
+     * <p><b>Disadvantages:</b></p>
+     * <ul>
+     *   <li>Slower than {@link #AT_GET_TILE_TIME} if only a few tiles will be 
used.</li>
+     *   <li>Consume memory for the full {@link RenderedImage} as long as the 
image is referenced.</li>
+     *   <li>May reload the same data many times if images are discarded and 
recreated.</li>
+     *   <li>Unchecked {@link CannotEvaluateException} can happen relatively 
late
+     *       (at {@code render(…)} invocation time) in a chain of coverage 
operations.</li>
+     * </ul>
+     */
+    AT_RENDER_TIME,
+
+    /**
+     * Raster data are loaded at {@link RenderedImage#getTile(int, int)} 
invocation time.
+     * This is the laziest loading strategy.
+     * This is also the only strategy that can handle very large {@link 
RenderedImage}s.
+     *
+     * <p><b>Advantages:</b></p>
+     * <ul>
+     *   <li>Only the tiles that are actually used are loaded.</li>
+     *   <li>Memory can be released at any time (tiles are kept by {@linkplain 
SoftReference soft references}).</li>
+     * </ul>
+     *
+     * <p><b>Disadvantages:</b></p>
+     * <ul>
+     *   <li>Slower read operations (numerous seeks when tiles are read in 
random order).</li>
+     *   <li>May reload the same data many times if tiles are discarded and 
recreated.</li>
+     *   <li>Unchecked {@link ImagingOpException} can happen late
+     *       (at {@code getTile(…)} invocation time) in a chain of image 
operations.</li>
+     * </ul>
+     */
+    AT_GET_TILE_TIME
+}
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java
index e21c73e..212c1b4 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridResource.java
@@ -20,13 +20,18 @@ import java.util.List;
 import java.util.Arrays;
 import java.awt.image.ColorModel;
 import java.awt.image.SampleModel;
+import java.awt.image.RenderedImage;
 import java.awt.image.WritableRaster;
 import java.awt.image.RasterFormatException;
+import org.opengis.coverage.CannotEvaluateException;
 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.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.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.event.StoreListeners;
 import org.apache.sis.util.collection.WeakValueHashMap;
@@ -96,6 +101,15 @@ public abstract class TiledGridResource extends 
AbstractGridResource {
     private final WeakValueHashMap<CacheKey, WritableRaster> rasters;
 
     /**
+     * Whether all tiles should be loaded at {@code read(…)} method call or 
deferred to a later time.
+     * This field is initially {@code null} and is initialized to its default 
value only when needed.
+     *
+     * @see #getLoadingStrategy()
+     * @see #setLoadingStrategy(RasterLoadingStrategy)
+     */
+    private RasterLoadingStrategy loadingStrategy;
+
+    /**
      * Creates a new resource.
      *
      * @param  parent  listeners of the parent resource, or {@code null} if 
none.
@@ -106,13 +120,21 @@ public abstract class TiledGridResource extends 
AbstractGridResource {
     }
 
     /**
+     * Returns the object on which to perform all synchronizations for 
thread-safety.
+     * In current implementation, this is the {@link DataStore} that created 
this resource.
+     * However this restriction may be relaxed in any future version.
+     *
+     * @return the synchronization lock.
+     */
+    protected abstract DataStore getSynchronizationLock();
+
+    /**
      * Returns the size of tiles in this resource.
      * The length of the returned array is the number of dimensions.
      *
      * @return the size of tiles in this resource.
-     * @throws DataStoreException if an error occurred while fetching tile 
size information.
      */
-    protected abstract int[] getTileSize() throws DataStoreException;
+    protected abstract int[] getTileSize();
 
     /**
      * Returns the Java2D sample model describing pixel type and layout for 
all bands.
@@ -329,4 +351,99 @@ public abstract class TiledGridResource extends 
AbstractGridResource {
             return selectedBands != null;
         }
     }
+
+    /**
+     * If the loading strategy is to load all tiles at {@code read(…)} time, 
replaces the given coverage
+     * by a coverage will all data in memory. This method should be invoked by 
subclasses at the end of
+     * their {@link #read(GridGeometry, int...)} method implementation.
+     *
+     * @param  coverage  the {@link TiledGridCoverage} to potentially replace 
by a coverage with preloaded data.
+     * @return a coverage with preloaded data, or the given coverage if 
preloading is not enabled.
+     * @throws DataStoreException if an error occurred while preloading data.
+     */
+    protected final GridCoverage preload(final GridCoverage coverage) throws 
DataStoreException {
+        assert Thread.holdsLock(getSynchronizationLock());
+        // Note: `loadingStrategy` may still be null if unitialized.
+        if (loadingStrategy != RasterLoadingStrategy.AT_RENDER_TIME) {
+            /*
+             * In theory the following condition is redundant with 
`supportImmediateLoading()`.
+             * We apply it anyway in case the coverage geometry is not what 
was announced.
+             * This condition is also necessary if `loadingStrategy` has not 
been initialized.
+             */
+            if (coverage.getGridGeometry().getDimension() == 
TiledGridCoverage.BIDIMENSIONAL) try {
+                final RenderedImage image = coverage.render(null);
+                return new GridCoverage2D(coverage.getGridGeometry(), 
coverage.getSampleDimensions(), image);
+            } catch (RuntimeException e) {
+                /*
+                 * The `coverage.render(…)` implementation may have wrapped 
the checked `DataStoreException`
+                 * because of API restriction. In that case we can unwrap the 
exception here since this API
+                 * allows it. This is one of the reasons for preferring the 
`AT_READ_TIME` loading mode.
+                 */
+                Throwable cause = e.getCause();
+                if (cause instanceof DataStoreException) {
+                    throw (DataStoreException) cause;
+                }
+                /*
+                 * The `CannotEvaluateException` wrapper is created by 
`TiledGridCoverage.render(…)`,
+                 * in which case we avoid that level of indirection for making 
stack trace simpler.
+                 * But if the exception is another kind, keep it.
+                 */
+                if (cause == null || !(e instanceof CannotEvaluateException)) {
+                    cause = e;
+                }
+                throw new DataStoreException(e.getLocalizedMessage(), cause);
+            }
+        }
+        return coverage;
+    }
+
+    /**
+     * Whether this resource supports immediate loading of raster data.
+     */
+    private boolean supportImmediateLoading() {
+        return getTileSize().length == TiledGridCoverage.BIDIMENSIONAL;
+    }
+
+    /**
+     * Returns an indication about when the "physical" loading of raster data 
will happen.
+     *
+     * @return current raster data loading strategy for this resource.
+     */
+    @Override
+    public final RasterLoadingStrategy getLoadingStrategy() {
+        synchronized (getSynchronizationLock()) {
+            if (loadingStrategy == null) {
+                setLoadingStrategy(supportImmediateLoading());
+            }
+            return loadingStrategy;
+        }
+    }
+
+    /**
+     * Sets the preferred strategy about when to do the "physical" loading of 
raster data.
+     *
+     * @param  strategy  the desired strategy for loading raster data.
+     * @return {@code true} if the given strategy has been accepted, or {@code 
false}
+     *         if this implementation replaced the given strategy by an 
alternative.
+     */
+    @Override
+    public final boolean setLoadingStrategy(final RasterLoadingStrategy 
strategy) {
+        synchronized (getSynchronizationLock()) {
+            if (strategy != null) {
+                setLoadingStrategy(strategy == 
RasterLoadingStrategy.AT_READ_TIME && supportImmediateLoading());
+            }
+            return super.setLoadingStrategy(strategy);
+        }
+    }
+
+    /**
+     * Sets the strategy for the given flag.
+     *
+     * @param  loadAtReadTime  whether all tiles should be read immediately
+     *         at {@code read(…)} method call or deferred at a later time.
+     */
+    private void setLoadingStrategy(final boolean loadAtReadTime) {
+        loadingStrategy = loadAtReadTime ? RasterLoadingStrategy.AT_READ_TIME
+                                         : 
RasterLoadingStrategy.AT_RENDER_TIME;
+    }
 }
diff --git 
a/storage/sis-storage/src/test/java/org/apache/sis/test/storage/CoverageReadConsistency.java
 
b/storage/sis-storage/src/test/java/org/apache/sis/test/storage/CoverageReadConsistency.java
index b9665e2..105b80e 100644
--- 
a/storage/sis-storage/src/test/java/org/apache/sis/test/storage/CoverageReadConsistency.java
+++ 
b/storage/sis-storage/src/test/java/org/apache/sis/test/storage/CoverageReadConsistency.java
@@ -27,6 +27,8 @@ import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.GridCoverageResource;
+import org.apache.sis.internal.storage.AbstractGridResource;
+import org.apache.sis.internal.storage.RasterLoadingStrategy;
 import org.apache.sis.internal.util.StandardDateFormat;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.image.PixelIterator;
@@ -236,6 +238,16 @@ public strictfp class CoverageReadConsistency extends 
TestCase {
     }
 
     /**
+     * Applies a random configuration on the resource.
+     */
+    private void randomConfigureResource() {
+        if (resource instanceof AbstractGridResource) {
+            final RasterLoadingStrategy[] choices = 
RasterLoadingStrategy.values();
+            ((AbstractGridResource) 
resource).setLoadingStrategy(choices[random.nextInt(choices.length)]);
+        }
+    }
+
+    /**
      * Creates a random domain to be used as a query on the {@link #resource} 
to test.
      * All arrays given to this method will have their values overwritten.
      *
@@ -291,6 +303,7 @@ public strictfp class CoverageReadConsistency extends 
TestCase {
      * @throws DataStoreException if an error occurred while using the 
resource.
      */
     private void readAndCompareRandomRegions() throws DataStoreException {
+        randomConfigureResource();
         final GridGeometry gg = resource.getGridGeometry();
         final int    dimension   = gg.getDimension();
         final long[] low         = new long[dimension];

Reply via email to