You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2026/03/25 17:22:33 UTC
(sis) branch geoapi-4.0 updated: Pyramid levels should be ordered from coarsest to finest resolution. https://issues.apache.org/jira/browse/SIS-628
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 <ma...@geomatys.com>
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.