sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 03/03: First draft of a netCDF Axis.toISO() method.
Date Fri, 09 Nov 2018 15:32:20 GMT
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 <martin.desruisseaux@geomatys.com>
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);


Mime
View raw message