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 252ab98b78 Adjust the default sample dimensions created by the GeoTIFF 
reader. If a "no data" category is added, then a "data" category should also be 
added.
252ab98b78 is described below

commit 252ab98b788da5290e30f0edb37c98806861ba53
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Jul 18 20:07:22 2025 +0200

    Adjust the default sample dimensions created by the GeoTIFF reader.
    If a "no data" category is added, then a "data" category should also be 
added.
---
 .../org/apache/sis/coverage/SampleDimension.java   | 10 ++-
 .../apache/sis/image/privy/ColorScaleBuilder.java  | 17 ++--
 .../main/org/apache/sis/storage/landsat/Band.java  | 13 ++--
 .../sis/storage/geotiff/ImageFileDirectory.java    | 90 ++++++++++++++++------
 .../sis/storage/modifier/CoverageModifier.java     | 42 ++++++----
 .../main/org/apache/sis/gui/map/StatusBar.java     | 55 +++++++------
 .../org/apache/sis/gui/map/ValuesFormatter.java    | 25 +++---
 .../org/apache/sis/gui/map/ValuesFromCoverage.java |  9 ++-
 8 files changed, 166 insertions(+), 95 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SampleDimension.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SampleDimension.java
index ae04583836..f70aead042 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SampleDimension.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/SampleDimension.java
@@ -542,7 +542,7 @@ public class SampleDimension implements Serializable {
      *
      * @author  Martin Desruisseaux (IRD, Geomatys)
      * @author  Alexis Manin (Geomatys)
-     * @version 1.4
+     * @version 1.5
      * @since   1.0
      */
     public static class Builder {
@@ -1150,7 +1150,8 @@ public class SampleDimension implements Serializable {
          *                  or {@code null} for a default "data" name.
          * @param  samples  the minimum and maximum sample values in the 
category. Element class is usually
          *                  {@link Integer}, but {@link Float} and {@link 
Double} types are accepted as well.
-         * @param  toUnits  the transfer function from sample values to real 
values in the specified units.
+         * @param  toUnits  the transfer function from sample values to real 
values in the specified units,
+         *                  or {@code null} if none.
          * @param  units    the units of measurement of values after 
conversion by the transfer function,
          *                  or {@code null} if unknown or not applicable.
          * @return {@code this}, for method call chaining.
@@ -1160,10 +1161,13 @@ public class SampleDimension implements Serializable {
          * @see TransferFunction
          */
         public Builder addQuantitative(CharSequence name, NumberRange<?> 
samples, MathTransform1D toUnits, Unit<?> units) {
-            ArgumentChecks.ensureNonNull("toUnits", toUnits);
+            ArgumentChecks.ensureNonNull("samples", samples);
             if (name == null) {
                 name = DATA;
             }
+            if (toUnits == null) {
+                toUnits = Category.identity();
+            }
             add(new Category(name, samples, toUnits, units, toNaN));
             return this;
         }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/privy/ColorScaleBuilder.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/privy/ColorScaleBuilder.java
index f813f2a9da..3d6872bc5a 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/privy/ColorScaleBuilder.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/privy/ColorScaleBuilder.java
@@ -413,7 +413,7 @@ public final class ColorScaleBuilder {
             var samples = NumberRange.create(1, true, MAX_VALUE, true);
             builder.setBackground(TRANSPARENT, 0).addQuantitative(COLOR_INDEX, 
samples, defaultRange);
         } else {
-            builder.addQuantitative(COLOR_INDEX, defaultRange, identity(), 
null);
+            builder.addQuantitative(COLOR_INDEX, defaultRange, null, null);
         }
         target = builder.build();
         /*
@@ -711,15 +711,12 @@ reuse:  if (source != null) {
      */
     public MathTransform1D getSampleToIndexValues() throws 
NoninvertibleTransformException {
         checkInitializationStatus(true);
-        return (target != null) ? 
target.getTransferFunction().orElseGet(ColorScaleBuilder::identity).inverse() : 
identity();
-    }
-
-    /**
-     * Returns the identity transform.
-     *
-     * @see Category#identity()
-     */
-    private static MathTransform1D identity() {
+        if (target != null) {
+            MathTransform1D toTarget = 
target.getTransferFunction().orElse(null);
+            if (toTarget != null) {
+                return toTarget.inverse();
+            }
+        }
         return (MathTransform1D) MathTransforms.identity(1);
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/Band.java
 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/Band.java
index 96ffd4e4e4..c07058fda3 100644
--- 
a/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/Band.java
+++ 
b/endorsed/src/org.apache.sis.storage.earthobservation/main/org/apache/sis/storage/landsat/Band.java
@@ -37,7 +37,6 @@ import 
org.apache.sis.metadata.iso.content.DefaultAttributeGroup;
 import org.apache.sis.metadata.iso.content.DefaultSampleDimension;
 import org.apache.sis.metadata.iso.content.DefaultBand;
 import org.apache.sis.coverage.SampleDimension;
-import org.apache.sis.measure.NumberRange;
 import org.apache.sis.measure.Units;
 import static org.apache.sis.util.privy.CollectionsExt.first;
 
@@ -175,8 +174,8 @@ final class Band extends GridResourceWrapper implements 
CoverageModifier {
             sd.setOffset     (sampleDimension.getOffset());
             sd.setUnits      (sampleDimension.getUnits());
             if (sampleDimension instanceof DefaultBand) {
-                final DefaultBand s = (DefaultBand) sampleDimension;
-                final DefaultBand t = (DefaultBand) sd;
+                final var s = (DefaultBand) sampleDimension;
+                final var t = (DefaultBand) sd;
                 t.setPeakResponse(s.getPeakResponse());
                 t.setBoundUnits(s.getBoundUnits());
             }
@@ -191,8 +190,8 @@ final class Band extends GridResourceWrapper implements 
CoverageModifier {
     public SampleDimension customize(final BandSource source, final 
SampleDimension.Builder dimension) {
         if (isMain(source) && source.getBandIndex() == 0) {
             dimension.setName(identifier);
-            final NumberRange<?> sampleRange = 
source.getSampleRange().orElse(null);
-            if (sampleRange != null) {
+            dimension.categories().clear();
+            source.getSampleRange().ifPresent((sampleRange) -> {
                 final Number min    = sampleRange.getMinValue();
                 final Number max    = sampleRange.getMaxValue();
                 final Double scale  = sampleDimension.getScaleFactor();
@@ -203,10 +202,10 @@ final class Band extends GridResourceWrapper implements 
CoverageModifier {
                         dimension.addQualitative(null, 0);
                         if (lower == 0) lower = 1;
                     }
-                    dimension.addQuantitative(this.band.group.measurement, 
lower, max.intValue(),
+                    dimension.addQuantitative(band.group.measurement, lower, 
max.intValue(),
                                               scale, offset, 
sampleDimension.getUnits());
                 }
-            }
+            });
         }
         return dimension.build();
     }
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index 618c6709f3..f15e51650e 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -347,6 +347,8 @@ final class ImageFileDirectory extends DataCube {
     /**
      * The minimum or maximum sample value found in the image, with one value 
per band.
      * May be a vector of length 1 if the same single value applies to all 
bands.
+     *
+     * @see #getValidValues(int, double)
      */
     private Vector minValues, maxValues;
 
@@ -1157,8 +1159,11 @@ final class ImageFileDirectory extends DataCube {
     }
 
     /**
-     * Computes the minimal or maximal values of the given vector. Those 
vectors do not need to have the same length.
+     * Computes the minimal or maximal values of the given vector.
+     * Those vectors do not need to have the same length.
      * One of those two vector will be modified in-place.
+     * This is a paranoiac safety in case a tag for minimal or maximum values 
appears more than once.
+     * This duplication is not so unlikely since each extremum can be 
described by two different tags.
      *
      * @param  a    the first vector, or {@code null} if none.
      * @param  b    the new vector to combine with the existing one. Cannot be 
null.
@@ -1399,8 +1404,8 @@ final class ImageFileDirectory extends DataCube {
             metadata.addNewBand(sampleDimensions.get(band));
             metadata.setBitPerSample(bitsPerSample);
             if (!metadata.hasSampleValueRange()) {
-                if (isMinSpecified) 
metadata.addMinimumSampleValue(minValues.doubleValue(Math.min(band, 
minValues.size()-1)));
-                if (isMaxSpecified) 
metadata.addMaximumSampleValue(maxValues.doubleValue(Math.min(band, 
maxValues.size()-1)));
+                if (isMinSpecified) 
metadata.addMinimumSampleValue(extremum(minValues, band).doubleValue());
+                if (isMaxSpecified) 
metadata.addMaximumSampleValue(extremum(maxValues, band).doubleValue());
             }
         }
         /*
@@ -1512,27 +1517,37 @@ final class ImageFileDirectory extends DataCube {
     }
 
     /**
-     * Information about which band is subject to modification. This 
information is given to
-     * {@link CoverageModifier} for allowing users to modify name, metadata or 
sample dimensions.
+     * Returns the minimum and maximum non-fill values in the specified band.
+     * This is the values explicitly defined in <abbr>TIFF</abbr> tags if 
present,
+     * otherwise the values derived from the data type if it is an integer 
type.
+     *
+     * @param  band     the band for which to get the minimum and maximum 
values.
+     * @param  exclude  the fill value to exclude, or NaN if none.
+     * @return the minimum and maximum values in the specified band.
      */
-    private final class Source extends CoverageModifier.BandSource {
-        /** Creates a new source for the specified band. */
-        Source(final int bandIndex, final DataType dataType) {
-            super(reader.store, index, bandIndex, samplesPerPixel, dataType);
+    private Optional<NumberRange<?>> getValidValues(final int band, final 
double exclude) {
+        Number min = extremum(minValues, band);
+        if (min != null) {
+            Number max = extremum(maxValues, band);
+            if (max != null) {
+                return Optional.of(NumberRange.createBestFit(
+                        sampleFormat == FLOAT,
+                        min, min.doubleValue() != exclude,      // Always true 
if `exclude` is NaN.
+                        max, max.doubleValue() != exclude));
+            }
         }
+        return Optional.empty();
+    }
 
-        /** Computes the range of sample values if requested. */
-        @Override public Optional<NumberRange<?>> getSampleRange() {
-            final Vector minValues = ImageFileDirectory.this.minValues;
-            final Vector maxValues = ImageFileDirectory.this.maxValues;
-            if (minValues != null && maxValues != null) {
-                final int band = getBandIndex();
-                return Optional.of(NumberRange.createBestFit(sampleFormat == 
FLOAT,
-                        minValues.get(Math.min(band, minValues.size()-1)), 
true,
-                        maxValues.get(Math.min(band, maxValues.size()-1)), 
true));
-            }
-            return Optional.empty();
-        }
+    /**
+     * Extracts a value from the vector of minimum or maximum values.
+     *
+     * @param  values  the vector of minimum or maximum values.
+     * @param  band    the index of the valud to get.
+     * @return the value for the given band, or {@code null} if none.
+     */
+    private static Number extremum(final Vector values, final int band) {
+        return (values == null) ? null : values.get(Math.min(band, 
values.size() - 1));
     }
 
     /**
@@ -1575,10 +1590,39 @@ final class ImageFileDirectory extends DataCube {
                     } else {
                         builder.setName(band + 1);
                     }
-                    builder.setBackground(fill);
+                    /*
+                     * If a "no data" value is present, declare both as 
background value and a qualitative category.
+                     * The latter will cause the image to be converted to 
floating point during resample operations,
+                     * with "no data" replaced by NaN. For completeness, we 
need to declare the range of real values.
+                     */
+                    final Optional<NumberRange<?>> sampleRange;
+                    if (fill != null) {
+                        builder.setBackground(fill);
+                        sampleRange = getValidValues(band, fill.doubleValue());
+                        sampleRange.ifPresent((range) -> {
+                            if (!range.containsAny(fill)) {
+                                builder.addQuantitative(null, range, null, 
null);
+                                builder.addQualitative(null, fill, fill);
+                            }
+                        });
+                    } else {
+                        // Do not declare any category. It will be understood 
as "visualization only".
+                        sampleRange = null;
+                    }
+                    /*
+                     * Give a change to users to replace the above default 
categories by their own.
+                     * The information provided to the user include the 
optional range of values,
+                     * computed only when requested if it was not already 
computed.
+                     */
                     final SampleDimension sd;
                     if (isIndexValid) {
-                        sd = reader.store.customizer.customize(new 
Source(band, dataType), builder);
+                        var source = new 
CoverageModifier.BandSource(reader.store, index, band, samplesPerPixel, 
dataType) {
+                            @Override public Optional<NumberRange<?>> 
getSampleRange() {
+                                if (sampleRange != null) return sampleRange;
+                                return getValidValues(getBandIndex(), 
Double.NaN);
+                            }
+                        };
+                        sd = reader.store.customizer.customize(source, 
builder);
                     } else {
                         sd = builder.build();
                     }
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/modifier/CoverageModifier.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/modifier/CoverageModifier.java
index c8e2d63585..b2ab2fff1b 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/modifier/CoverageModifier.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/modifier/CoverageModifier.java
@@ -79,7 +79,7 @@ public interface CoverageModifier {
      * @param  connector  the storage connector from which to get the modifier.
      * @return the modifier to use, never {@code null}.
      */
-    static CoverageModifier getOrDefault(StorageConnector connector) {
+    public static CoverageModifier getOrDefault(StorageConnector connector) {
         final CoverageModifier customizer = 
connector.getOption(DataOptionKey.COVERAGE_MODIFIER);
         return (customizer != null) ? customizer : DEFAULT;
     }
@@ -330,22 +330,44 @@ public interface CoverageModifier {
 
     /**
      * Invoked when a sample dimension is created in a coverage.
-     * The data store invokes this method with a builder initialized to a 
default name,
+     * The data store invokes this method with a {@link SampleDimension} 
builder initialized to a default name,
      * which may be the {@linkplain SampleDimension.Builder#setName(int) band 
number}.
-     * The builder may also contain a {@linkplain 
SampleDimension.Builder#setBackground(Number) background value}.
+     * The builder may also contain a {@linkplain 
SampleDimension.Builder#setBackground(Number) background value}
+     * and {@linkplain SampleDimension.Builder#categories() categories}.
      * Implementations can override this method for setting a better name
-     * or for declaring the meaning of sample values (by adding categories).
+     * or for declaring the meaning of sample values (by replacing categories).
      *
      * <h4>Default implementation</h4>
-     * The default implementation creates a "no data" category for the
-     * {@linkplain SampleDimension.Builder#getBackground() background value} 
if such value exists.
+     * The default implementation returns {@code dimensions.build()} with no 
modification on the given builder.
+     *
+     * <h4>Example: measurement data</h4>
+     * The following example declares that the values 0 means "no data".
      * The presence of such "no data" category will cause the raster to be 
converted to floating point
      * values before operations such as {@code resample}, in order to replace 
those "no data" by NaN values.
-     * If this replacement is not desired, then subclass should override this 
method for example like below:
+     * When a "no data" category is declared, it is strongly recommended to 
also declare the range of real data.
+     * The following example declares the range 1 to 255 inclusive.
      *
      * {@snippet lang="java" :
      * @Override
      * public SampleDimension customize(BandSource source, 
SampleDimension.Builder dimension) {
+     *     dimension.categories().clear();      // Discard the categories 
created by the store.
+     *     dimension.addQualitative(null, 0);   // Declare value 0 as "no 
data".
+     *     dimension.addQuantitative("Some name for my data", 1, 255, null);
+     *     return dimension.build();
+     * }
+     * }
+     *
+     * See the various {@code addQuantitative(…)} methods for information 
about how to declare a transfer function
+     * (a conversion from pixel values to the unit of measurement).
+     *
+     * <h4>Example: visualization only</h4>
+     * If the pixel values have no meaning other than visualization, this 
method can be overridden
+     * as below for making sure that they raster is not interpreted as 
measurement data:
+     *
+     * {@snippet lang="java" :
+     * @Override
+     * public SampleDimension customize(BandSource source, 
SampleDimension.Builder dimension) {
+     *     dimension.categories().clear();      // Discard the categories 
created by the store.
      *     return dimension.build();
      * }
      * }
@@ -359,12 +381,6 @@ public interface CoverageModifier {
     default SampleDimension customize(final BandSource source, final 
SampleDimension.Builder dimension)
             throws DataStoreException
     {
-        final Number fill = dimension.getBackground();
-        if (fill != null) {
-            @SuppressWarnings({"unchecked", "rawtypes"})
-            NumberRange<?> samples = new NumberRange(fill.getClass(), fill, 
true, fill, true);
-            dimension.addQualitative(null, samples);
-        }
         return dimension.build();
     }
 
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 5392aa1577..85590488cd 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
@@ -125,7 +125,7 @@ import org.opengis.coordinate.MismatchedDimensionException;
  * {@link #setLocalCoordinates(double, double)} explicitly instead.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.5
  * @since   1.1
  */
 public class StatusBar extends Widget implements EventHandler<MouseEvent> {
@@ -147,6 +147,11 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      */
     private static final int VALUES_PADDING = 9;
 
+    /**
+     * Minimal size of the text field where sample values are shown.
+     */
+    private static final int MINIMAL_VALUES_SIZE = 60;
+
     /**
      * The container of controls making the status bar.
      * Contains {@link #message}, {@link #position} and {@link #sampleValues}.
@@ -1038,6 +1043,7 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      *
      * @see #positionReferenceSystem
      */
+    @SuppressWarnings("StringEquality")
     private void setFormatCRS(final CoordinateReferenceSystem crs, final 
Quantity<Length> accuracy) {
         int dimension = localToPositionCRS.getTargetDimensions();
         GeneralDirectPosition target = targetCoordinates;
@@ -1060,7 +1066,7 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
         precisionUnit = null;
         String text = null;
         if (crs != null) {
-            final StringBuilder b = new StringBuilder().append('(');
+            final var b = new StringBuilder().append('(');
             final CoordinateSystem cs = crs.getCoordinateSystem();
             dimension = (cs != null) ? cs.getDimension() : 0;       // 
Paranoiac check (should never be null).
             for (int i=0; i<dimension; i++) {
@@ -1474,6 +1480,7 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
     /**
      * Returns {@code true} if the position contains a valid coordinates.
      */
+    @SuppressWarnings("StringEquality")
     private boolean isPositionVisible() {
         if (position.isVisible()) {
             final String text = position.getText();
@@ -1491,23 +1498,23 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
      */
     private void setSampleValuesVisible(final boolean visible) {
         final ObservableList<Node> c = view.getChildren();
-        Label view = sampleValues;
+        Label sampleView = sampleValues;
         if (visible) {
-            if (view == null) {
-                view = new Label();
-                view.setAlignment(Pos.CENTER_RIGHT);
-                view.setTextAlignment(TextAlignment.RIGHT);
-                view.setMinWidth(Label.USE_PREF_SIZE);
-                view.setMaxWidth(Label.USE_PREF_SIZE);
-                sampleValues = view;
+            if (sampleView == null) {
+                sampleView = new Label();
+                sampleView.setAlignment(Pos.CENTER_RIGHT);
+                sampleView.setTextAlignment(TextAlignment.RIGHT);
+                sampleView.setMinWidth(Label.USE_PREF_SIZE);
+                sampleView.setMaxWidth(Label.USE_PREF_SIZE);
+                sampleValues = sampleView;
             }
-            if (c.lastIndexOf(view) < 0) {
+            if (c.lastIndexOf(sampleView) < 0) {
                 final Separator separator = new 
Separator(Orientation.VERTICAL);
-                c.addAll(separator, view);
+                c.addAll(separator, sampleView);
             }
-        } else if (view != null) {
-            view.setText(null);
-            int i = c.lastIndexOf(view);
+        } else if (sampleView != null) {
+            sampleView.setText(null);
+            int i = c.lastIndexOf(sampleView);
             if (i >= 0) {
                 c.remove(i);
                 if (--i >= 0) {
@@ -1533,14 +1540,14 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
     final boolean computeSizeOfSampleValues(final String prototype, final 
Iterable<String> others) {
         setSampleValuesVisible(prototype != null && !prototype.isEmpty());
         if (isSampleValuesVisible) {
-            final Label view = sampleValues;
-            view.setText(prototype);
-            view.setPrefWidth(Label.USE_COMPUTED_SIZE);                 // 
Enable `prefWidth(…)` computation.
-            double width = view.prefWidth(view.getHeight());
-            final double max = Math.max(width * 1.25, 200);                    
 // Arbitrary limit.
+            final Label sampleView = sampleValues;
+            sampleView.setText(prototype);
+            sampleView.setPrefWidth(Label.USE_COMPUTED_SIZE);               // 
Enable `prefWidth(…)` computation.
+            double width = sampleView.prefWidth(sampleView.getHeight());
+            final double max = Math.max(width * 1.25, 200);                 // 
Arbitrary limit.
             for (final String other : others) {
-                view.setText(other);
-                final double cw = view.prefWidth(view.getHeight());
+                sampleView.setText(other);
+                final double cw = sampleView.prefWidth(sampleView.getHeight());
                 if (cw > width) {
                     width = cw;
                     if (width > max) {
@@ -1549,11 +1556,11 @@ public class StatusBar extends Widget implements 
EventHandler<MouseEvent> {
                     }
                 }
             }
-            view.setText(null);
+            sampleView.setText(null);
             if (!(width > 0)) {                 // May be 0 if canvas is not 
yet added to scene graph.
                 return false;
             }
-            view.setPrefWidth(width + VALUES_PADDING);
+            sampleView.setPrefWidth(Math.max(width + VALUES_PADDING, 
MINIMAL_VALUES_SIZE));
         }
         return true;
     }
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/ValuesFormatter.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/ValuesFormatter.java
index 7eb697a26a..5fd734d7d3 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/ValuesFormatter.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/ValuesFormatter.java
@@ -103,8 +103,8 @@ final class ValuesFormatter extends 
ValuesUnderCursor.Formatter {
     private final FieldPosition field;
 
     /**
-     * Unit symbol to write after each value.
-     * Array shall not be modified after construction.
+     * Unit symbol to write after each value, including the leading space 
separator if needed.
+     * The content of this array shall not be modified after construction.
      */
     private final String[] units;
 
@@ -188,9 +188,9 @@ final class ValuesFormatter extends 
ValuesUnderCursor.Formatter {
          * if selected or not. We do that on the assumption that the same 
format and symbol
          * are typically shared by all bands.
          */
-        final Map<Integer,NumberFormat> sharedFormats = new HashMap<>();
-        final Map<Unit<?>,String>       sharedSymbols = new HashMap<>();
-        final UnitFormat                unitFormat    = new UnitFormat(locale);
+        final var sharedFormats = new HashMap<Integer,NumberFormat>();
+        final var sharedSymbols = new HashMap<Unit<?>,String>();
+        final var unitFormat    = new UnitFormat(locale);
         for (int b=0; b<numBands; b++) {
             /*
              * Build the list of texts to show for missing values. A coverage 
can have
@@ -215,8 +215,9 @@ final class ValuesFormatter extends 
ValuesUnderCursor.Formatter {
              * Infer a number of fraction digits to use for the resolution of 
sample values in each band.
              */
             final SampleDimension isd = sd.forConvertedValues(false);
-            final Integer nf = isd.getTransferFunctionFormula().map(
-                    (formula) -> suggestFractionDigits(formula, 
isd)).orElse(DEFAULT_FORMAT);
+            final Integer nf = isd.getTransferFunctionFormula()
+                    .map((formula) -> suggestFractionDigits(formula, isd))
+                    .orElse(DEFAULT_FORMAT);
             /*
              * Create number formats with a number of fraction digits inferred 
from sample value resolution.
              * The same format instances are shared when possible. Keys are 
the number of fraction digits.
@@ -284,7 +285,7 @@ final class ValuesFormatter extends 
ValuesUnderCursor.Formatter {
     final String setSelectedBands(final BitSet selection, final String[] 
labels, final HashSet<String> others) {
         synchronized (buffer) {
             final List<SampleDimension> bands = 
evaluator.getCoverage().getSampleDimensions();
-            final StringBuilder names = new StringBuilder().append('(');
+            final var names = new StringBuilder().append('(');
             buffer.setLength(0);
             for (int i = -1; (i = selection.nextSetBit(i+1)) >= 0;) {
                 if (buffer.length() != 0) {
@@ -293,9 +294,11 @@ final class ValuesFormatter extends 
ValuesUnderCursor.Formatter {
                 }
                 names.append(labels[i]);
                 final int start = buffer.length();
-                final Comparable<?>[] sampleValues = 
bands.get(i).forConvertedValues(true)
-                        .getSampleRange().map((r) -> new Comparable<?>[] 
{r.getMinValue(), r.getMaxValue()})
-                        .orElseGet(() -> new Comparable<?>[] {0xFFFF});        
         // Arbitrary value.
+                final Comparable<?>[] sampleValues = bands.get(i)
+                        .forConvertedValues(true)
+                        .getSampleRange()
+                        .map((range)  -> new Comparable<?>[] 
{range.getMinValue(), range.getMaxValue()})
+                        .orElseGet(() -> new Comparable<?>[] {0xFFFF});     // 
Arbitrary value.
                 for (final Comparable<?> value : sampleValues) {
                     final int end = buffer.length();
                     sampleFormats[i].format(value, buffer, field);
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/ValuesFromCoverage.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/ValuesFromCoverage.java
index 3ff489d440..98acefe32f 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/ValuesFromCoverage.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/map/ValuesFromCoverage.java
@@ -176,7 +176,7 @@ final class ValuesFromCoverage extends ValuesUnderCursor 
implements ChangeListen
                      * (u,v) components of velocity vectors, which we want to 
keep together by default.
                      */
                     final int numBands = bands.size();
-                    final CheckMenuItem[] menuItems = new 
CheckMenuItem[numBands];
+                    final var menuItems = new CheckMenuItem[numBands];
                     final BitSet selection = selectedBands;
                     selection.clear();
                     selection.set(0, (numBands <= 3) ? numBands : 1, true);
@@ -227,15 +227,16 @@ final class ValuesFromCoverage extends ValuesUnderCursor 
implements ChangeListen
         if (refreshing) {
             return null;
         }
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
         final ValuesFormatter formatter = this.formatter;
         if (formatter != null && needsBandRefresh && usePrototype()) {
             final ObservableList<MenuItem> menus = valueChoices.getItems();
-            final String[] labels = new String[menus.size()];
+            final var labels = new String[menus.size()];
             for (int i=0; i<labels.length; i++) {
                 labels[i] = menus.get(i).getText();
             }
-            final HashSet<String> others = new HashSet<>();
-            final BitSet selection = (BitSet) selectedBands.clone();
+            final var others = new HashSet<String>();
+            final var selection = (BitSet) selectedBands.clone();
             BackgroundThreads.execute(new Task<String>() {
                 /** Invoked in background thread for configuring the 
formatter. */
                 @Override protected String call() {

Reply via email to