You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2022/09/28 15:39:05 UTC

[sis] 04/04: Add a workaround for missing "no data" value in some GCOM files. Because the workaround may introduce two categories with same name, add a "#2", "#3", etc. suffix after duplicated names.

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 5c4559bba6a0ef592c25eaf7d60d6f70ed170a98
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Wed Sep 28 17:26:11 2022 +0200

    Add a workaround for missing "no data" value in some GCOM files.
    Because the workaround may introduce two categories with same name,
    add a "#2", "#3", etc. suffix after duplicated names.
---
 .../apache/sis/internal/earth/netcdf/GCOM_C.java   | 52 +++++++++++++++++++---
 .../apache/sis/internal/netcdf/RasterResource.java | 23 +++++-----
 2 files changed, 55 insertions(+), 20 deletions(-)

diff --git a/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_C.java b/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_C.java
index 0f47ed5f66..b8e13667e5 100644
--- a/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_C.java
+++ b/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_C.java
@@ -47,6 +47,7 @@ import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.coverage.Category;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.measure.Units;
+import org.apache.sis.util.Workaround;
 import ucar.nc2.constants.CF;
 
 
@@ -171,22 +172,39 @@ public final class GCOM_C extends Convention {
 
     /**
      * Names of attributes for sample values having "no-data" meaning.
-     * All those names have {@value #SUFFIX} suffix.
+     * Pad values should be first, followed by missing values.
+     * All those names have the {@value #SUFFIX} suffix.
      *
      * @see #nodataValues(Variable)
      */
     private static final String[] NO_DATA = {
         "Error_DN",                                 // Must be first: will be used as "no data" value.
-        "Land_DN",
+        "Retrieval_error_DN",                       // First fallback if "Error_DN" is not found.
         "Cloud_error_DN",
-        "Retrieval_error_DN"
+        "Land_DN"
     };
 
+    /**
+     * Names of attributes for minimum and maximum valid sample values.
+     */
+    private static final String MINIMUM = "Minimum_valid_DN",
+                                MAXIMUM = "Maximum_valid_DN";
+
     /**
      * Suffix of all attribute names enumerated in {@link #NO_DATA}.
      */
     private static final String SUFFIX = "_DN";
 
+    /**
+     * A "No data" value which should be present in all GCOM files but appear to be missing.
+     * All values in the range {@value} to {@code 0xFFFF} will be "no data", unless the range
+     * of valid values overlap.
+     *
+     * <p>This hack may be removed after VGI data files fixed their missing "no data" attribute.</p>
+     */
+    @Workaround(library = "Vegetation Index (VGI)", version = "3 (2021)")
+    private static final int MISSING_NODATA = 0xFFFE;
+
     /**
      * Creates a new instance of GCOM-C conventions.
      */
@@ -472,8 +490,8 @@ public final class GCOM_C extends Convention {
     public NumberRange<?> validRange(final Variable data) {
         NumberRange<?> range = super.validRange(data);
         if (range == null) {
-            final Number min = data.getAttributeAsNumber("Minimum_valid_DN");
-            final Number max = data.getAttributeAsNumber("Maximum_valid_DN");
+            final Number min = data.getAttributeAsNumber(MINIMUM);
+            final Number max = data.getAttributeAsNumber(MAXIMUM);
             if (min != null || max != null) {
                 range = NumberRange.createBestFit(min, true, max, true);
             }
@@ -484,13 +502,15 @@ public final class GCOM_C extends Convention {
     /**
      * Returns all no-data values declared for the given variable, or an empty map if none.
      * The map keys are the no-data values (pad sample values or missing sample values).
-     * The map values are {@link String} instances containing the description of the no-data value.
+     * The map values are {@link String} instances containing the description of the no-data value,
+     * except for {@code "Error_DN"} which is used as a more generic pad value.
      *
      * @param  data  the variable for which to get no-data values.
      * @return no-data values with textual descriptions.
      */
     @Override
     public Map<Number,Object> nodataValues(final Variable data) {
+        boolean addMissingNodata = true;
         final Map<Number, Object> pads = super.nodataValues(data);
         for (int i=0; i<NO_DATA.length; i++) {
             String name = NO_DATA[i];
@@ -505,7 +525,25 @@ public final class GCOM_C extends Convention {
                 } else {
                     label = FILL_VALUE_MASK | MISSING_VALUE_MASK;
                 }
-                pads.put(value, label);
+                if (pads.putIfAbsent(value, label) == null && addMissingNodata) {
+                    addMissingNodata = Math.floor(value.doubleValue()) > MISSING_NODATA;
+                }
+            }
+        }
+        /*
+         * Workaround for missing "no data" attribute in VGI files. As of September 2022, GCOM files have a
+         * "Land_DN" attribute for missing data caused by land, but no "Sea_DN" attribute for the converse.
+         * As an heuristic rule, if valid values are short integers and "no data" values are either absent
+         * of greater than `MISSING_NODATA`, add "missing values" category for all values up to 0xFFFF.
+         */
+        if (addMissingNodata) {
+            final double valid = data.getAttributeAsDouble(MAXIMUM);
+            if (valid >= 0x100 && valid < MISSING_NODATA) {
+                int label = FILL_VALUE_MASK | MISSING_VALUE_MASK;
+                for (int value=0xFFFF; value >= MISSING_NODATA; value--) {
+                    pads.putIfAbsent(value, label);
+                    label = MISSING_VALUE_MASK;
+                }
             }
         }
         return pads;
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
index 7250fa9369..65aaee48a3 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
@@ -533,14 +533,13 @@ public final class RasterResource extends AbstractGridCoverageResource implement
             listeners.warning(e);
         }
         /*
-         * Adds the "missing value" or "fill value" as qualitative categories.  If a value has both roles, use "missing value"
-         * as category name. If the sample values are already real values, then the "no data" values have been replaced by NaN
-         * values by Variable.replaceNaN(Object). The qualitative categories constructed below must be consistent with the NaN
-         * values created by `replaceNaN`.
+         * Adds the "missing value" or "fill value" as qualitative categories. If the sample values are already
+         * real values, then the "no data" values have been replaced by NaN values by Variable.replaceNaN(Object).
+         * The qualitative categories constructed below must be consistent with NaN values created by `replaceNaN`.
          */
         boolean setBackground = true;
+        int missingValueOrdinal = 0;
         int ordinal = band.hasRealValues() ? 0 : -1;
-        final CharSequence[] names = new CharSequence[2];
         for (final Map.Entry<Number,Object> entry : band.getNodataValues().entrySet()) {
             final Number n;
             if (ordinal >= 0) {
@@ -551,18 +550,16 @@ public final class RasterResource extends AbstractGridCoverageResource implement
             CharSequence name;
             final Object label = entry.getValue();
             if (label instanceof Integer) {
-                final int role = (Integer) label;               // Bit 0 set (value 1) = pad value, bit 1 set = missing value.
-                final int i = (role == Convention.FILL_VALUE_MASK) ? 1 : 0;   // i=1 if role is only pad value, i=0 otherwise.
-                name = names[i];
-                if (name == null) {
-                    name = Vocabulary.formatInternational(i == 0 ? Vocabulary.Keys.MissingValue : Vocabulary.Keys.FillValue);
-                    names[i] = name;
-                }
-                if (setBackground & (role & Convention.FILL_VALUE_MASK) != 0) {
+                final boolean isFill = setBackground && (((Integer) label) & Convention.FILL_VALUE_MASK) != 0;
+                name = Vocabulary.formatInternational(isFill ? Vocabulary.Keys.FillValue : Vocabulary.Keys.MissingValue);
+                if (isFill) {
                     setBackground = false;                      // Declare only one fill value.
                     builder.setBackground(name, n);
                     continue;
                 }
+                if (++missingValueOrdinal >= 2) {
+                    name = name.toString() + " #" + missingValueOrdinal;
+                }
             } else {
                 name = (CharSequence) label;
             }