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 a3003a5d58 Pyramid levels should be ordered from coarsest to finest 
resolution. https://issues.apache.org/jira/browse/SIS-628
a3003a5d58 is described below

commit a3003a5d58690bfc73790f1d1aedbfb02e77bd72
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Mar 25 17:51:14 2026 +0100

    Pyramid levels should be ordered from coarsest to finest resolution.
    https://issues.apache.org/jira/browse/SIS-628
---
 .../coverage/MultiResolutionCoverageLoader.java    | 32 ++++++------
 .../org/apache/sis/map/coverage/RenderingData.java |  7 ++-
 .../MultiResolutionCoverageLoaderTest.java         | 16 +++---
 .../sis/storage/geotiff/ImageFileDirectory.java    | 36 +++++++++-----
 .../org/apache/sis/storage/CoverageSubset.java     |  2 +-
 .../apache/sis/storage/GridCoverageResource.java   | 13 +++--
 .../sis/storage/base/GridResourceWrapper.java      |  2 +-
 .../apache/sis/storage/tiling/ImagePyramid.java    |  7 +--
 .../apache/sis/storage/tiling/TileReadEvent.java   |  6 +--
 .../storage/tiling/TiledGridCoverageResource.java  | 57 ++++++++++++++++------
 .../apache/sis/gui/coverage/CoverageCanvas.java    | 21 +-------
 11 files changed, 114 insertions(+), 85 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/map/coverage/MultiResolutionCoverageLoader.java
 
