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.
*/