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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new f646188 Modify the way attribute values are stored in netCDF reader. This change makes easier to fix parsing of map projection parameters.
f646188 is described below
commit f6461882be269d21254cdc7b3651966ebaef4320
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Tue May 28 19:04:58 2019 +0200
Modify the way attribute values are stored in netCDF reader.
This change makes easier to fix parsing of map projection parameters.
---
.../org/apache/sis/internal/netcdf/Convention.java | 65 ++++----
.../org/apache/sis/internal/netcdf/DataType.java | 2 +-
.../org/apache/sis/internal/netcdf/Decoder.java | 31 +++-
.../java/org/apache/sis/internal/netcdf/Node.java | 170 ++++++++++++++++-----
.../apache/sis/internal/netcdf/RasterResource.java | 28 ++--
.../org/apache/sis/internal/netcdf/Resources.java | 5 +
.../sis/internal/netcdf/Resources.properties | 1 +
.../sis/internal/netcdf/Resources_fr.properties | 1 +
.../sis/internal/netcdf/impl/ChannelDecoder.java | 53 ++++---
.../sis/internal/netcdf/impl/FeaturesInfo.java | 18 +--
.../sis/internal/netcdf/impl/VariableInfo.java | 149 +++---------------
.../sis/internal/netcdf/ucar/DecoderWrapper.java | 2 +-
.../sis/internal/netcdf/ucar/GroupWrapper.java | 8 +-
.../sis/internal/netcdf/ucar/VariableWrapper.java | 54 ++++---
.../apache/sis/storage/netcdf/MetadataReader.java | 26 ++--
.../org/apache/sis/internal/netcdf/GridTest.java | 23 ++-
.../org/apache/sis/internal/netcdf/TestCase.java | 2 +-
.../apache/sis/internal/netcdf/VariableTest.java | 47 +++++-
.../sis/internal/netcdf/impl/VariableInfoTest.java | 13 --
19 files changed, 393 insertions(+), 305 deletions(-)
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 0c59f8e..699db49 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
@@ -439,7 +439,7 @@ public class Convention {
}
final Map<String,Object> definition = new HashMap<>();
definition.put(CF.GRID_MAPPING_NAME, method);
- for (final String name : node.getAttributeNames()) {
+ for (final String name : node.getAttributeNames()) try {
final String ln = name.toLowerCase(Locale.US);
Object value;
switch (ln) {
@@ -450,10 +450,10 @@ public class Convention {
* with the CRS is deprecated (the hard-coded WGS84 target datum is not always suitable) but still
* a common practice as of 2019. We require at least the 3 translation parameters.
*/
- final Object[] values = node.getAttributeValues(name, true);
- if (values.length < 3) continue;
+ final Vector values = node.getAttributeAsVector(name);
+ if (values == null || values.size() < 3) continue;
final BursaWolfParameters bp = new BursaWolfParameters(CommonCRS.WGS84.datum(), null);
- bp.setValues(Vector.create(values, false).doubleValues());
+ bp.setValues(values.doubleValues());
value = bp;
break;
}
@@ -472,23 +472,14 @@ public class Convention {
}
/*
* Assume that all map projection parameters in netCDF files are numbers or array of numbers.
- * If the array contains float value, returns a `float[]` array for letting the caller know
- * that it may need to use base 10 for conversion to `double[]` array.
+ * If values are array, then they are converted to an array of {@code double[]} type.
*/
- final Object[] values = node.getAttributeValues(name, true);
- switch (values.length) {
- case 0: continue; // Attribute not found or not numeric.
- case 1: value = values[0]; break; // This is the usual case.
- default: {
- boolean isFloat = false;
- for (final Object e : values) {
- isFloat = (e instanceof Float);
- if (!isFloat) break;
- }
- final Vector v = Vector.create(values, false);
- value = isFloat ? v.floatValues() : v.doubleValues();
- break;
- }
+ final Vector data = node.getAttributeAsVector(name);
+ if (data == null) continue;
+ switch (data.size()) {
+ case 0: continue;
+ case 1: value = data.get(0); break;
+ default: value = data.doubleValues(); break;
}
break;
}
@@ -496,6 +487,9 @@ public class Convention {
if (definition.putIfAbsent(name, value) != null) {
node.error(Convention.class, "projection", null, Errors.Keys.DuplicatedIdentifier_1, name);
}
+ } catch (NumberFormatException e) {
+ // May happen in the vector contains number stored as texts.
+ node.decoder.illegalAttributeValue(name, node.getAttributeAsString(name), e);
}
return definition;
}
@@ -574,24 +568,28 @@ public class Convention {
Number maximum = null;
Class<? extends Number> type = null;
for (final String attribute : RANGE_ATTRIBUTES) {
- for (final Object element : data.getAttributeValues(attribute, true)) {
- if (element instanceof Number) {
- Number value = (Number) element;
- if (element instanceof Float) {
- final float fp = (Float) element;
+ final Vector values = data.getAttributeAsVector(attribute);
+ if (values != null) {
+ final int length = values.size();
+ for (int i=0; i<length; i++) try {
+ Number value = values.get(i); // May throw NumberFormatException if value was stored as text.
+ if (value instanceof Float) {
+ final float fp = (Float) value;
if (fp == +Float.MAX_VALUE) value = Float.POSITIVE_INFINITY;
else if (fp == -Float.MAX_VALUE) value = Float.NEGATIVE_INFINITY;
- } else if (element instanceof Double) {
- final double fp = (Double) element;
+ } else if (value instanceof Double) {
+ final double fp = (Double) value;
if (fp == +Double.MAX_VALUE) value = Double.POSITIVE_INFINITY;
else if (fp == -Double.MAX_VALUE) value = Double.NEGATIVE_INFINITY;
}
- type = Numbers.widestClass(type, value.getClass());
+ type = Numbers.widestClass(type, value.getClass());
minimum = Numbers.cast(minimum, type);
maximum = Numbers.cast(maximum, type);
value = Numbers.cast(value, type);
if (!attribute.endsWith("max") && (minimum == null || compare(value, minimum) < 0)) minimum = value;
if (!attribute.endsWith("min") && (maximum == null || compare(value, maximum) > 0)) maximum = value;
+ } catch (NumberFormatException e) {
+ data.decoder.illegalAttributeValue(attribute, values.stringValue(i), e);
}
}
if (minimum != null && maximum != null) {
@@ -651,9 +649,14 @@ public class Convention {
public Map<Number,Object> nodataValues(final Variable data) {
final Map<Number,Object> pads = new LinkedHashMap<>();
for (int i=0; i < NODATA_ATTRIBUTES.length; i++) {
- for (final Object value : data.getAttributeValues(NODATA_ATTRIBUTES[i], true)) {
- if (value instanceof Number) {
- pads.merge((Number) value, 1 << i, (v1, v2) -> ((Integer) v1) | ((Integer) v2));
+ final String name = NODATA_ATTRIBUTES[i];
+ final Vector values = data.getAttributeAsVector(name);
+ if (values != null) {
+ final int length = values.size();
+ for (int j=0; j<length; j++) try {
+ pads.merge(values.get(i), 1 << i, (v1, v2) -> ((Integer) v1) | ((Integer) v2));
+ } catch (NumberFormatException e) {
+ data.decoder.illegalAttributeValue(name, values.stringValue(i), e);
}
}
}
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/DataType.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/DataType.java
index b86a8cc..45a4184 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/DataType.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/DataType.java
@@ -94,7 +94,7 @@ public enum DataType {
USHORT(Numbers.SHORT, true, true, (byte) 3, DataBuffer.TYPE_USHORT),
/**
- * 43 bits unsigned integer (netCDF type 9).
+ * 32 bits unsigned integer (netCDF type 9).
* Not available in netCDF classic format.
*/
UINT(Numbers.INTEGER, true, true, (byte) 4, DataBuffer.TYPE_INT),
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
index 6e069df..4977861 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Decoder.java
@@ -229,24 +229,45 @@ public abstract class Decoder extends ReferencingFactoryContainer implements Clo
/**
* Convenience method for {@link #numericValue(String)} implementation.
*
+ * @param name the attribute name, used only in case of error.
* @param value the attribute value to parse.
* @return the parsed attribute value, or {@code null} if the given value can not be parsed.
*/
- protected final Number parseNumber(String value) {
+ protected final Number parseNumber(final String name, String value) {
final int s = value.indexOf(' ');
if (s >= 0) {
/*
* Sometime, numeric values as string are followed by
- * a unit of measurement. We ignore that unit for now...
+ * a unit of measurement. We ignore that unit for now.
*/
value = value.substring(0, s);
}
+ Number n;
try {
- return Double.valueOf(value);
+ if (value.indexOf('.') >= 0) {
+ n = Double.valueOf(value);
+ } else {
+ n = Long.valueOf(value);
+ }
} catch (NumberFormatException e) {
- listeners.warning(null, e);
+ illegalAttributeValue(name, value, e);
+ n = null;
}
- return null;
+ return n;
+ }
+
+ /**
+ * Logs a warning for an illegal attribute value. This may be due to a failure to parse a string as a number.
+ * This method should be invoked from methods that are invoked only once per attribute because we do not keep
+ * track of which warnings have already been emitted.
+ *
+ * @param name the attribute name.
+ * @param value the illegal value.
+ * @param e the exception, or {@code null} if none.
+ */
+ final void illegalAttributeValue(final String name, final String value, final NumberFormatException e) {
+ listeners.warning(Resources.forLocale(listeners.getLocale()).getString(
+ Resources.Keys.IllegalAttributeValue_3, getFilename(), name, value), e);
}
/**
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Node.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Node.java
index 0135eef..876920a 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Node.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Node.java
@@ -18,7 +18,9 @@ package org.apache.sis.internal.netcdf;
import java.util.Locale;
import java.util.Collection;
+import org.apache.sis.math.Vector;
import org.apache.sis.math.DecimalFunctions;
+import org.apache.sis.util.CharSequences;
import org.apache.sis.util.resources.Errors;
@@ -54,8 +56,8 @@ public abstract class Node extends NamedElement {
public abstract Collection<String> getAttributeNames();
/**
- * Returns the type of the attribute of the given name,
- * or {@code null} if the given attribute is not found.
+ * Returns the type of the attribute of the given name, or {@code null} if the given attribute is not found.
+ * If the attribute contains more than one value, then this method returns {@code Vector.class}.
*
* @param attributeName the name of the attribute for which to get the type.
* @return type of the given attribute, or {@code null} if the attribute does not exist.
@@ -65,56 +67,111 @@ public abstract class Node extends NamedElement {
public abstract Class<?> getAttributeType(String attributeName);
/**
- * Returns the sequence of values for the given attribute, or an empty array if none.
- * The elements will be of class {@link String} if {@code numeric} is {@code false},
- * or {@link Number} if {@code numeric} is {@code true}. Some elements may be null
- * if they are not of the expected type.
+ * Returns the single value or vector of values for the given attribute, or {@code null} if none.
+ * The returned value can be an instance of:
+ *
+ * <ul>
+ * <li>{@link String} if the attribute contains a single textual value.</li>
+ * <li>{@link Number} if the attribute contains a single numerical value.</li>
+ * <li>{@link Vector} if the attribute contains many numerical values.</li>
+ * <li>{@code String[]} if the attribute contains many textual values.</li>
+ * </ul>
+ *
+ * If the value is a {@code String}, then leading and trailing spaces and control characters
+ * should be trimmed by {@link String#trim()}.
*
* @param attributeName the name of the attribute for which to get the values.
- * @param numeric {@code true} if the values are expected to be numeric, or {@code false} for strings.
- * @return the sequence of {@link String} or {@link Number} values for the named attribute.
- * May contain null elements.
+ * @return value(s) for the named attribute, or {@code null} if none.
*/
- public abstract Object[] getAttributeValues(String attributeName, boolean numeric);
+ protected abstract Object getAttributeValue(String attributeName);
/**
- * Returns the singleton value for the given attribute, or {@code null} if none or ambiguous.
+ * Returns the value of the given attribute as a non-blank string with leading/trailing spaces removed.
*
* @param attributeName the name of the attribute for which to get the value.
- * @param numeric {@code true} if the value is expected to be numeric, or {@code false} for string.
- * @return the {@link String} or {@link Number} value for the named attribute.
+ * @return the singleton attribute value, or {@code null} if none, empty, blank or ambiguous.
*/
- private Object getAttributeValue(final String attributeName, final boolean numeric) {
- Object singleton = null;
- for (final Object value : getAttributeValues(attributeName, numeric)) {
- if (value != null) {
- if (singleton != null && !singleton.equals(value)) { // Paranoiac check.
- return null;
- }
- singleton = value;
+ public final String getAttributeAsString(final String attributeName) {
+ final Object value = getAttributeValue(attributeName);
+ if (value == null || value instanceof String) {
+ return (String) value;
+ }
+ final String[] values = toArray(value);
+ if (values == null) {
+ return value.toString();
+ }
+ String singleton = null;
+ for (final String c : values) {
+ if (singleton == null) {
+ singleton = c;
+ } else if (!singleton.equals(c)) {
+ return null;
}
}
return singleton;
}
/**
- * Returns the value of the given attribute as a non-blank string with leading/trailing spaces removed.
- * This is a convenience method for {@link #getAttributeValues(String, boolean)} when a singleton value
- * is expected and blank strings ignored.
+ * Returns the values of the given attribute as an array of non-blank texts.
+ * If the attribute is not stored as an array in the netCDF file, then this
+ * method splits the single {@link String} value around the given separator.
*
- * @param attributeName the name of the attribute for which to get the value.
- * @return the singleton attribute value, or {@code null} if none, empty, blank or ambiguous.
+ * @param attributeName the name of the attribute for which to get the values.
+ * @param separator separator to use for splitting a single {@link String} value into a list of values.
+ * @return the attribute values, or {@code null} if none.
*/
- public String getAttributeAsString(final String attributeName) {
- final Object value = getAttributeValue(attributeName, false);
+ public final CharSequence[] getAttributeAsStrings(final String attributeName, final char separator) {
+ final Object value = getAttributeValue(attributeName);
if (value != null) {
- final String text = value.toString().trim();
- if (!text.isEmpty()) return text;
+ CharSequence[] ts = toArray(value);
+ if (ts == null) {
+ ts = CharSequences.split(value.toString(), separator);
+ }
+ if (ts.length != 0) {
+ return ts;
+ }
}
return null;
}
/**
+ * Converts the given value into an array of strings, or returns {@code null}
+ * if the given value is not an array or a vector or contains only null values.
+ */
+ private static String[] toArray(final Object value) {
+ final String[] array;
+ if (value instanceof Object[]) {
+ final Object[] values = (Object[]) value;
+ array = new String[values.length];
+ for (int i=0; i<array.length; i++) {
+ final Object e = values[i];
+ if (e != null) {
+ array[i] = e.toString();
+ }
+ }
+ } else if (value instanceof Vector) {
+ final Vector values = (Vector) value;
+ array = new String[values.size()];
+ for (int i=0; i<array.length; i++) {
+ array[i] = values.stringValue(i);
+ }
+ } else {
+ return null;
+ }
+ boolean hasValues = false;
+ for (int i=0; i<array.length; i++) {
+ String e = array[i];
+ if (e != null) {
+ e = e.trim();
+ if (e.isEmpty()) e = null;
+ else hasValues = true;
+ array[i] = e;
+ }
+ }
+ return hasValues ? array : null;
+ }
+
+ /**
* Returns the value of the given attribute as a number, or {@link Double#NaN}.
* If the number is stored with single-precision, it is assumed casted from a
* representation in base 10.
@@ -123,16 +180,55 @@ public abstract class Node extends NamedElement {
* @return the singleton attribute value, or {@code NaN} if none or ambiguous.
*/
public final double getAttributeAsNumber(final String attributeName) {
- final Object value = getAttributeValue(attributeName, true);
+ final Object value = getAttributeValue(attributeName);
+ Number singleton = null;
if (value instanceof Number) {
- double dp = ((Number) value).doubleValue();
- final float sp = (float) dp;
- if (sp == dp) { // May happen even if the number was stored as a double.
- dp = DecimalFunctions.floatToDouble(sp);
+ singleton = (Number) value;
+ } else if (value instanceof String) {
+ singleton = decoder.parseNumber(attributeName, (String) value);
+ } else if (value instanceof Vector) {
+ final Vector data = (Vector) value;
+ final int length = data.size();
+ for (int i=0; i<length; i++) {
+ final Number n = data.get(i);
+ if (n != null) {
+ if (singleton == null) {
+ singleton = n;
+ } else if (!singleton.equals(n)) {
+ return Double.NaN;
+ }
+ }
}
- return dp;
}
- return Double.NaN;
+ if (singleton == null) {
+ return Double.NaN;
+ }
+ double dp = singleton.doubleValue();
+ final float sp = (float) dp;
+ if (sp == dp) { // May happen even if the number was stored as a double.
+ dp = DecimalFunctions.floatToDouble(sp);
+ }
+ return dp;
+ }
+
+ /**
+ * Returns the values of the given attribute as a vector of numbers, or {@code null} if none.
+ * If the numbers are stored with single-precision, they are assumed casted from a representation in base 10.
+ *
+ * @param attributeName the name of the attribute for which to get the values.
+ * @return the attribute values, or {@code null} if none, ambiguous or not a vector.
+ */
+ public final Vector getAttributeAsVector(final String attributeName) {
+ final Object value = getAttributeValue(attributeName);
+ if (value instanceof Vector) {
+ return (Vector) value;
+ } else if (value instanceof Float) {
+ return Vector.createForDecimal(new float[] {(Float) value});
+ } else if (value instanceof Number) {
+ return Vector.create(new Number[] {(Number) value}, false);
+ } else {
+ return null;
+ }
}
/**
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 08ffea1..ee94c4a 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
@@ -45,8 +45,8 @@ import org.apache.sis.storage.Resource;
import org.apache.sis.math.MathFunctions;
import org.apache.sis.measure.MeasurementRange;
import org.apache.sis.measure.NumberRange;
+import org.apache.sis.math.Vector;
import org.apache.sis.util.Numbers;
-import org.apache.sis.util.CharSequences;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.internal.jdk9.JDK9;
@@ -500,24 +500,22 @@ public final class RasterResource extends AbstractGridResource implements Resour
* @return {@code true} if flag attributes have been found, or {@code false} otherwise.
*/
private static boolean createEnumeration(final SampleDimension.Builder builder, final Variable band, final int index) {
- Object[] names = band.getAttributeValues(AttributeNames.FLAG_NAMES, false);
- if (names.length == 0) {
- names = band.getAttributeValues(AttributeNames.FLAG_MEANINGS, false);
- if (names.length == 0) return false;
+ CharSequence[] names = band.getAttributeAsStrings(AttributeNames.FLAG_NAMES, ' ');
+ if (names == null) {
+ names = band.getAttributeAsStrings(AttributeNames.FLAG_MEANINGS, ' ');
+ if (names == null) return false;
}
- Object[] values = band.getAttributeValues(AttributeNames.FLAG_VALUES, true);
- if (values.length == 0) {
- values = band.getAttributeValues(AttributeNames.FLAG_MASKS, true);
- if (values.length == 0) return false;
+ Vector values = band.getAttributeAsVector(AttributeNames.FLAG_VALUES);
+ if (values == null) {
+ values = band.getAttributeAsVector(AttributeNames.FLAG_MASKS);
+ if (values == null) return false;
}
- if (names.length == 1) {
- names = CharSequences.split((CharSequence) names[0], ' ');
- }
- for (int i=0; i<values.length; i++) {
- final Number value = (Number) values[i];
+ final int length = values.size();
+ for (int i=0; i<length; i++) {
+ final Number value = values.get(i);
final CharSequence name;
if (i < names.length) {
- name = (CharSequence) names[i];
+ name = names[i];
} else {
name = Vocabulary.formatInternational(Vocabulary.Keys.Unnamed);
}
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.java
index 4baff51..be7780a 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.java
@@ -118,6 +118,11 @@ public final class Resources extends IndexedResourceBundle {
public static final short DuplicatedAxis_2 = 7;
/**
+ * Illegal value “{2}” for attribute “{1}” in netCDF file “{0}”.
+ */
+ public static final short IllegalAttributeValue_3 = 21;
+
+ /**
* Illegal value range {2,number} … {3,number} for variable “{1}” in netCDF file “{0}”.
*/
public static final short IllegalValueRange_4 = 16;
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.properties b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.properties
index 82f6080..839956f 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.properties
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.properties
@@ -30,6 +30,7 @@ CanNotUseAxis_1 = Can not use axis \u201c{0}\u201d in a grid g
CanNotUseUCAR = Can not use UCAR library for netCDF format. Fallback on Apache SIS implementation.
DimensionNotFound_3 = Dimension \u201c{2}\u201d declared by attribute \u201c{1}\u201d is not found in the \u201c{0}\u201d file.
DuplicatedAxis_2 = Duplicated axis \u201c{1}\u201d in a grid of netCDF file \u201c{0}\u201d.
+IllegalAttributeValue_3 = Illegal value \u201c{2}\u201d for attribute \u201c{1}\u201d in netCDF file \u201c{0}\u201d.
IllegalValueRange_4 = Illegal value range {2,number} \u2026 {3,number} for variable \u201c{1}\u201d in netCDF file \u201c{0}\u201d.
MismatchedVariableSize_3 = The declared size of variable \u201c{1}\u201d in netCDF file \u201c{0}\u201d is {2,number} bytes greater than expected.
MismatchedVariableType_3 = Variables \u201c{1}\u201d and \u201c{2}\u201d in netCDF file \u201c{0}\u201d does not have the same type.
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources_fr.properties b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources_fr.properties
index 79e59f4..bf8df73 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources_fr.properties
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources_fr.properties
@@ -35,6 +35,7 @@ CanNotUseAxis_1 = Ne peut pas utiliser l\u2019axe \u00ab\u202f
CanNotUseUCAR = Ne peut pas utiliser la biblioth\u00e8que de l\u2019UCAR pour le format netCDF. L\u2019impl\u00e9mentation de Apache SIS sera utilis\u00e9e \u00e0 la place.
DimensionNotFound_3 = La dimension \u00ab\u202f{2}\u202f\u00bb d\u00e9clar\u00e9e par l\u2019attribut \u00ab\u202f{1}\u202f\u00bb n\u2019a pas \u00e9t\u00e9 trouv\u00e9e dans le fichier \u00ab\u202f{0}\u202f\u00bb.
DuplicatedAxis_2 = Axe \u00ab\u202f{1}\u202f\u00bb dupliqu\u00e9 dans une grille du fichier netCDF \u00ab\u202f{0}\u202f\u00bb.
+IllegalAttributeValue_3 = La valeur \u00ab\u202f{2}\u202f\u00bb est ill\u00e9gale pour l\u2019attribut \u00ab\u202f{1}\u202f\u00bb dans le fichier netCDF \u00ab\u202f{0}\u202f\u00bb.
IllegalValueRange_4 = Plage de valeurs {2,number} \u2026 {3,number} ill\u00e9gale pour la variable \u00ab\u202f{1}\u202f\u00bb dans le fichier netCDF \u00ab\u202f{0}\u202f\u00bb.
MismatchedVariableSize_3 = La longueur d\u00e9clar\u00e9e de la variable \u00ab\u202f{1}\u202f\u00bb dans le fichier netCDF \u00ab\u202f{0}\u202f\u00bb d\u00e9passe de {2,number} octets la valeur attendue.
MismatchedVariableType_3 = Les variables \u00ab\u202f{1}\u202f\u00bb et \u00ab\u202f{2}\u202f\u00bb dans le fichier netCDF \u00ab\u202f{0}\u202f\u00bb ne sont pas du m\u00eame type.
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 70113e6..38a7804 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
@@ -57,11 +57,13 @@ import org.apache.sis.storage.DataStore;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.DataStoreContentException;
import org.apache.sis.util.iso.DefaultNameSpace;
+import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.util.logging.WarningListeners;
import org.apache.sis.setup.GeometryLibrary;
import org.apache.sis.measure.Units;
+import org.apache.sis.math.Vector;
import ucar.nc2.constants.CF;
@@ -184,6 +186,7 @@ public final class ChannelDecoder extends Decoder {
/**
* The attributes found in the netCDF file.
* Values in this map give directly the attribute value (there is no {@code Attribute} object).
+ * Values are {@link String}, wrappers such as {@link Double}, or {@link Vector} objects.
*
* @see #findAttribute(String)
*/
@@ -416,8 +419,8 @@ public final class ChannelDecoder extends Decoder {
/**
* Returns the values of the given type. In the given type is {@code CHAR}, then this method returns the values
- * as a {@link String}. Otherwise this method returns the value as an array of the corresponding primitive type
- * and the given length.
+ * as a {@link String}. Otherwise if the length is 1, then this method returns the primitive value in its wrapper.
+ * Otherwise this method returns the value as a {@link Vector} of the corresponding primitive type and given length.
*
* <p>If the value is a {@code String}, then leading and trailing spaces and control characters have been trimmed
* by {@link String#trim()}.</p>
@@ -425,10 +428,10 @@ public final class ChannelDecoder extends Decoder {
* @return the value, or {@code null} if it was an empty string or an empty array.
*/
private Object readValues(final DataType type, final int length) throws IOException, DataStoreContentException {
- if (length == 0) {
- return null;
- }
- if (length < 0) {
+ if (length <= 0) {
+ if (length == 0) {
+ return null;
+ }
throw malformedHeader();
}
if (length == 1) {
@@ -444,6 +447,7 @@ public final class ChannelDecoder extends Decoder {
case DOUBLE: return input.readDouble();
}
}
+ final Object data;
switch (type) {
case CHAR: {
final String text = input.readString(length, encoding);
@@ -455,41 +459,49 @@ public final class ChannelDecoder extends Decoder {
final byte[] array = new byte[length];
input.readFully(array);
align(length);
- return array;
+ data = array;
+ break;
}
case SHORT:
case USHORT: {
final short[] array = new short[length];
input.readFully(array, 0, length);
align(length << 1);
- return array;
+ data = array;
+ break;
}
case INT:
case UINT: {
final int[] array = new int[length];
input.readFully(array, 0, length);
- return array;
+ data = array;
+ break;
}
case INT64:
case UINT64: {
final long[] array = new long[length];
input.readFully(array, 0, length);
- return array;
+ data = array;
+ break;
}
case FLOAT: {
final float[] array = new float[length];
input.readFully(array, 0, length);
- return array;
+ return Vector.createForDecimal(array);
}
case DOUBLE: {
final double[] array = new double[length];
input.readFully(array, 0, length);
- return array;
+ final float[] asFloats = ArraysExt.copyAsFloatsIfLossless(array);
+ if (asFloats != null) return Vector.createForDecimal(asFloats);
+ data = array;
+ break;
}
default: {
throw malformedHeader();
}
}
+ return Vector.create(data, type.isUnsigned);
}
/**
@@ -532,8 +544,9 @@ public final class ChannelDecoder extends Decoder {
* <li>The actual values as a variable length list (use {@link #readValues(DataType,int)})</li>
* </ul>
*
- * If the value is a {@code String}, then leading and trailing spaces and control characters
- * have been trimmed by {@link String#trim()}.
+ * If the value is a {@code String}, then leading and trailing spaces and control characters have been
+ * trimmed by {@link String#trim()}. If the value has more than one element, then the values are stored
+ * in a {@link Vector}.
*
* @param nelems the number of attributes to read.
*/
@@ -813,11 +826,15 @@ public final class ChannelDecoder extends Decoder {
@Override
public Number numericValue(final String name) {
final Object value = findAttribute(name);
- if (value instanceof String) {
- return parseNumber((String) value);
+ if (value instanceof Number) {
+ return (Number) value;
+ } else if (value instanceof String) {
+ return parseNumber(name, (String) value);
+ } else if (value instanceof Vector) {
+ return ((Vector) value).get(0);
+ } else {
+ return null;
}
- final Number[] v = VariableInfo.numberValues(value);
- return (v.length != 0) ? v[0] : null;
}
/**
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
index 0bcd1fb..373f7af 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
@@ -156,8 +156,8 @@ final class FeaturesInfo extends DiscreteSampling {
* Returns {@code true} if the given attribute value is one of the {@code cf_role} attribute values
* supported by this implementation.
*/
- private static boolean isSupportedRole(final Object role) {
- return (role instanceof String) && ((String) role).equalsIgnoreCase(CF.TRAJECTORY_ID);
+ private static boolean isSupportedRole(final String role) {
+ return CF.TRAJECTORY_ID.equalsIgnoreCase(role);
}
/**
@@ -186,10 +186,10 @@ search: for (final VariableInfo counts : decoder.variables) {
* counts:sample_dimension = "points";
*/
if (counts.dimensions.length == 1 && counts.getDataType().isInteger) {
- final Object sampleDimName = counts.getAttributeValue(CF.SAMPLE_DIMENSION);
- if (sampleDimName instanceof String) {
+ final String sampleDimName = counts.getAttributeAsString(CF.SAMPLE_DIMENSION);
+ if (sampleDimName != null) {
final DimensionInfo featureDimension = counts.dimensions[0];
- final DimensionInfo sampleDimension = decoder.findDimension((String) sampleDimName);
+ final DimensionInfo sampleDimension = decoder.findDimension(sampleDimName);
if (sampleDimension == null) {
decoder.listeners.warning(decoder.resources().getString(Resources.Keys.DimensionNotFound_3,
decoder.getFilename(), counts.getName(), sampleDimName), null);
@@ -203,11 +203,11 @@ search: for (final VariableInfo counts : decoder.variables) {
* part of CF convention and will be accepted only if there is no ambiguity.
*/
VariableInfo identifiers = decoder.findVariable(featureDimension.name);
- if (identifiers == null || !isSupportedRole(identifiers.getAttributeValue(CF.CF_ROLE))) {
+ if (identifiers == null || !isSupportedRole(identifiers.getAttributeAsString(CF.CF_ROLE))) {
VariableInfo replacement = null;
for (final VariableInfo alt : decoder.variables) {
if (alt.dimensions.length != 0 && alt.dimensions[0] == featureDimension
- && isSupportedRole(alt.getAttributeValue(CF.CF_ROLE)))
+ && isSupportedRole(alt.getAttributeAsString(CF.CF_ROLE)))
{
if (replacement != null) {
replacement = null;
@@ -267,10 +267,10 @@ search: for (final VariableInfo counts : decoder.variables) {
final List<VariableInfo> properties = new ArrayList<>();
for (final VariableInfo data : decoder.variables) {
if (data.dimensions.length == 1 && data.dimensions[0] == sampleDimension) {
- final Object axisType = data.getAttributeValue(CF.AXIS);
+ final String axisType = data.getAttributeAsString(CF.AXIS);
if (axisType == null) {
properties.add(data);
- } else if (coordinates.put(axisType.toString(), data) != null) {
+ } else if (coordinates.put(axisType, data) != null) {
continue search; // Two axes of the same type: abort.
}
}
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java
index 40e8fc6..be6f2e7 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java
@@ -24,7 +24,6 @@ import java.util.HashSet;
import java.util.Collection;
import java.util.Collections;
import java.util.regex.Matcher;
-import java.lang.reflect.Array;
import java.io.IOException;
import javax.measure.Unit;
import ucar.nc2.constants.CF;
@@ -47,6 +46,7 @@ import org.apache.sis.storage.DataStoreContentException;
import org.apache.sis.storage.netcdf.AttributeNames;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.ArraysExt;
+import org.apache.sis.util.Classes;
import org.apache.sis.util.Numbers;
import org.apache.sis.measure.Units;
import org.apache.sis.math.Vector;
@@ -65,11 +65,6 @@ import org.apache.sis.math.Vector;
*/
final class VariableInfo extends Variable implements Comparable<VariableInfo> {
/**
- * The array to be returned by {@link #numberValues(Object)} when the given value is null.
- */
- private static final Number[] EMPTY = new Number[0];
-
- /**
* The names of attributes where to look for the description to be returned by {@link #getDescription()}.
* We use the same attributes than the one documented in the {@link ucar.nc2.Variable#getDescription()} javadoc.
*/
@@ -127,17 +122,13 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo> {
* Values can be:
*
* <ul>
- * <li>a {@link String}</li>
- * <li>A {@link Number}</li>
- * <li>an array of primitive type</li>
+ * <li>{@link String} if the attribute contains a single textual value.</li>
+ * <li>{@link Number} if the attribute contains a single numerical value.</li>
+ * <li>{@link Vector} if the attribute contains many numerical values.</li>
* </ul>
*
* If the value is a {@code String}, then leading and trailing spaces and control characters
* should be trimmed by {@link String#trim()}.
- *
- * @see #stringValues(Object)
- * @see #numberValues(Object)
- * @see #booleanValue(Object)
*/
private final Map<String,Object> attributes;
@@ -211,8 +202,8 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo> {
this.dimensions = dimensions;
this.attributes = attributes;
final Object isUnsigned = getAttributeValue(CDM.UNSIGNED, "_unsigned");
- if (isUnsigned != null) {
- dataType = dataType.unsigned(booleanValue(isUnsigned));
+ if (isUnsigned instanceof String) {
+ dataType = dataType.unsigned(Boolean.valueOf((String) isUnsigned));
}
this.dataType = dataType;
/*
@@ -272,15 +263,13 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo> {
* enumeration since those attributes may be verbose and "pollute" the variable definition.
*/
if (!attributes.isEmpty()) { // For avoiding UnsupportedOperationException if unmodifiable map.
- String[] meanings = stringValues(attributes.remove(AttributeNames.FLAG_MEANINGS));
- switch (meanings.length) {
- case 0: meanings = null; break;
- case 1: meanings = (String[]) CharSequences.split(meanings[0], ' '); break;
+ final Object flags = attributes.remove(AttributeNames.FLAG_MEANINGS);
+ if (flags != null) {
+ meanings = (String[]) CharSequences.split(flags.toString(), ' ');
+ return;
}
- this.meanings = meanings;
- } else {
- meanings = null;
}
+ meanings = null;
}
/**
@@ -514,18 +503,15 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo> {
}
/**
- * Returns the type of the attribute of the given name,
- * or {@code null} if the given attribute is not found.
+ * Returns the type of the attribute of the given name, or {@code null} if the given attribute is not found.
+ * If the attribute contains more than one value, then this method returns a {@code Vector.class} subtype.
+ *
+ * @param attributeName the name of the attribute for which to get the type, preferably in lowercase.
+ * @return type of the given attribute, or {@code null} if the attribute does not exist.
*/
@Override
public Class<?> getAttributeType(final String attributeName) {
- final Object value = getAttributeValue(attributeName);
- if (value != null) {
- final Class<?> type = value.getClass();
- final Class<?> c = type.getComponentType();
- return (c != null) ? c : type;
- }
- return null;
+ return Classes.getClass(getAttributeValue(attributeName));
}
/**
@@ -549,108 +535,15 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo> {
* This method does not search the lower-case variant of the given name because the argument given to this method
* is usually a hard-coded value from {@link CF} or {@link CDM} conventions, which are already in lower-cases.
*
- * <p>All {@code getAttributeValue(…)} methods in this class ultimately invokes this method.
- * This provide a single point to override if the functionality needs to be extended.</p>
+ * <p>All other {@code getAttributeFoo(…)} methods in this class or parent class ultimately invoke this method.
+ * This provides a single point to override if the functionality needs to be extended.</p>
*
* @param attributeName name of attribute to search, in the expected case.
* @return variable attribute value of the given name, or {@code null} if none.
*/
- final Object getAttributeValue(final String attributeName) {
- return attributes.get(attributeName);
- }
-
- /**
- * Returns the sequence of values for the given attribute, or an empty array if none.
- * The elements will be of class {@link String} if {@code numeric} is {@code false},
- * or {@link Number} if {@code numeric} is {@code true}.
- */
@Override
- public Object[] getAttributeValues(final String attributeName, final boolean numeric) {
- final Object value = getAttributeValue(attributeName);
- return numeric ? numberValues(value) : stringValues(value);
- }
-
- /**
- * Returns the value of the given attribute as a non-blank string, or {@code null} if none.
- */
- @Override
- public String getAttributeAsString(final String attributeName) {
- final Object value = getAttributeValue(attributeName);
- if (value instanceof String) {
- final String text = ((String) value).trim();
- if (!text.isEmpty()) return text;
- }
- return null;
- }
-
- /**
- * Returns the attribute values as an array of {@link String}s, or an empty array if none.
- * The given argument is typically a value of the {@link #attributes} map.
- *
- * @see #getAttributeValues(String, boolean)
- */
- static String[] stringValues(final Object value) {
- if (value == null) {
- return CharSequences.EMPTY_ARRAY;
- }
- if (value.getClass().isArray()) {
- final String[] values = new String[Array.getLength(value)];
- for (int i=0; i<values.length; i++) {
- values[i] = Array.get(value, i).toString();
- }
- return values;
- }
- return new String[] {value.toString()};
- }
-
- /**
- * Returns the attribute values as an array of {@link Number}, or an empty array if none.
- * The given argument is typically a value of the {@link #attributes} map.
- *
- * @see #getAttributeValues(String, boolean)
- */
- @SuppressWarnings("ReturnOfCollectionOrArrayField")
- static Number[] numberValues(final Object value) {
- if (value != null) {
- if (value instanceof Number) {
- return new Number[] {(Number) value};
- }
- if (value.getClass().isArray()) {
- final Number[] values = new Number[Array.getLength(value)];
- for (int i=0; i<values.length; i++) {
- final Object element = Array.get(value, i);
- final Number n;
- if (element instanceof Number) {
- n = (Number) element;
- } else if (element instanceof String) {
- final String t = (String) element;
- try {
- if (t.indexOf('.') >= 0) {
- n = Double.valueOf(t);
- } else {
- n = Long.valueOf(t);
- }
- } catch (NumberFormatException e) {
- // TODO: log warning. See also Decoder.parseNumber(String).
- continue;
- }
- } else {
- continue;
- }
- values[i] = n;
- }
- return values;
- }
- }
- return EMPTY;
- }
-
- /**
- * Returns the attribute value as a boolean, or {@code false} if the attribute is not a boolean.
- * The given argument is typically a value of the {@link #attributes} map.
- */
- private static boolean booleanValue(final Object value) {
- return (value instanceof String) && Boolean.valueOf((String) value);
+ protected Object getAttributeValue(final String attributeName) {
+ return attributes.get(attributeName);
}
/**
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
index a5d9fe2..69e52a5 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
@@ -294,7 +294,7 @@ public final class DecoderWrapper extends Decoder implements CancelTask {
}
String asString = Utils.nonEmpty(attribute.getStringValue());
if (asString != null) {
- return parseNumber(asString);
+ return parseNumber(name, asString);
}
}
}
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/GroupWrapper.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/GroupWrapper.java
index 2a7dc45..6b5664d 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/GroupWrapper.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/GroupWrapper.java
@@ -69,10 +69,12 @@ final class GroupWrapper extends Node {
}
/**
- * Returns the sequence of values for the given attribute, or an empty array if none.
+ * Returns the single value or vector of values for the given attribute, or {@code null} if none.
+ * The returned value can be an instance of {@link String}, {@link Number},
+ * {@link org.apache.sis.math.Vector} or {@code String[]}.
*/
@Override
- public Object[] getAttributeValues(final String attributeName, final boolean numeric) {
- return VariableWrapper.getAttributeValues(group.findAttributeIgnoreCase(attributeName), numeric);
+ protected Object getAttributeValue(final String attributeName) {
+ return VariableWrapper.getAttributeValues(group.findAttributeIgnoreCase(attributeName));
}
}
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/VariableWrapper.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/VariableWrapper.java
index 497710d..953a15e 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/VariableWrapper.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/VariableWrapper.java
@@ -311,6 +311,9 @@ final class VariableWrapper extends Variable {
*/
static Class<?> getAttributeType(final Attribute attribute) {
if (attribute != null) {
+ if (attribute.isArray()) {
+ return Vector.class;
+ }
switch (attribute.getDataType()) {
case BYTE: return Byte.class;
case SHORT: return Short.class;
@@ -326,42 +329,43 @@ final class VariableWrapper extends Variable {
}
/**
- * Returns the sequence of values for the given attribute, or an empty array if none.
- * The elements will be of class {@link String} if {@code numeric} is {@code false},
- * or {@link Number} if {@code numeric} is {@code true}.
+ * Returns the single value or vector of values for the given attribute, or {@code null} if none.
+ * The returned value can be an instance of {@link String}, {@link Number}, {@link Vector} or {@code String[]}.
+ *
+ * @param attributeName the name of the attribute for which to get the values.
+ * @return value(s) for the named attribute, or {@code null} if none.
*/
@Override
- public Object[] getAttributeValues(final String attributeName, final boolean numeric) {
- return getAttributeValues(raw.findAttributeIgnoreCase(attributeName), numeric);
+ protected Object getAttributeValue(final String attributeName) {
+ return getAttributeValues(raw.findAttributeIgnoreCase(attributeName));
}
/**
- * Implementation of {@link #getAttributeValues(String, boolean)} shared with {@link GroupWrapper}.
+ * Implementation of {@link #getAttributeValue(String)} shared with {@link GroupWrapper}.
*/
- static Object[] getAttributeValues(final Attribute attribute, final boolean numeric) {
+ static Object getAttributeValues(final Attribute attribute) {
if (attribute != null) {
- boolean hasValues = false;
- final Object[] values = new Object[attribute.getLength()];
- for (int i=0; i<values.length; i++) {
- if (numeric) {
- values[i] = Utils.fixSign(attribute.getNumericValue(i), attribute.isUnsigned());
- hasValues |= (values[i] != null);
- } else {
- Object value = attribute.getValue(i);
- if (value != null) {
- String text = Utils.nonEmpty(value.toString());
- if (text != null) {
- values[i] = text;
- hasValues = true;
- }
+ final int length = attribute.getLength();
+ if (length != 0) {
+ boolean hasValues = false;
+ final boolean isString = attribute.isString();
+ final Object[] values = isString ? new String[length] : new Number[length];
+ for (int i=0; i<values.length; i++) {
+ final Object value;
+ if (isString) {
+ value = Utils.nonEmpty(attribute.getStringValue(i));
+ } else {
+ value = Utils.fixSign(attribute.getNumericValue(i), attribute.isUnsigned());
}
+ values[i] = value;
+ hasValues |= (value != null);
+ }
+ if (hasValues) {
+ return (values.length == 1) ? values[0] : Vector.create(values, attribute.isUnsigned());
}
- }
- if (hasValues) {
- return values;
}
}
- return new Object[0];
+ return null;
}
/**
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 9818d05..010ff70 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
@@ -71,6 +71,7 @@ import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.CharSequences;
import org.apache.sis.referencing.CRS;
import org.apache.sis.measure.Units;
+import org.apache.sis.math.Vector;
// The following dependency is used only for static final String constants.
// Consequently the compiled class files should not have this dependency.
@@ -892,6 +893,7 @@ split: while ((start = CharSequences.skipLeadingWhitespaces(value, start, lengt
* Adds information about all netCDF variables. This is the {@code <mdb:contentInfo>} element in XML.
* This method groups variables by their domains, i.e. variables having the same set of axes are grouped together.
*/
+ @SuppressWarnings("null")
private void addContentInfo() {
final Map<List<String>, List<Variable>> contents = new HashMap<>(4);
for (final Variable variable : decoder.getVariables()) {
@@ -915,17 +917,21 @@ split: while ((start = CharSequences.skipLeadingWhitespaces(value, start, lengt
setProcessingLevelCode(null, processingLevel);
for (final Variable variable : group) {
addSampleDimension(variable);
- final Object[] names = variable.getAttributeValues(FLAG_NAMES, false);
- final Object[] meanings = variable.getAttributeValues(FLAG_MEANINGS, false);
- final Object[] masks = variable.getAttributeValues(FLAG_MASKS, true);
- final Object[] values = variable.getAttributeValues(FLAG_VALUES, true);
- final int length = Math.max(masks.length, Math.max(values.length, Math.max(names.length, meanings.length)));
+ final CharSequence[] names = variable.getAttributeAsStrings(FLAG_NAMES, ' ');
+ final CharSequence[] meanings = variable.getAttributeAsStrings(FLAG_MEANINGS, ' ');
+ final Vector masks = variable.getAttributeAsVector (FLAG_MASKS);
+ final Vector values = variable.getAttributeAsVector (FLAG_VALUES);
+ final int s1 = (names != null) ? names.length : 0;
+ final int s2 = (meanings != null) ? meanings.length : 0;
+ final int s3 = (masks != null) ? masks .size() : 0;
+ final int s4 = (values != null) ? values.size() : 0;
+ final int length = Math.max(s1, Math.max(s2, Math.max(s3, s4)));
for (int i=0; i<length; i++) {
addSampleValueDescription(variable,
- (i < names .length) ? (String) names [i] : null,
- (i < meanings.length) ? (String) meanings[i] : null,
- (i < masks .length) ? (Number) masks [i] : null,
- (i < values .length) ? (Number) values [i] : null);
+ (i < s1) ? names [i] : null,
+ (i < s2) ? meanings [i] : null,
+ (i < s3) ? masks .get(i) : null,
+ (i < s4) ? values.get(i) : null);
}
}
}
@@ -974,7 +980,7 @@ split: while ((start = CharSequences.skipLeadingWhitespaces(value, start, lengt
* @param value one of the elements in the {@link AttributeNames#FLAG_VALUES} attribute or {@code null}.
*/
private void addSampleValueDescription(final Variable variable,
- final String name, final String meaning, final Number mask, final Number value)
+ final CharSequence name, final CharSequence meaning, final Number mask, final Number value)
{
addSampleValueDescription(name, meaning);
// TODO: create a record from values (and possibly from the masks).
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java
index c754a42..60639fe 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridTest.java
@@ -17,13 +17,15 @@
package org.apache.sis.internal.netcdf;
import java.io.IOException;
+import org.opengis.referencing.crs.ProjectedCRS;
+import org.opengis.parameter.ParameterValueGroup;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.test.DependsOn;
import org.apache.sis.test.DependsOnMethod;
import org.opengis.test.dataset.TestData;
import org.junit.Test;
-import static org.junit.Assert.*;
+import static org.opengis.test.Assert.*;
import static org.apache.sis.test.TestUtilities.getSingleton;
@@ -125,4 +127,23 @@ public strictfp class GridTest extends TestCase {
assertEquals( 4, z.getSize());
assertEquals( 1, t.getSize());
}
+
+ /**
+ * Tests {@link GridMapping#forVariable(Variable)} with a projected CRS.
+ *
+ * @throws IOException if an I/O error occurred while opening the file.
+ * @throws DataStoreException if a logical error occurred.
+ */
+ @Test
+ public void testGridMapping() throws IOException, DataStoreException {
+ final Node data = selectDataset(TestData.NETCDF_4D_PROJECTED).findNode("CIP");
+ final GridMapping mapping = GridMapping.forVariable((Variable) data);
+ assertNotNull("mapping", mapping);
+ assertInstanceOf("crs", ProjectedCRS.class, mapping.crs);
+ final ParameterValueGroup pg = ((ProjectedCRS) mapping.crs).getConversionFromBase().getParameterValues();
+ assertEquals("Latitude of false origin", 25, pg.parameter("Latitude of false origin") .doubleValue(), STRICT);
+ assertEquals("Longitude of false origin", -95, pg.parameter("Longitude of false origin") .doubleValue(), STRICT);
+ assertEquals("Latitude of 1st standard parallel", 25, pg.parameter("Latitude of 1st standard parallel").doubleValue(), STRICT);
+ assertEquals("Latitude of 2nd standard parallel", 25.05, pg.parameter("Latitude of 2nd standard parallel").doubleValue(), 1E-6);
+ }
}
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java
index c49ef04..157922d 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/TestCase.java
@@ -61,7 +61,7 @@ public abstract strictfp class TestCase extends org.apache.sis.test.TestCase {
/**
* The decoders cached by {@link #selectDataset(TestData)}.
- * The make is non-empty only during the execution of a single test class.
+ * The map is non-empty only during the execution of a single test class.
*
* @see #closeAllDecoders()
*/
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/VariableTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/VariableTest.java
index e200ff0..5572cc0 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/VariableTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/VariableTest.java
@@ -17,6 +17,7 @@
package org.apache.sis.internal.netcdf;
import java.util.List;
+import java.util.Arrays;
import java.io.IOException;
import java.time.Instant;
import org.apache.sis.math.Vector;
@@ -227,7 +228,7 @@ public strictfp class VariableTest extends TestCase {
}
/**
- * Tests {@link Variable#getAttributeValues(String, boolean)}.
+ * Tests {@link Variable#getAttributeValue(String)} and related methods.
*
* @throws IOException if an I/O error occurred while opening the file.
* @throws DataStoreException if a logical error occurred.
@@ -237,15 +238,47 @@ public strictfp class VariableTest extends TestCase {
final Variable[] variables = getVariablesCIP(selectDataset(TestData.NETCDF_4D_PROJECTED));
Variable variable = variables[6];
assertEquals("CIP", variable.getName());
- assertArrayEquals("CIP:_FillValue", new Number[] { -1f }, variable.getAttributeValues("_FillValue", true));
- assertArrayEquals("CIP:_FillValue", new String[] {"-1.0"}, variable.getAttributeValues("_FillValue", false));
- assertArrayEquals("CIP:units", new String[] { "%"}, variable.getAttributeValues("units", false));
- assertArrayEquals("CIP:units", new Number[] { }, variable.getAttributeValues("units", true));
+ assertSingletonEquals(variable, "_FillValue", -1f);
+ assertSingletonEquals(variable, "units", "%");
variable = variables[0];
assertEquals("grid_mapping_0", variable.getName());
- assertArrayEquals("standard_parallel", new Number[] { 25.f, 25.05f}, variable.getAttributeValues("standard_parallel", true));
- assertArrayEquals("standard_parallel", new String[] {"25.0", "25.05"}, variable.getAttributeValues("standard_parallel", false));
+ assertVectorEquals(variable, "standard_parallel", 25.f, 25.05f);
+ }
+
+ /**
+ * Asserts that the attribute of given name contains a value equals to the expected value.
+ * This method is used for attributes that are expected to contain singleton.
+ */
+ private static void assertSingletonEquals(final Node variable, final String name, final Object expected) {
+ final String t = expected.toString();
+ assertEquals ("getAttributeValue", expected, variable.getAttributeValue (name));
+ assertEquals ("getAttributeAsString", t, variable.getAttributeAsString (name));
+ assertArrayEquals("getAttributeAsStrings", new String[] {t}, variable.getAttributeAsStrings(name, ' '));
+ if (expected instanceof Number) {
+ final double en = ((Number) expected).doubleValue();
+ assertEquals("getAttributeAsNumber", en, variable.getAttributeAsNumber(name), STRICT);
+ final Vector vector = variable.getAttributeAsVector(name);
+ assertNotNull("getAttributeAsVector", vector);
+ assertEquals(1, vector.size());
+ assertEquals(en, vector.get(0).doubleValue(), STRICT);
+ } else {
+ assertNull("getAttributeAsVector", variable.getAttributeAsVector(name));
+ }
+ }
+
+ /**
+ * Asserts that the attribute of given name contains a value equals to the expected value.
+ * This method is used for attributes that are expected to contain vector.
+ */
+ private static void assertVectorEquals(final Node variable, final String name, final Number... expected) {
+ final Vector values = variable.getAttributeAsVector(name);
+ assertNotNull(name, values);
+ assertEquals ("size", expected.length, values.size());
+ assertTrue ("getAttributeAsNumber", Double.isNaN(variable.getAttributeAsNumber(name)));
+ assertEquals ("getAttributeValue", values, variable.getAttributeValue(name));
+ final Object[] texts = Arrays.stream(expected).map(Object::toString).toArray();
+ assertArrayEquals("getAttributeAsStrings", texts, variable.getAttributeAsStrings(name, ' '));
}
/**
diff --git a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/VariableInfoTest.java b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/VariableInfoTest.java
index 97f9708..4bc4e4d 100644
--- a/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/VariableInfoTest.java
+++ b/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/impl/VariableInfoTest.java
@@ -22,9 +22,6 @@ import org.apache.sis.internal.netcdf.VariableTest;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.test.DependsOn;
import org.opengis.test.dataset.TestData;
-import org.junit.Test;
-
-import static org.junit.Assert.*;
/**
@@ -57,14 +54,4 @@ public final strictfp class VariableInfoTest extends VariableTest {
protected Decoder createDecoder(final TestData file) throws IOException, DataStoreException {
return ChannelDecoderTest.createChannelDecoder(file);
}
-
- /**
- * Tests the {@link VariableInfo#numberValues(Object)} and {@link VariableInfo#stringValues(Object)} methods.
- */
- @Test
- public void testNumberValues() {
- final float[] a = new float[] {10, 20, 1};
- assertArrayEquals("numberValues", new Number[] { 10f, 20f, 1f }, VariableInfo.numberValues(a));
- assertArrayEquals("stringValues", new String[] {"10.0", "20.0", "1.0"}, VariableInfo.stringValues(a));
- }
}
|