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

asf-gitbox-commits 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 ccfa3c1130 Fix the order of pyramid levels in HEIF file. Opportunistic 
fix in the formatting of scale factors.
ccfa3c1130 is described below

commit ccfa3c11306edc60f8adcddc9b4cea8d3c6a69d3
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Thu Jun 18 18:02:54 2026 +0200

    Fix the order of pyramid levels in HEIF file.
    Opportunistic fix in the formatting of scale factors.
---
 .../org/apache/sis/coverage/grid/GridExtent.java   |  38 ++++++++
 .../org/apache/sis/coverage/grid/GridGeometry.java |   6 +-
 .../main/org/apache/sis/io/wkt/Formatter.java      |   2 +-
 .../apache/sis/storage/tiling/TileMatrixSet.java   |   2 +-
 .../sis/storage/tiling/TileMatrixSetFormat.java    |  43 +++++----
 .../apache/sis/util/internal/shared/Numerics.java  | 100 ++-------------------
 .../sis/storage/geoheif/CoverageBuilder.java       |   3 +-
 .../org/apache/sis/storage/geoheif/ImageModel.java |   6 +-
 .../apache/sis/storage/geoheif/ImageResource.java  |  14 +--
 .../org/apache/sis/storage/geoheif/Pyramid.java    |  40 ++++++++-
 .../isobmff/mpeg/CompressionConfiguration.java     |   4 +
 11 files changed, 126 insertions(+), 132 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
index e8aff27060..45b5d7e4bf 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridExtent.java
@@ -22,6 +22,7 @@ import java.util.SortedMap;
 import java.util.Arrays;
 import java.util.Optional;
 import java.util.Objects;
+import java.util.Comparator;
 import java.util.Spliterator;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
@@ -919,6 +920,7 @@ public class GridExtent implements GridEnvelope, 
LenientComparable, Serializable
      * @see #getLow(int)
      * @see #getHigh(int)
      * @see #resize(long...)
