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 32af8e44e5 Implement `ImagePyramid.toString()` for easier debugging. 
Use that string representation for now in the widget.
32af8e44e5 is described below

commit 32af8e44e57cc15db0e9043a297fe9aaf6c252c1
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Mon Feb 23 19:46:46 2026 +0100

    Implement `ImagePyramid.toString()` for easier debugging.
    Use that string representation for now in the widget.
---
 .../apache/sis/coverage/grid/GridDerivation.java   |   2 +-
 .../main/org/apache/sis/referencing/CommonCRS.java |   8 +-
 .../referencing/internal/shared/WKTUtilities.java  |   4 +-
 .../sis/referencing/operation/matrix/Matrices.java |   4 +-
 .../operation/transform/PassThroughTransform.java  |   2 +-
 .../apache/sis/storage/tiling/ImagePyramid.java    |  23 +-
 .../apache/sis/storage/tiling/ImageTileMatrix.java |  14 +-
 .../sis/storage/tiling/TileMatrixFormatter.java    | 310 +++++++++++++++++++++
 .../sis/storage/tiling/TiledGridCoverage.java      |  14 +-
 .../storage/tiling/TiledGridCoverageResource.java  |  18 +-
 .../main/org/apache/sis/math/DecimalFunctions.java |   2 +-
 .../main/org/apache/sis/math/Vector.java           |   2 +-
 .../apache/sis/util/internal/shared/Numerics.java  |  55 ++--
 .../org/apache/sis/util/resources/Vocabulary.java  |  10 +
 .../sis/util/resources/Vocabulary.properties       |   2 +
 .../sis/util/resources/Vocabulary_fr.properties    |   4 +-
 .../main/org/apache/sis/gui/Widget.java            |   8 +-
 .../apache/sis/gui/controls/SyncWindowList.java    |   6 +-
 .../apache/sis/gui/coverage/CoverageExplorer.java  |   4 +-
 .../apache/sis/gui/coverage/GridSliceSelector.java |  12 +-
 .../sis/gui/coverage/ImagePropertyExplorer.java    |   6 +-
 .../apache/sis/gui/coverage/TileMatrixSetPane.java | 138 +++++----
 .../main/org/apache/sis/gui/dataset/LogViewer.java |   5 +-
 .../apache/sis/gui/dataset/ResourceExplorer.java   |  41 ++-
 .../org/apache/sis/gui/dataset/package-info.java   |   2 +-
 .../apache/sis/gui/internal/io/FileAccessView.java |   4 +-
 .../main/org/apache/sis/gui/map/StatusBar.java     |   6 +-
 .../apache/sis/gui/metadata/MetadataSummary.java   |   8 +-
 28 files changed, 543 insertions(+), 171 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
index 982c68a090..8baa7b22f7 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridDerivation.java
@@ -336,7 +336,7 @@ public class GridDerivation {
     }
 
     /**
-     * Specifies an number of cells by which to expand {@code GridExtent} 
after rounding.
+     * Specifies a number of cells by which to expand {@code GridExtent} after 
rounding.
      * This setting modifies computations performed by the following methods:
      * <ul>
      *   <li>{@link #subgrid(GridGeometry)}</li>
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
index a70311ecab..6b09decb55 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/CommonCRS.java
@@ -393,18 +393,18 @@ public enum CommonCRS {
 
     /**
      * The Universal Transverse Mercator (UTM) or Universal Polar 
Stereographic (UPS) projections,
-     * created when first needed. The UPS projections are arbitrarily given 
zone numbers
+     * created when first needed. The <abbr>UPS</abbr> projections are 
arbitrarily given zone numbers
      * {@value #POLAR} and -{@value #POLAR} for North and South poles 
respectively.
      *
      * <p>All accesses to this map shall be synchronized on {@code 
cachedProjections}.</p>
      *
      * @see #universal(double, double)
      */
-    private final Map<Integer,ProjectedCRS> cachedProjections;
+    private final Map<Integer, ProjectedCRS> cachedProjections;
 
     /**
      * The special zone number used as key in {@link #cachedProjections} for 
polar stereographic projections.
-     * Must be outside the range of UTM zone numbers.
+     * Must be outside the range of <abbr>UTM</abbr> zone numbers.
      */
     private static final int POLAR = 90;
 