b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/map/coverage/MultiResolutionCoverageLoader.java
index 6fc7eafc60..3cb9209229 100644
--- 
a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/map/coverage/MultiResolutionCoverageLoader.java
+++ 
b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/map/coverage/MultiResolutionCoverageLoader.java
@@ -85,7 +85,7 @@ public class MultiResolutionCoverageLoader {
     public final GridCoverageResource resource;
 
     /**
-     * Squares of resolution at each pyramid level, from finest (smaller 
numbers) to coarsest (largest numbers).
+     * Squares of resolution at each pyramid level, from coarsest (largest 
numbers) to finest (smaller numbers).
      * This is same same order as {@link 
GridCoverageResource#getResolutions()}. For a given level, the array
      * {@code resolutionSquared[level]} gives the squares of the resolution 
for each CRS dimension.
      */
@@ -156,7 +156,7 @@ public class MultiResolutionCoverageLoader {
      *
      * @param  envelope  bounding box of the coverage in units of the coverage 
CRS.
      * @param  base      resolution of the finest level.
-     * @return default resolutions from finest to coarsest. The first element 
is always {@code base}.
+     * @return default resolutions from coarsest to finest. The last element 
is always {@code base}.
      */
     private static double[][] defaultResolutions(final GridGeometry gg, 
double[] base) {
         /*
@@ -179,12 +179,13 @@ public class MultiResolutionCoverageLoader {
             numLevels = DEFAULT_NUM_LEVELS;     // Arbitrary number of levels 
if we cannot compute it.
         }
         /*
-         * Build the arrays of resolutions from finest to coarsest.
+         * Build the arrays of resolutions from coarsest to finest.
          * The `base` array is cloned then updated to become the base of next 
level.
          */
         final var resolutions = new double[numLevels][];
-        resolutions[0] = base;
-        for (int j=1; j<numLevels; j++) {
+        int j = numLevels - 1;
+        resolutions[j] = base;
+        while (--j >= 0) {
             resolutions[j] = base = base.clone();
             for (int i=0; i<base.length; i++) {
                 base[i] *= (1 << DEFAULT_SCALE_LOG);
@@ -194,9 +195,9 @@ public class MultiResolutionCoverageLoader {
     }
 
     /**
-     * Returns the maximal level (the level with coarsest resolution).
+     * Returns the maximal level (the level with finest resolution).
      */
-    final int getLastLevel() {
+    private int getLastLevel() {
         return Math.max(resolutionSquared.length - 1, 0);
     }
 
@@ -208,14 +209,14 @@ public class MultiResolutionCoverageLoader {
      * @param  objectiveToDisplay  transform used for rendering the coverage 
on screen.
      * @param  objectivePOI        point where to compute resolution, in 
coordinates of objective CRS.
      *                             Can be null if {@code dataToObjective} is 
null or linear.
-     * @return pyramid level for the zoom determined by the given transform. 
Finest level is 0.
+     * @return pyramid level for the zoom determined by the given transform. 
coarsest level is 0.
      * @throws TransformException if an error occurred while computing 
resolution from given transforms.
      */
     final int findPyramidLevel(final MathTransform dataToObjective, final 
LinearTransform objectiveToDisplay,
                                final DirectPosition objectivePOI) throws 
TransformException
     {
-        int level = getLastLevel();
-        if (level != 0) {
+        int level = 0;
+        if (resolutionSquared.length > 1) {
             final LinearTransform displayToObjective = 
objectiveToDisplay.inverse();
             final Matrix m = displayToObjective.getMatrix();
             final Matrix d;
@@ -250,7 +251,7 @@ dimensions: for (int j=0; j<tgtDim; j++) {
                 }
                 /*
                  * Cannot use `Arrays.binarySearch(…)` because elements are 
not guaranteed to be sorted.
-                 * Even if `GridCoverageResource.getResolutions()` contract 
said "finest to coarsest",
+                 * Even if `GridCoverageResource.getResolutions()` contract 
said "coarsest to finest",
                  * it may not be possible to respect this condition on all 
dimensions in same time.
                  * The main goal is to have a `level` value as high as 
possible while having a resolution
                  * equals or better than `sum`.
@@ -262,11 +263,10 @@ dimensions: for (int j=0; j<tgtDim; j++) {
                         minimum = r;
                         levelOfMin = level;
                     }
-                    if (level == 0) {
+                    if (++level == resolutionSquared.length) {
                         level = levelOfMin;
                         break dimensions;
                     }
-                    level--;
                 }
             }
         }
@@ -315,7 +315,7 @@ dimensions: for (int j=0; j<tgtDim; j++) {
     }
 
     /**
-     * If the a grid coverage for the given domain and range is in the cache, 
returns that coverage.
+     * If a grid coverage for the given domain and range is in the cache, 
returns that coverage.
      * Otherwise loads the coverage and eventually caches it. The caching 
happens only if the given
      * domain and range are managed by this loader.
      *
@@ -330,7 +330,7 @@ dimensions: for (int j=0; j<tgtDim; j++) {
              * Fot now we leverage the cache only at level 0.
              * Future versions of this class may try to use the cache at other 
levels too.
              */
-            return getOrLoad(0);
+            return getOrLoad(getLastLevel());
         }
         if (domain == null) {
             domain = resource.getGridGeometry();
@@ -350,7 +350,7 @@ dimensions: for (int j=0; j<tgtDim; j++) {
         final int count = getLastLevel();
         double delta = magnitude(0);
         if (count != 0) {
-            delta = (magnitude(count) - delta) / count;
+            delta = (delta - magnitude(count)) / count;
         }
         final int n = 
Math.max(Math.min(DecimalFunctions.fractionDigitsForDelta(delta, false), 6), 0);
         final NumberFormat f = NumberFormat.getInstance();
diff --git 
a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/map/coverage/RenderingData.java
 
b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/map/coverage/RenderingData.java
index 3792d369ea..3e93c01d82 100644
--- 
a/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/map/coverage/RenderingData.java
+++ 
b/endorsed/src/org.apache.sis.portrayal/main/org/apache/sis/map/coverage/RenderingData.java
@@ -148,7 +148,7 @@ public class RenderingData implements CloneAccess {
 
     /**
      * The pyramid level of {@linkplain #data} loaded by the {@linkplain 
#coverageLoader}.
-     * Value 0 is finest resolution.
+     * Value 0 is coarsest resolution (the overview).
      */
     private int currentPyramidLevel;
 
@@ -546,13 +546,12 @@ public class RenderingData implements CloneAccess {
             RenderedImage image = data;
             final MultiResolutionCoverageLoader loader = coverageLoader;
             if (loader != null) {
-                final int level = loader.getLastLevel();
-                if (level != currentPyramidLevel) {
+                if (currentPyramidLevel != 0) {
                     /*
                      * If coarser data are available, we will compute 
statistics on those data instead of on the
                      * current pyramid level. We need to adjust the slice 
extent to the coordinates of coarser data.
                      */
-                    final GridCoverage coarse = 
loader.getOrLoad(level).forConvertedValues(true);
+                    final GridCoverage coarse = 
loader.getOrLoad(0).forConvertedValues(true);
                     GridExtent sliceExtent = currentSlice;
                     if (sliceExtent != null) {
                         if (sliceExtent.getDimension() <= BIDIMENSIONAL) {
diff --git 
a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/map/coverage/MultiResolutionCoverageLoaderTest.java
 
b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/map/coverage/MultiResolutionCoverageLoaderTest.java
index 2cfa93f0a4..0ec37ee89b 100644
--- 
a/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/map/coverage/MultiResolutionCoverageLoaderTest.java
+++ 
b/endorsed/src/org.apache.sis.portrayal/test/org/apache/sis/map/coverage/MultiResolutionCoverageLoaderTest.java
@@ -96,7 +96,7 @@ public final class MultiResolutionCoverageLoaderTest extends 
TestCase {
 
     /**
      * A dummy resource with arbitrary resolutions for testing purpose.
-     * Resolutions are ordered from finest (smallest numbers) to coarsest 
(largest numbers).
+     * Resolutions are ordered from coarsest (largest numbers) to finest 
(smallest numbers).
      */
     private static final class DummyResource extends 
AbstractGridCoverageResource {
         /** Creates a dummy resource. */
@@ -106,9 +106,9 @@ public final class MultiResolutionCoverageLoaderTest 
extends TestCase {
 
         /** Returns the preferred resolutions in units of CRS axes. */
         @Override public List<double[]> getResolutions() {
-            return List.of(new double[] {2, 3, 1},
+            return List.of(new double[] {8, 9, 5},
                            new double[] {4, 4, 3},
-                           new double[] {8, 9, 5});
+                           new double[] {2, 3, 1});
         }
 
         /** Returns a grid geometry with the resolution of finest level. */
@@ -141,10 +141,10 @@ public final class MultiResolutionCoverageLoaderTest 
extends TestCase {
      */
     @Test
     public void testFindPyramidLevel() throws TransformException {
-        assertLevelEquals(3, 2, 2, 0);
-        assertLevelEquals(4, 5, 2, 0);
+        assertLevelEquals(3, 2, 2, 2);
+        assertLevelEquals(4, 5, 2, 2);
         assertLevelEquals(4, 5, 4, 1);
-        assertLevelEquals(9, 9, 5, 2);
+        assertLevelEquals(9, 9, 5, 0);
         assertLevelEquals(9, 8, 5, 1);
     }
 
@@ -168,8 +168,8 @@ public final class MultiResolutionCoverageLoaderTest 
extends TestCase {
      */
     @Test
     public void testGetOrLoad() throws DataStoreException {
-        assertLoadEquals(2, 8, 9, 5);
-        assertLoadEquals(0, 2, 3, 1);
+        assertLoadEquals(0, 8, 9, 5);
+        assertLoadEquals(2, 2, 3, 1);
         assertLoadEquals(1, 4, 4, 3);
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index ddfde1137e..476836cb3b 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -2024,7 +2024,7 @@ final class ImageFileDirectory extends DataCube {
     }
 
     /**
-     * Sets a list of overviews from finest resolution to coarsest resolution.
+     * Sets a list of overviews from coarsest resolution (the overview) to 
finest resolution.
      * The full-resolution image shall be {@code this} and shall not be 
included in the given list.
      */
     final void setOverviews(final List<ImageFileDirectory> images) {
@@ -2034,7 +2034,7 @@ final class ImageFileDirectory extends DataCube {
     }
 
     /**
-     * A list of Image File Directories (FID) where the first entry is the 
image at finest resolution
+     * A list of Image File Directories (FID) where the first entry is the 
image at coarsest resolution
      * and following entries are images at finer resolutions. The entry at 
finest resolution is the
      * enclosing {@link ImageFileDirectory}.
      */
@@ -2052,7 +2052,7 @@ final class ImageFileDirectory extends DataCube {
         private final ImageFileDirectory[] levels;
 
         /**
-         * Creates a list of overviews from finest resolution to coarsest 
resolution.
+         * Creates a list of overviews from coarsest resolution to finest 
resolution.
          * The full-resolution image shall be the enclosing {@link 
ImageFileDirectory}
          * and is not included in the given list.
          */
@@ -2068,24 +2068,38 @@ final class ImageFileDirectory extends DataCube {
             return OptionalInt.of(levels.length + 1);
         }
 
+        /**
+         * Returns a resource which is representative of all pyramid levels 
except for the resolution.
+         * This method is invoked for fetching metadata such as the Coordinate 
Reference System
+         * when the resolution does not matter. For a <abbr>TIFF</abbr> file, 
this is the image
+         * with the finest resolution.
+         *
+         * @return a resource representative of all levels (ignoring 
resolution).
+         */
+        @Override
+        public TiledGridCoverageResource representative() {
+            return ImageFileDirectory.this;
+        }
+
         /**
          * Completes and returns the image at the given pyramid level.
-         * Indices are in the same order as the images appear in the 
<abbr>TIFF</abbr> file,
-         * with 0 for the full resolution image.
+         * Indices are in the reverse order of the images in the 
<abbr>TIFF</abbr> file,
+         * with 0 for the image at the coarsest resolution (the overview).
          *
-         * @param  level  image index (level) in the pyramid, with 0 for 
finest resolution.
+         * @param  level  image index (level) in the pyramid, with 0 for 
coarsest resolution (the overview).
          * @return image at the given pyramid level, or {@code null} if the 
given level is out of bounds.
          */
         @Override
         public TiledGridCoverageResource forPyramidLevel(final int level) 
throws DataStoreException {
-            if (level == 0) {
-                return ImageFileDirectory.this;
-            }
-            if (level > levels.length) {
+            final int n;
+            if (level < 0 || (n = levels.length - 1 - level) < -1) {
                 return null;
             }
+            if (n == -1) {
+                return ImageFileDirectory.this;
+            }
             synchronized (getSynchronizationLock()) {
-                final ImageFileDirectory image = levels[level - 1];
+                final ImageFileDirectory image = levels[n];
                 final Reader reader = image.reader;
                 try {
                     // Effective the first time that this method is invoked, 
no-op on other invocations.
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/CoverageSubset.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/CoverageSubset.java
index e764690ba0..2544cc414e 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/CoverageSubset.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/CoverageSubset.java
@@ -144,7 +144,7 @@ final class CoverageSubset extends 
AbstractGridCoverageResource {
     public List<double[]> getResolutions() throws DataStoreException {
         List<double[]> resolutions = source.getResolutions();
         if (reduction != null) try {
-            JDK16.toList(resolutions.stream()
+            resolutions = JDK16.toList(resolutions.stream()
                     .map((resolution) -> reduction.apply(new 
DirectPositionView.Double(resolution)).getCoordinates()));
         } catch (BackingStoreException e) {
             throw e.unwrapOrRethrow(DataStoreException.class);
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/GridCoverageResource.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/GridCoverageResource.java
index eec1a421cb..88e2ef6be4 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/GridCoverageResource.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/GridCoverageResource.java
@@ -42,7 +42,7 @@ import org.apache.sis.util.ArraysExt;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 1.5
+ * @version 1.7
  * @since   1.0
  */
 public interface GridCoverageResource extends DataSet {
@@ -131,9 +131,13 @@ public interface GridCoverageResource extends DataSet {
     /**
      * Returns the preferred resolutions (in units of CRS axes) for read 
operations in this data store.
      * If the storage supports pyramid, then the list should contain the 
resolution at each pyramid level
-     * ordered from finest (smallest numerical values) to coarsest (largest 
numerical values) resolution.
-     * Otherwise, the list contains a single element which is the {@linkplain 
#getGridGeometry() grid geometry}
-     * resolution, or an empty list if no resolution is applicable to the 
coverage (e.g. because non-constant).
+     * ordered from coarsest (largest numerical values) resolution to finest 
(smallest numerical values).
+     * This ordering should be the same as in {@link 
org.apache.sis.storage.tiling.TileMatrixSet},
+     * which is itself based on the order specified by <abbr>OGC</abbr> tile 
matrix specifications.
+     *
+     * <p>If the storage does not support pyramid, then the returned list 
contains a single element which
+     * is the {@linkplain #getGridGeometry() grid geometry} resolution, or an 
empty list if no resolution
+     * is applicable to the coverage (e.g. because non-constant).</p>
      *
      * <p>Each element shall be an array with a length equals to the number of 
<abbr>CRS</abbr> dimensions.
      * In each array, value at index <var>i</var> is the cell size along 
<abbr>CRS</abbr> dimension <var>i</var>
@@ -147,6 +151,7 @@ public interface GridCoverageResource extends DataSet {
      * @throws DataStoreException if an error occurred while reading 
definitions from the underlying data store.
      *
      * @see GridGeometry#getResolution(boolean)
+     * @see org.apache.sis.storage.tiling.TileMatrixSet#getTileMatrices()
      *
      * @since 1.2
      */
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/GridResourceWrapper.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/GridResourceWrapper.java
index 8780fb42e5..0f3f6c8023 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/GridResourceWrapper.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/GridResourceWrapper.java
@@ -157,7 +157,7 @@ public abstract class GridResourceWrapper implements 
GridCoverageResource {
 
     /**
      * Returns the preferred resolutions (in units of CRS axes) for read 
operations in this data store.
-     * Elements are ordered from finest (smallest numbers) to coarsest 
(largest numbers) resolution.
+     * Elements are ordered from coarsest (largest numbers) resolution to 
finest (smallest numbers).
      *
      * @return preferred resolutions for read operations in this data store, 
or an empty array if none.
      * @throws DataStoreException if an error occurred while reading 
definitions from the underlying data store.
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImagePyramid.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImagePyramid.java
index da376fb1f8..740323171c 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImagePyramid.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImagePyramid.java
@@ -210,8 +210,9 @@ final class ImagePyramid extends AbstractMap<GenericName, 
ImageTileMatrix>
 
     /**
      * Compares two generic names for order based on the resolution of the 
associated tile matrix.
-     * If a call to {@code compare(o1, o2)}, the comparator returns a positive 
number of {@code o1}
-     * is the identifier of a tile matrix having a finer resolution than 
{@code o2}.
+     * If a call to {@code compare(o1, o2)}, the comparator returns a positive 
number if {@code o1}
+     * is the identifier of a tile matrix having a finer resolution than 
{@code o2}
+     * (i.e., is ordered after the coarser tile matrix).
      */
     @Override
     public Comparator<GenericName> comparator() {
@@ -375,7 +376,7 @@ final class ImagePyramid extends AbstractMap<GenericName, 
ImageTileMatrix>
         final ImageTileMatrix tm;
         synchronized (matrices) {
             final int size = size();     // Implementation of `size()` fills 
the list.
-            tm = (size != 0) ? matrices.get(size - 1) : null;
+            tm = (size != 0) ? matrices.get(lowerMatrixIndex + (size - 1)) : 
null;
         }
         if (tm != null) {
             return tm.getIdentifier();
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileReadEvent.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileReadEvent.java
index 4ff4cd8958..c15f30b9b9 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileReadEvent.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileReadEvent.java
@@ -64,7 +64,7 @@ public class TileReadEvent extends StoreEvent {
 
         /**
          * Zero-based index of the pyramid level of the tile which is read.
-         * The level with finest resolution is the level 0.
+         * The level with coarsest resolution (the overview) is the level 0.
          *
          * @see #getPyramidLevel()
          * @see #getResolution()
@@ -101,7 +101,7 @@ public class TileReadEvent extends StoreEvent {
         /**
          * Creates a new context.
          *
-         * @param  pyramidLevel  index of the pyramid level of the tile which 
is read, where 0 is the level with finest resolution.
+         * @param  pyramidLevel  index of the pyramid level of the tile which 
is read, where 0 is the level with coarsest resolution.
          * @param  domain        the grid geometry of the coverage of which a 
slice is rendered as an image.
          * @param  aoi           the coordinates requested by the user.
          * @param  xDimension    dimension of the grid which is mapped to the 
<var>x</var> axis in rendered images.
@@ -170,7 +170,7 @@ public class TileReadEvent extends StoreEvent {
      * Returns the zero-based index of the pyramid level of the tile which is 
read.
      * This is typically the index in the {@linkplain 
TiledGridCoverageResource#getResolutions() list
      * of resource's resolution} where the values returned by {@link 
#getResolution()} can be found.
-     * The level with finest resolution is the level 0.
+     * The level with coarsest resolution (the overview) is the level 0.
      *
      * @return zero-based index of the pyramid level of the tile which is read.
      *
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverageResource.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverageResource.java
index d8e6142da1..feb89429e5 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverageResource.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverageResource.java
@@ -422,7 +422,7 @@ check:  if (dataType.isInteger()) {
 
     /**
      * Returns the preferred resolutions (in units of <abbr>CRS</abbr> axes) 
for read operations in this data store.
-     * The list elements are ordered from finest (smallest numerical values) 
to coarsest (largest numerical values).
+     * The list elements are ordered from coarsest (largest numerical values) 
to finest (smallest numerical values).
      *
      * <p>The default implementation uses information in the first element 
returned by {@link #getPyramids()}.
      * It is generally easier for subclasses to override {@link 
#getPyramids()} instead of this method.</p>
@@ -889,22 +889,33 @@ check:  if (dataType.isInteger()) {
              * the requested resolution.
              */
             final Pyramid pyramid = choosePyramid(domain, ranges);
-            if (pyramid == null || (bestFit = pyramid.forPyramidLevel(0)) == 
null) {
+            if (pyramid == null || (bestFit = pyramid.representative()) == 
null) {
                 return readAtThisPyramidLevel(domain, ranges, null);
             }
             int level = 0;
             final double[] request = bestFit.convertResolutionOf(domain);
-            if (request != null) {
+            bestFit = null;
+            if (request == null) {
+                final OptionalInt numberOfLevels = pyramid.numberOfLevels();
+                if (numberOfLevels.isPresent()) {
+                    level   = numberOfLevels.getAsInt() - 1;
+                    bestFit = pyramid.forPyramidLevel(level);
+                }
+            }
+            if (bestFit == null) {
+                level = -1;
                 TiledGridCoverageResource c;
-                while ((c = pyramid.forPyramidLevel(level)) != null) {
-                    final double[] resolution = 
c.getGridGeometry().getResolution(true);
-                    if (!(request[xDimension] >= resolution[xDimension] &&  // 
Use `!` for catching NaN.
-                          request[yDimension] >= resolution[yDimension])) 
break;
+                while ((c = pyramid.forPyramidLevel(level + 1)) != null) {
                     bestFit = c;
                     level++;
+                    if (request != null) {
+                        final double[] resolution = 
c.getGridGeometry().getResolution(true);
+                        if (!(request[xDimension] < resolution[xDimension] ||  
// Use `!` for catching NaN.
+                              request[yDimension] < resolution[yDimension])) 
break;
+                    }
                 }
             }
-            if (bestFit == this) {
+            if (bestFit == null || bestFit == this) {
                 return readAtThisPyramidLevel(domain, ranges, null);
             }
             bestFit.pyramidLevel = level;
@@ -1141,7 +1152,7 @@ check:  if (dataType.isInteger()) {
      *
      * <p>Each pyramid can have an arbitrary number of levels.
      * It is recommended to have one pyramid level for each {@linkplain 
#getResolutions() preferred resolutions}.
-     * The pyramid levels must be sorted from finest resolution (at level 0) 
to coarsest resolution.</p>
+     * The pyramid levels must be sorted from coarsest resolution (at level 0) 
to finest resolution.</p>
      *
      * <p>The number of levels is unspecified because some data stores cannot 
provide this information in advance.
      * Instead, the {@link #forPyramidLevel(int)} method will be invoked with 
different argument values when each
@@ -1171,7 +1182,7 @@ check:  if (dataType.isInteger()) {
          * Returns an identifier for the given level of this pyramid. The 
returned identifier
          * will be local in the namespace of the pyramid {@linkplain 
#identifier() identifier}.
          *
-         * @param  level  the pyramid level where 0 is the level with the 
finest resolution.
+         * @param  level  the pyramid level where 0 is the level with the 
coarsest resolution.
          * @return a local identifier for the specified level.
          */
         default String identifierOfLevel(int level) {
@@ -1207,18 +1218,34 @@ check:  if (dataType.isInteger()) {
             return OptionalInt.empty();
         }
 
+        /**
+         * Returns a resource which is representative of all pyramid levels 
except for the resolution.
+         * The default implementation returns the resource at level 0, 
<i>i.e.</i> the overview.
+         * Some formats such as <abbr>TIFF</abbr> rather use the image at the 
finest resolution
+         * as the base image from which other images are derived.
+         *
+         * <p>This method is invoked for fetching metadata such as the 
Coordinate Reference System.
+         * It is usually not invoked for reading pixel values, as the 
resolution can be anything.</p>
+         *
+         * @return a resource representative of all levels (ignoring 
resolution), or {@code null} if none.
+         * @throws DataStoreException if an error occurred while creating the 
resource.
+         */
+        default TiledGridCoverageResource representative() throws 
DataStoreException {
+            return forPyramidLevel(0);
+        }
+
         /**
          * Returns a resource for the same data as this resource but at a 
different resolution level.
-         * The resource at index 0 shall be the resource with the finest 
resolution, and resources at
-         * increasing index values shall be resources with increasingly 
coarser resolutions.
+         * The resource at index 0 shall be the resource with the coarsest 
resolution (the overview),
+         * and resources at increasing index values shall be resources with 
increasingly finer resolutions.
          * If the specified level is equal or greater than the number of 
levels in this pyramid,
          * then this method shall return {@code null}.
          *
          * <p>If this method returns a non-null instance <var>r</var>, then 
the following condition should hold:
-         * {@code r.getGridGeometry().getResolution(false)} should be equal, 
ignoring NaN values and rounding
-         * errors, to {@code getResolutions().get(level)}.</p>
+         * {@code r.getGridGeometry().getResolution(false)} should be equal, 
ignoring NaN values and rounding errors,
+         * to {@code getResolutions().get(level)}.</p>
          *
-         * @param  level  the pyramid level where 0 is the level with the 
finest resolution.
+         * @param  level  the pyramid level where 0 is the level with the 
coarsest resolution (the overview).
          * @return a resource for data at the specified pyramid level, or 
{@code null} if the given level is too high.
          * @throws DataStoreException if an error occurred while creating the 
resource.
          *
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
index a6a90d2f23..635cb296d6 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageCanvas.java
@@ -95,6 +95,7 @@ import org.apache.sis.util.Debug;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.collection.BackingStoreException;
+import org.apache.sis.util.collection.Containers;
 import org.apache.sis.io.TableAppender;
 import org.apache.sis.measure.Units;
 import static org.apache.sis.gui.internal.LogHandler.LOGGER;
@@ -665,7 +666,7 @@ public class CoverageCanvas extends MapCanvasAWT {
                         } else try {
                             domain = resource.getGridGeometry();
                             ranges = resource.getSampleDimensions();
-                            scales = lastNonNull(resource.getResolutions());
+                            scales = 
Containers.peekFirst(resource.getResolutions());
                         } catch (BackingStoreException e) {
                             throw e.unwrapOrRethrow(DataStoreException.class);
                         }
@@ -747,24 +748,6 @@ public class CoverageCanvas extends MapCanvasAWT {
         }
     }
 
-    /**
-     * Returns the last non-null element of the given list.
-     *
-     * @param  <T>   the type of elements contained in the list.
-     * @param  list  the list from which to get the last non-null element, or 
{@code null}.
-     * @return the last non-null element, or {@code null} if the given list is 
null or empty.
-     */
-    private static <T> T lastNonNull(final List<T> list) {
-        if (list != null) {
-            int i = list.size();
-            while (--i >= 0) {
-                T e = list.get(i);
-                if (e != null) return e;
-            }
-        }
-        return null;
-    }
-
     /**
      * Clears the rendered image but keep the resource, coverage, grid 
geometry and sample dimensions unchanged.
      * Invoking this method alone is useful when only the selected 
two-dimensional slice changed.

Reply via email to