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 <[email protected]> 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; }
