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 5b8c29b65aef1d3543a37d6158a0026a61170437
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Tue Nov 30 00:18:46 2021 +0100

    Allow netCDF reader to distinguish "discrete coverage" from "continuous 
coverage".
    In GCOM-C case, the "QA_flag" (data quality) variable is a discrete 
coverage.
---
 .../java/org/apache/sis/coverage/Category.java     |  8 +++----
 .../org/apache/sis/coverage/SampleDimension.java   | 10 ++++----
 .../apache/sis/internal/earth/netcdf/GCOM_C.java   | 16 +++++++++----
 .../apache/sis/internal/netcdf/RasterResource.java | 10 +++++---
 .../org/apache/sis/internal/netcdf/Variable.java   |  2 ++
 .../apache/sis/internal/netcdf/VariableRole.java   | 27 +++++++++++++++++++---
 .../sis/internal/netcdf/impl/ChannelDecoder.java   |  6 +++--
 .../apache/sis/storage/netcdf/MetadataReader.java  |  2 +-
 8 files changed, 60 insertions(+), 21 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/Category.java 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/Category.java
index 8d21e70..019c4ea 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/Category.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/Category.java
@@ -227,14 +227,14 @@ public class Category implements Serializable {
         ArgumentChecks.ensureNonNull("samples", samples);
         if (units != null) {
             ArgumentChecks.ensureNonNull("toUnits", toUnits);
-            // The converse is not true: we allow 'units' to be null even if 
'toUnits' is non-null.
+            // The converse is not true: we allow `units` to be null even if 
`toUnits` is non-null.
         }
         this.name = Types.toInternationalString(name);
         final double  minimum = samples.getMinDouble(true);
         final double  maximum = samples.getMaxDouble(true);
         final boolean isNaN   = Double.isNaN(minimum);
         /*
-         * Following arguments check uses '!' in comparison in order to reject 
NaN values in quantitative category.
+         * Following arguments check uses `!` in comparison in order to reject 
NaN values in quantitative category.
          * For qualitative category, NaN is accepted provided that it is the 
same NaN for both ends of the range.
          */
         if (!(minimum <= maximum)) {
@@ -301,7 +301,7 @@ public class Category implements Serializable {
         name       = original.name;
         toConverse = Objects.requireNonNull(toSamples);
         /*
-         * Compute 'minimum' and 'maximum' (which must be real numbers) using 
the conversion from samples
+         * Compute `minimum` and `maximum` (which must be real numbers) using 
the conversion from samples
          * to real values. To be strict, we should use some numerical 
algorithm for finding a function's
          * minimum and maximum. For linear and logarithmic functions, minimum 
and maximum are always at
          * the bounding input values, so we are using a very simple algorithm 
for now.
@@ -449,7 +449,7 @@ public class Category implements Serializable {
     public Optional<MathTransform1D> getTransferFunction() {
         /*
          * Note: if this method is invoked on "real values category", then we 
need to return
-         * the identity transform instead of 'toConverse'. This is done by 
ConvertedCategory.
+         * the identity transform instead of `toConverse`. This is done by 
ConvertedCategory.
          */
         if (converse.isConvertedQualitative()) {
             return Optional.empty();
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java
index ce4e432..819634f 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java
@@ -1033,7 +1033,7 @@ public class SampleDimension implements Serializable {
          * @param  name     the category name as a {@link String} or {@link 
InternationalString} object.
          * @param  minimum  the minimum value (inclusive) in the given units.
          * @param  maximum  the maximum value (inclusive) in the given units.
-         * @param  units    the units of measurement.
+         * @param  units    the units of measurement, or {@code null} if 
unknown or not applicable.
          * @return {@code this}, for method call chaining.
          * @throws IllegalArgumentException if a value is NaN or if {@code 
minimum} is greater than {@code maximum}.
          */
@@ -1051,7 +1051,7 @@ public class SampleDimension implements Serializable {
          * @param  name     the category name as a {@link String} or {@link 
InternationalString} object.
          * @param  minimum  the minimum value (inclusive) in the given units.
          * @param  maximum  the maximum value (inclusive) in the given units.
-         * @param  units    the units of measurement.
+         * @param  units    the units of measurement, or {@code null} if 
unknown or not applicable.
          * @return {@code this}, for method call chaining.
          * @throws IllegalArgumentException if a value is NaN or if {@code 
minimum} is greater than {@code maximum}.
          */
@@ -1075,7 +1075,8 @@ public class SampleDimension implements Serializable {
          * @param  upper   the upper sample value, exclusive.
          * @param  scale   the scale value which is multiplied to sample 
values for the category. Must be different than zero.
          * @param  offset  the offset value to add to sample values for this 
category.
-         * @param  units   the units of measurement of values after conversion 
by the scale factor and offset.
+         * @param  units   the units of measurement of values after conversion 
by the scale factor and offset,
+         *                 or {@code null} if unknown or not applicable.
          * @return {@code this}, for method call chaining.
          * @throws IllegalArgumentException if {@code lower} is not smaller 
than {@code upper},
          *         or if {@code scale} or {@code offset} are not real numbers, 
or if {@code scale} is zero.
@@ -1100,7 +1101,8 @@ public class SampleDimension implements Serializable {
          * @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  units    the units of measurement of values after 
conversion by the transfer function.
+         * @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.
          * @throws ClassCastException if the range element class is not a 
{@link Number} subclass.
          * @throws IllegalArgumentException if the range is invalid.
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 ad4aebb..9df14da 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
@@ -113,7 +113,7 @@ import ucar.nc2.constants.CF;
  *
  * @author  Alexis Manin (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  *
  * @see <a href="http://global.jaxa.jp/projects/sat/gcom_c/";>SHIKISAI (GCOM-C) 
on JAXA</a>
  * @see <a 
href="https://en.wikipedia.org/wiki/Global_Change_Observation_Mission";>GCOM on 
Wikipedia</a>
@@ -128,6 +128,11 @@ public final class GCOM_C extends Convention {
     private static final Pattern SENTINEL_VALUE = 
Pattern.compile(".*\\bGCOM-C\\b.*");
 
     /**
+     * Name of the variable storing data quality flags.
+     */
+    private static final String QA_FLAG = "QA_flag";
+
+    /**
      * Mapping from ACDD or CF-Convention attribute names to names of 
attributes used by GCOM-C.
      * This map does not include attributes for geographic extent because the 
"Lower_left_latitude",
      * "Lower_left_longitude", "Lower_right_latitude", <i>etc.</i> attributes 
are difficult to use.
@@ -238,7 +243,7 @@ public final class GCOM_C extends Convention {
      */
     @Override
     public VariableRole roleOf(final Variable variable) {
-        VariableRole role = super.roleOf(variable);
+        final VariableRole role = super.roleOf(variable);
         if (role == VariableRole.COVERAGE) {
             /*
              * Exclude (for now) some variables associated to longitude and 
latitude: Obs_time, Sensor_zenith, Solar_zenith.
@@ -246,7 +251,10 @@ public final class GCOM_C extends Convention {
              */
             final String group = variable.getGroupName();
             if (GEOMETRY_DATA.equalsIgnoreCase(group)) {
-                role = VariableRole.OTHER;
+                return VariableRole.OTHER;
+            }
+            if (QA_FLAG.equals(variable.getName())) {
+                return VariableRole.DISCRETE_COVERAGE;
             }
         }
         return role;
@@ -272,7 +280,7 @@ public final class GCOM_C extends Convention {
     public String nameOfDimension(final Variable dataOrAxis, final int index) {
         String name = super.nameOfDimension(dataOrAxis, index);
         if (name == null) {
-            if ("QA_flag".equals(dataOrAxis.getName())) {
+            if (QA_FLAG.equals(dataOrAxis.getName())) {
                 /*
                  * The "QA_flag" variable is missing "Dim0" and "Dim1" 
attribute in GCOM-C version 1.00.
                  * However not all GCOM-C files use a localization grid. We 
use the presence of spatial
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 7fa5889..7001f2e 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
@@ -210,7 +210,7 @@ public final class RasterResource extends 
AbstractGridResource implements Resour
         final Map<GenericName,List<RasterResource>> byName = new HashMap<>();  
 // For detecting name collisions.
         for (int i=0; i<variables.length; i++) {
             final Variable variable = variables[i];
-            if (variable == null || variable.getRole() != 
VariableRole.COVERAGE) {
+            if (!VariableRole.isCoverage(variable)) {
                 continue;                                                   // 
Skip variables that are not grid coverages.
             }
             final GridGeometry grid = variable.getGridGeometry();
@@ -268,7 +268,7 @@ public final class RasterResource extends 
AbstractGridResource implements Resour
                         int suffixLength = name.length() - suffixStart;
                         for (int j=i; ++j < variables.length;) {
                             final Variable candidate = variables[j];
-                            if (candidate == null || candidate.getRole() != 
VariableRole.COVERAGE) {
+                            if (!VariableRole.isCoverage(candidate)) {
                                 variables[j] = null;                           
     // For avoiding to revisit that variable again.
                                 continue;
                             }
@@ -504,7 +504,11 @@ public final class RasterResource extends 
AbstractGridResource implements Resour
             } else {
                 String name = band.getDescription();
                 if (name == null) name = band.getName();
-                builder.addQuantitative(name, range, mt, band.getUnit());
+                if (band.getRole() == VariableRole.DISCRETE_COVERAGE) {
+                    builder.addQualitative(name, range);
+                } else {
+                    builder.addQuantitative(name, range, mt, band.getUnit());
+                }
             }
         } catch (TransformException e) {
             /*
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
index e71bfa5..55b301a 100644
--- 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
@@ -933,6 +933,8 @@ public abstract class Variable extends Node {
 
     /**
      * Builds the function converting values from their packed formats in the 
variable to "real" values.
+     * This method is invoked in contexts where a transfer function is assumed 
to exist. Consequently it
+     * shall never return {@code null}, but may return the identity function.
      */
     final TransferFunction getTransferFunction() {
         return decoder.convention().transferFunction(this);
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/VariableRole.java
 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/VariableRole.java
index 2c37ac8..97a092d 100644
--- 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/VariableRole.java
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/VariableRole.java
@@ -21,7 +21,7 @@ package org.apache.sis.internal.netcdf;
  * Specifies whether a variable is used as a coordinate system axis, a 
coverage or other purpose.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   1.0
  * @module
  */
@@ -32,11 +32,18 @@ public enum VariableRole {
     AXIS,
 
     /**
-     * The variable is a grid coverage.
+     * The variable is a continuous grid coverage.
+     * Interpolation between cells is allowed.
      */
     COVERAGE,
 
     /**
+     * The variable is a discrete grid coverage, for example data quality 
masks.
+     * Interpolation between cells is not allowed.
+     */
+    DISCRETE_COVERAGE,
+
+    /**
      * The variable is a property of a feature.
      */
     FEATURE,
@@ -50,5 +57,19 @@ public enum VariableRole {
     /**
      * Unidentified kind of variable.
      */
-    OTHER
+    OTHER;
+
+    /**
+     * Returns {@code true} if the role of the given variable is {@link 
#COVERAGE} or {@link #DISCRETE_COVERAGE}.
+     *
+     * @param  candidate  the variable for which to check the role, or {@code 
null}.
+     * @return whether the given variable is non-null and its role is a 
continuous or discrete coverage.
+     */
+    public static boolean isCoverage(final Variable candidate) {
+        if (candidate != null) {
+            final VariableRole role = candidate.getRole();
+            return (role == COVERAGE || role == DISCRETE_COVERAGE);
+        }
+        return false;
+    }
 }
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
index 6452e53..e0d9792 100644
--- 
a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
@@ -74,7 +74,7 @@ import org.apache.sis.math.Vector;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  *
  * @see <a 
href="http://portal.opengeospatial.org/files/?artifact_id=43734";>NetCDF Classic 
and 64-bit Offset Format (1.0)</a>
  *
@@ -974,7 +974,9 @@ public final class ChannelDecoder extends Decoder {
             final Map<DimensionInfo, List<VariableInfo>> dimToAxes = new 
IdentityHashMap<>();
             for (final VariableInfo variable : variables) {
                 switch (variable.getRole()) {
-                    case COVERAGE: {
+                    case COVERAGE:
+                    case DISCRETE_COVERAGE:
+                    {
                         // If Convention.roleOf(…) overwrote the value 
computed by VariableInfo,
                         // remember the new value for avoiding to ask again in 
next loops.
                         variable.isCoordinateSystemAxis = false;
diff --git 
a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
 
b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
index 7bd6ae3..be567fd 100644
--- 
a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
+++ 
b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
@@ -882,7 +882,7 @@ split:  while ((start = 
CharSequences.skipLeadingWhitespaces(value, start, lengt
     private void addContentInfo() {
         final Map<List<String>, List<Variable>> contents = new HashMap<>(4);
         for (final Variable variable : decoder.getVariables()) {
-            if (variable.getRole() == VariableRole.COVERAGE) {
+            if (VariableRole.isCoverage(variable)) {
                 final List<org.apache.sis.internal.netcdf.Dimension> 
dimensions = variable.getGridDimensions();
                 final String[] names = new String[dimensions.size()];
                 for (int i=0; i<names.length; i++) {

Reply via email to