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 57da403b954c198c6d61309e3ee102af5605aaa2
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Mon Dec 6 16:04:21 2021 +0100

    Support the reading of tiles at `RenderedImage.getTile(int, int)` 
invocation time.
    This is enabled when loading mode is 
`RasterLoadingStrategy.AT_GET_TILE_TIME`.
---
 .../internal/coverage/j2d/BatchComputedImage.java  | 210 +++++++++++++++++++++
 .../coverage/MultiResolutionCoverageLoader.java    |   2 +
 .../org/apache/sis/storage/geotiff/DataSubset.java |   2 +-
 .../sis/internal/storage/TiledDeferredImage.java   | 110 +++++++++++
 .../sis/internal/storage/TiledGridCoverage.java    |  66 +++++--
 .../sis/internal/storage/TiledGridResource.java    |  23 ++-
 6 files changed, 399 insertions(+), 14 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/BatchComputedImage.java
 
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/BatchComputedImage.java
new file mode 100644
index 0000000..2d596a8
--- /dev/null
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/BatchComputedImage.java
@@ -0,0 +1,210 @@
+/*
+ * 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.coverage.j2d;
+
+import java.util.Map;
+import java.util.Collections;
+import java.awt.Rectangle;
+import java.awt.Image;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+import java.awt.image.RenderedImage;
+import java.awt.image.SampleModel;
+import java.awt.image.ImagingOpException;
+import org.apache.sis.image.ComputedImage;
+import org.apache.sis.internal.jdk9.JDK9;
+import org.apache.sis.util.Disposable;
+import org.apache.sis.util.resources.Errors;
+
+
+/**
+ * A computed image for which it is more efficient to compute tiles in batch 
instead of one-by-one.
+ * This class is useful only when users may prefetch in advance groups of 
tiles by calls to the
+ * {@link org.apache.sis.image.ImageProcessor#prefetch(RenderedImage, 
Rectangle)} method.
+ *
+ * <h2>Caching</h2>
+ * Implementations should manage their own cache for avoiding to compute the 
same tiles many times.
+ * The caching mechanism inherited from {@link ComputedImage} is less useful 
here.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since   1.2
+ * @module
+ */
+public abstract class BatchComputedImage extends ComputedImage {
+    /**
+     * Image properties, or an empty map if none.
+     * May contain instances of {@link DeferredProperty}.
+     */
+    private final Map<String,Object> properties;
+
+    /**
+     * Tiles fetched by a calls to {@link #prefetch(Rectangle)}, or {@code 
null} if none.
+     * This is a linked list, but the list will rarely have more than 1 
element.
+     */
+    private Rasters prefetched;
+
+    /**
+     * The set of tiles fetched by a single call to {@link 
#prefetch(Rectangle)}.
+     * This is a node in a linked list.
+     */
+    private final class Rasters implements Disposable {
+        /** Tile indices of the fetched region. */
+        final int x, y, width, height;
+
+        /** The fetched tiles. */
+        final Raster[] tiles;
+
+        /** Next set of tiles in the linked list. */
+        Rasters next;
+
+        /** Creates a new set of fetched tiles. */
+        Rasters(final Rectangle r, final Raster[] tiles) {
+            x      = r.x;
+            y      = r.y;
+            width  = r.width;
+            height = r.height;
+            this.tiles = tiles;
+        }
+
+        /** Discards this set of tiles. */
+        @Override public void dispose() {
+            remove(this);
+        }
+    }
+
+    /**
+     * Creates an initially empty image with the given sample model.
+     *
+     * @param  sampleModel  the sample model shared by all tiles in this image.
+     * @param  properties   image properties ({@link DeferredProperty} 
supported), or {@code null} if none.
+     * @param  sources      sources of this image (may be an empty array), or 
a null array if unknown.
+     */
+    protected BatchComputedImage(final SampleModel sampleModel, final 
Map<String,Object> properties, final RenderedImage... sources) {
+        super(sampleModel, sources);
+        this.properties = (properties != null) ? JDK9.copyOf(properties) : 
Collections.emptyMap();
+    }
+
+    /**
+     * Gets a property from this image.
+     *
+     * @param  key  the name of the property to get.
+     * @return the property value, or {@link Image#UndefinedProperty} if none.
+     */
+    @Override
+    public Object getProperty(final String key) {
+        Object value = properties.getOrDefault(key, Image.UndefinedProperty);
+        if (value instanceof DeferredProperty) {
+            value = ((DeferredProperty) value).compute(this);
+        }
+        return value;
+    }
+
+    /**
+     * Returns the names of all recognized properties,
+     * or {@code null} if this image has no properties.
+     *
+     * @return names of all recognized properties, or {@code null} if none.
+     */
+    @Override
+    public String[] getPropertyNames() {
+        final int n = properties.size();
+        return (n == 0) ? null : properties.keySet().toArray(new String[n]);
+    }
+
+    /**
+     * Computes immediately and returns all tiles in the given ranges of tile 
indices.
+     * It is implementer responsibility to ensure that all rasters have 
consistent
+     * {@link Raster#getMinX()}/{@code getMinY()} values.
+     *
+     * @param  tiles  range of tile indices for which to precompute tiles.
+     * @return precomputed tiles for the given indices, in row-major fashion.
+     * @throws Exception if an error occurred when computing tiles.
+     */
+    protected abstract Raster[] computeTiles(Rectangle tiles) throws Exception;
+
+    /**
+     * Invoked when a single tile need to be computed.
+     * The default implementation delegates to {@link 
#computeTiles(Rectangle)}.
+     *
+     * @param  tileX     the column index of the tile to compute.
+     * @param  tileY     the row index of the tile to compute.
+     * @param  previous  ignored (this method creates a new raster on each 
invocation).
+     * @return computed tile for the given indices.
+     * @throws Exception if an error occurred while computing the tile.
+     */
+    @Override
+    protected final Raster computeTile(final int tileX, final int tileY, 
WritableRaster previous) throws Exception {
+        synchronized (this) {
+            for (Rasters r = prefetched; r != null; r = r.next) {
+                final int x = tileX - r.x;
+                final int y = tileY - r.y;
+                if ((x | y) >= 0 && x < r.width && y < r.height) {
+                    return r.tiles[x + y * r.width];
+                }
+            }
+        }
+        final Raster[] tiles = computeTiles(new Rectangle(tileX, tileY, 1, 1));
+        if (tiles.length == 1) {
+            return tiles[0];
+        }
+        throw new 
ImagingOpException(Errors.format(Errors.Keys.OutsideDomainOfValidity));
+    }
+
+    /**
+     * Notifies this image that tiles will be computed soon in the given 
region.
+     *
+     * @param  region  indices of the tiles which will be prefetched.
+     * @return handler on which to invoke {@code dispose()} after the prefetch 
operation.
+     */
+    @Override
+    protected Disposable prefetch(final Rectangle region) {
+        final Raster[] tiles;
+        try {
+            tiles = computeTiles(region);
+        } catch (Exception e) {
+            throw (ImagingOpException) new 
ImagingOpException(null).initCause(e);
+        }
+        final Rasters r = new Rasters(region, tiles);
+        synchronized (this) {
+            r.next = prefetched;
+            prefetched = r;
+        }
+        return r;
+    }
+
+    /**
+     * Discards the given set of tiles. This method is invoked when the 
fetched tiles are no longer needed.
+     *
+     * @param  tiles  fetched tiles to removed from the {@link #prefetched} 
linked list.
+     */
+    private synchronized void remove(final Rasters tiles) {
+        Rasters previous = null;
+        Rasters r = prefetched;
+        while (r != tiles) {
+            if (r == null) return;
+            previous = r;
+            r = r.next;
+        }
+        r = r.next;
+        if (previous != null) {
+            previous.next = r;
+        } else {
+            prefetched = r;
+        }
+    }
+}
diff --git 
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/coverage/MultiResolutionCoverageLoader.java
 
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/coverage/MultiResolutionCoverageLoader.java
index 2025215..93e0238 100644
--- 
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/coverage/MultiResolutionCoverageLoader.java
+++ 
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/coverage/MultiResolutionCoverageLoader.java
@@ -28,6 +28,7 @@ import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.transform.LinearTransform;
+import org.apache.sis.storage.RasterLoadingStrategy;
 import org.apache.sis.storage.GridCoverageResource;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.coverage.grid.GridGeometry;