@@ -1193,7 +1193,7 @@ public enum CommonCRS {
             CartesianCS cs = null;
             if (isUTM) {
                 synchronized (DEFAULT.cachedProjections) {
-                    for (final Map.Entry<Integer,ProjectedCRS> entry : 
DEFAULT.cachedProjections.entrySet()) {
+                    for (final Map.Entry<Integer, ProjectedCRS> entry : 
DEFAULT.cachedProjections.entrySet()) {
                         if (Math.abs(entry.getKey()) != POLAR) {
                             cs = entry.getValue().getCoordinateSystem();
                             break;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/WKTUtilities.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/WKTUtilities.java
index 17a19e841f..840fff8344 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/WKTUtilities.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/shared/WKTUtilities.java
@@ -281,7 +281,7 @@ public final class WKTUtilities {
     }
 
     /**
-     * Suggests an number of fraction digits to use for formatting numbers in 
each column of the given matrix.
+     * Suggests a number of fraction digits to use for formatting numbers in 
each column of the given matrix.
      * The number of fraction digits may be negative if we could round the 
numbers to 10, 100, <i>etc</i>.
      *
      * @param  rows  the matrix rows. It is not required that each row has the 
same length.
@@ -318,7 +318,7 @@ public final class WKTUtilities {
     }
 
     /**
-     * Suggests an number of fraction digits to use for formatting numbers in 
each column of the given sequence
+     * Suggests a number of fraction digits to use for formatting numbers in 
each column of the given sequence
      * of points. The number of fraction digits may be negative if we could 
round the numbers to 10, <i>etc</i>.
      *
      * @param  crs     the coordinate reference system for each points, or 
{@code null} if unknown.
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/Matrices.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/Matrices.java
index 66bed3e256..0f9574d43c 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/Matrices.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/matrix/Matrices.java
@@ -609,12 +609,12 @@ public final class Matrices {
      *
      * <p>This method builds a new matrix with the following content:</p>
      * <ul>
-     *   <li>An number of {@code firstAffectedCoordinate} rows and columns are 
inserted before the first
+     *   <li>A number of {@code firstAffectedCoordinate} rows and columns are 
inserted before the first
      *       row and columns of the sub-matrix. The elements for the new rows 
and columns are set to 1
      *       on the diagonal, and 0 elsewhere.</li>
      *   <li>The sub-matrix - except for its last row and column - is copied 
in the new matrix starting
      *       at index ({@code firstAffectedCoordinate}, {@code 
firstAffectedCoordinate}).</li>
-     *   <li>An number of {@code numTrailingCoordinates} rows and columns are 
appended after the above sub-matrix.
+     *   <li>A number of {@code numTrailingCoordinates} rows and columns are 
appended after the above sub-matrix.
      *       Their elements are set to 1 on the pseudo-diagonal ending in the 
lower-right corner, and 0 elsewhere.</li>
      *   <li>The last sub-matrix row is copied in the last row of the new 
matrix, and the last sub-matrix column
      *       is copied in the last column of the sub-matrix.</li>
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/PassThroughTransform.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/PassThroughTransform.java
index fd1e6c7d78..bacccd1442 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/PassThroughTransform.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/PassThroughTransform.java
@@ -233,7 +233,7 @@ public class PassThroughTransform extends 
AbstractMathTransform implements Seria
      * then the current implementation of this method adds the following 
restrictions:
      *
      * <ul>
-     *   <li>The sub-transform must have an number of target dimensions equal 
to the number of source dimensions.</li>
+     *   <li>The sub-transform must have a number of target dimensions equal 
to the number of source dimensions.</li>
      *   <li>The sub-transform must be {@linkplain TransformSeparator 
separable}.</li>
      * </ul>
      *
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 e89c072a44..5f5b8f3b5c 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
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Locale;
 import java.util.NoSuchElementException;
 import java.util.Optional;
 import java.util.SortedMap;
@@ -32,7 +33,6 @@ import org.apache.sis.coverage.grid.GridCoverageProcessor;
 import org.apache.sis.coverage.grid.IncompleteGridGeometryException;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.geometry.ImmutableEnvelope;
-import org.apache.sis.measure.NumberRange;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.base.StoreUtilities;
 import org.apache.sis.util.collection.BackingStoreException;
@@ -40,7 +40,6 @@ import org.apache.sis.util.iso.Names;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.internal.shared.AbstractMap;
-import org.apache.sis.util.internal.shared.Strings;
 
 
 /**
@@ -98,20 +97,28 @@ final class ImagePyramid extends AbstractMap<GenericName, 
ImageTileMatrix>
      */
     private final GridCoverageProcessor processor;
 
+    /**
+     * The locale for error messages, or {@code null} for the default locale.
+     */
+    final Locale locale;
+
     /**
      * Creates a new tile matrix set.
      *
      * @param  parent     identifier of the {@code TiledResource} that 
contains this {@code TileMatrixSet}.
      * @param  provider   information about the tile matrices to create, and 
provider of pyramid levels.
      * @param  processor  the grid coverage processor to use when tiles use a 
subset of the bands.
+     * @param  locale     the locale for error messages, or {@code null} for 
the default locale.
      */
     @SuppressWarnings("LocalVariableHidesMemberVariable")
     ImagePyramid(final GenericName parent,
                  final TiledGridCoverageResource.Pyramid provider,
-                 final GridCoverageProcessor processor)
+                 final GridCoverageProcessor processor,
+                 final Locale locale)
     {
         this.provider  = provider;
         this.processor = processor;
+        this.locale    = locale;
         identifier = Names.createScopedName(parent, null, 
provider.identifier());
         matrices = new ArrayList<>();
         lowerMatrixIndex = 0;
@@ -125,6 +132,7 @@ final class ImagePyramid extends AbstractMap<GenericName, 
ImageTileMatrix>
         this.identifier = parent.identifier;
         this.provider   = parent.provider;
         this.processor  = parent.processor;
+        this.locale     = parent.locale;
         this.matrices   = parent.matrices;
         this.lowerMatrixIndex = lowerMatrixIndex;
         this.upperMatrixIndex = upperMatrixIndex;
@@ -196,7 +204,7 @@ final class ImagePyramid extends AbstractMap<GenericName, 
ImageTileMatrix>
             }
         }
         if (required) {
-            throw new 
IllegalArgumentException(Errors.format(Errors.Keys.NoSuchValue_1, name));
+            throw new 
IllegalArgumentException(Errors.forLocale(locale).getString(Errors.Keys.NoSuchValue_1,
 name));
         }
         return -1;
     }
@@ -432,16 +440,15 @@ final class ImagePyramid extends AbstractMap<GenericName, 
ImageTileMatrix>
 
     /**
      * Returns a string representation for debugging purposes.
+     * The tile matrices are formatted in a table.
      *
      * @return a string representation for debugging purposes.
      */
     @Override
     public String toString() {
-        final Object upper;
+        final var f = new TileMatrixFormatter(locale);
         synchronized (matrices) {
-            final Integer max = (upperMatrixIndex == Integer.MAX_VALUE) ? null 
: upperMatrixIndex;
-            upper = (matrices.size() >= upperMatrixIndex) ? max : new 
NumberRange<>(Integer.class, lowerMatrixIndex, true, max, true);
+            return f.format(this);
         }
-        return Strings.toString(getClass(), null, identifier.toString(), 
"lowerMatrixIndex", lowerMatrixIndex, "upperMatrixIndex", upper);
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImageTileMatrix.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImageTileMatrix.java
index 51feda835d..7cf69fe985 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImageTileMatrix.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/ImageTileMatrix.java
@@ -38,6 +38,7 @@ import org.apache.sis.coverage.grid.GridExtent;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridCoverage2D;
 import org.apache.sis.coverage.grid.GridCoverageProcessor;
+import org.apache.sis.coverage.grid.IncompleteGridGeometryException;
 import org.apache.sis.image.internal.shared.ReshapedImage;
 import org.apache.sis.pending.jdk.JDK18;
 import org.apache.sis.util.ArgumentChecks;
@@ -183,6 +184,9 @@ final class ImageTileMatrix implements TileMatrix {
      * Returns the resolution (in units of CRS axes) at which tiles in this 
matrix should be used.
      * The array length is the number of <abbr>CRS</abbr> dimensions, and 
value at index <var>i</var>
      * is the resolution along CRS dimension <var>i</var> in units of the CRS 
axis <var>i</var>.
+     *
+     * @throws IncompleteGridGeometryException if the tiling scheme has no 
resolution.
+     *         Tile matrices with such tiling scheme should not have been 
constructed.
      */
     @Override
     public double[] getResolution() {
@@ -193,6 +197,14 @@ final class ImageTileMatrix implements TileMatrix {
         }
     }
 
+    /**
+     * Returns the tile size of the given matrix if known, or {@code null} 
otherwise.
+     * This method returns a direct reference to the internal array, caller 
shall not modify.
+     */
+    static int[] getTileSize(final TileMatrix matrix) {
+        return (matrix instanceof ImageTileMatrix) ? ((ImageTileMatrix) 
matrix).tileSize : null;
+    }
+
     /**
      * Returns a description about how space is partitioned into individual 
tiled units.
      * The description contains the extent of valid tile indices, the spatial 
reference system,
@@ -367,7 +379,7 @@ final class ImageTileMatrix implements TileMatrix {
                 low [i] = Math.max(extent.getLow(i), 
tileToCell(indiceRanges.getLow(i), i));
                 final long span = high[i] - low[i];
                 if (span < 0 || span > Integer.MAX_VALUE) {
-                    throw new 
ArithmeticException(Errors.format(Errors.Keys.IntegerOverflow_1, Integer.SIZE));
+                    throw new 
ArithmeticException(resource.errors().getString(Errors.Keys.IntegerOverflow_1, 
Integer.SIZE));
                 }
                 if (coverage.deferredTileReading) {
                     final long remain = Math.min(extent.getSize(i), 
Integer.MAX_VALUE) - span;
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrixFormatter.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrixFormatter.java
new file mode 100644
index 0000000000..21b7123042
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TileMatrixFormatter.java
@@ -0,0 +1,310 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.storage.tiling;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.Locale;
+import java.util.ArrayList;
+import java.text.NumberFormat;
+import org.opengis.util.GenericName;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.util.CharSequences;
+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;
+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.io.TableAppender;
+
+
+/**
+ * Formatter of tile matrix sets.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ */
+final class TileMatrixFormatter {
+    /**
+     * Helper class for string representation of an image pyramids as a table.
+     * Each instance describes one table row for one {@link ImageTileMatrix}.
+     */
+    private static final class Row {
+        /** The tile matrix identifier. */
+        private final String identifier;
+
+        /** The tile matrix resolution in each <abbr>CRS</abbr> dimensions. */
+        private final double[] resolution;
+
+        /** The string representations of the tile matrix resolution. */
+        private final String[] formattedResolution;
+
+        /** The number of tiles in each grid dimension. */
+        private final String[] tileCount;
+
+        /** The tile sizes in pixels. */
+        private final String[] tileSize;
+
+        /**
+         * Creates one row in the table for the given matrix.
+         *
+         * @param  matrix  the matrix for which to store information.
+         * @param  integerFormat  the format to use for integer values.
+         * @throws BackingStoreException if an error occurred while extracting 
information.
+         * @throws IncompleteGridGeometryException if the tiling scheme has 
not extent or resolution.
+         *         Tile matrices with such tiling scheme should not have been 
constructed in first place.
+         */
+        private Row(final TileMatrix matrix, final NumberFormat integerFormat) 
{
+            final GenericName id = matrix.getIdentifier();
+            identifier = (id != null) ? id.toString() : "";
+            resolution = matrix.getResolution();
+            formattedResolution = new String[resolution.length];
+            final GridExtent ge = matrix.getTilingScheme().getExtent();
+            tileCount = new String[ge.getDimension()];
+            for (int i=0; i<tileCount.length; i++) {
+                tileCount[i] = integerFormat.format(ge.getSize(i));
+            }
+            final int[] ts = ImageTileMatrix.getTileSize(matrix);
+            tileSize = new String[ts != null ? ts.length : 0];
+            for (int i=0; i<tileSize.length; i++) {
+                tileSize[i] = integerFormat.format(ts[i]);
+            }
+        }
+
+        /**
+         * Creates the string representation of resolutions.
+         */
+        final void formatResolutions(final NumberFormat[] formats) {
+            for (int i=0; i<resolution.length; i++) {
+                formattedResolution[i] = formats[i].format(resolution[i]);
+            }
+        }
+    }
+
+    /**
+     * The locale specified at construction time. May be {@code null}.
+     */
+    private final Locale locale;
+
+    /**
+     * Resources for table header.
+     */
+    private final Vocabulary vocabulary;
+
+    /**
+     * The object to use for formatting integer values.
+     */
+    private final NumberFormat integerFormat;
+
+    /**
+     * The error that occurred while formatting a value.
+     */
+    private Throwable error;
+
+    /**
+     * Creates a new formatter using the given locale.
+     */
+    TileMatrixFormatter(final Locale locale) {
+        this.locale = locale;
+        vocabulary = Vocabulary.forLocale(locale);
+        integerFormat = (locale != null)
+                ? NumberFormat.getIntegerInstance(locale)
+                : NumberFormat.getIntegerInstance();
+    }
+
+    /**
+     * Returns a string representation of the given tile matrices.
+     * Each tile matrix is formatted as a row in a table.
+     *
+     * @param  matrices  the tile matrices to format.
+     * @return the string representation of the table of tile matrices.
+     */
+    final String format(final TileMatrixSet matrices) {
+        final var buffer = new StringBuilder(1000);
+        try {
+            formatHeader(matrices, buffer);
+            formatTable(matrices, buffer);
+            if (error == null) {
+                return buffer.toString();
+            }
+            final var writer = new 
StringWriter(buffer.length()).append(buffer);
+            vocabulary.appendLabel(Vocabulary.Keys.Warnings, writer);
+            error.printStackTrace(new PrintWriter(writer.append(' ')));
+            return writer.append(System.lineSeparator()).toString();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    /**
+     * Formats the header of the Tile Matrix Set (<abbr>TMS</abbr>) 
representation.
+     * The header contains the <abbr>TMS</abbr> identifier, the Coordinate 
Reference System
+     * and the geographic bounding box.
+     *
+     * @param  matrices  the Tile Matrix Set (<abbr>TMS</abbr>) to format.
+     * @param  buffer    where to format the header.
+     * @throws IOException should never happen but handled by the caller for 
convenience.
+     */
+    private void formatHeader(final TileMatrixSet matrices, final 
StringBuilder buffer) throws IOException {
+        
buffer.append(vocabulary.getString(Vocabulary.Keys.TileMatrixSets)).append(' ')
+              .append(vocabulary.getString(Vocabulary.Keys.Quoted_1, 
matrices.getIdentifier()))
+              .append(System.lineSeparator());
+        try {
+            final String crs = 
IdentifiedObjects.getDisplayName(matrices.getCoordinateReferenceSystem(), 
locale);
+            if (crs != null) {
+                vocabulary.appendLabel(Vocabulary.Keys.ReferenceSystem, 
buffer);
+                buffer.append(' ').append(crs).append(System.lineSeparator());
+            }
+            matrices.getEnvelope().ifPresent((envelope) -> {
+                try {
+                    final var bbox = new DefaultGeographicBoundingBox();
+                    bbox.setBounds(envelope);
+                    bbox.setInclusion(null);
+                    final var mf = new TreeTableFormat(locale, null);
+                    mf.setColumns(TableColumn.NAME, TableColumn.VALUE);
+                    buffer.append(mf.format(bbox.asTreeTable()));
+                } catch (TransformException e) {
+                    addError(e);
+                }
+            });
+        } catch (BackingStoreException e) {
+            addError(e.getCause());
+        }
+    }
+
+    /**
+     * Updates an array of maximal length of string representations in the 
given columns.
+     * The {@code lenghts} array is updated in-place.
+     */
+    private static void updateMaximalLengths(final int[] lengths, final 
String[] columns) {
+        for (int i = Math.min(lengths.length, columns.length); --i >= 0;) {
+            final int length = columns[i].length();
+            if (length > lengths[i]) {
+                lengths[i] = length;
+            }
+        }
+    }
+
+    /**
+     * Appends spaces in front of the given columns in order to have the 
specified lengths.
+     */
+    private static void rightAlign(final int[] lengths, final String[] 
columns) {
+        for (int i = Math.min(lengths.length, columns.length); --i >= 0;) {
+            final String column = columns[i];
+            final int more = lengths[i] - column.length();
+            if (more > 0) {
+                columns[i] = CharSequences.spaces(more) + columns[i];
+            }
+        }
+    }
+
+    /**
+     * Formats the main body of the Tile Matrix Set (<abbr>TMS</abbr>) 
representation.
+     * This is formatted as a table.
+     *
+     * @param  matrices  the Tile Matrix Set (<abbr>TMS</abbr>) to format.
+     * @param  buffer    where to format the main body.
+     * @throws IOException should never happen but handled by the caller for 
convenience.
+     */
+    private void formatTable(final TileMatrixSet matrices, final StringBuilder 
buffer) throws IOException {
+        final var rows = new ArrayList<Row>();
+        int crsDimension = 0, gridDimension = 0, sizeDimension = 0;
+        for (final TileMatrix matrix : matrices.getTileMatrices().values()) 
try {
+            final var row = new Row(matrix, integerFormat);
+            crsDimension  = Math.max(crsDimension,  
row.formattedResolution.length);
+            gridDimension = Math.max(gridDimension, row.tileCount.length);
+            sizeDimension = Math.max(sizeDimension, row.tileSize.length);
+            rows.add(row);  // Add only on success.
+        } catch (RuntimeException e) {
+            addError(e);
+        }
+        /*
+         * Find the number of fraction digits to use for showing the 
resolution.
+         */
+        final var values  = new double[rows.size()];
+        final var formats = new NumberFormat[crsDimension];
+        for (int i=0; i<crsDimension; i++) {
+            for (int j=0; j<values.length; j++) {
+                values[j] = rows.get(j).resolution[i];
+            }
+            final NumberFormat format = (locale != null)
+                    ? NumberFormat.getNumberInstance(locale)
+                    : NumberFormat.getNumberInstance();
+            final int n = Numerics.suggestFractionDigits(values);
+            format.setMinimumFractionDigits(n);
+            format.setMaximumFractionDigits(n);
+            formats[i] = format;
+        }
+        /*
+         * At this point, all values have been formatted as character strings.
+         * Compute the maximum lengths in each column in order to align the 
values.
+         */
+        final int[] resolutionLengths = new int[crsDimension];
+        final int[]  tileCountLengths = new int[gridDimension];
+        final int[]   tileSizeLengths = new int[sizeDimension];
+        for (final Row row : rows) {
+            row.formatResolutions(formats);
+            updateMaximalLengths(resolutionLengths, row.formattedResolution);
+            updateMaximalLengths(tileCountLengths,  row.tileCount);
+            updateMaximalLengths(tileSizeLengths,   row.tileSize);
+        }
+        for (final Row row : rows) {
+            rightAlign(resolutionLengths, row.formattedResolution);
+            rightAlign(tileCountLengths,  row.tileCount);
+            rightAlign(tileSizeLengths,   row.tileSize);
+        }
+        /*
+         * All data are prepared. Write the table.
+         */
+        final var table = new TableAppender(buffer);
+        table.appendHorizontalSeparator();
+        
table.append(vocabulary.getString(Vocabulary.Keys.Identifier)).nextColumn();
+        if  (crsDimension != 0) 
table.append(vocabulary.getString(Vocabulary.Keys.Resolution)).nextColumn();
+        if (gridDimension != 0) 
table.append(vocabulary.getString(Vocabulary.Keys.TileCount)) .nextColumn();
+        if (sizeDimension != 0) 
table.append(vocabulary.getString(Vocabulary.Keys.TileSize))  .nextColumn();
+        table.appendHorizontalSeparator();
+        for (final Row row : rows) {
+            table.setCellAlignment(TableAppender.ALIGN_LEFT);
+            table.append(row.identifier).nextColumn();
+            table.setCellAlignment(TableAppender.ALIGN_RIGHT);
+            if  (crsDimension != 0) table.append(String.join(" × ", 
row.formattedResolution)).nextColumn();
+            if (gridDimension != 0) table.append(String.join(" × ", 
row.tileCount)) .nextColumn();
+            if (sizeDimension != 0) table.append(String.join(" × ", 
row.tileSize))  .nextColumn();
+            table.nextLine();
+        }
+        table.appendHorizontalSeparator();
+        table.flush();
+    }
+
+    /**
+     * Records that an error occurred.
+     *
+     * @param  e  the error that occurred.
+     */
+    private void addError(final Throwable e) {
+        if (error == null) {
+            error = e;
+        } else {
+            error.addSuppressed(e);
+        }
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverage.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverage.java
index 151beac5fe..c3561e64a3 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverage.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/tiling/TiledGridCoverage.java
@@ -333,6 +333,13 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
         return null;
     }
 
+    /**
+     * Returns the localized resources for error messages.
+     */
+    private Errors errors() {
+        return Errors.forLocale(getLocale());
+    };
+
     /**
      * Returns a unique name that identifies this coverage.
      * The name shall be unique in the {@link TileMatrixSet}.
@@ -546,8 +553,8 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
         } else {
             final int sd = sliceExtent.getDimension();
             if (sd < dimension || sd > available.getDimension()) {
-                throw new MismatchedDimensionException(Errors.format(
-                        Errors.Keys.MismatchedDimension_3, "sliceExtent", 
dimension, sd));
+                throw new MismatchedDimensionException(errors().getString(
+                            Errors.Keys.MismatchedDimension_3, "sliceExtent", 
dimension, sd));
             }
         }
         final int[] selectedDimensions = 
sliceExtent.getSubspaceDimensions(BIDIMENSIONAL);
@@ -569,8 +576,7 @@ public abstract class TiledGridCoverage extends 
GridCoverage {
                 final long tileUp = 
incrementExact(coverageCellToResourceTile(Math.min(aoiMax, max), i));
                 final long tileLo =                
coverageCellToResourceTile(Math.max(aoiMin, min), i);
                 if (tileUp <= tileLo) {
-                    final String message = Errors.forLocale(getLocale())
-                            .getString(Errors.Keys.IllegalRange_2, aoiMin, 
aoiMax);
+                    final String message = 
errors().getString(Errors.Keys.IllegalRange_2, aoiMin, aoiMax);
                     if (aoiMin > aoiMax) {
                         throw new IllegalArgumentException(message);
                     } else {
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 9df789e7fe..14854d64de 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
@@ -172,13 +172,6 @@ public abstract class TiledGridCoverageResource extends 
AbstractGridCoverageReso
      */
     private int yDimension;
 
-    /**
-     * The resolutions of each levels of the default pyramid. Computed when 
first needed, then cached.
-     *
-     * @see #getResolutions()
-     */
-    private List<double[]> resolutions;
-
     /**
      * Creates a new resource.
      *
@@ -497,7 +490,7 @@ check:  if (dataType.isInteger()) {
                                 () -> 
pyramids.get(0).nameFactory().createLocalName(null, listeners.getSourceName()));
                     final var processor = new GridCoverageProcessor();
                     for (int i=0; i<sets.length; i++) {
-                        sets[i] = new ImagePyramid(scope, pyramids.get(i), 
processor);
+                        sets[i] = new ImagePyramid(scope, pyramids.get(i), 
processor, listeners.getLocale());
                     }
                 }
                 tileMatrixSets = List.of(sets);
@@ -1072,7 +1065,7 @@ check:  if (dataType.isInteger()) {
         ArgumentChecks.ensureBetween("xDimension", 0, max, xDimension);
         ArgumentChecks.ensureBetween("yDimension", 0, max, yDimension);
         if (xDimension == yDimension) {
-            throw new 
IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, 
"yDimension", "xDimension"));
+            throw new 
IllegalArgumentException(errors().getString(Errors.Keys.IllegalArgumentValue_2, 
"yDimension", "xDimension"));
         }
         this.xDimension = xDimension;
         this.yDimension = yDimension;
@@ -1211,4 +1204,11 @@ check:  if (dataType.isInteger()) {
             return DefaultNameFactory.provider();
         }
     }
+
+    /**
+     * Returns the localized resources for error messages.
+     */
+    final Errors errors() {
+        return Errors.forLocale(listeners.getLocale());
+    }
 }
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/DecimalFunctions.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/DecimalFunctions.java
index 3a5bfac210..62bb3b7288 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/DecimalFunctions.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/DecimalFunctions.java
@@ -433,7 +433,7 @@ public final class DecimalFunctions {
      * is a choice:
      *
      * <ul>
-     *   <li>If after rounding the given {@code value} to an number of 
fraction digits given by ({@code fractionDigits}
+     *   <li>If after rounding the given {@code value} to a number of fraction 
digits given by ({@code fractionDigits}
      *       - {@code uncertainDigits}) the 4 last fraction digits before the 
rounded ones are zero, then this method
      *       returns {@code fractionDigits} - {@code uncertainDigits}.</li>
      *   <li>Otherwise this method returns {@code fractionDigits}.</li>
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Vector.java 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Vector.java
index 103815c47d..71af8410fb 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Vector.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Vector.java
@@ -669,7 +669,7 @@ public abstract class Vector extends AbstractList<Number> 
implements RandomAcces
              * array to be returned. Following algorithm applies to deeper 
levels.
              *
              * The `skip` variable is an optimization. Code below would work 
with skip = 0 all the times, but this is
-             * very slow when r0 = 1 because equals(…) is invoked for all 
values.  Computing an number of values that
+             * very slow when r0 = 1 because equals(…) is invoked for all 
values.  Computing a number of values that
              * we can skip in the special case where r0 = 1 increases the 
speed a lot.
              */
             int candidateIndex = 0;
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 2a584b08d5..dae6a203b6 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,6 +16,7 @@
  */
 package org.apache.sis.util.internal.shared;
 
+import java.util.Arrays;
 import java.text.Format;
 import java.text.DecimalFormat;
 import java.math.BigInteger;
@@ -220,7 +221,7 @@ public final class Numerics {
     public static int snapToCeil(int value, final int divisor) {
         final int r = value % divisor;      // Always has the sign of `value`.
         if (r > 0) {
-            value += Math.abs(divisor) - r;
+            value += abs(divisor) - r;
         } else {
             value -= r;
         }
@@ -295,8 +296,8 @@ public final class Numerics {
      * @return the value clamped to the range convertible to {@code double} 
without precision lost.
      */
     public static long roundAndClamp(final double value) {
-        return Math.max(-MAX_INTEGER_CONVERTIBLE_TO_DOUBLE,
-               Math.min(+MAX_INTEGER_CONVERTIBLE_TO_DOUBLE, 
Math.round(value)));
+        return max(-MAX_INTEGER_CONVERTIBLE_TO_DOUBLE,
+               min(+MAX_INTEGER_CONVERTIBLE_TO_DOUBLE, Math.round(value)));
     }
 
     /**
@@ -553,7 +554,7 @@ public final class Numerics {
     }
 
     /**
-     * Suggests an number of fraction digits for formatting in base 10 numbers 
of the given accuracy.
+     * Suggests a number of fraction digits for formatting in base 10 numbers 
of the given accuracy.
      * This method uses heuristic rules that may change in any future SIS 
version:
      *
      * <ul>
@@ -574,34 +575,54 @@ public final class Numerics {
      * @see DecimalFunctions#fractionDigitsForDelta(double, boolean)
      */
     public static int fractionDigitsForDelta(final double ulp) {
-        return (ulp != 0) ? Math.max(0, Math.min(16, 
DecimalFunctions.fractionDigitsForDelta(ulp, false))) : 0;
+        return (ulp != 0) ? max(0, min(16, 
DecimalFunctions.fractionDigitsForDelta(ulp, false))) : 0;
     }
 
     /**
-     * Suggests an number of fraction digits for the given values, ignoring 
NaN and infinities.
-     * This method uses heuristic rules that may change in any future SIS 
version.
-     * Current implementation returns a value which avoid printing "garbage" 
digits
-     * with highest numbers, at the cost of loosing significant digits on 
smallest numbers.
+     * Suggests a number of fraction digits for the given values, ignoring NaN 
and infinities.
+     * 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.
      * @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;
-        if (values != null) {
-            for (final double v : values) {
-                final double e = Math.ulp(v);
-                if (e > ulp && e != Double.POSITIVE_INFINITY) {
-                    ulp = e;
+        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;
             }
         }
-        return fractionDigitsForDelta(ulp);
+        // Arbitrarily add 6 digits the the difference between values.
+        return min(fractionDigitsForDelta(ulp), fractionDigitsForDelta(delta) 
+ 6);
     }
 
     /**
-     * Suggests an number of fraction digits for data having the given 
statistics.
+     * Suggests a number of fraction digits for data having the given 
statistics.
      * This method uses heuristic rules that may be modified in any future SIS 
version.
      *
      * @param  stats  statistics on the data to format.
@@ -646,7 +667,7 @@ public final class Numerics {
             final DecimalFormat df = (DecimalFormat) format;
             final int maxFD = df.getMaximumFractionDigits();
             final double m = abs(((Number) value).doubleValue());
-            if (m > 0 && (m >= 1E+9 || m < 
MathFunctions.pow10(-Math.min(maxFD, 6)))) {
+            if (m > 0 && (m >= 1E+9 || m < MathFunctions.pow10(-min(maxFD, 
6)))) {
                 final int minFD = df.getMinimumFractionDigits();
                 final String pattern = df.toPattern();
                 try {
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
index 51f06ea65a..2bf1d664eb 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
@@ -1249,6 +1249,11 @@ public class Vocabulary extends IndexedResourceBundle {
          */
         public static final short Thermal = 260;
 
+        /**
+         * Tile count
+         */
+        public static final short TileCount = 284;
+
         /**
          * Tile matrix sets
          */
@@ -1259,6 +1264,11 @@ public class Vocabulary extends IndexedResourceBundle {
          */
         public static final short TileSize = 194;
 
+        /**
+         * Tiling
+         */
+        public static final short Tiling = 283;
+
         /**
          * Time
          */
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties
index ce0dbaabdc..da9cf8472e 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties
@@ -255,7 +255,9 @@ TemporalExtent          = Temporal extent
 TemporaryFiles          = Temporary files
 Thermal                 = Thermal
 TileMatrixSets          = Tile matrix sets
+TileCount               = Tile count
 TileSize                = Tile size
+Tiling                  = Tiling
 Time                    = Time
 Time_1                  = {0} time
 Timezone                = Timezone
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties
index 0075eb99e2..67fa5590ea 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties
@@ -261,8 +261,10 @@ Temporal_1              = Temporel ({0})
 TemporalExtent          = Plage temporelle
 TemporaryFiles          = Fichiers temporaires
 Thermal                 = Thermique
-TileMatrixSets          = Set de matrices de tuile
+TileMatrixSets          = Ensembles de matrices de tuiles
+TileCount               = Nombre de tuiles
 TileSize                = Taille des tuiles
+Tiling                  = Tuilage
 Time                    = Temps
 Time_1                  = Heure {0}
 Timezone                = Fuseau horaire
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/Widget.java 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/Widget.java
index 548bab1de2..6f79c0704d 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/Widget.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/Widget.java
@@ -22,13 +22,13 @@ import org.apache.sis.util.Localized;
 
 
 /**
- * Base class of user interfaces provided by Apache SIS.
+ * Base class of user interfaces provided by Apache <abbr>SIS</abbr>.
  * This base class is used for components that encapsulate JavaFX controls 
instead of extending them.
- * We use this indirection level for hiding implementation details such as the 
exact JavaFX classes used
- * for implementing the widget.
+ * We use this indirection level for hiding implementation details such as the 
exact JavaFX classes
+ * used for implementing the widget.
  *
  * <h2>Other controls</h2>
- * Not all Apache SIS widgets extent this class.
+ * Not all Apache <abbr>SIS</abbr> widgets extent this class.
  * Other widgets extending directly a JavaFX control or other classes are
  * {@link org.apache.sis.gui.metadata.MetadataTree},
  * {@link org.apache.sis.gui.dataset.ResourceTree},
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/SyncWindowList.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/SyncWindowList.java
index 0dee5df9a4..25ae55f80c 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/SyncWindowList.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/controls/SyncWindowList.java
@@ -134,7 +134,7 @@ public final class SyncWindowList extends TabularWidget 
implements ListChangeLis
      * @param  vocabulary  localized resources, given because already known by 
the caller
      *                     (those arguments would be removed if this 
constructor was public API).
      */
-    @SuppressWarnings("unchecked")
+    @SuppressWarnings({"this-escape", "unchecked"})
     public SyncWindowList(final WindowHandler owner, final Resources 
resources, final Vocabulary vocabulary) {
         this.owner = owner;
         table = newTable();
@@ -171,7 +171,7 @@ public final class SyncWindowList extends TabularWidget 
implements ListChangeLis
      * @return the new row.
      */
     private static TableRow<Link> newRow(final TableView<Link> table) {
-        TableRow<Link> row = new TableRow<>();
+        final var row = new TableRow<Link>();
         row.addEventFilter(MouseEvent.MOUSE_CLICKED, 
SyncWindowList::onMouseClicked);
         return row;
     }
@@ -198,7 +198,7 @@ public final class SyncWindowList extends TabularWidget 
implements ListChangeLis
     }
 
     /**
-     * Returns the encapsulated JavaFX component to add in a scene graph for 
making the table visible.
+     * Returns the encapsulated JavaFX component to add in a scene graph for 
making the widget visible.
      * The {@code Region} subclass is implementation dependent and may change 
in any future SIS version.
      *
      * @return the JavaFX component to insert in a scene graph.
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java
index 8e6f1b9e4b..427ff26992 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/CoverageExplorer.java
@@ -344,13 +344,13 @@ public class CoverageExplorer extends Widget {
      * {@code CoverageExplorer}. The {@link Region} subclass returned by this 
method is implementation dependent
      * and may change in any future version.
      *
-     * @return the region to show.
+     * @return the JavaFX component to insert in a scene graph.
      *
      * @see #getDataView(View)
      * @see #getControls(View)
      */
     @Override
-    public final Region getView() {
+    public Region getView() {
         assert Platform.isFxApplicationThread();
         /*
          * We build when first requested because `ResourceExplorer` for 
example will never request this view.
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/GridSliceSelector.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/GridSliceSelector.java
index a8c2354969..28bd73e208 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/GridSliceSelector.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/GridSliceSelector.java
@@ -40,7 +40,6 @@ import javafx.beans.property.SimpleObjectProperty;
 import javax.measure.Unit;
 import org.opengis.geometry.Envelope;
 import org.opengis.util.FactoryException;
-import org.opengis.metadata.spatial.DimensionNameType;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.TemporalCRS;
@@ -447,12 +446,9 @@ public class GridSliceSelector extends Widget {
             /*
              * Fallback on grid axis name if no CRS axis name is found.
              */
-            final DimensionNameType axis = 
extent.getAxisType(dimension).orElse(null);
-            if (axis != null) {
-                return Types.getCodeTitle(axis).toString(locale);
-            } else {
-                return vocabulary.getString(Vocabulary.Keys.Axis_1, dimension);
-            }
+            return extent.getAxisType(dimension)
+                    .map((axisType) -> 
Types.getCodeTitle(axisType).toString(locale))
+                    .orElseGet(() -> 
vocabulary.getString(Vocabulary.Keys.Axis_1, dimension));
         }
 
         /**
@@ -622,7 +618,7 @@ public class GridSliceSelector extends Widget {
      * @return the JavaFX component to insert in a scene graph.
      */
     @Override
-    public final Region getView() {
+    public Region getView() {
         return view;
     }
 
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/ImagePropertyExplorer.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/ImagePropertyExplorer.java
index 881fbfd841..ba33fc6082 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/ImagePropertyExplorer.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/ImagePropertyExplorer.java
@@ -790,10 +790,10 @@ nextProp:       for (final String property : properties) {
     }
 
     /**
-     * Returns the view of this explorer. The subclass is implementation 
dependent
-     * and may change in any future version.
+     * Returns the encapsulated JavaFX component to add in a scene graph for 
making the explorer visible.
+     * The {@code Region} subclass is implementation dependent and may change 
in any future SIS version.
      *
-     * @return this explorer view.
+     * @return the JavaFX component to insert in a scene graph.
      */
     @Override
     public Region getView() {
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/TileMatrixSetPane.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/TileMatrixSetPane.java
index 453af8e00b..d12c51bb99 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/TileMatrixSetPane.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/coverage/TileMatrixSetPane.java
@@ -16,113 +16,105 @@
  */
 package org.apache.sis.gui.coverage;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
 import javafx.scene.control.TextArea;
-import javafx.scene.layout.BorderPane;
-import org.apache.sis.geometry.GeneralEnvelope;
-import org.apache.sis.referencing.IdentifiedObjects;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.value.ObservableValue;
+import javafx.scene.layout.Region;
 import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.Resource;
-import org.apache.sis.storage.tiling.TileMatrix;
 import org.apache.sis.storage.tiling.TileMatrixSet;
 import org.apache.sis.storage.tiling.TiledResource;
-import org.apache.sis.util.Classes;
+import org.apache.sis.gui.Widget;
+
 
 /**
- * A view to the internal tile matrices defined in a Resource.
+ * A visual representation of the internal tile matrices defined in a {@link 
TiledResource}.
  *
  * @todo change the text area to a split pane with a tree view on the left and 
a description pane on the right
  * @todo if the resource is writable, add tiling modification controls
+ *
  * @author Johann Sorel (Geomatys)
+ *
+ * @sinec 1.7
  */
-public class TileMatrixSetPane extends BorderPane{
+public class TileMatrixSetPane extends Widget {
+    /**
+     * The data shown in this widget.
+     *
+     * @see #getContent()
+     * @see #setContent(TiledResource)
+     */
+    public final ObjectProperty<TiledResource> contentProperty;
 
     private final TextArea area = new TextArea();
 
+    /**
+     * Creates an initially empty pane showing the content of a tile matrix.
+     */
     public TileMatrixSetPane() {
         area.setMaxSize(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
         area.setEditable(false);
-        setCenter(area);
+        contentProperty = new SimpleObjectProperty<>(this, "content");
+        contentProperty.addListener(TileMatrixSetPane::applyChange);
     }
 
-    public void setContent(Resource resource) {
-        final StringBuilder sb = new StringBuilder();
-        if (resource instanceof TiledResource) {
-            final TiledResource tr = (TiledResource) resource;
-            try {
-                for (TileMatrixSet tms : tr.getTileMatrixSets()) {
-                    sb.append(toString(tms));
-                }
-            } catch (DataStoreException ex){
-                sb.append(ex.getMessage());
-            }
-        } else {
-            sb.append("Resource has no tile matrices.");
-        }
-
-        area.setText(sb.toString());
+    /**
+     * Sets the data for which to show the tiling.
+     * This is a convenience method for setting {@link #contentProperty} value.
+     *
+     * @param  data  the data for which to show the tiling, or {@code null} if 
none.
+     */
+    public final void setContent(final TiledResource data) {
+        contentProperty.setValue(data);
     }
 
     /**
-     * Pretty print output of given pyramid.
-     * @param pyramid not null
+     * Returns the data for which the tiling is currently shown, or {@code 
null} if none.
+     * This is a convenience method for fetching {@link #contentProperty} 
value.
+     *
+     * @return the table for which the tiling is currently shown, or {@code 
null} if none.
+     *
+     * @see #contentProperty
+     * @see #setContent(TiledResource)
      */
-    public static String toString(TileMatrixSet pyramid) {
-        final List<String> elements = new ArrayList<>();
-        elements.add("id : " + pyramid.getIdentifier());
-        elements.add("crs : " + 
IdentifiedObjects.getIdentifierOrName(pyramid.getCoordinateReferenceSystem()));
-        elements.add(toStringTree("matrices", 
pyramid.getTileMatrices().values().stream().map(TileMatrixSetPane::toString).toList()));
-        return toStringTree(Classes.getShortClassName(pyramid), elements);
+    public final TiledResource getContent() {
+        return contentProperty.getValue();
     }
 
     /**
-     * Pretty print outut of given mosaic.
-     * @param matrix not null
+     * Returns the region containing the visual components managed by this 
{@code TileMatrixSetPane}.
+     * The subclass is implementation dependent and may change in any future 
version.
+     *
+     * @return the JavaFX component to insert in a scene graph.
      */
-    public static String toString(TileMatrix matrix) {
-        final StringBuilder sb = new 
StringBuilder(Classes.getShortClassName(matrix));
-        sb.append("   id = ").append(matrix.getIdentifier());
-        sb.append("   resolution = 
").append(Arrays.toString(matrix.getTilingScheme().getResolution(true)));
-        sb.append("   gridSize = 
").append(matrix.getTilingScheme().getExtent());
-        sb.append("   bbox = ").append(new 
GeneralEnvelope(matrix.getTilingScheme().getEnvelope()).toString());
-        return sb.toString();
+    @Override
+    public Region getView() {
+        return area;
     }
 
     /**
-     * Returns a graphical representation of the specified objects. This 
representation can be
-     * printed to the {@linkplain System#out standard output stream} (for 
example) if it uses
-     * a monospaced font and supports unicode.
+     * Invoked when {@link #contentProperty} value changed.
      *
-     * @param  root  The root name of the tree to format.
-     * @param  objects The objects to format as root children.
-     * @return A string representation of the tree.
+     * @param  property  the property which has been modified.
+     * @param  oldValue  the old tree table.
+     * @param  newValue  the tree table to use for building new content.
      */
-    public static String toStringTree(String root, final Iterable<?> objects) {
-        final StringBuilder sb = new StringBuilder();
-        if (root != null) {
-            sb.append(root);
+    private static void applyChange(final ObservableValue<? extends 
TiledResource> property,
+                                    final TiledResource oldValue, final 
TiledResource newValue)
+    {
+        final var s = (TileMatrixSetPane) ((SimpleObjectProperty) 
property).getBean();
+        if (newValue == null) {
+            s.area.setText(null);
+            return;
         }
-        if (objects != null) {
-            final Iterator<?> ite = objects.iterator();
-            while (ite.hasNext()) {
-                sb.append('\n');
-                final Object next = ite.next();
-                final boolean last = !ite.hasNext();
-                sb.append(last ? "\u2514\u2500 " : "\u251C\u2500 ");
-
-                final String[] parts = String.valueOf(next).split("\n");
-                sb.append(parts[0]);
-                for (int k=1;k<parts.length;k++) {
-                    sb.append('\n');
-                    sb.append(last ? ' ' : '\u2502');
-                    sb.append("  ");
-                    sb.append(parts[k]);
-                }
+        final var sb = new StringBuilder();
+        try {
+            for (TileMatrixSet tms : newValue.getTileMatrixSets()) {
+                sb.append(tms);
             }
+        } catch (DataStoreException ex) {
+            sb.append(ex.getMessage());
         }
-        return sb.toString();
+        s.area.setText(sb.toString());
     }
 }
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/LogViewer.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/LogViewer.java
index 9e4612ab17..0d15552d8d 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/LogViewer.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/LogViewer.java
@@ -507,6 +507,7 @@ public class LogViewer extends Widget {
      * Invoked when a log record is selected.
      */
     private void selected(final LogRecord log) {
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
         String level = null, time = null, logger = null, classe = null, method 
= null;
         if (log != null) {
             level   = toString(log.getLevel());
@@ -613,8 +614,8 @@ public class LogViewer extends Widget {
     }
 
     /**
-     * Returns the control to show in the scene graph.
-     * The implementation class may change in any future version.
+     * Returns the encapsulated JavaFX component to add in a scene graph for 
making the logs visible.
+     * The {@code Region} subclass is implementation dependent and may change 
in any future SIS version.
      */
     @Override
     public Region getView() {
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ResourceExplorer.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ResourceExplorer.java
index 53f0798734..15b183dce3 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ResourceExplorer.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/ResourceExplorer.java
@@ -44,6 +44,7 @@ import org.apache.sis.storage.GridCoverageResource;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreProvider;
 import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.tiling.TiledResource;
 import org.apache.sis.gui.Widget;
 import org.apache.sis.gui.metadata.MetadataSummary;
 import org.apache.sis.gui.metadata.MetadataTree;
@@ -65,7 +66,8 @@ import org.apache.sis.gui.internal.LogHandler;
  *
  * @author  Smaniotto Enzo (GSoC)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.7
  * @since   1.1
  */
 public class ResourceExplorer extends Widget {
@@ -97,7 +99,7 @@ public class ResourceExplorer extends Widget {
      * The widget showing selected resource tile matrix sets.
      * Its content will be updated only when the tab is visible.
      */
-    private final TileMatrixSetPane matrices;
+    private final TileMatrixSetPane tiling;
 
     /**
      * The tab containing {@link #nativeMetadata}.
@@ -106,9 +108,9 @@ public class ResourceExplorer extends Widget {
     private final Tab nativeMetadataTab;
 
     /**
-     * The tab containing TileMatrixSet view.
+     * The tab containing the view over the tile matrices of a resource.
      */
-    private final Tab tileMatrixSetTab;
+    private final Tab tilingTab;
 
     /**
      * Default label for {@link #nativeMetadataTab} when no resource is 
selected.
@@ -190,7 +192,7 @@ public class ResourceExplorer extends Widget {
         
resources.getSelectionModel().getSelectedItems().addListener(this::onResourceSelected);
         resources.setPrefWidth(400);
         final Vocabulary vocabulary = Vocabulary.forLocale(resources.locale);
-        final TitledPane resourcesPane = new 
TitledPane(vocabulary.getString(Vocabulary.Keys.Resources), resources);
+        final var resourcesPane = new 
TitledPane(vocabulary.getString(Vocabulary.Keys.Resources), resources);
         controls = new Accordion(resourcesPane);
         controls.setExpandedPane(resourcesPane);
         expandedPane = new EnumMap<>(CoverageExplorer.View.class);
@@ -202,24 +204,25 @@ public class ResourceExplorer extends Widget {
          */
         metadata = new MetadataSummary();
         nativeMetadata = new MetadataTree(metadata);
-        matrices = new TileMatrixSetPane();
-        final LogViewer logging = new LogViewer(vocabulary);
+        tiling = new TileMatrixSetPane();
+        final var logging = new LogViewer(vocabulary);
         selectedResource = new ReadOnlyObjectWrapper<>(this, 
"selectedResource");
         logging.source.bind(selectedResource);
         final Tab summaryTab, metadataTab, loggingTab;
-        final TabPane tabs = new TabPane(
+        final var tabs = new TabPane(
             summaryTab        = new 
Tab(vocabulary.getString(Vocabulary.Keys.Summary),  metadata.getView()),
             viewTab           = new 
Tab(vocabulary.getString(Vocabulary.Keys.Visual)),
             tableTab          = new 
Tab(vocabulary.getString(Vocabulary.Keys.Data)),
             metadataTab       = new 
Tab(vocabulary.getString(Vocabulary.Keys.Metadata), new 
StandardMetadataTree(metadata)),
             nativeMetadataTab = new 
Tab(vocabulary.getString(Vocabulary.Keys.Format),   nativeMetadata),
-            tileMatrixSetTab  = new 
Tab(vocabulary.getString(Vocabulary.Keys.TileMatrixSets),   matrices),
+            tilingTab         = new 
Tab(vocabulary.getString(Vocabulary.Keys.Tiling),   tiling.getView()),
             loggingTab        = new 
Tab(vocabulary.getString(Vocabulary.Keys.Logs),     logging.getView()));
 
         tabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
         tabs.setTabDragPolicy(TabPane.TabDragPolicy.REORDER);
         defaultNativeTabLabel = nativeMetadataTab.getText();
         nativeMetadataTab.setDisable(true);
+        tilingTab.setDisable(true);
         /*
          * Build the main pane which put everything together.
          */
@@ -267,10 +270,10 @@ public class ResourceExplorer extends Widget {
      * by this {@code ResourceExplorer}. The subclass is implementation 
dependent and may change in
      * any future version.
      *
-     * @return the region to show.
+     * @return the JavaFX component to insert in a scene graph.
      */
     @Override
-    public final Region getView() {
+    public Region getView() {
         return content;
     }
 
@@ -400,6 +403,7 @@ public class ResourceExplorer extends Widget {
         selectedResource.set(resource);
         metadata.setMetadata(metadataShown.get() ? resource : null);
         updateDataTabWithDefault(dataShown.get() ? resource : null);
+        updateTilingTab(resource);
         /*
          * Update the label and disabled state of the native metadata tab. We 
do not have a reliable way
          * to know if metadata are present without trying to fetch them, so 
current implementation only
@@ -428,6 +432,15 @@ public class ResourceExplorer extends Widget {
         }
     }
 
+    /**
+     * Updates the "Tiling" tab with the content of the given resource.
+     */
+    private void updateTilingTab(final Resource resource) {
+        final TiledResource tiled = (resource instanceof TiledResource) ? 
(TiledResource) resource : null;
+        tilingTab.setDisable(tiled == null);
+        tiling.setContent(tiled);
+    }
+
     /**
      * Loads native metadata in a background thread and shows them in the 
"native metadata" tab.
      * This method is invoked when the tab become visible, or when a new 
resource is loaded.
@@ -481,7 +494,6 @@ public class ResourceExplorer extends Widget {
      * @see #updateDataTabWithDefault(Resource)
      */
     private boolean updateDataTab(final Resource resource) {
-        if (resource != null) 
System.out.println(resource.getClass().getName());
         Region       image  = null;
         Region       table  = null;
         FeatureSet   data   = null;
@@ -502,7 +514,6 @@ public class ResourceExplorer extends Widget {
             }
             grid = new ImageRequest((GridCoverageResource) resource, null, 
null);
             cpanes = coverage.getControls(type);
-            matrices.setContent(resource);
         } else if (resource instanceof FeatureSet) {
             data = (FeatureSet) resource;
             if (features == null) {
@@ -578,7 +589,9 @@ public class ResourceExplorer extends Widget {
                 /** Invoked in JavaFX thread for showing the resource. */
                 @Override protected void succeeded() {
                     if (getSelectedResource() == resource) {
-                        updateDataTab(getValue());
+                        Resource result = getValue();
+                        updateTilingTab(result);
+                        updateDataTab(result);
                     }
                 }
 
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/package-info.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/package-info.java
index 92f497b3b1..132ef13ed2 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/package-info.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/dataset/package-info.java
@@ -24,7 +24,7 @@
  * @author  Smaniotto Enzo (GSoC)
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.6
+ * @version 1.7
  * @since   1.1
  */
 package org.apache.sis.gui.dataset;
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/io/FileAccessView.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/io/FileAccessView.java
index f1a9571a8a..47f4313396 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/io/FileAccessView.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/internal/io/FileAccessView.java
@@ -78,9 +78,7 @@ public final class FileAccessView extends Widget implements 
UnaryOperator<Channe
     }
 
     /**
-     * Returns the node to show in a window.
-     *
-     * @return the node to show.
+     * Returns the encapsulated JavaFX component to add in a scene graph for 
making the widget visible.
      */
     @Override
     public Region getView() {
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/StatusBar.java 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/StatusBar.java
index 546894f452..396b1a2eda 100644
--- a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/StatusBar.java
+++ b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/StatusBar.java
@@ -635,10 +635,11 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
     }
 
     /**
-     * Returns the node to add to the scene graph for showing the status bar.
+     * Returns the encapsulated JavaFX component to add in a scene graph for 
making the status bar visible.
+     * The {@code Region} subclass is implementation dependent and may change 
in any future SIS version.
      */
     @Override
-    public final Region getView() {
+    public Region getView() {
         return view;
     }
 
@@ -1347,6 +1348,7 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      * @param  y  the <var>y</var> coordinate local to the view.
      * @return string representation of coordinates or an error message.
      */
+    @SuppressWarnings("UseSpecificCatch")
     private String formatLocalCoordinates(final double x, final double y) {
         sourceCoordinates[xDimension] = x;
         sourceCoordinates[yDimension] = y;
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/MetadataSummary.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/MetadataSummary.java
index de8c591e6c..6202a35172 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/MetadataSummary.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/metadata/MetadataSummary.java
@@ -155,13 +155,13 @@ public class MetadataSummary extends Widget {
     }
 
     /**
-     * Returns the region containing the visual components managed by this 
{@code MetadataSummary}.
-     * The subclass is implementation dependent and may change in any future 
version.
+     * Returns the encapsulated JavaFX component to add in a scene graph for 
making the metadata visible.
+     * The {@code Region} subclass is implementation dependent and may change 
in any future SIS version.
      *
-     * @return the region to show.
+     * @return the JavaFX component to insert in a scene graph.
      */
     @Override
-    public final Region getView() {
+    public Region getView() {
         return content;
     }
 

Reply via email to