You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2018/11/16 19:31:16 UTC

[sis] 03/03: When using the UCAR library for reading netCDF file, also use that library for parsing units.

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 108d1f36106545893b50ba11cd14140404ce349e
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri Nov 16 20:30:33 2018 +0100

    When using the UCAR library for reading netCDF file, also use that library for parsing units.
---
 .../main/java/org/apache/sis/measure/Units.java    |  4 +-
 ide-project/NetBeans/nbproject/project.properties  |  2 +
 .../org/apache/sis/internal/netcdf/CRSBuilder.java |  8 ++--
 .../org/apache/sis/internal/netcdf/Variable.java   | 51 ++++++++--------------
 .../sis/internal/netcdf/impl/VariableInfo.java     | 25 ++++++++++-
 .../sis/internal/netcdf/ucar/VariableWrapper.java  | 44 +++++++++++++++++++
 6 files changed, 94 insertions(+), 40 deletions(-)

diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java b/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java
index 5bfde64..81f353e 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java
@@ -1177,7 +1177,7 @@ public final class Units extends Static {
         /*
          * All Unit<Temperature>.
          */
-        K.related(1);
+        K.related(2);
         KELVIN       = K;
         CELSIUS      = add(K, LinearConverter.offset(  27315, 100), "°C", SI,    (short) 0);
         FAHRENHEIT   = add(K, new LinearConverter(100, 45967, 180), "°F", OTHER, (short) 0);
@@ -1267,7 +1267,7 @@ public final class Units extends Static {
     private static <Q extends Quantity<Q>> ConventionalUnit<Q> add(SystemUnit<Q> target, UnitConverter toTarget, String symbol, byte scope, short epsg) {
         final ConventionalUnit<Q> unit = UnitRegistry.init(new ConventionalUnit<>(target, toTarget, symbol, scope, epsg));
         final ConventionalUnit<Q>[] related = target.related();
-        if (related != null && unit.scope != UnitRegistry.SI) {
+        if (related != null && (unit.scope != UnitRegistry.SI || !toTarget.isLinear())) {
             // Search first empty slot. This algorithm is inefficient, but the length of those arrays is small (<= 7).
             int i = 0;
             while (related[i] != null) i++;
diff --git a/ide-project/NetBeans/nbproject/project.properties b/ide-project/NetBeans/nbproject/project.properties
index 7b55c26..3746e8e 100644
--- a/ide-project/NetBeans/nbproject/project.properties
+++ b/ide-project/NetBeans/nbproject/project.properties
@@ -164,7 +164,9 @@ javac.test.processorpath=\
 run.classpath=\
     ${javac.classpath}:\
     ${build.classes.dir}:\
+    ${maven.repository}/edu/ucar/udunits/${netcdf.version}/udunits-${netcdf.version}.jar:\
     ${maven.repository}/org/apache/derby/derby/${derby.version}/derby-${derby.version}.jar:\
+    ${maven.repository}/joda-time/joda-time/${joda-time.version}/joda-time-${joda-time.version}.jar:\
     ${maven.repository}/org/webjars/material-design-icons/${icons.version}/material-design-icons-${icons.version}.jar
 run.test.classpath=\
     ${javac.test.classpath}:\
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
index 79000bb..b614187 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/CRSBuilder.java
@@ -22,7 +22,6 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.StringJoiner;
 import java.util.function.Supplier;
-import java.util.Date;
 import java.time.Instant;
 import javax.measure.Unit;
 import org.opengis.util.FactoryException;
@@ -35,6 +34,7 @@ import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.cs.DefaultSphericalCS;
 import org.apache.sis.referencing.cs.DefaultEllipsoidalCS;
 import org.apache.sis.storage.DataStoreContentException;
+import org.apache.sis.internal.util.TemporalUtilities;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.measure.Units;
 
@@ -534,13 +534,15 @@ previous:   for (int i=components.size(); --i >= 0;) {
 
         /** Creates a {@link VerticalDatum} for <cite>"Unknown datum based on …"</cite>. */
         @Override void createDatum(DatumFactory factory, Map<String,?> properties) throws FactoryException {
-            final Instant epoch = getFirstAxis().coordinates.getEpoch();
+            final Axis axis = getFirstAxis();
+            axis.getUnit();                                     // Force epoch parsing if not already done.
+            Instant epoch = axis.coordinates.epoch;
             final CommonCRS.Temporal c = CommonCRS.Temporal.forEpoch(epoch);
             if (c != null) {
                 datum = c.datum();
             } else {
                 properties = properties("Time since " + epoch);
-                datum = factory.createTemporalDatum(properties, (epoch != null) ? Date.from(epoch) : null);
+                datum = factory.createTemporalDatum(properties, TemporalUtilities.toDate(epoch));
             }
         }
 
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 3b01060..e3b4b59 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,16 +19,11 @@ package org.apache.sis.internal.netcdf;
 import java.util.Locale;
 import java.util.Collection;
 import java.util.regex.Pattern;
-import java.util.regex.Matcher;
 import java.io.IOException;
 import java.awt.image.DataBuffer;
 import java.time.Instant;
 import javax.measure.Unit;
-import javax.measure.format.ParserException;
-import java.time.format.DateTimeParseException;
-import org.apache.sis.internal.util.StandardDateFormat;
 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;
@@ -47,7 +42,7 @@ public abstract class Variable extends NamedElement {
     /**
      * The pattern to use for parsing temporal units of the form "days since 1970-01-01 00:00:00".
      */
-    private static final Pattern TIME_PATTERN = Pattern.compile("(.+)\\Wsince\\W(.+)", Pattern.CASE_INSENSITIVE);
+    protected static final Pattern TIME_PATTERN = Pattern.compile("(.+)\\Wsince\\W(.+)", Pattern.CASE_INSENSITIVE);
 
     /**
      * Minimal number of dimension for accepting a variable as a coverage variable.
@@ -63,9 +58,9 @@ public abstract class Variable extends NamedElement {
 
     /**
      * If the unit is a temporal unit of the form "days since 1970-01-01 00:00:00", the epoch.
-     * Otherwise {@code null}.
+     * Otherwise {@code null}. This value can be set
      */
-    private Instant epoch;
+    protected Instant epoch;
 
     /**
      * Whether an attempt to parse the unit has already be done. This is used for avoiding
@@ -124,28 +119,28 @@ public abstract class Variable extends NamedElement {
     protected abstract String getUnitsString();
 
     /**
+     * Parses the given unit symbol and set the {@link #epoch} if the parsed unit is a temporal unit.
+     *
+     * @param  symbols  the unit symbol to parse.
+     * @return the parsed unit.
+     * @throws Exception if the unit can not be parsed. This wide exception type is used by the UCAR library.
+     */
+    protected abstract Unit<?> parseUnit(String symbols) throws Exception;
+
+    /**
      * Returns the unit of measurement for this variable, or {@code null} if unknown.
-     * This method parse the units from {@link #getUnitsString()} when first needed.
+     * This method parse the units from {@link #getUnitsString()} when first needed
+     * and sets {@link #epoch} as a side-effect if the unit is temporal.
      *
      * @return the unit of measurement, or {@code null}.
      */
     public final Unit<?> getUnit() {
         if (!unitParsed) {
             unitParsed = true;                          // Set first for avoiding to report errors many times.
-            String symbols = getUnitsString();
+            final String symbols = getUnitsString();
             if (symbols != null && !symbols.isEmpty()) try {
-                final Matcher parts = TIME_PATTERN.matcher(symbols);
-                if (parts.matches()) {
-                    /*
-                     * If we enter in this block, the unit is of the form "days since 1970-01-01 00:00:00".
-                     * The TIME_PATTERN splits the string in two parts, "days" and "1970-01-01 00:00:00".
-                     * The parse method will replace the space between date and time by 'T' letter.
-                     */
-                    epoch = StandardDateFormat.parseInstantUTC(parts.group(2));
-                    symbols = parts.group(1);
-                }
-                unit = Units.valueOf(symbols);
-            } catch (ParserException | DateTimeParseException ex) {
+                unit = parseUnit(symbols);
+            } catch (Exception ex) {
                 warning(listeners, Variable.class, "getUnit", ex, Errors.getResources(listeners.getLocale()),
                         Errors.Keys.CanNotAssignUnitToVariable_2, getName(), symbols);
             }
@@ -154,18 +149,6 @@ public abstract class Variable extends NamedElement {
     }
 
     /**
-     * Returns the epoch of the temporal unit, or {@code null} if none.
-     *
-     * @return the epoch, or {@code null}.
-     */
-    final Instant getEpoch() {
-        if (epoch == null) {
-            getUnit();          // Epoch calculation as a side-effect.
-        }
-        return epoch;
-    }
-
-    /**
      * Returns the variable data type.
      *
      * @return the variable data type, or {@link DataType#UNKNOWN} if unknown.
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 6a0de7f..e321dde 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
@@ -19,8 +19,10 @@ package org.apache.sis.internal.netcdf.impl;
 import java.util.Map;
 import java.util.Collection;
 import java.util.Collections;
-import java.io.IOException;
+import java.util.regex.Matcher;
 import java.lang.reflect.Array;
+import java.io.IOException;
+import javax.measure.Unit;
 import ucar.nc2.constants.CF;
 import ucar.nc2.constants.CDM;
 import ucar.nc2.constants._Coordinate;
@@ -31,10 +33,12 @@ import org.apache.sis.internal.netcdf.Resources;
 import org.apache.sis.internal.storage.io.ChannelDataInput;
 import org.apache.sis.internal.storage.io.HyperRectangleReader;
 import org.apache.sis.internal.storage.io.Region;
+import org.apache.sis.internal.util.StandardDateFormat;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.netcdf.AttributeNames;
 import org.apache.sis.util.logging.WarningListeners;
 import org.apache.sis.util.CharSequences;
+import org.apache.sis.measure.Units;
 import org.apache.sis.math.Vector;
 
 
@@ -349,6 +353,25 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo> {
     }
 
     /**
+     * Parses the given unit symbol and set the {@link #epoch} if the parsed unit is a temporal unit.
+     * This method is called by {@link #getUnit()}.
+     */
+    @Override
+    protected Unit<?> parseUnit(String symbols) {
+        final Matcher parts = TIME_PATTERN.matcher(symbols);
+        if (parts.matches()) {
+            /*
+             * If we enter in this block, the unit is of the form "days since 1970-01-01 00:00:00".
+             * The TIME_PATTERN splits the string in two parts, "days" and "1970-01-01 00:00:00".
+             * The parse method will replace the space between date and time by 'T' letter.
+             */
+            epoch = StandardDateFormat.parseInstantUTC(parts.group(2));
+            symbols = parts.group(1);
+        }
+        return Units.valueOf(symbols);
+    }
+
+    /**
      * Returns the type of data, or {@code UNKNOWN} if the data type is unknown to this method.
      * If this variable has a {@code "_Unsigned = true"} attribute, then the returned data type
      * will be a unsigned variant.
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 3a2aaf7..f2a8f5a 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
@@ -20,6 +20,7 @@ import java.util.List;
 import java.io.File;
 import java.io.IOException;
 import java.util.Collection;
+import javax.measure.Unit;
 import ucar.ma2.Array;
 import ucar.ma2.Section;
 import ucar.ma2.InvalidRangeException;
@@ -27,12 +28,15 @@ import ucar.nc2.Attribute;
 import ucar.nc2.Dimension;
 import ucar.nc2.VariableIF;
 import ucar.nc2.dataset.VariableEnhanced;
+import ucar.nc2.units.SimpleUnit;
+import ucar.nc2.units.DateUnit;
 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;
+import org.apache.sis.measure.Units;
 
 
 /**
@@ -115,6 +119,46 @@ final class VariableWrapper extends Variable {
     }
 
     /**
+     * Parses the given unit symbol and set the {@link #epoch} if the parsed unit is a temporal unit.
+     * This method is called by {@link #getUnit()}. This implementation delegates the work to the UCAR
+     * library and converts the result to {@link Unit} and {@link java.time.Instant} objects.
+     */
+    @Override
+    protected Unit<?> parseUnit(final String symbols) throws Exception {
+        if (TIME_PATTERN.matcher(symbols).matches()) {
+            /*
+             * UCAR library has to methods for getting epoch: getDate() and getDateOrigin().
+             * The former takes in account numbers that may appear before the unit, for example
+             * "2 hours since 1970-01-01 00:00:00". If there is no such number, then the two methods
+             * are equivalent.
+             */
+            final DateUnit temporal = new DateUnit(symbols);
+            epoch = temporal.getDate().toInstant();
+            return Units.SECOND.multiply(temporal.getTimeUnit().getValueInSeconds());
+        } else {
+            /*
+             * For all other units, we get the base unit (meter, radian, Kelvin, etc.) and multiply by the scale factor.
+             * We also need to take the offset in account for constructing the °C unit as a unit shifted from its Kelvin
+             * base. The UCAR library does not provide method giving directly this information, so we infer it indirectly
+             * by converting value zero.
+             */
+            final SimpleUnit ucar = SimpleUnit.factoryWithExceptions(symbols);
+            if (ucar.isUnknownUnit()) {
+                return Units.valueOf(symbols);
+            }
+            final String baseUnit = ucar.getUnitString();
+            Unit<?> unit = Units.valueOf(baseUnit);
+            final double scale  = ucar.getValue();
+            final double offset = ucar.convertTo(0, SimpleUnit.factoryWithExceptions(baseUnit));
+            unit = unit.shift(offset);
+            if (!Double.isNaN(scale)) {
+                unit = unit.multiply(scale);
+            }
+            return unit;
+        }
+    }
+
+    /**
      * Returns the variable data type.
      * This method may return {@code UNKNOWN} if the datatype is unknown.
      */