+     * @see #SIZES_COMPARATOR
      */
     @Override
     public long getSize(final int index) {
@@ -936,6 +938,8 @@ public class GridExtent implements GridEnvelope, 
LenientComparable, Serializable
      * @param  index     the dimension for which to obtain the size.
      * @param  minusOne  {@code true} for returning <var>size</var>−1 instead 
of <var>size</var>.
      * @return the number of cells along the given dimension, optionally minus 
one.
+     *
+     * @see #SIZES_COMPARATOR
      */
     public double getSize(final int index, final boolean minusOne) {
         final int dimension = getDimension();
@@ -947,6 +951,40 @@ public class GridExtent implements GridEnvelope, 
LenientComparable, Serializable
         return Numerics.toUnsignedDouble(size);
     }
 
+    /**
+     * A comparator of grid extent sizes where each dimension is compared in 
increasing index order.
+     * For a given pair of {@code GridExtent} instances, the comparison begins 
with the
+     * {@linkplain #getSize(int) size} of each extent in the first dimension 
(index 0):
+     *
+     * <ul>
+     *   <li>If the first extent has a smaller size, a negative number is 
returned.</li>
+     *   <li>If the second extent has a smaller size, a positive number is 
returned.</li>
+     *   <li>Otherwise, the comparison is repeated with the next dimensions 
(1, 2, …,
+     *       {@linkplain #getDimension() dimension} - 1) until a dimension is 
found
+     *       where the sizes are not equal.
+     * </ul>
+     *
+     * If all dimensions have the same {@linkplain #getSize(int) size},
+     * then the extent with the smallest number of dimensions is considered 
before the other extent.
+     * Null values are considered after all non-null values.
+     *
+     * @since 1.7
+     */
+    public static final Comparator<GridExtent> SIZES_COMPARATOR = (e1, e2) -> {
+        if (e1 == e2)   return  0;
+        if (e1 == null) return +1;
+        if (e2 == null) return -1;
+        final int d1 = e1.getDimension();
+        final int d2 = e2.getDimension();
+        final int dim = Math.min(d1, d2);
+        for (int i=0; i<dim; i++) {
+            int c = Long.compareUnsigned(e1.coordinates[d1 + i] - 
e1.coordinates[i],
+                                         e2.coordinates[d2 + i] - 
e2.coordinates[i]);
+            if (c != 0) return c;
+        }
+        return d1 - d2;
+    };
+
     /**
      * Returns the low and high coordinates packaged in a single array.
      * The length of this array is twice the number of dimensions.
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
index 77e8cffc39..46f7035f68 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridGeometry.java
@@ -2460,8 +2460,8 @@ public class GridGeometry implements LenientComparable, 
Serializable {
                     final double lower = envelope.getLower(i);
                     final double upper = envelope.getUpper(i);
                     final double delta = (resolution != null) ? resolution[i] 
: Double.NaN;
-                    
nf.setMinimumFractionDigits(Numerics.fractionDigitsForDelta(delta));
-                    
nf.setMaximumFractionDigits(Numerics.suggestFractionDigits(lower, upper) - 1);  
  // The -1 is for rounding errors.
+                    
nf.setMaximumFractionDigits(Numerics.fractionDigitsForRange(lower, upper) - 1); 
  // The -1 is for rounding errors.
+                    
nf.setMinimumFractionDigits(Numerics.fractionDigitsForDelta(delta));            
  // May adjust above max value.
                     final CoordinateSystemAxis axis = (cs != null) ? 
cs.getAxis(i) : null;
                     final String name = (axis != null) ? 
axis.getName().getCode() : vocabulary.getString(Vocabulary.Keys.Dimension_1, i);
                     table.append(name).append(": ").nextColumn();
@@ -2633,7 +2633,7 @@ public class GridGeometry implements LenientComparable, 
Serializable {
             } else {
                 final NumberFormat nf = numberFormat();
                 final int n = Double.isNaN(delta)
-                        ? Numerics.suggestFractionDigits(value) / 2
+                        ? Numerics.fractionDigitsForDelta(value) / 2
                         : Numerics.fractionDigitsForDelta(delta);
                 nf.setMaximumFractionDigits(n + 1);
                 out.append(nf.format(value));
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
index 45aa889fc3..9b45a63daa 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Formatter.java
@@ -1018,7 +1018,7 @@ public class Formatter implements Localized {
             final double max = range.getMaxDouble();
             int minimumFractionDigits = Numerics.fractionDigitsForDelta(max - 
min);
             int maximumFractionDigits = Math.min(Math.min(
-                    Numerics.suggestFractionDigits(min, max),
+                    Numerics.fractionDigitsForRange(min, max),
                     minimumFractionDigits + 2), VERTICAL_ACCURACY);            
 // Arbitrarily limit to 2 more digits.
             openElement(true, WKTKeywords.VerticalExtent);
             setColor(ElementKind.EXTENT);
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrixSet.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrixSet.java
index 11e37f43af..a25843f0e0 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrixSet.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrixSet.java
@@ -129,7 +129,7 @@ public interface TileMatrixSet {
     /**
      * Returns all {@code TileMatrix} instances in this set, together with 
their identifiers.
      * For each value in the map, the associated key is {@link 
TileMatrix#getIdentifier()}.
-     * Entries are sorted from coarser resolution (highest scale denominator) 
to most detailed
+     * Entries are sorted from coarsest resolution (highest scale denominator) 
to most detailed
      * resolution (lowest scale denominator).
      * This is not necessarily the natural ordering of the {@link GenericName} 
instances used as keys.
      *
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrixSetFormat.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrixSetFormat.java
index 321700a504..b528691764 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrixSetFormat.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrixSetFormat.java
@@ -43,7 +43,6 @@ import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
-import org.apache.sis.util.internal.shared.Numerics;
 import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.util.collection.TreeTableFormat;
 import org.apache.sis.util.collection.BackingStoreException;
@@ -52,6 +51,7 @@ import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.IncompleteGridGeometryException;
 import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
+import org.apache.sis.math.DecimalFunctions;
 import org.apache.sis.io.CompoundFormat;
 import org.apache.sis.io.TableAppender;
 
@@ -113,6 +113,8 @@ public class TileMatrixSetFormat extends 
CompoundFormat<TileMatrixSet> {
 
         /**
          * Creates one row in the table for the given matrix.
+         * This constructor prepares string representations of all values 
except resolutions.
+         * Resolutions must be formatted by a call to {@link 
#formatResolutions(List, NumberFormat)}.
          *
          * @param  matrix         the matrix for which to store information.
          * @param  integerFormat  a number format configured for integer 
values.
@@ -176,27 +178,34 @@ public class TileMatrixSetFormat extends 
CompoundFormat<TileMatrixSet> {
          *         This value should be equal to the number of dimensions in 
the <abbr>CRS</abbr>.
          */
         static int formatResolutions(final List<Row> rows, final NumberFormat 
format) {
-            final var values = new double[rows.size()];
-            for (int i=0; ; i++) {
-                int count = 0;
+            final int numRow = rows.size();
+            for (int column = 0; ; column++) {
+                boolean found = false;
+                double maximum = 0;
+                int n = 0;
                 for (final Row row : rows) {
-                    if (i < row.resolution.length) {
-                        values[count++] = row.resolution[i];
+                    if (column < row.resolution.length) {
+                        found = true;
+                        double value = Math.abs(row.resolution[column]);
+                        if (value > maximum) maximum = value;
+                        value -= Math.floor(value);
+                        if (value > 0) {
+                            n = Math.max(n, 
DecimalFunctions.fractionDigitsForDelta(value, true));
+                        }
                     }
                 }
-                if (count == 0) return i;
-                final int n = 
Numerics.suggestFractionDigits(ArraysExt.resize(values, count));
+                if (!found) return column;
+                if (n != 0) n++;    // TODO: should do a better work for 
finding the number of fraction digits.
+                n = Math.min(n, 
DecimalFunctions.fractionDigitsForValue(maximum));
                 format.setMinimumFractionDigits(n);
                 format.setMaximumFractionDigits(n);
-                final int column = i;   // Because lambda requires final 
values.
-                final String[] formatted = 
Numerics.formatAndTrimTrailingZeros(format, values.length, (j) -> {
-                    final double[] resolution = rows.get(j).resolution;
-                    return (column < resolution.length) ? resolution[column] : 
Double.NaN;
-                });
-                for (int j=0; j<values.length; j++) {
-                    final String[] resolution = 
rows.get(j).formattedResolution;
-                    if (i < resolution.length) {
-                        resolution[i] = formatted[j];
+                for (int i=0; i<numRow; i++) {
+                    final Row row = rows.get(i);
+                    final String[] target = row.formattedResolution;
+                    if (column < target.length) {
+                        final double[] resolution = row.resolution;
+                        final double value = (column < resolution.length) ? 
resolution[column] : Double.NaN;
+                        target[column] = format.format(value);
                     }
                 }
             }
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/Numerics.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/Numerics.java
index 6440d7ac52..a9de7d6262 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/Numerics.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/internal/shared/Numerics.java
@@ -16,14 +16,10 @@
  */
 package org.apache.sis.util.internal.shared;
 
-import java.util.Arrays;
 import java.text.Format;
-import java.text.NumberFormat;
 import java.text.DecimalFormat;
-import java.text.FieldPosition;
 import java.math.BigInteger;
 import java.util.function.BiFunction;
-import java.util.function.IntToDoubleFunction;
 import static java.lang.Math.min;
 import static java.lang.Math.max;
 import static java.lang.Math.abs;
@@ -597,46 +593,20 @@ public final class Numerics {
     }
 
     /**
-     * Suggests a number of fraction digits for the given values, ignoring NaN 
and infinities.
+     * Suggests a number of fraction digits for the given range of values.
      * This method uses heuristic rules that may change in any future 
<abbr>SIS</abbr> version.
      * Current implementation returns a value which avoid printing "garbage" 
digits with highest numbers,
      * at the cost of loosing significant digits on smallest numbers. It may 
further reduce the number of
      * digits if this is sufficient for distinguishing the given values.
-     * An arbitrary limit is set to 16 digits, which is the number of digits 
for {@code Math.ulp(1.0)}}.
      *
-     * <p>This method modifies the given array in-place. Callers should not 
pass original data.</p>
-     *
-     * @param  values  the values for which to get suggested number of 
fraction digits.
+     * @param  minimum  the minimum value of the range.
+     * @param  maximum  the maximum value of the range.
      * @return suggested number of fraction digits for the given values. 
Always positive.
      */
-    public static int suggestFractionDigits(final double... values) {
-        switch (values.length) {
-            case 0: return 0;
-            case 1: return DecimalFunctions.fractionDigitsForValue(values[0]);
-        }
-        for (int i=0; i<values.length; i++) {
-            values[i] = abs(values[i]);
-        }
-        Arrays.sort(values);
-        double ulp = 0;
-        double delta = Double.POSITIVE_INFINITY;
-        for (int i = values.length; --i >= 0;) {
-            double value = values[i];
-            if (Double.isFinite(value)) {
-                ulp = ulp(value);
-                while (--i >= 0) {
-                    final double lower = values[i];
-                    final double diff = value - lower;
-                    if (diff > 0 && diff < delta) {
-                        delta = diff;
-                    }
-                    value = lower;
-                }
-                break;
-            }
-        }
-        // Arbitrarily add 6 digits the difference between values.
-        return min(fractionDigitsForDelta(ulp), fractionDigitsForDelta(delta) 
+ 6);
+    public static int fractionDigitsForRange(final double minimum, final 
double maximum) {
+        final double ulp = max(ulp(minimum), ulp(maximum));
+        // Arbitrarily add 6 digits to the value computed from the difference 
between values.
+        return min(fractionDigitsForDelta(ulp), fractionDigitsForDelta(maximum 
- minimum) + 6);
     }
 
     /**
@@ -670,62 +640,6 @@ public final class Numerics {
         return fractionDigitsForDelta(delta);
     }
 
-    /**
-     * Gets the character used for zero.
-     * If the given format is not a {@link DecimalFormat}, arbitrarily returns 
the white space.
-     *
-     * @param  format  the format for which to get the zero digit.
-     * @return the zero digit, or {@code ' '} if the format is not decimal.
-     */
-    private static char getZeroDigit(final Format format) {
-        return (format instanceof DecimalFormat) ? ((DecimalFormat) 
format).getDecimalFormatSymbols().getZeroDigit() : ' ';
-    }
-
-    /**
-     * Formats values then removes the same number of trailing zeros in the 
fraction digits of all values.
-     * For this method to be useful, the given format should use a fixed 
number of fraction digits.
-     * NaN and infinite values are formatted as usual but ignored in the 
removal of trailing zeros.
-     *
-     * @param  format  the format to use.
-     * @param  count   number of values to format.
-     * @param  values  provider of values to format.
-     * @return an array of length {@code count} with the formatted values.
-     */
-    public static String[] formatAndTrimTrailingZeros(final NumberFormat 
format, final int count, final IntToDoubleFunction values) {
-        final var  formatted     = new String[count];
-        final var  buffer        = new StringBuffer();
-        final var  fractionEnd   = new int[count];
-        final var  fractionField = new 
FieldPosition(NumberFormat.Field.FRACTION);
-        final char zeroDigit     = getZeroDigit(format);
-        int numberOfTrailingZeros = Integer.MAX_VALUE;
-        for (int i=0; i<count; i++) {
-            final double value = values.applyAsDouble(i);
-            String text = format.format(value, buffer, 
fractionField).toString();
-            formatted[i] = text;
-            if (numberOfTrailingZeros != 0 && Double.isFinite(value)) {
-                final int end    = fractionField.getEndIndex();
-                fractionEnd[i]   = end;
-                int firstNonZero = end;
-                final int start  = Math.max(fractionField.getBeginIndex(), end 
- numberOfTrailingZeros);
-                while (--firstNonZero > start) {
-                    if (text.charAt(firstNonZero) != zeroDigit) break;
-                }
-                numberOfTrailingZeros = Math.min(numberOfTrailingZeros, end - 
(firstNonZero + 1));
-            }
-            buffer.setLength(0);
-        }
-        if (numberOfTrailingZeros != 0) {
-            for (int i=0; i<count; i++) {
-                final int end = fractionEnd[i];
-                if (end != 0) {
-                    formatted[i] = buffer.append(formatted[i]).delete(end - 
numberOfTrailingZeros, end).toString();
-                    buffer.setLength(0);
-                }
-            }
-        }
-        return formatted;
-    }
-
     /**
      * Formats the given value with the given format, using scientific 
notation if needed.
      * This is a workaround for {@link DecimalFormat} not switching 
automatically to scientific notation for large numbers.
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/CoverageBuilder.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/CoverageBuilder.java
index 551fc2fec8..16690e47a9 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/CoverageBuilder.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/CoverageBuilder.java
@@ -596,8 +596,9 @@ final class CoverageBuilder implements Emptiable {
      * This method should be invoked at most once.
      * It may be invoked not at all when this object is used for building a 
tile instead of an image.
      *
-     * @todo Need to add information from the {@code ExtraDimensionProperty} 
box.
+     * @todo Need to add information from the {@code ExtraDimensionProperty} 
(edim) box.
      *       These information include name, minimum, maximum and resolution.
+     *       See https://docs.ogc.org/per/24-038r1.html
      *
      * @return the grid geometry.
      * @throws DataStoreException if the "grid to <abbr>CRS</abbr>" transform 
cannot be created.
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageModel.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageModel.java
index 0da11c285f..a8d64494bd 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageModel.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageModel.java
@@ -85,8 +85,9 @@ final class ImageModel {
     /**
      * Computes date type, color model, sample model, and sample dimensions.
      *
-     * @todo Need to add information from the {@code CellPropertyTypeProperty} 
box.
+     * @todo Need to add information from the {@code CellPropertyTypeProperty} 
("pcel") box.
      *       These information include sample dimension name and unit of 
measurement.
+     *       See https://docs.ogc.org/per/24-038r1.html
      *
      * @param  width           the width  (in pixels) of the reconstructed 
image.
      * @param  height          the height (in pixels) of the reconstructed 
image.
@@ -156,7 +157,8 @@ final class ImageModel {
             }
             /*
              * Create the sample dimension and derive metadata from it.
-             * TODO: parse CellPropertyTypeProperty and 
CellPropertyCategoriesProperty boxes.
+             * TODO: parse CellPropertyTypeProperty (pcel) and 
CellPropertyCategoriesProperty (pcat) boxes.
+             *       See https://docs.ogc.org/per/24-038r1.html
              */
             if (colorType != null) {
                 sb.setName(colorType.toString());
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java
index 1aa77f32c1..c1a0f9ee4b 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/ImageResource.java
@@ -182,18 +182,10 @@ final class ImageResource extends 
TiledGridCoverageResource implements StoreReso
      * Declares that this image is the pyramid level of the given base grid.
      * This method does nothing if this image already has its own "grid to 
<abbr>CRS</abbr>" transform.
      *
-     * <p>If {@code base} is null, then the base is assumed to be the grid 
geometry of this level.
-     * Configuring a level relative to itself is useful when the grid geometry 
is incomplete,
-     * in which case the resolution of the base level is set to 1.
-     * This is needed because resolutions are mandatory in a grid.</p>
-     *
-     * @param  base  grid geometry of the pyramid level at the finest 
resolution, or {@code null} if this level.
-     * @return grid geometry of the pyramid level at the finest resolution 
({@code base} if it was non-null).
+     * @param  base  grid geometry of the pyramid level at the finest 
resolution.
      * @throws TransformException if an error occurred while deriving the 
"grid to <abbr>CRS</abbr>" transform.
      */
-    final GridGeometry setPyramidLevelOf(GridGeometry base) throws 
TransformException {
-        final boolean self = (base == null);
-        if (self) base = gridGeometry;
+    final void setPyramidLevelOf(final GridGeometry base) throws 
TransformException {
         if (!gridGeometry.isDefined(GridGeometry.GRID_TO_CRS)) {
             final GridExtent levelExtent = gridGeometry.getExtent();
             final GridExtent baseExtent  = base.getExtent();
@@ -202,9 +194,7 @@ final class ImageResource extends TiledGridCoverageResource 
implements StoreReso
                 factors[i] = 1 / Numerics.divide(levelExtent.getSize(i), 
baseExtent.getSize(i));
             }
             gridGeometry = new GridGeometry(base, levelExtent, 
MathTransforms.scale(factors));
-            if (self) base = gridGeometry;
         }
-        return base;
     }
 
     /**
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/Pyramid.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/Pyramid.java
index 9cf9d01abd..234dcbb2e9 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/Pyramid.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/Pyramid.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.storage.geoheif;
 
+import java.util.Arrays;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Optional;
 import java.util.OptionalInt;
@@ -23,6 +25,7 @@ import org.opengis.util.GenericName;
 import org.opengis.util.NameFactory;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.coverage.SampleDimension;
+import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.isobmff.image.ImagePyramid;
@@ -58,13 +61,22 @@ final class Pyramid extends TiledGridCoverageResource 
implements TiledGridCovera
      */
     private final ImageResource[] levels;
 
+    /**
+     * A comparator of pyramid level based on the size of their grid extent.
+     * We use the grid extent because this is sometime the only information 
which is known.
+     * The "grid to <abbr>CRS</abbr>", and therefore the resolution, may not 
have been computed yet.
+     */
+    private static final Comparator<ImageResource> LEVEL_COMPARATOR =
+            Comparator.comparing((image) -> 
image.getGridGeometry().getExtent(),
+                                 GridExtent.SIZES_COMPARATOR.reversed());
+
     /**
      * Creates a new pyramid.
      *
      * @param  store    the parent of this pyramid.
      * @param  name     the name of this pyramid, or {@code null} if none.
      * @param  pyramid  information about the pyramid.
-     * @param  levels   the child resources from finest resolution to coarsest 
resolution.
+     * @param  levels   the child resources in arbitrary order. This array 
will be sorted in-place.
      * @throws TransformException if an error occurred while deriving a "grid 
to <abbr>CRS</abbr>" transform.
      */
     Pyramid(final GeoHeifStore store, final GenericName name, final 
ImagePyramid pyramid, final ImageResource[] levels)
@@ -75,9 +87,33 @@ final class Pyramid extends TiledGridCoverageResource 
implements TiledGridCovera
         tileSizeX = pyramid.tileSizeX;
         tileSizeY = pyramid.tileSizeY;
         this.levels = levels;
+        Arrays.sort(levels, LEVEL_COMPARATOR);
+        /*
+         * Select the finest level for which the "grid to CRS" transform, and 
therefore the resolution,
+         * is defined. If none, select the very fist level, which should have 
the finest resolution.
+         */
         GridGeometry base = null;
+        boolean updateBase = true;
+        for (final ImageResource level : levels) {
+            final GridGeometry grid = level.getGridGeometry();
+            if (grid.isDefined(GridGeometry.GRID_TO_CRS)) {
+                updateBase = false;   // Base is already complete.
+                base = grid;
+                break;
+            } else if (base == null) {
+                base = grid;
+            }
+        }
+        /*
+         * Now compute the "grid to CRS" transform for each pyramid level.
+         * It may include the base level itself if the transform was not 
specified anywhere.
+         */
         for (ImageResource level : levels) {
-            base = level.setPyramidLevelOf(base);
+            level.setPyramidLevelOf(base);
+            if (updateBase) {
+                updateBase = false;
+                base = level.getGridGeometry();
+            }
         }
     }
 
diff --git 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/CompressionConfiguration.java
 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/CompressionConfiguration.java
index 1a050b4356..cb79b3b409 100644
--- 
a/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/CompressionConfiguration.java
+++ 
b/incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/isobmff/mpeg/CompressionConfiguration.java
@@ -39,11 +39,15 @@ public final class CompressionConfiguration extends FullBox 
{
 
     /**
      * The {@code "zlib"} value for {@link #compressionType}.
+     *
+     * @see <a href="https://www.ietf.org/rfc/rfc1950.pdf";>IETF RFC 1950</a>
      */
     public static final int COMPRESSION_ZLIB = ((((('z' << 8) | 'l') << 8) | 
'i') << 8) | 'b';
 
     /**
      * The {@code "defl"} value for {@link #compressionType}.
+     *
+     * @see <a href="https://www.ietf.org/rfc/rfc1951.pdf";>IETF RFC 1951</a>
      */
     public static final int COMPRESSION_DEFLATE = ((((('d' << 8) | 'e') << 8) 
| 'f') << 8) | 'l';
 

Reply via email to