sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 02/04: Add SampleDimension.getBackground() method.
Date Sat, 08 Dec 2018 18:01:51 GMT
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

commit 6c3139813db16a839ae4397d316ff0097e29777b
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Sat Dec 8 14:57:46 2018 +0100

    Add SampleDimension.getBackground() method.
---
 .../main/java/org/apache/sis/xml/NilReason.java    |   2 +-
 .../java/org/apache/sis/coverage/CategoryList.java |  46 ++---
 .../org/apache/sis/coverage/SampleDimension.java   | 195 ++++++++++++++++-----
 .../apache/sis/coverage/SampleDimensionTest.java   |   4 +-
 .../org/apache/sis/util/resources/Vocabulary.java  |   5 +
 .../sis/util/resources/Vocabulary.properties       |   1 +
 .../sis/util/resources/Vocabulary_fr.properties    |   1 +
 .../apache/sis/storage/netcdf/GridResource.java    |  22 +--
 8 files changed, 189 insertions(+), 87 deletions(-)

diff --git a/core/sis-metadata/src/main/java/org/apache/sis/xml/NilReason.java b/core/sis-metadata/src/main/java/org/apache/sis/xml/NilReason.java
index 54876d4..91864b4 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/xml/NilReason.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/xml/NilReason.java
@@ -444,7 +444,7 @@ public final class NilReason implements Serializable {
      *
      * @throws IllegalArgumentException if the given type is not a supported type.
      */
-    @SuppressWarnings({"RedundantStringConstructorCall", "BooleanConstructorCall", "UnnecessaryBoxing"})
+    @SuppressWarnings({"deprecation", "RedundantStringConstructorCall", "BooleanConstructorCall",
"UnnecessaryBoxing"})
     private static Object createNilPrimitive(final Class<?> type) {
         if (type == String .class) return new String("");         // REALLY need a new instance.
         if (type == Boolean.class) return new Boolean(false);     // REALLY need a new instance,
not Boolean.FALSE.
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/CategoryList.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/CategoryList.java
index 311d8b4..8a3ec8a 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/CategoryList.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/CategoryList.java
@@ -83,12 +83,6 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
     private final Category[] categories;
 
     /**
-     * The "main" category, or {@code null} if there is none. The main category
-     * is the quantitative category with the widest range of sample values.
-     */
-    final Category main;
-
-    /**
      * The category to use if {@link #search(double)} is invoked with a sample value greater
than all ranges in this list.
      * This is usually a reference to the last category to have a range of real values. A
{@code null} value means that no
      * extrapolation should be used. By extension, a {@code null} value also means that {@link
#search(double)} should not
@@ -130,7 +124,6 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
         range         = null;
         minimums      = ArraysExt.EMPTY_DOUBLE;
         categories    = new Category[0];
-        main          = null;
         extrapolation = null;
         converse      = this;
     }
@@ -164,14 +157,20 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
         this.categories = categories;
         /*
          * Constructs the array of Category.minimum values. During the loop, we make sure
there is no overlapping ranges.
-         * We also take the "main" category as the category with the widest range of values.
          */
-        double widest = 0;
-        Category main = null;
         NumberRange<?> range = null;
         minimums = new double[categories.length];
         for (int i=categories.length; --i >= 0;) {
             final Category category = categories[i];
+            minimums[i] = category.minimum;
+            if (i != 0) {
+                final Category previous = categories[i-1];
+                if (Category.compare(category.minimum, previous.maximum) <= 0) {
+                    throw new IllegalArgumentException(Resources.format(Resources.Keys.CategoryRangeOverlap_4,
new Object[] {
+                                previous.name, previous.getRangeLabel(),
+                                category.name, category.getRangeLabel()}));
+                }
+            }
             final NumberRange<?> extent = category.range;
             if (extent != null) {
                 /*
@@ -184,26 +183,7 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
                 }
                 range = range.unionAny(extent);
             }
-            final double minimum = category.minimum;
-            minimums[i] = minimum;
-            if (category.converse.range != null) {                          // Category.isQuantitative()
inlined.
-                final double span = category.maximum - minimum;             // NaN if "converted
qualitative" category.
-                if (span >= widest) {
-                    widest = span;
-                    main = category;
-                }
-            }
-            if (i != 0) {
-                final Category previous = categories[i-1];
-                if (Category.compare(minimum, previous.maximum) <= 0) {
-                    throw new IllegalArgumentException(Resources.format(Resources.Keys.CategoryRangeOverlap_4,
new Object[] {
-                                previous.name, previous.getRangeLabel(),
-                                category.name, category.getRangeLabel()}));
-                }
-            }
         }
-        this.main  = main;
-        this.last  = (main != null || categories.length == 0) ? main : categories[0];
         this.range = range;
         /*
          * At this point we have two branches:
@@ -237,6 +217,9 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
         }
         this.extrapolation = extrapolation;
         this.converse      = converse;
+        if (categories.length != 0) {
+            last = categories[0];
+        }
     }
 
     /**
@@ -249,9 +232,10 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
     private Object readResolve() throws ObjectStreamException {
         if (categories.length == 0) {
             return EMPTY;
+        } else {
+            last = categories[0];
+            return this;
         }
-        last = (main != null) ? main : categories[0];
-        return this;
     }
 
     /**
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
index fbe54ed..55be3f6 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
@@ -25,6 +25,7 @@ import java.util.Optional;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Locale;
+import java.util.Objects;
 import java.io.Serializable;
 import javax.measure.Unit;
 import org.opengis.util.InternationalString;
@@ -87,6 +88,14 @@ public class SampleDimension implements Serializable {
     private final InternationalString name;
 
     /**
+     * The background value, or {@code null} if unspecified. Should be a sample value of
+     * a qualitative category in the {@link #categories} list, but this is not mandatory.
+     *
+     * @see #getBackground()
+     */
+    private final Number background;
+
+    /**
      * The list of categories making this sample dimension. May be empty but shall never
be null.
      */
     private final CategoryList categories;
@@ -121,23 +130,43 @@ public class SampleDimension implements Serializable {
      * be invoked only for sample dimensions having at least one quantitative category.
      *
      * @param  original  the original sample dimension for packed values.
+     * @param  bc        category of the background value in original sample dimension, or
{@code null}.
      */
-    private SampleDimension(final SampleDimension original) {
+    private SampleDimension(final SampleDimension original, Category bc) {
         converse         = original;
         name             = original.name;
         categories       = original.categories.converse;
         transferFunction = Category.identity();
         assert hasQuantitative();
+        if (bc == null) {
+            background = null;
+        } else {
+            bc = bc.converse;
+            final NumberRange<?> range = bc.range;
+            if (range != null) {
+                background = range.getMinValue();
+            } else {
+                background = (float) bc.minimum;
+            }
+        }
     }
 
     /**
      * Creates a sample dimension with the specified name and categories.
-     * Note that {@link Builder} provides a more convenient way to create sample dimensions.
+     * The sample dimension name is used as a way to perform a band select
+     * by using human comprehensible descriptions instead of numbers.
+     * The background value is used for filling empty space in map reprojections.
+     * The background value (if specified) should be the value of a qualitative category
+     * present in the {@code categories} collection, but this is not mandatory.
+     *
+     * <p>Note that {@link Builder} provides a more convenient way to create sample
dimensions.</p>
      *
-     * @param name        the sample dimension title or description, or {@code null} for
default.
-     * @param categories  the list of categories.
+     * @param name        the sample dimension title or description.
+     * @param background  the background value, or {@code null} if none.
+     * @param categories  the list of categories. May be empty if none.
      */
-    public SampleDimension(CharSequence name, final Collection<? extends Category>
categories) {
+    public SampleDimension(final InternationalString name, final Number background, final
Collection<? extends Category> categories) {
+        ArgumentChecks.ensureNonNull("name", name);
         ArgumentChecks.ensureNonNull("categories", categories);
         final CategoryList list;
         if (categories.isEmpty()) {
@@ -145,14 +174,8 @@ public class SampleDimension implements Serializable {
         } else {
             list = new CategoryList(categories.toArray(new Category[categories.size()]),
null);
         }
-        if (name == null) {
-            if (list.main != null) {
-                name = list.main.name;
-            } else {
-                name = Vocabulary.formatInternational(Vocabulary.Keys.Untitled);
-            }
-        }
-        this.name = Types.toInternationalString(name);
+        this.name       = name;
+        this.background = background;
         this.categories = list;
         if (list.range == null) {               // !hasQuantitative() inlined since we can
not yet invoke that method.
             transferFunction = null;
@@ -163,7 +186,7 @@ public class SampleDimension implements Serializable {
         } else {
             assert !list.isEmpty();             // Verified by inlined !hasQuantitative()
above.
             transferFunction = list.getTransferFunction();
-            converse = new SampleDimension(this);
+            converse = new SampleDimension(this, (background != null) ? list.search(background.doubleValue())
: null);
         }
     }
 
@@ -185,6 +208,8 @@ public class SampleDimension implements Serializable {
      * in order to perform band sub-setting as directed from a user request.
      *
      * @return the title or description of this sample dimension.
+     *
+     * @see org.opengis.metadata.content.RangeDimension#getSequenceIdentifier()
      */
     public InternationalString getName() {
         return name;
@@ -203,6 +228,16 @@ public class SampleDimension implements Serializable {
     }
 
     /**
+     * Returns the background value. If this sample dimensions has quantitative categories,
then the background
+     * value should be one of the value returned by {@link #getNoDataValues()}. However this
is not mandatory.
+     *
+     * @return the background value.
+     */
+    public Optional<Number> getBackground() {
+        return Optional.ofNullable(background);
+    }
+
+    /**
      * Returns {@code true} if this list contains at least one quantitative category.
      * We use the converted range has a criterion, since it shall be null if the result
      * of all conversions is NaN.
@@ -346,23 +381,22 @@ public class SampleDimension implements Serializable {
      * @see #getMeasurementRange()
      */
     public Optional<Unit<?>> getUnits() {
-        Unit<?> main = null;
+        Unit<?> unit = null;
         final SampleDimension converted = converted();
-        for (final Category c : converted.categories) {
-            final NumberRange<?> r = c.range;
+        for (final Category category : converted.categories) {
+            final NumberRange<?> r = category.range;
             if (r instanceof MeasurementRange<?>) {
-                final Unit<?> unit = ((MeasurementRange<?>) r).unit();
-                if (unit != null) {
-                    if (main != null && !main.equals(unit)) {
+                final Unit<?> c = ((MeasurementRange<?>) r).unit();
+                if (c != null) {
+                    if (unit == null) {
+                        unit = c;
+                    } else if (!unit.equals(c)) {
                         throw new IllegalStateException();
                     }
-                    if (main == null || c == converted.categories.main) {
-                        main = unit;
-                    }
                 }
             }
         }
-        return Optional.ofNullable(main);
+        return Optional.ofNullable(unit);
     }
 
     /**
@@ -404,7 +438,7 @@ public class SampleDimension implements Serializable {
         }
         if (object instanceof SampleDimension) {
             final SampleDimension that = (SampleDimension) object;
-            return name.equals(that.name) && categories.equals(that.categories);
+            return name.equals(that.name) && Objects.equals(background, that.background)
&& categories.equals(that.categories);
         }
         return false;
     }
@@ -429,6 +463,7 @@ public class SampleDimension implements Serializable {
      *
      * <ul>
      *   <li>An optional name for the {@code SampleDimension}.</li>
+     *   <li>A single optional category for the background value.</li>
      *   <li>An arbitrary amount of <cite>qualitative</cite> categories.</li>
      *   <li>An arbitrary amount of <cite>quantitative</cite> categories.</li>
      * </ul>
@@ -437,9 +472,10 @@ public class SampleDimension implements Serializable {
      * For example 0 = cloud, 1 = sea, 2 = land, <i>etc</i>.
      * A <cite>quantitative category</cite> is a range of sample values associated
to numbers with units of measurement.
      * For example 10 = 1.0°C, 11 = 1.1°C, 12 = 1.2°C, <i>etc</i>.
-     * Those two kind of categories are created by the following methods:
+     * Those three kinds of category are created by the following methods:
      *
      * <ul>
+     *   <li>{@link #setBackground(CharSequence, Number)}</li>
      *   <li>{@link #addQualitative(CharSequence, NumberRange)}</li>
      *   <li>{@link #addQuantitative(CharSequence, NumberRange, MathTransform1D, Unit)}</li>
      * </ul>
@@ -461,6 +497,11 @@ public class SampleDimension implements Serializable {
         private CharSequence dimensionName;
 
         /**
+         * The background value, or {@code null} if unspecified.
+         */
+        private Number background;
+
+        /**
          * The categories for this sample dimension.
          */
         private final List<Category> categories;
@@ -483,6 +524,8 @@ public class SampleDimension implements Serializable {
         /**
          * Sets the name or description of the sample dimension.
          * This is the value to be returned by {@link SampleDimension#getName()}.
+         * If this method is invoked more than once, then the last specified name prevails
+         * (previous sample dimension names are discarded).
          *
          * @param  name the name or description of the sample dimension.
          * @return {@code this}, for method call chaining.
@@ -493,6 +536,44 @@ public class SampleDimension implements Serializable {
         }
 
         /**
+         * Creates a range for the given number. We use the static factory methods instead
than the
+         * {@link NumberRange} constructor for sharing existing range instances. This is
also a way
+         * to ensure that the number type is one of the primitive wrappers.
+         */
+        private static NumberRange<?> range(final Number sample) {
+            switch (Numbers.getEnumConstant(sample.getClass())) {
+                case Numbers.BYTE:    {byte   v = sample.byteValue();   return NumberRange.create(v,
true, v, true);}
+                case Numbers.SHORT:   {short  v = sample.shortValue();  return NumberRange.create(v,
true, v, true);}
+                case Numbers.INTEGER: {int    v = sample.intValue();    return NumberRange.create(v,
true, v, true);}
+                case Numbers.LONG:    {long   v = sample.longValue();   return NumberRange.create(v,
true, v, true);}
+                case Numbers.FLOAT:   {float  v = sample.floatValue();  return NumberRange.create(v,
true, v, true);}
+                default:              {double v = sample.doubleValue(); return NumberRange.create(v,
true, v, true);}
+            }
+        }
+
+        /**
+         * Adds a qualitative category and marks that category as the background value.
+         * This is the value to be returned by {@link SampleDimension#getBackground()}.
+         * If this method is invoked more than once, then the last specified value prevails
+         * (previous values become ordinary qualitative categories).
+         *
+         * @param  name    the category name as a {@link String} or {@link InternationalString}
object,
+         *                 or {@code null} for a default "fill value" name.
+         * @param  sample  the background value. Can not be NaN.
+         * @return {@code this}, for method call chaining.
+         */
+        public Builder setBackground(CharSequence name, final Number sample) {
+            ArgumentChecks.ensureNonNull("sample", sample);
+            if (name == null) {
+                name = Vocabulary.formatInternational(Vocabulary.Keys.FillValue);
+            }
+            final NumberRange<?> samples = range(sample);
+            categories.add(new Category(name, samples, null, null, padValues));
+            background = samples.getMinValue();
+            return this;
+        }
+
+        /**
          * Adds a qualitative category for samples of the given boolean value.
          * The {@code true} value is represented by 1 and the {@code false} value is represented
by 0.
          *
@@ -592,6 +673,22 @@ public class SampleDimension implements Serializable {
         }
 
         /**
+         * Adds a qualitative category for samples of the given value.
+         *
+         * <div class="note"><b>Implementation note:</b>
+         * this convenience method delegates to {@link #addQualitative(CharSequence, NumberRange)}.</div>
+         *
+         * @param  name    the category name as a {@link String} or {@link InternationalString}
object,
+         *                 or {@code null} for a default "no data" name.
+         * @param  sample  the sample value. Can not be NaN.
+         * @return {@code this}, for method call chaining.
+         * @throws IllegalArgumentException if the given value is NaN.
+         */
+        public Builder addQualitative(final CharSequence name, final Number sample) {
+            return addQualitative(name, range(sample));
+        }
+
+        /**
          * Adds a qualitative category for all samples in the specified range of values.
          * This is the most generic method for adding a qualitative category.
          * All other {@code addQualitative(name, …)} methods are convenience methods delegating
their work to this method.
@@ -612,13 +709,13 @@ public class SampleDimension implements Serializable {
 
         /**
          * Constructs a quantitative category mapping samples to real values in the specified
range.
-         * Sample values in the {@code samples} range will be mapped to real values in the
{@code geophysics} range
+         * Sample values in the {@code samples} range will be mapped to real values in the
{@code converted} range
          * through a linear equation of the form:
          *
          * <blockquote><var>measure</var> = <var>sample</var>
× <var>scale</var> + <var>offset</var></blockquote>
          *
          * where <var>scale</var> and <var>offset</var> coefficients
are computed from the ranges supplied in arguments.
-         * The units of measurement will be taken from the {@code geophysics} range if it
is an instance of {@link MeasurementRange}.
+         * The units of measurement will be taken from the {@code converted} range if it
is an instance of {@link MeasurementRange}.
          *
          * <p><b>Warning:</b> this method is provided for convenience when
the scale and offset factors are not explicitly specified.
          * If those factor are available, then the other {@code addQuantitative(name, samples,
…)} methods are more reliable.</p>
@@ -628,40 +725,40 @@ public class SampleDimension implements Serializable {
          *
          * @param  name        the category name as a {@link String} or {@link InternationalString}
object.
          * @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  geophysics  the range of real values for this category, as an instance
of {@link MeasurementRange}
+         *                     {@link Integer}, but {@link Float} and {@link Double} values
are accepted as well.
+         * @param  converted   the range of real values for this category, as an instance
of {@link MeasurementRange}
          *                     if those values are associated to an unit of measurement.
          * @return {@code this}, for method call chaining.
          * @throws ClassCastException if the range element class is not a {@link Number}
subclass.
          * @throws IllegalArgumentException if the range is invalid.
          */
-        public Builder addQuantitative(final CharSequence name, final NumberRange<?>
samples, final NumberRange<?> geophysics) {
+        public Builder addQuantitative(final CharSequence name, final NumberRange<?>
samples, final NumberRange<?> converted) {
             ArgumentChecks.ensureNonNull("samples", samples);
-            ArgumentChecks.ensureNonNull("geophysics", geophysics);
+            ArgumentChecks.ensureNonNull("converted", converted);
             /*
-             * We need to perform calculation using the same "included versus excluded" characteristic
for sample and geophysics
+             * We need to perform calculation using the same "included versus excluded" characteristics
for sample and converted
              * values. We pickup the characteristics of the range using floating point values
because it is easier to adjust the
              * bounds of the range using integer values (we just add or subtract 1 for integers,
while the amount to add to real
-             * numbers is not so clear). If both ranges use floating point values, arbitrarily
adjust the geophysics values.
+             * numbers is not so clear). If both ranges use floating point values, arbitrarily
adjust the converted values.
              */
             final boolean isMinIncluded, isMaxIncluded;
             if (Numbers.isInteger(samples.getElementType())) {
-                isMinIncluded = geophysics.isMinIncluded();                         // This
is the usual case.
-                isMaxIncluded = geophysics.isMaxIncluded();
+                isMinIncluded = converted.isMinIncluded();                         // This
is the usual case.
+                isMaxIncluded = converted.isMaxIncluded();
             } else {
                 isMinIncluded = samples.isMinIncluded();                            // Less
common case.
                 isMaxIncluded = samples.isMaxIncluded();
             }
-            final double minValue  = geophysics.getMinDouble(isMinIncluded);
-            final double Δvalue    = geophysics.getMaxDouble(isMaxIncluded) - minValue;
-            final double minSample =    samples.getMinDouble(isMinIncluded);
-            final double Δsample   =    samples.getMaxDouble(isMaxIncluded) - minSample;
+            final double minValue  = converted.getMinDouble(isMinIncluded);
+            final double Δvalue    = converted.getMaxDouble(isMaxIncluded) - minValue;
+            final double minSample =   samples.getMinDouble(isMinIncluded);
+            final double Δsample   =   samples.getMaxDouble(isMaxIncluded) - minSample;
             final double scale     = Δvalue / Δsample;
             final TransferFunction transferFunction = new TransferFunction();
             transferFunction.setScale(scale);
             transferFunction.setOffset(minValue - scale * minSample);               // TODO:
use Math.fma with JDK9.
             return addQuantitative(name, samples, transferFunction.getTransform(),
-                    (geophysics instanceof MeasurementRange<?>) ? ((MeasurementRange<?>)
geophysics).unit() : null);
+                    (converted instanceof MeasurementRange<?>) ? ((MeasurementRange<?>)
converted).unit() : null);
         }
 
         /**
@@ -722,14 +819,14 @@ public class SampleDimension implements Serializable {
         }
 
         /**
-         * Returns {@code true} if the given range intersect the range of at least one category
previously added.
+         * Returns {@code true} if the given range intersects the range of a previously added
category.
          * This method can be invoked before to add a new category for checking if it would
cause a range collision.
          *
          * @param  minimum  minimal value of the range to test, inclusive.
          * @param  maximum  maximal value of the range to test, inclusive.
          * @return whether the given range intersects at least one previously added range.
          */
-        public boolean intersect(final double minimum, final double maximum) {
+        public boolean rangeCollides(final double minimum, final double maximum) {
             for (final Category category : categories) {
                 if (maximum >= category.minimum && minimum <= category.maximum)
{
                     return true;
@@ -744,7 +841,17 @@ public class SampleDimension implements Serializable {
          * @return the sample dimension.
          */
         public SampleDimension build() {
-            return new SampleDimension(dimensionName, categories);
+            InternationalString name = Types.toInternationalString(dimensionName);
+dfname:     if (name == null) {
+                for (final Category category : categories) {
+                    if (category.isQuantitative()) {
+                        name = category.name;
+                        break dfname;
+                    }
+                }
+                name = Vocabulary.formatInternational(Vocabulary.Keys.Untitled);
+            }
+            return new SampleDimension(name, background, categories);
         }
     }
 }
diff --git a/core/sis-raster/src/test/java/org/apache/sis/coverage/SampleDimensionTest.java
b/core/sis-raster/src/test/java/org/apache/sis/coverage/SampleDimensionTest.java
index 2ae4014..754e92a 100644
--- a/core/sis-raster/src/test/java/org/apache/sis/coverage/SampleDimensionTest.java
+++ b/core/sis-raster/src/test/java/org/apache/sis/coverage/SampleDimensionTest.java
@@ -45,13 +45,14 @@ public final strictfp class SampleDimensionTest extends TestCase {
         final double scale  = 0.1;
         final double offset = 5.0;
         final SampleDimension dimension = new SampleDimension.Builder()
-                .addQualitative(null,      0)           // Default to "No data" name, potentially
locale.
+                .setBackground (null,      0)           // Default to "Fill value" name,
potentially localized.
                 .addQualitative("Clouds",  1)
                 .addQualitative("Lands", 255)
                 .addQuantitative("Temperature", lower, upper, scale, offset, Units.CELSIUS)
                 .build();
 
         assertEquals("name", "Temperature", String.valueOf(dimension.getName()));
+        assertEquals("background", 0, dimension.getBackground().get());
 
         final Set<Number> nodataValues = dimension.getNoDataValues();
         assertArrayEquals(new Integer[] {0, 1, 255}, nodataValues.toArray());
@@ -77,5 +78,6 @@ public final strictfp class SampleDimensionTest extends TestCase {
         assertSame   (dimension,  converted.forConvertedValues(false));
         assertSame   (converted,  converted.forConvertedValues(true));
         assertTrue   ("identity", converted.getTransferFunction().get().isIdentity());
+        assertTrue   ("background", Double.isNaN(converted.getBackground().get().doubleValue()));
     }
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
index 4b13da5..83167db 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
@@ -522,6 +522,11 @@ public final class Vocabulary extends IndexedResourceBundle {
         public static final short MinimumValue = 70;
 
         /**
+         * Missing value
+         */
+        public static final short MissingValue = 160;
+
+        /**
          * Modified Julian
          */
         public static final short ModifiedJulian = 71;
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
index e93052d..6577d2f 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
@@ -105,6 +105,7 @@ Mapping                 = Mapping
 MaximumValue            = Maximum value
 MeanValue               = Mean value
 MinimumValue            = Minimum value
+MissingValue            = Missing value
 Measures                = Measures
 Methods                 = Methods
 ModifiedJulian          = Modified Julian
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
index 6c739ab..7fe1ebb 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
@@ -112,6 +112,7 @@ Mapping                 = Cartographie
 MaximumValue            = Valeur maximale
 MeanValue               = Valeur moyenne
 MinimumValue            = Valeur minimale
+MissingValue            = Valeur manquante
 Measures                = Mesures
 Methods                 = M\u00e9thodes
 ModifiedJulian          = Julien modifi\u00e9
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/GridResource.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/GridResource.java
index 1cc0b41..68906c6 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/GridResource.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/GridResource.java
@@ -191,25 +191,27 @@ final class GridResource extends AbstractGridResource implements ResourceOnFileS
                 builder.addQuantitative(data.getName(), range, mt, data.getUnit());
             }
             /*
-             * Adds the "no data" or "fill value" as qualitative categories.
+             * Adds the "missing value" or "fill value" as qualitative categories.
              */
             for (final String attribute : NODATA_ATTRIBUTES) {
                 InternationalString name = null;
+                boolean isFillValue = false;
                 for (final Object value : data.getAttributeValues(attribute, true)) {
                     if (value instanceof Number) {
                         final Number n = (Number) value;
                         final double fp = n.doubleValue();
-                        if (!builder.intersect(fp, fp)) {
+                        if (!builder.rangeCollides(fp, fp)) {
                             if (name == null) {
-                                short key = Vocabulary.Keys.Nodata;
-                                if (CDM.FILL_VALUE.equalsIgnoreCase(attribute)) {
-                                    key = Vocabulary.Keys.FillValue;
-                                }
-                                name = Vocabulary.formatInternational(key);
+                                isFillValue = CDM.FILL_VALUE.equalsIgnoreCase(attribute);
+                                name = Vocabulary.formatInternational(isFillValue ? Vocabulary.Keys.FillValue
+                                                                                  : Vocabulary.Keys.MissingValue);
+                            }
+                            if (isFillValue) {
+                                isFillValue = false;                              // Declare
only one fill value.
+                                builder.setBackground(name, n);
+                            } else {
+                                builder.addQualitative(name, n);
                             }
-                            @SuppressWarnings({"unchecked", "rawtypes"})
-                            NumberRange<?> r = new NumberRange(value.getClass(), n,
true, n, true);
-                            builder.addQualitative(name, r);
                         }
                     }
                 }


Mime
View raw message