Avoid range collision if the minimum or maximum value in a netCDF file is equals to a pad/fill value.
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 b74c9c4..bd14569 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
@@ -110,7 +110,7 @@
*
* @author Alexis Manin (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
*
* @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>
@@ -430,11 +430,12 @@
* Returns the range of valid values, or {@code null} if unknown.
*
* @param data the variable to get valid range of values for.
+ * @param nodataValues the fill values and padding values.
* @return the range of valid values, or {@code null} if unknown.
*/
@Override
- public NumberRange<?> validRange(final Variable data) {
- NumberRange<?> range = super.validRange(data);
+ public NumberRange<?> validRange(final Variable data, final Set<Number> nodataValues) {
+ NumberRange<?> range = super.validRange(data, nodataValues);
if (range == null) {
final double min = data.getAttributeAsNumber("Minimum_valid_DN");
final double max = data.getAttributeAsNumber("Maximum_valid_DN");
@@ -470,7 +471,7 @@
/**
* Builds the function converting values from their packed formats in the variable to "real" values.
- * This method is invoked only if {@link #validRange(Variable)} returned a non-null value.
+ * This method is invoked only if {@link #validRange(Variable, Set)} returned a non-null value.
*
* @param data the variable from which to determine the transfer function.
* @return a transfer function built from the attributes defined in the given variable.
diff --git a/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/package-info.java b/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/package-info.java
index f353184..1381a2a 100644
--- a/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/package-info.java
+++ b/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/package-info.java
@@ -21,7 +21,7 @@
*
* @author Alexis Manin (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
* @since 1.0
* @module
*/
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
index 6310d20..03c3666 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
@@ -63,7 +63,7 @@
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @author Alexis Manin (Geomatys)
- * @version 1.0
+ * @version 1.1
*
* @see <a href="https://issues.apache.org/jira/browse/SIS-315">SIS-315</a>
*
@@ -92,7 +92,7 @@
/**
* Names of attributes where to fetch minimum and maximum sample values, in preference order.
*
- * @see #validRange(Variable)
+ * @see #validRange(Variable, Set)
*/
private static final String[] RANGE_ATTRIBUTES = {
"valid_range", // Expected "reasonable" range for variable.
@@ -555,13 +555,13 @@
* Otherwise if this method returns the range of real values, then that range shall be an instance
* of {@link MeasurementRange} for allowing the caller to distinguish the two cases.
*
- * @param data the variable to get valid range of values for.
- * This is usually a variable containing raster data.
+ * @param data the variable to get valid range of values for (usually a variable containing raster data).
+ * @param nodataValues the fill values and padding values.
* @return the range of valid values, or {@code null} if unknown.
*
* @see Variable#getRangeFallback()
*/
- public NumberRange<?> validRange(final Variable data) {
+ public NumberRange<?> validRange(final Variable data, final Set<Number> nodataValues) {
Number minimum = null;
Number maximum = null;
Class<? extends Number> type = null;
@@ -590,23 +590,47 @@
data.decoder.illegalAttributeValue(attribute, values.stringValue(i), e);
}
}
+ /*
+ * Stop the loop and return a range as soon as we have enough information.
+ * Note that we may loop over many attributes before to complete information.
+ */
if (minimum != null && maximum != null) {
/*
* Heuristic rule defined in UCAR documentation (see EnhanceScaleMissing interface):
* if the type of the range is equal to the type of the scale, and the type of the
* data is not wider, then assume that the minimum and maximum are real values.
*/
+ final Class<?> scaleType = data.getAttributeType(CDM.SCALE_FACTOR);
+ final Class<?> offsetType = data.getAttributeType(CDM.ADD_OFFSET);
final int rangeType = Numbers.getEnumConstant(type);
- if (rangeType >= data.getDataType().number &&
- rangeType >= Math.max(Numbers.getEnumConstant(data.getAttributeType(CDM.SCALE_FACTOR)),
- Numbers.getEnumConstant(data.getAttributeType(CDM.ADD_OFFSET))))
+ if ((scaleType != null || offsetType != null)
+ && rangeType >= data.getDataType().number
+ && rangeType >= Math.max(Numbers.getEnumConstant(scaleType),
+ Numbers.getEnumConstant(offsetType)))
{
@SuppressWarnings({"unchecked", "rawtypes"})
final NumberRange<?> range = new MeasurementRange(type, minimum, true, maximum, true, data.getUnit());
return range;
} else {
+ /*
+ * The range use sample values (before conversion to the unit of measurement).
+ * Before to return that range, check if the minimum or maximum overlaps with
+ * a pad value. If this is the case, resolve the overlapping by making that
+ * value exclusive instead than inclusive.
+ */
+ boolean isMinIncluded = true;
+ boolean isMaxIncluded = true;
+ if (!nodataValues.isEmpty()) {
+ final double minValue = minimum.doubleValue();
+ final double maxValue = maximum.doubleValue();
+ for (final Number pad : nodataValues) {
+ final double value = pad.doubleValue();
+ isMinIncluded &= (minValue != value);
+ isMaxIncluded &= (maxValue != value);
+ }
+ }
@SuppressWarnings({"unchecked", "rawtypes"})
- final NumberRange<?> range = new NumberRange(type, minimum, true, maximum, true);
+ final NumberRange<?> range = new NumberRange(type, minimum, isMinIncluded, maximum, isMaxIncluded);
return range;
}
}
@@ -616,7 +640,7 @@
/**
* Compares two numbers which shall be of the same class.
- * This is a helper method for {@link #validRange(Variable)}.
+ * This is a helper method for {@link #validRange(Variable, Set)}.
*/
@SuppressWarnings("unchecked")
private static int compare(final Number n1, final Number n2) {
@@ -669,8 +693,8 @@
* to be created for each variable.
*
* <p>This method is invoked in contexts where a transfer function is assumed to exist, for example
- * because {@link #validRange(Variable)} returned a non-null value. Consequently this method shall
- * never return {@code null}, but can return the identity function.</p>
+ * because {@link #validRange(Variable, Set)} returned a non-null value. Consequently this method
+ * shall never return {@code null}, but can return the identity function.</p>
*
* @param data the variable from which to determine the transfer function.
* This is usually a variable containing raster data.
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 308f47d..8860b1b 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
@@ -787,14 +787,14 @@
/**
* Returns the range of valid values, or {@code null} if unknown. This is a shortcut for
- * {@link Convention#validRange(Variable)} with a fallback on {@link #getRangeFallback()}.
+ * {@link Convention#validRange(Variable, Set)} with a fallback on {@link #getRangeFallback()}.
*
* @return the range of valid values, or {@code null} if unknown.
*
- * @see Convention#validRange(Variable)
+ * @see Convention#validRange(Variable, Set)
*/
final NumberRange<?> getValidRange() {
- NumberRange<?> range = decoder.convention().validRange(this);
+ NumberRange<?> range = decoder.convention().validRange(this, getNodataValues().keySet());
if (range == null) {
range = getRangeFallback();
}
@@ -803,8 +803,8 @@
/**
* Returns the range of values as determined by the data type or other means, or {@code null} if unknown.
- * This method is invoked only as a fallback if {@link Convention#validRange(Variable)} did not found a
- * range of values by application of CF conventions. The returned range may be a range of packed values
+ * This method is invoked only as a fallback if {@link Convention#validRange(Variable, Set)} did not found
+ * a range of values by application of CF conventions. The returned range may be a range of packed values
* or a range of real values. In the later case, the range shall be an instance of
* {@link org.apache.sis.measure.MeasurementRange}.
*
@@ -814,7 +814,7 @@
*
* @return the range of valid values, or {@code null} if unknown.
*
- * @see Convention#validRange(Variable)
+ * @see Convention#validRange(Variable, Set)
*/
protected NumberRange<?> getRangeFallback() {
final DataType dataType = getDataType();