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 d777342b8c8b93ee8b593c106777dc441f9689ef Author: Martin Desruisseaux <[email protected]> AuthorDate: Fri Nov 9 16:31:23 2018 +0100 First draft of a netCDF Axis.toISO() method. --- .../java/org/apache/sis/internal/netcdf/Axis.java | 144 ++++++++++++++++++--- .../apache/sis/internal/netcdf/GridGeometry.java | 32 ++++- .../org/apache/sis/internal/netcdf/Variable.java | 66 +++++++++- .../sis/internal/netcdf/impl/GridGeometryInfo.java | 14 +- .../sis/internal/netcdf/impl/VariableInfo.java | 21 +-- .../sis/internal/netcdf/ucar/DecoderWrapper.java | 24 +++- .../internal/netcdf/ucar/GridGeometryWrapper.java | 46 ++++--- .../sis/internal/netcdf/ucar/VariableWrapper.java | 28 +++- .../apache/sis/storage/netcdf/MetadataReader.java | 17 +-- 9 files changed, 321 insertions(+), 71 deletions(-) diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java index 8d41055..3adf0d4 100644 --- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java +++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Axis.java @@ -16,13 +16,25 @@ */ package org.apache.sis.internal.netcdf; +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; import java.io.IOException; +import javax.measure.Unit; +import org.opengis.util.GenericName; import org.opengis.referencing.cs.AxisDirection; +import org.opengis.referencing.cs.CoordinateSystemAxis; import org.apache.sis.internal.metadata.AxisDirections; +import org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis; +import org.apache.sis.referencing.NamedIdentifier; +import org.apache.sis.metadata.iso.citation.Citations; import org.apache.sis.storage.netcdf.AttributeNames; import org.apache.sis.storage.DataStoreException; import org.apache.sis.util.iso.Types; import org.apache.sis.util.ArraysExt; +import ucar.nc2.constants.CDM; +import ucar.nc2.constants.CF; /** @@ -41,6 +53,33 @@ import org.apache.sis.util.ArraysExt; */ public final class Axis { /** + * The abbreviation, also used as a way to identify the axis type. Possible values are: + * <ul> + * <li>λ for longitude</li> + * <li>φ for latitude</li> + * <li>t for time</li> + * <li>h for ellipsoidal height</li> + * <li>H for geoidal height</li> + * <li>D for depth</li> + * <li>E for easting</li> + * <li>N for northing</li> + * <li>θ for spherical longitude (azimuthal angle)</li> + * <li>Ω for spherical latitude (polar angle)</li> + * <li>r for geocentric radius</li> + * <li>x,y,z for axes that are labeled as such, without more information.</li> + * <li>zero for unknown axes.</li> + * </ul> + * + * @see AxisDirections#fromAbbreviation(char) + */ + private final char abbreviation; + + /** + * The axis direction, or {@code null} if unknown. + */ + private final AxisDirection direction; + + /** * The attributes to use for fetching dimension (in ISO-19115 sense) information, or {@code null} if unknown. * Example: {@code "geospatial_lat_min"}, {@code "geospatial_lat_resolution"}, {@code DimensionNameType.ROW}. * This is used by {@link org.apache.sis.storage.netcdf.MetadataReader} for information purpose only. @@ -66,26 +105,76 @@ public final class Axis { public final int[] sourceSizes; /** + * Values of coordinates on this axis for given grid indices. This variables is often one-dimensional, + * but can also be two-dimensional. + */ + private final Variable coordinates; + + /** * Constructs a new axis associated to an arbitrary number of grid dimension. * In the particular case where the number of dimensions is equals to 2, this constructor will detect * by itself which grid dimension varies fastest and reorder in-place the elements in the given arrays * (those array are modified, not cloned). * * @param owner provides callback for the conversion from grid coordinates to geodetic coordinates. - * @param axis an implementation-dependent object representing the two-dimensional axis, or {@code null} if none. + * @param axis an implementation-dependent object representing the axis. * @param attributeNames the attributes to use for fetching dimension information, or {@code null} if unknown. + * @param abbreviation axis abbreviation, also identifying its type. This is a controlled vocabulary. + * @param direction direction of positive values ("up" or "down"), or {@code null} if unknown. * @param sourceDimensions the index of the grid dimension associated to this axis. * @param sourceSizes the number of cell elements along that axis. * @throws IOException if an I/O operation was necessary but failed. * @throws DataStoreException if a logical error occurred. * @throws ArithmeticException if the size of an axis exceeds {@link Integer#MAX_VALUE}, or other overflow occurs. */ - public Axis(final GridGeometry owner, final Object axis, final AttributeNames.Dimension attributeNames, - final int[] sourceDimensions, final int[] sourceSizes) throws IOException, DataStoreException + public Axis(final GridGeometry owner, final Variable axis, final AttributeNames.Dimension attributeNames, + final char abbreviation, final String direction, final int[] sourceDimensions, final int[] sourceSizes) + throws IOException, DataStoreException { + /* + * Try to get the axis direction from one of the following sources, in preference order: + * + * 1) The "positive" attribute value, which can be "up" or "down". + * 2) The abbreviation, which indirectly tells us the axis type inferred by UCAR library. + * 3) The direction in unit of measurement formatted as "degrees east" or "degrees north". + * + * Choice #1 is preferred because it tells us the direction of increasing values. By contrast, + * choice #2 said nothing about that. However if we find an inconsistency between directions + * inferred in those different ways, we give precedence to choices #2 and #3 in that order. + * Choice #1 is not considered authoritative because it applies (in principle) to only one of axis. + */ + AxisDirection dir = Types.forCodeName(AxisDirection.class, direction, false); + AxisDirection check = AxisDirections.fromAbbreviation(abbreviation); + final boolean isSigned = (dir != null); // Whether is specify the direction of positive values. + boolean isConsistent = true; + if (dir == null) { + dir = check; + } else if (check != null) { + isConsistent = AxisDirections.absolute(dir).equals(check); + } + if (isConsistent) { + check = direction(axis.getUnitsString()); + if (dir == null) { + dir = check; + } else if (check != null) { + isConsistent = AxisDirections.absolute(dir).equals(AxisDirections.absolute(check)); + } + } + if (!isConsistent) { + // TODO: report a warning here. + if (isSigned) { + dir = check; + if (AxisDirections.isOpposite(check)) { + dir = AxisDirections.opposite(dir); + } + } + } + this.direction = dir; + this.abbreviation = abbreviation; this.attributeNames = attributeNames; this.sourceDimensions = sourceDimensions; this.sourceSizes = sourceSizes; + this.coordinates = axis; if (sourceDimensions.length == 2) { final int up0 = sourceSizes[0]; final int up1 = sourceSizes[1]; @@ -104,24 +193,49 @@ public final class Axis { } /** - * Returns the axis direction for the given unit of measurement as a sign relative to the given direction. + * Returns the axis direction for the given unit of measurement, or {@code null} if unknown. * This method performs the second half of the work of parsing "degrees_east" or "degrees_west" units. * - * @param unit the string representation of the netCDF unit, or {@code null}. - * @param positive the direction to take as positive value: {@link AxisDirection#EAST} or {@link AxisDirection#NORTH}. - * @return the axis direction as a sign relative to the positive direction, or 0 if unrecognized. + * @param unit the string representation of the netCDF unit, or {@code null}. + * @return the axis direction, or {@code null} if unrecognized. */ - public static int direction(final String unit, final AxisDirection positive) { + public static AxisDirection direction(final String unit) { if (unit != null) { - final int s = unit.indexOf('_'); + int s = unit.indexOf('_'); + if (s < 0) { + s = unit.indexOf(' '); + } if (s > 0) { - final AxisDirection dir = Types.forCodeName(AxisDirection.class, unit.substring(s+1), false); - if (dir != null) { - if (dir.equals(positive)) return +1; - if (dir.equals(AxisDirections.opposite(positive))) return -1; - } + return Types.forCodeName(AxisDirection.class, unit.substring(s+1), false); } } - return 0; + return null; + } + + private CoordinateSystemAxis toISO() { + final String name = coordinates.getName().trim(); + final Map<String,Object> properties = new HashMap<>(4); + properties.put(CoordinateSystemAxis.NAME_KEY, name); + /* + * Aliases (optional property) + */ + final List<GenericName> aliases = new ArrayList<>(2); + final String standardName = coordinates.getAttributeString(CF.STANDARD_NAME); + if (standardName != null) { + aliases.add(new NamedIdentifier(Citations.NETCDF, standardName)); + } + final String alt = coordinates.getAttributeString(CDM.LONG_NAME); + if (alt != null && !alt.equals(standardName)) { + aliases.add(new NamedIdentifier(Citations.NETCDF, alt)); + } + properties.put(CoordinateSystemAxis.ALIAS_KEY, aliases.toArray(new GenericName[aliases.size()])); + + String a = Character.toString(abbreviation).intern(); // TODO: need default value is zero. + AxisDirection dir = direction; + if (dir == null) { + dir = AxisDirection.OTHER; + } + Unit<?> unit = coordinates.getUnit(); // TODO: need default value if null. + return new DefaultCoordinateSystemAxis(properties, a, dir, unit); } } diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridGeometry.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridGeometry.java index 9137f3d..76b6518 100644 --- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridGeometry.java +++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridGeometry.java @@ -33,6 +33,13 @@ import org.apache.sis.storage.DataStoreException; */ public abstract class GridGeometry { /** + * The axes, created when first needed. + * + * @see #getAxes() + */ + private Axis[] axes; + + /** * Constructs a new grid geometry information. */ protected GridGeometry() { @@ -59,15 +66,32 @@ public abstract class GridGeometry { /** * Returns the axes of the coordinate reference system. The size of this array is expected equals to the * value returned by {@link #getTargetDimensions()}, but the caller should be robust to inconsistencies. + * The axis order is as declared in the netCDF file (reverse of "natural" order). * - * <p>This method is used mostly for producing ISO 19115 metadata. It is typically invoked only once.</p> + * <p>This method returns a direct reference to the cached array; do not modify.</p> + * + * @return the CRS axes, in netCDF order (reverse of "natural" order). + * @throws IOException if an I/O operation was necessary but failed. + * @throws DataStoreException if a logical error occurred. + * @throws ArithmeticException if the size of an axis exceeds {@link Integer#MAX_VALUE}, or other overflow occurs. + */ + @SuppressWarnings("ReturnOfCollectionOrArrayField") + public final Axis[] getAxes() throws IOException, DataStoreException { + if (axes == null) { + axes = createAxes(); + } + return axes; + } + + /** + * Creates the axes to be returned by {@link #getAxes()}. This method is invoked only once when first needed. * * @return the CRS axes, in netCDF order (reverse of "natural" order). * @throws IOException if an I/O operation was necessary but failed. * @throws DataStoreException if a logical error occurred. * @throws ArithmeticException if the size of an axis exceeds {@link Integer#MAX_VALUE}, or other overflow occurs. */ - public abstract Axis[] getAxes() throws IOException, DataStoreException; + protected abstract Axis[] createAxes() throws IOException, DataStoreException; /** * Returns a coordinate for the given two-dimensional grid coordinate axis. This is (indirectly) a callback @@ -75,7 +99,7 @@ public abstract class GridGeometry { * they get reordered by the {@link Axis} constructor. In the netCDF UCAR API, this method maps directly to * {@link ucar.nc2.dataset.CoordinateAxis2D#getCoordValue(int, int)}. * - * @param axis an implementation-dependent object representing the two-dimensional axis, or {@code null} if none. + * @param axis an implementation-dependent object representing the two-dimensional axis. * @param j the fastest varying (right-most) index. * @param i the slowest varying (left-most) index. * @return the coordinate at the given index, or {@link Double#NaN} if it can not be computed. @@ -83,5 +107,5 @@ public abstract class GridGeometry { * @throws DataStoreException if a logical error occurred. * @throws ArithmeticException if the axis size exceeds {@link Integer#MAX_VALUE}, or other overflow occurs. */ - protected abstract double coordinateForAxis(Object axis, int j, int i) throws IOException, DataStoreException; + protected abstract double coordinateForAxis(Variable axis, int j, int i) throws IOException, DataStoreException; } 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 bbccb00..15840a2 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 @@ -19,8 +19,13 @@ package org.apache.sis.internal.netcdf; import java.util.Collection; import java.io.IOException; import java.awt.image.DataBuffer; +import javax.measure.Unit; +import javax.measure.format.ParserException; import org.apache.sis.math.Vector; +import org.apache.sis.measure.Units; import org.apache.sis.storage.DataStoreException; +import org.apache.sis.util.logging.WarningListeners; +import org.apache.sis.util.resources.Errors; /** @@ -39,9 +44,30 @@ public abstract class Variable extends NamedElement { public static final int MIN_DIMENSION = 2; /** + * The unit of measurement, parsed from {@link #getUnitsString()} when first needed. + * We do not try to parse the unit at construction time because this variable may be + * never requested by the user. + */ + private Unit<?> unit; + + /** + * Whether an attempt to parse the unit has already be done. This is used for avoiding + * to report the same failure many times when {@link #unit} stay null. + */ + private boolean unitParsed; + + /** + * Where to report warnings, if any. + */ + protected final WarningListeners<?> listeners; + + /** * Creates a new variable. + * + * @param listeners where to report warnings. */ - protected Variable() { + protected Variable(final WarningListeners<?> listeners) { + this.listeners = listeners; } /** @@ -62,14 +88,39 @@ public abstract class Variable extends NamedElement { /** * Returns the unit of measurement as a string, or {@code null} if none. * + * <p>Note: the UCAR library has its own API for handling units (e.g. {@link ucar.nc2.units.SimpleUnit}). + * However as of November 2018, this API does not allow us to identify the quantity type except for some + * special cases. We will parse the unit symbol ourselves instead, but we still need the full unit string + * for parsing also its {@linkplain Axis#direction direction}.</p> + * + * @return the unit of measurement, or {@code null}. + */ + protected abstract String getUnitsString(); + + /** + * Returns the unit of measurement for this variable, or {@code null} if unknown. + * This method parse the units from {@link #getUnitsString()} when first needed. + * * @return the unit of measurement, or {@code null}. */ - public abstract String getUnitsString(); + public final Unit<?> getUnit() { + if (!unitParsed) { + unitParsed = true; // Set first for avoiding to report errors many times. + final String symbols = getUnitsString(); + if (symbols != null) try { + unit = Units.valueOf(symbols); + } catch (ParserException e) { + listeners.warning(Errors.getResources(listeners.getLocale()) + .getString(Errors.Keys.CanNotAssignUnitToVariable_2, getName(), symbols), e); + } + } + return unit; + } /** * Returns the variable data type. * - * @return the variable data type, or {@code UNKNOWN} if unknown. + * @return the variable data type, or {@link DataType#UNKNOWN} if unknown. */ public abstract DataType getDataType(); @@ -182,6 +233,15 @@ public abstract class Variable extends NamedElement { public abstract Object[] getAttributeValues(String attributeName, boolean numeric); /** + * Returns the value of the given attribute as a string. This is a convenience method + * for {@link #getAttributeValues(String, boolean)} when a singleton value is expected. + * + * @param attributeName the name of the attribute for which to get the values. + * @return the singleton attribute value, or {@code null} if none or ambiguous. + */ + public abstract String getAttributeString(final String attributeName); + + /** * Reads all the data for this variable and returns them as an array of a Java primitive type. * Multi-dimensional variables are flattened as a one-dimensional array (wrapped in a vector). * Example: diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/GridGeometryInfo.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/GridGeometryInfo.java index 6ecbdb8..51df17c 100644 --- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/GridGeometryInfo.java +++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/GridGeometryInfo.java @@ -21,11 +21,13 @@ import java.util.TreeMap; import java.util.SortedMap; import org.apache.sis.util.ArraysExt; import org.apache.sis.internal.netcdf.Axis; +import org.apache.sis.internal.netcdf.Variable; import org.apache.sis.internal.netcdf.GridGeometry; import org.apache.sis.internal.netcdf.Resources; import org.apache.sis.storage.DataStoreContentException; import org.apache.sis.storage.netcdf.AttributeNames; import org.apache.sis.storage.DataStoreException; +import ucar.nc2.constants.CF; /** @@ -109,7 +111,7 @@ final class GridGeometryInfo extends GridGeometry { /** * Returns all axes of the netCDF coordinate system, together with the grid dimension to which the axis - * is associated. See {@code org.apache.sis.internal.netcdf.ucar.GridGeometryWrapper.getAxes()} for a + * is associated. See {@link org.apache.sis.internal.netcdf.ucar.GridGeometryWrapper#getAxes()} for a * closer look on the relationship between this algorithm and the UCAR library. * * <p>In this method, the words "domain" and "range" are used in the netCDF sense: they are the input @@ -125,7 +127,7 @@ final class GridGeometryInfo extends GridGeometry { * @throws ArithmeticException if the size of an axis exceeds {@link Integer#MAX_VALUE}, or other overflow occurs. */ @Override - public Axis[] getAxes() throws IOException, DataStoreException { + protected Axis[] createAxes() throws IOException, DataStoreException { /* * Process the variables in the order the appear in the sequence of bytes that make the netCDF files. * This is often the same order than the indices, but not necessarily. The intent is to reduce the @@ -178,9 +180,9 @@ final class GridGeometryInfo extends GridGeometry { } } } - axes[targetDim] = new Axis(this, axis, attributeNames, - ArraysExt.resize(indices, i), - ArraysExt.resize(sizes, i)); + char abbreviation = 0; + axes[targetDim] = new Axis(this, axis, attributeNames, abbreviation, axis.getAttributeString(CF.POSITIVE), + ArraysExt.resize(indices, i), ArraysExt.resize(sizes, i)); } return axes; } @@ -192,7 +194,7 @@ final class GridGeometryInfo extends GridGeometry { * @throws ArithmeticException if the axis size exceeds {@link Integer#MAX_VALUE}, or other overflow occurs. */ @Override - protected double coordinateForAxis(final Object axis, final int j, final int i) throws IOException, DataStoreException { + protected double coordinateForAxis(final Variable axis, final int j, final int i) throws IOException, DataStoreException { final VariableInfo v = (VariableInfo) axis; final int n = v.dimensions[0].length; return v.read().doubleValue(j + n*i); 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 8bdadf9..a14bcaf 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 @@ -164,11 +164,6 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo> { private final String[] meanings; /** - * Where to report warnings, if any. - */ - private final WarningListeners<?> listeners; - - /** * Creates a new variable. * * @param input the channel together with a buffer for reading the variable data. @@ -191,6 +186,7 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo> { final long offset, final WarningListeners<?> listeners) throws DataStoreContentException { + super(listeners); final Object isUnsigned = attributes.get(CDM.UNSIGNED); if (isUnsigned != null) { dataType = dataType.unsigned(booleanValue(isUnsigned)); @@ -199,7 +195,6 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo> { this.dimensions = dimensions; this.attributes = attributes; this.dataType = dataType; - this.listeners = listeners; /* * The 'size' value is provided in the netCDF files, but doesn't need to be stored since it * is redundant with the dimension lengths and is not large enough for big variables anyway. @@ -354,9 +349,8 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo> { * Returns the unit of measurement as a string, or {@code null} if none. */ @Override - public String getUnitsString() { - final Object value = getAttributeValue(CDM.UNITS); - return (value instanceof String) ? (String) value : null; + protected String getUnitsString() { + return getAttributeString(CDM.UNITS); } /** @@ -481,6 +475,15 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo> { } /** + * Returns the value of the given attribute as a string, or {@code null} if none. + */ + @Override + public String getAttributeString(final String attributeName) { + final Object value = getAttributeValue(attributeName); + return (value instanceof String) ? (String) value : 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. * 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 1f4c7e0..ac55d23 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 @@ -53,7 +53,7 @@ import org.apache.sis.storage.DataStoreException; * Provides netCDF decoding services based on the netCDF library. * * @author Martin Desruisseaux (Geomatys) - * @version 0.8 + * @version 1.0 * @since 0.3 * @module */ @@ -82,7 +82,7 @@ public final class DecoderWrapper extends Decoder implements CancelTask { * * @see #getVariables() */ - private transient Variable[] variables; + private transient VariableWrapper[] variables; /** * The discrete sampling features, or {@code null} if none. @@ -350,15 +350,29 @@ public final class DecoderWrapper extends Decoder implements CancelTask { public Variable[] getVariables() { if (variables == null) { final List<? extends VariableIF> all = file.getVariables(); - variables = new Variable[(all != null) ? all.size() : 0]; + variables = new VariableWrapper[(all != null) ? all.size() : 0]; for (int i=0; i<variables.length; i++) { - variables[i] = new VariableWrapper(all.get(i)); + variables[i] = new VariableWrapper(listeners, all.get(i)); } } return variables; } /** + * Returns the Apache SIS wrapper for the given UCAR variable. The given variable shall be non-null + * and should be one of the variables wrapped by the instances returned by {@link #getVariables()}. + */ + final VariableWrapper getWrapperFor(final VariableIF variable) { + for (VariableWrapper c : (VariableWrapper[]) getVariables()) { + if (c.isWrapperFor(variable)) { + return c; + } + } + // We should not reach this point, but let be safe. + return new VariableWrapper(listeners, variable); + } + + /** * If this decoder can handle the file content as features, returns handlers for them. * * @return {@inheritDoc} @@ -409,7 +423,7 @@ public final class DecoderWrapper extends Decoder implements CancelTask { } geometries = new GridGeometry[(systems != null) ? systems.size() : 0]; for (int i=0; i<geometries.length; i++) { - geometries[i] = new GridGeometryWrapper(systems.get(i)); + geometries[i] = new GridGeometryWrapper(this, systems.get(i)); } } return geometries; diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/GridGeometryWrapper.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/GridGeometryWrapper.java index 9f2336c..22c4324 100644 --- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/GridGeometryWrapper.java +++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/GridGeometryWrapper.java @@ -24,10 +24,12 @@ import ucar.nc2.dataset.CoordinateAxis; import ucar.nc2.dataset.CoordinateAxis2D; import ucar.nc2.dataset.CoordinateSystem; import org.apache.sis.internal.netcdf.Axis; +import org.apache.sis.internal.netcdf.Variable; import org.apache.sis.internal.netcdf.GridGeometry; import org.apache.sis.storage.netcdf.AttributeNames; import org.apache.sis.storage.DataStoreException; import org.apache.sis.util.ArraysExt; +import ucar.nc2.VariableIF; /** @@ -37,12 +39,18 @@ import org.apache.sis.util.ArraysExt; * of the grid geometry information. * * @author Martin Desruisseaux (Geomatys) - * @version 0.8 + * @version 1.0 * @since 0.3 * @module */ final class GridGeometryWrapper extends GridGeometry { /** + * The decoder which is creating this grid geometry. + * Used for fetching the variables when first needed. + */ + private final DecoderWrapper decoder; + + /** * The netCDF coordinate system to wrap. */ private final CoordinateSystem netcdfCS; @@ -50,9 +58,10 @@ final class GridGeometryWrapper extends GridGeometry { /** * Creates a new grid geometry for the given netCDF coordinate system. * - * @param cs the netCDF coordinate system, or {@code null} if none. + * @param cs the netCDF coordinate system. */ - GridGeometryWrapper(final CoordinateSystem cs) { + GridGeometryWrapper(final DecoderWrapper decoder, final CoordinateSystem cs) { + this.decoder = decoder; netcdfCS = cs; } @@ -94,7 +103,7 @@ final class GridGeometryWrapper extends GridGeometry { * @throws ArithmeticException if the size of an axis exceeds {@link Integer#MAX_VALUE}, or other overflow occurs. */ @Override - public Axis[] getAxes() throws IOException, DataStoreException { + protected Axis[] createAxes() throws IOException, DataStoreException { final List<Dimension> domain = netcdfCS.getDomain(); final List<CoordinateAxis> range = netcdfCS.getCoordinateAxes(); /* @@ -109,15 +118,22 @@ final class GridGeometryWrapper extends GridGeometry { * The AttributeNames are for ISO 19115 metadata. They are not used for locating grid cells * on Earth, but we nevertheless get them now for making MetadataReader work easier. */ + char abbreviation = 0; AttributeNames.Dimension attributeNames = null; final AxisType type = axis.getAxisType(); if (type != null) switch (type) { - case Lon: attributeNames = AttributeNames.LONGITUDE; break; - case Lat: attributeNames = AttributeNames.LATITUDE; break; - case Pressure: // Fallthrough: consider as Height - case Height: attributeNames = AttributeNames.VERTICAL; break; - case RunTime: // Fallthrough: consider as Time - case Time: attributeNames = AttributeNames.TIME; break; + case GeoX: abbreviation = 'x'; break; + case GeoY: abbreviation = 'y'; break; + case GeoZ: abbreviation = 'z'; break; + case Lon: abbreviation = 'λ'; attributeNames = AttributeNames.LONGITUDE; break; + case Lat: abbreviation = 'φ'; attributeNames = AttributeNames.LATITUDE; break; + case Pressure: // Fallthrough: consider as Height + case Height: abbreviation = 'H'; attributeNames = AttributeNames.VERTICAL; break; + case RunTime: // Fallthrough: consider as Time + case Time: abbreviation = 't'; attributeNames = AttributeNames.TIME; break; + case RadialAzimuth: abbreviation = 'θ'; break; // Spherical longitude + case RadialElevation: abbreviation = 'Ω'; break; // Spherical latitude + case RadialDistance: abbreviation = 'r'; break; // Geocentric radius } /* * Get the grid dimensions (part of the "domain" in UCAR terminology) used for computing @@ -140,9 +156,8 @@ final class GridGeometryWrapper extends GridGeometry { * package, we can proceed as if the dimension does not exist ('i' not incremented). */ } - axes[targetDim] = new Axis(this, axis, attributeNames, - ArraysExt.resize(indices, i), - ArraysExt.resize(sizes, i)); + axes[targetDim] = new Axis(this, decoder.getWrapperFor(axis), attributeNames, abbreviation, + axis.getPositive(), ArraysExt.resize(indices, i), ArraysExt.resize(sizes, i)); } return axes; } @@ -152,7 +167,8 @@ final class GridGeometryWrapper extends GridGeometry { * This is (indirectly) a callback method for {@link #getAxes()}. */ @Override - protected double coordinateForAxis(final Object axis, final int j, final int i) { - return (axis instanceof CoordinateAxis2D) ? ((CoordinateAxis2D) axis).getCoordValue(j, i) : Double.NaN; + protected double coordinateForAxis(final Variable axis, final int j, final int i) { + final VariableIF v = ((VariableWrapper) axis).variable; + return (v instanceof CoordinateAxis2D) ? ((CoordinateAxis2D) v).getCoordValue(j, i) : Double.NaN; } } 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 268ea20..079690e 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 @@ -30,6 +30,7 @@ import org.apache.sis.math.Vector; import org.apache.sis.internal.netcdf.DataType; import org.apache.sis.internal.netcdf.Variable; import org.apache.sis.internal.util.UnmodifiableArrayList; +import org.apache.sis.util.logging.WarningListeners; import org.apache.sis.storage.DataStoreException; @@ -46,7 +47,7 @@ final class VariableWrapper extends Variable { /** * The netCDF variable. This is typically an instance of {@link VariableEnhanced}. */ - private final VariableIF variable; + final VariableIF variable; /** * The variable without enhancements. May be the same instance than {@link #variable} @@ -60,7 +61,8 @@ final class VariableWrapper extends Variable { /** * Creates a new variable wrapping the given netCDF interface. */ - VariableWrapper(VariableIF v) { + VariableWrapper(final WarningListeners<?> listeners, VariableIF v) { + super(listeners); variable = v; if (v instanceof VariableEnhanced) { v = ((VariableEnhanced) v).getOriginalVariable(); @@ -91,7 +93,7 @@ final class VariableWrapper extends Variable { * Returns the unit of measurement as a string, or {@code null} if none. */ @Override - public String getUnitsString() { + protected String getUnitsString() { return variable.getUnitsString(); } @@ -200,6 +202,19 @@ final class VariableWrapper extends Variable { } /** + * Returns the value of the given attribute as a string. This is a convenience method + * for {@link #getAttributeValues(String, boolean)} when a singleton value is expected. + * + * @param attributeName the name of the attribute for which to get the values. + * @return the singleton attribute value, or {@code null} if none or ambiguous. + */ + @Override + public String getAttributeString(final String attributeName) { + Object[] values = getAttributeValues(attributeName, false); + return (values.length == 1) ? values[0].toString() : null; + } + + /** * Returns the names of all attributes in the given list. */ static List<String> toNames(final List<Attribute> attributes) { @@ -245,4 +260,11 @@ final class VariableWrapper extends Variable { } return Vector.create(array.get1DJavaArray(array.getElementType()), variable.isUnsigned()); } + + /** + * Returns {@code true} if this Apache SIS variable is a wrapper for the given UCAR variable. + */ + final boolean isWrapperFor(final VariableIF v) { + return (variable == v) || (raw == v); + } } 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 d43677f..f673325 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 @@ -63,6 +63,7 @@ import org.apache.sis.internal.netcdf.GridGeometry; import org.apache.sis.internal.storage.io.IOUtilities; import org.apache.sis.internal.storage.MetadataBuilder; import org.apache.sis.internal.storage.wkt.StoreFormat; +import org.apache.sis.internal.metadata.AxisDirections; import org.apache.sis.internal.util.CollectionsExt; import org.apache.sis.util.resources.Errors; import org.apache.sis.util.CharSequences; @@ -834,7 +835,7 @@ split: while ((start = CharSequences.skipLeadingWhitespaces(value, start, lengt } boolean reverse = false; if (positive != null) { - reverse = Axis.direction(symbol, positive) < 0; + reverse = AxisDirections.opposite(positive).equals(Axis.direction(symbol)); } else if (dim.POSITIVE != null) { // For now, only the vertical axis have a "positive" attribute. reverse = CF.POSITIVE_DOWN.equals(stringValue(dim.POSITIVE)); @@ -930,24 +931,18 @@ split: while ((start = CharSequences.skipLeadingWhitespaces(value, start, lengt final NameFactory f = decoder.nameFactory; setBandIdentifier(f.createMemberName(null, name, f.createTypeName(null, variable.getDataTypeName()))); } - Object[] v = variable.getAttributeValues(CF.STANDARD_NAME, false); - final String id = (v.length == 1) ? trim((String) v[0]) : null; + final String id = trim(variable.getAttributeString(CF.STANDARD_NAME)); if (id != null && !id.equals(name)) { - v = variable.getAttributeValues(ACDD.standard_name_vocabulary, false); - addBandName(v.length == 1 ? (String) v[0] : null, id); + addBandName(variable.getAttributeString(ACDD.standard_name_vocabulary), id); } final String description = trim(variable.getDescription()); if (description != null && !description.equals(name) && !description.equals(id)) { addBandDescription(description); } - final String units = variable.getUnitsString(); - if (units != null) try { - setSampleUnits(Units.valueOf(units)); - } catch (ParserException e) { - warning(Errors.Keys.CanNotAssignUnitToVariable_2, name, units, e); - } + setSampleUnits(variable.getUnit()); double scale = Double.NaN; double offset = Double.NaN; + Object[] v; v = variable.getAttributeValues(CDM.SCALE_FACTOR, true); if (v.length == 1) scale = ((Number) v[0]).doubleValue(); v = variable.getAttributeValues(CDM.ADD_OFFSET, true); if (v.length == 1) offset = ((Number) v[0]).doubleValue(); setTransferFunction(scale, offset);