@@ -143,6 +144,7 @@ public class MultiResolutionCoverageLoader {
             }
         }
         coverages = new Reference[Math.max(resolutions.length, 1)];
+        resource.setLoadingStrategy(RasterLoadingStrategy.AT_GET_TILE_TIME);
     }
 
     /**
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 0bbccfb..5da38e2 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
@@ -288,7 +288,7 @@ class DataSubset extends TiledGridCoverage implements 
Localized {
      * The {@link Raster#getMinX()} and {@code getMinY()} coordinates of 
returned rasters
      * will start at the given {@code offsetAOI} values.
      *
-     * <p>This method is thread-safe.</p>
+     * <p>This method is thread-safe. Synchronization is done on {@link 
DataCube#getSynchronizationLock()}.</p>
      *
      * @param  iterator  an iterator over the tiles that intersect the Area Of 
Interest specified by user.
      * @return tiles decoded from the TIFF file.
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledDeferredImage.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledDeferredImage.java
new file mode 100644
index 0000000..262be29
--- /dev/null
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledDeferredImage.java
@@ -0,0 +1,110 @@
+/*
+ * 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.util.Map;
+import java.awt.Rectangle;
+import java.awt.image.ColorModel;
+import java.awt.image.Raster;
+import org.apache.sis.internal.coverage.j2d.BatchComputedImage;
+
+
+/**
+ * A rendered image where tiles are loaded only when first needed.
+ * Used for {@link 
org.apache.sis.storage.RasterLoadingStrategy#AT_GET_TILE_TIME}.
+ * Other loading strategies should not instantiate this class.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.2
+ * @since   1.2
+ * @module
+ */
+final class TiledDeferredImage extends BatchComputedImage {
+    /**
+     * Number of pixels along X or Y axis in the whole rendered image.
+     */
+    private final int width, height;
+
+    /**
+     * Index of the first tile in the image.
+     */
+    private final int minTileX, minTileY;
+
+    /**
+     * Iterator over tiles. The iterator position should not be modified;
+     * instead subsets of this iterator will be created when needed.
+     */
+    private final TiledGridCoverage.AOI iterator;
+
+    /**
+     * Creates a new tiled image.
+     *
+     * @param imageSize   full image size, after subsampling.
+     * @param tileLower   indices of first tile to read, inclusive.
+     * @param properties  image properties, or {@code null} if none.
+     */
+    TiledDeferredImage(final int[] imageSize, final int[] tileLower,
+                       final Map<String,Object> properties, final 
TiledGridCoverage.AOI iterator)
+    {
+        super(iterator.getCoverage().model, properties);
+        this.width    = imageSize[TiledGridCoverage.X_DIMENSION];
+        this.height   = imageSize[TiledGridCoverage.Y_DIMENSION];
+        this.minTileX = tileLower[TiledGridCoverage.X_DIMENSION];
+        this.minTileY = tileLower[TiledGridCoverage.Y_DIMENSION];
+        this.iterator = iterator;
+    }
+
+    /** Returns the color model, or {@code null} if none. */
+    @Override public ColorModel getColorModel() {
+        return iterator.getCoverage().colors;
+    }
+
+    /** Returns the minimum <var>x</var> coordinate (inclusive) of this image. 
*/
+    @Override public final int getMinX() {
+        return iterator.getTileOrigin(TiledGridCoverage.X_DIMENSION);
+    }
+
+    /** Returns the minimum <var>y</var> coordinate (inclusive) of this image. 
*/
+    @Override public final int getMinY() {
+        return iterator.getTileOrigin(TiledGridCoverage.Y_DIMENSION);
+    }
+
+    /** Returns the number of pixels along X axis in the whole rendered image. 
*/
+    @Override public final int getWidth() {return width;}
+
+    /** Returns the number of pixels along Y axis in the whole rendered image. 
*/
+    @Override public final int getHeight() {return height;}
+
+    /** Returns the minimum tile index in the X direction. */
+    @Override public final int getMinTileX() {return minTileX;}
+
+    /** Returns the minimum tile index in the Y direction. */
+    @Override public final int getMinTileY() {return minTileY;}
+
+    /**
+     * Loads immediately and returns all tiles in the given ranges of tile 
indices.
+     *
+     * @param  tiles  range of tile indices for which to load tiles.
+     * @return loaded tiles for the given indices, in row-major fashion.
+     */
+    @Override
+    protected Raster[] computeTiles(final Rectangle tiles) throws Exception {
+        final TiledGridCoverage.AOI aoi = iterator.subset(new int[] {tiles.x, 
tiles.y},
+                new int[] {Math.addExact(tiles.x, tiles.width), 
Math.addExact(tiles.y, tiles.height)});
+        return aoi.getCoverage().readTiles(aoi);
+    }
+}
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java
index b5ffb7b..78490e0 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/TiledGridCoverage.java
@@ -196,6 +196,11 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
     protected final Number fillValue;
 
     /**
+     * Whether the reading of tiles is deferred to {@link 
RenderedImage#getTile(int, int)} time.
+     */
+    private final boolean deferredTileReading;
+
+    /**
      * Creates a new tiled grid coverage. All parameters should have been 
validated before this call.
      *
      * @param  subset  description of the {@link TiledGridResource} subset to 
cover.
@@ -205,6 +210,7 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
         super(subset.domain, subset.ranges);
         final GridExtent extent = subset.domain.getExtent();
         final int dimension = subset.sourceExtent.getDimension();
+        deferredTileReading = subset.deferredTileReading();
         readExtent          = subset.readExtent;
         subsampling         = subset.subsampling;
         subsamplingOffsets  = subset.subsamplingOffsets;
@@ -389,7 +395,7 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
             // TODO
             throw new UnsupportedOperationException("Non-horizontal slices not 
yet implemented.");
         }
-        final TiledImage image;
+        final RenderedImage image;
         try {
             final int[] tileLower = new int[dimension];         // Indices of 
first tile to read, inclusive.
             final int[] tileUpper = new int[dimension];         // Indices of 
last tile to read, exclusive.
@@ -420,17 +426,23 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
                 tileUpper[i] = toIntExact(subtractExact(tileUp, 
tmcOfFirstTile[i]));
             }
             /*
-             * Get all tiles in the specified region. I/O operations, if 
needed, happen here.
-             */
-            final Raster[] result = readTiles(new AOI(tileLower, tileUpper, 
offsetAOI, dimension));
-            /*
-             * Wraps in an image all the tiles that we just read, together 
with the following properties:
+             * Prepare an iterator over all tiles to read, together with the 
following properties:
              *    - Two-dimensional conversion from pixel coordinates to "real 
world" coordinates.
              */
