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++) {