+            final AOI iterator = new AOI(tileLower, tileUpper, offsetAOI, 
dimension);
             final Map<String,Object> properties = 
DeferredProperty.forGridGeometry(getGridGeometry(), selectedDimensions);
-            image = new TiledImage(properties, colors,
-                    imageSize[X_DIMENSION], imageSize[Y_DIMENSION],
-                    tileLower[X_DIMENSION], tileLower[Y_DIMENSION], result);
+            if (deferredTileReading) {
+                image = new TiledDeferredImage(imageSize, tileLower, 
properties, iterator);
+            } else {
+                /*
+                 * If the loading strategy is not 
`RasterLoadingStrategy.AT_GET_TILE_TIME`, get all tiles
+                 * in the area of interest now. I/O operations, if needed, 
happen in `readTiles(…)` call.
+                 */
+                final Raster[] result = readTiles(iterator);
+                image = new TiledImage(properties, colors,
+                        imageSize[X_DIMENSION], imageSize[Y_DIMENSION],
+                        tileLower[X_DIMENSION], tileLower[Y_DIMENSION], 
result);
+            }
         } catch (Exception e) {     // Too many exception types for listing 
them all.
             throw new 
CannotEvaluateException(Resources.forLocale(getLocale()).getString(
                     Resources.Keys.CanNotRenderImage_1, getDisplayName()), e);
@@ -511,7 +523,7 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
              * converts the `tileLower` coordinates to index in the 
`tileOffsets` and `tileByteCounts` vectors.
              */
             indexInTileVector = indexOfFirstTile;
-            int tileCountInQuery   =  1;
+            int tileCountInQuery = 1;
             for (int i=0; i<dimension; i++) {
                 final int lower   = tileLower[i];
                 final int count   = subtractExact(tileUpper[i], lower);
@@ -532,6 +544,37 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
         }
 
         /**
+         * Returns a new {@code AOI} instance over a sub-region of this Area 
Of Interest.
+         * The region is specified by tile indices, with (0,0) being the first 
tile of the enclosing grid coverage.
+         * The given region is intersected with the region of this {@code AOI}.
+         * The {@code tileLower} and {@code tileUpper} array can have any 
length;
+         * extra indices are ignored and missing indices are inherited from 
this AOI.
+         * This method is independent to the iterator position of this {@code 
AOI}.
+         *
+         * @param  tileLower  indices (relative to enclosing {@code 
TiledGridCoverage}) of the upper-left tile to read.
+         * @param  tileUpper  indices (relative to enclosing {@code 
TiledGridCoverage}) after the bottom-right tile to read.
+         * @return a new {@code AOI} instance for the specified sub-region.
+         */
+        public AOI subset(final int[] tileLower, final int[] tileUpper) {
+            final int[] offset = this.offsetAOI.clone();
+            final int[] lower  = this.tileLower.clone();
+            for (int i = Math.min(tileLower.length, lower.length); --i >= 0;) {
+                final int base = lower[i];
+                final int s = tileLower[i];
+                if (s > base) {
+                    lower[i] = s;
+                    // Use of `ceilDiv(…)` is for consistency with 
`getTileOrigin(int)`.
+                    offset[i] = addExact(offset[i], ceilDiv(multiplyExact(s - 
base, tileSize[i]), subsampling[i]));
+                }
+            }
+            final int[] upper = this.tileUpper.clone();
+            for (int i = Math.min(tileUpper.length, upper.length); --i >= 0;) {
+                upper[i] = Math.max(lower[i], Math.min(upper[i], 
tileUpper[i]));
+            }
+            return new AOI(lower, upper, offset, offset.length);
+        }
+
+        /**
          * Returns the enclosing coverage.
          */
         final TiledGridCoverage getCoverage() {
@@ -787,7 +830,8 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
      * The {@link Raster#getMinX()} and {@code getMinY()} coordinates of 
returned rasters
      * shall start at the given {@code iterator.offsetAOI} values.
      *
-     * <p>This method must be thread-safe.</p>
+     * <p>This method must be thread-safe. It is implementer responsibility to 
ensure synchronization,
+     * for example using {@link 
TiledGridResource#getSynchronizationLock()}.</p>
      *
      * @param  iterator  an iterator over the tiles that intersect the Area Of 
Interest specified by user.
      * @return tiles decoded from the {@link TiledGridResource}.
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 ff37952..e4fc973 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
@@ -435,6 +435,21 @@ public abstract class TiledGridResource extends 
AbstractGridResource {
         public boolean isXContiguous() {
             return includedBands == null && subsampling[X_DIMENSION] == 1;
         }
+
+        /**
+         * Whether the reading of tiles is deferred to {@link 
RenderedImage#getTile(int, int)} time.
+         */
+        final boolean deferredTileReading() {
+            if (loadingStrategy != RasterLoadingStrategy.AT_GET_TILE_TIME) {
+                return false;
+            }
+            for (int i = subsampling.length; --i >= 0;) {
+                if (subsampling[i] >= tileSize[i]) {
+                    return false;
+                }
+            }
+            return true;
+        }
     }
 
     /**
@@ -449,7 +464,7 @@ public abstract class TiledGridResource extends 
AbstractGridResource {
     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) {
+        if (loadingStrategy == null || loadingStrategy == 
RasterLoadingStrategy.AT_READ_TIME) {
             /*
              * In theory the following condition is redundant with 
`supportImmediateLoading()`.
              * We apply it anyway in case the coverage geometry is not what 
was announced.
@@ -484,6 +499,8 @@ public abstract class TiledGridResource extends 
AbstractGridResource {
 
     /**
      * Whether this resource supports immediate loading of raster data.
+     * Current implementation does not support immediate loading if the data 
cube has more than 2 dimensions.
+     * Non-immediate loading allows users to specify two-dimensional slices.
      */
     private boolean supportImmediateLoading() {
         return getTileSize().length == TiledGridCoverage.BIDIMENSIONAL;
@@ -516,7 +533,9 @@ public abstract class TiledGridResource extends 
AbstractGridResource {
     @Override
     public final boolean setLoadingStrategy(final RasterLoadingStrategy 
strategy) throws DataStoreException {
         synchronized (getSynchronizationLock()) {
-            if (strategy != null) {
+            if (strategy == RasterLoadingStrategy.AT_GET_TILE_TIME) {
+                loadingStrategy = strategy;
+            } else if (strategy != null) {
                 setLoadingStrategy(strategy == 
RasterLoadingStrategy.AT_READ_TIME && supportImmediateLoading());
             }
             return super.setLoadingStrategy(strategy);

Reply via email to