introduced variable identification portable across GRIB versions

Change-Id: I1ca20cc66e99b9c495c4403f63923559a8007912
This commit is contained in:
Axel Uhl
2017-02-03 16:20:19 +01:00
parent 7a6adae037
commit ff2738f874
13 changed files with 330 additions and 101 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry exported="true" kind="lib" path="lib/netcdfAll-4.6.8.jar">
<classpathentry exported="true" kind="lib" path="lib/netcdfAll-4.6.8.jar" sourcepath="C:/data/java/thredds/grib/src/main/java">
<attributes>
<attribute name="javadoc_location" value="https://www.unidata.ucar.edu/software/thredds/v4.3/netcdf-java/v4.3/javadoc/"/>
</attributes>
@@ -22,5 +22,5 @@ import ucar.nc2.ft.FeatureDataset;
public interface GribWindFieldFactory {
GribWindFieldFactory INSTANCE = new GribWindFieldFactoryImpl();
GribWindField createGribWindField(FeatureDataset dataSet);
GribWindField createGribWindField(FeatureDataset... dataSet);
}
@@ -1,7 +1,10 @@
package com.sap.sailing.grib.impl;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Optional;
import java.util.stream.StreamSupport;
import com.sap.sailing.domain.common.Bounds;
import com.sap.sailing.domain.common.Position;
@@ -19,9 +22,7 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.common.impl.TimeRangeImpl;
import ucar.ma2.Array;
import ucar.nc2.Attribute;
import ucar.nc2.Dimension;
import ucar.nc2.VariableSimpleIF;
import ucar.nc2.dataset.VariableDS;
import ucar.nc2.dt.GridCoordSystem;
import ucar.nc2.dt.GridDatatype;
@@ -41,59 +42,48 @@ import ucar.unidata.geoloc.LatLonRect;
*
*/
public abstract class AbstractGribWindFieldImpl implements GribWindField {
private final FeatureDataset dataSet;
private final FeatureDataset[] dataSets;
private final Weigher<TimePoint> timeConfidenceWeigher;
/**
* The base confidence of the wind data coming from the underlying {@link #dataSet}, between 0 (not confident at
* The base confidence of the wind data coming from the underlying {@link #dataSets}, between 0 (not confident at
* all) and 1 (really confident observation at this point in time)
*/
private final double baseConfidence;
public AbstractGribWindFieldImpl(FeatureDataset dataSet, double baseConfidence) {
this.dataSet = dataSet;
public AbstractGribWindFieldImpl(double baseConfidence, FeatureDataset... dataSets) {
this.dataSets = dataSets;
this.baseConfidence = baseConfidence;
timeConfidenceWeigher = ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher(
// use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero
Duration.ONE_SECOND.times(30).asMillis(), /* minimum confidence */0.0000000001);
}
protected static boolean hasVariable(FeatureDataset dataSet, int variableId) {
for (VariableSimpleIF variable : dataSet.getDataVariables()) {
Optional<Integer> id = getVariableId(variable);
if (id.isPresent() && id.get() == variableId) {
return true;
}
}
return false;
}
/**
* Obtains the {@code Grib1_Parameter} value which gives the ID of the variable, such as 33 for the
* "u-component of wind" variable
*
* @return an optional {@link Integer} that, if present, represents the {@code Grib1_Parameter} value for the
* variable
*/
protected static Optional<Integer> getVariableId(VariableSimpleIF variable) {
final Optional<Integer> result;
final Attribute idVariable = variable.findAttributeIgnoreCase("Grib1_Parameter");
if (idVariable != null) {
result = Optional.of(idVariable.getNumericValue().intValue());
} else {
result = Optional.empty();
}
return result;
protected static boolean hasVariable(VariableSpecification variableSpecification, FeatureDataset... dataSets) {
return variableSpecification.appearsInAnyOf(dataSets);
}
protected boolean hasVariable(VariableSpecification variableSpecification) {
return variableSpecification.appearsInAnyOf(dataSets);
}
protected double getBaseConfidence() {
return baseConfidence;
}
@Override
public Bounds getBounds() {
return toBounds(getDataSet().getBoundingBox());
Bounds result = null;
for (final FeatureDataset dataSet : getDataSets()) {
final Bounds dataSetBounds = toBounds(dataSet.getBoundingBox());
if (result == null) {
result = dataSetBounds;
} else {
result = result.extend(dataSetBounds);
}
}
return result;
}
@Override
@@ -129,11 +119,11 @@ public abstract class AbstractGribWindFieldImpl implements GribWindField {
}
protected TimePoint getStartTime() {
return toTimePoint(getDataSet().getCalendarDateStart());
return StreamSupport.stream(Arrays.asList(getDataSets()).spliterator(), /* parallel */ false).map(dataSet->toTimePoint(dataSet.getCalendarDateEnd())).min(Comparator.naturalOrder()).get();
}
private TimePoint getEndTime() {
return toTimePoint(getDataSet().getCalendarDateEnd());
return StreamSupport.stream(Arrays.asList(getDataSets()).spliterator(), /* parallel */ false).map(dataSet->toTimePoint(dataSet.getCalendarDateEnd())).max(Comparator.naturalOrder()).get();
}
private Bounds toBounds(LatLonRect boundingBox) {
@@ -144,8 +134,8 @@ public abstract class AbstractGribWindFieldImpl implements GribWindField {
return new DegreePosition(point.getLatitude(), point.getLongitude());
}
protected FeatureDataset getDataSet() {
return dataSet;
protected FeatureDataset[] getDataSets() {
return dataSets;
}
/**
@@ -254,4 +244,17 @@ public abstract class AbstractGribWindFieldImpl implements GribWindField {
return result;
}
protected boolean isMetersPerSecond(String unit) {
final String spacelessUnit = removeSpacesAndToLowercase(unit);
return spacelessUnit.equals("m/s") || spacelessUnit.equals("ms^-1");
}
protected boolean isDegreesTrue(String unit) {
final String spacelessUnit = removeSpacesAndToLowercase(unit).replaceAll("_", "");
return spacelessUnit.equals("degreetrue") || spacelessUnit.equals("degtrue");
}
private String removeSpacesAndToLowercase(String unit) {
return unit.replaceAll(" ", "").toLowerCase();
}
}
@@ -0,0 +1,30 @@
package com.sap.sailing.grib.impl;
import java.util.Arrays;
import ucar.nc2.VariableSimpleIF;
public class CompositeVariableSpecification implements VariableSpecification {
private final VariableSpecification[] variableSpecifications;
public CompositeVariableSpecification(VariableSpecification... variableSpecifications) {
super();
this.variableSpecifications = variableSpecifications;
}
@Override
public boolean matches(VariableSimpleIF variable) {
for (final VariableSpecification s : variableSpecifications) {
if (s.matches(variable)) {
return true;
}
}
return false;
}
@Override
public String toString() {
return "CompositeVariableSpecification [variableSpecifications=" + Arrays.toString(variableSpecifications)
+ "]";
}
}
@@ -0,0 +1,44 @@
package com.sap.sailing.grib.impl;
import java.util.Optional;
import ucar.nc2.Attribute;
import ucar.nc2.VariableSimpleIF;
public class Grib1VariableSpecification implements VariableSpecification {
private final int indicatorOfParameter;
public Grib1VariableSpecification(int indicatorOfParameter) {
super();
this.indicatorOfParameter = indicatorOfParameter;
}
@Override
public boolean matches(VariableSimpleIF variable) {
Optional<Integer> variableId = getGrib1VariableId(variable);
return variableId.isPresent() && variableId.get() == indicatorOfParameter;
}
/**
* Obtains the {@code Grib1_Parameter} value which gives the ID of the variable, such as 33 for the
* "u-component of wind" variable
*
* @return an optional {@link Integer} that, if present, represents the {@code Grib1_Parameter} value for the
* variable
*/
private static Optional<Integer> getGrib1VariableId(VariableSimpleIF variable) {
final Optional<Integer> result;
final Attribute idVariable = variable.findAttributeIgnoreCase("Grib1_Parameter");
if (idVariable != null) {
result = Optional.of(idVariable.getNumericValue().intValue());
} else {
result = Optional.empty();
}
return result;
}
@Override
public String toString() {
return "Grib1VariableSpecification [indicatorOfParameter=" + indicatorOfParameter + "]";
}
}
@@ -0,0 +1,63 @@
package com.sap.sailing.grib.impl;
import java.util.Arrays;
import java.util.Optional;
import ucar.ma2.Array;
import ucar.nc2.Attribute;
import ucar.nc2.VariableSimpleIF;
public class Grib2VariableSpecification implements VariableSpecification {
private final int[] disciplineCategoryNumberAndMoreOptionalSpecifiers;
public Grib2VariableSpecification(int[] disciplineCategoryNumberAndMoreOptionalSpecifiers) {
super();
this.disciplineCategoryNumberAndMoreOptionalSpecifiers = disciplineCategoryNumberAndMoreOptionalSpecifiers;
}
@Override
public boolean matches(VariableSimpleIF variable) {
final boolean result;
final Optional<int[]> grib2ParameterInfo = getGrib2VariableId(variable);
if (grib2ParameterInfo.isPresent() && grib2ParameterInfo.get().length >= disciplineCategoryNumberAndMoreOptionalSpecifiers.length) {
result = Arrays.equals(Arrays.copyOf(grib2ParameterInfo.get(), disciplineCategoryNumberAndMoreOptionalSpecifiers.length), disciplineCategoryNumberAndMoreOptionalSpecifiers);
} else {
result = false;
}
return result;
}
/**
* Obtains the {@code Grib2_Parameter} value which gives the ID of the variable, such as "VAR_0-2-1_L103" for the
* "WIND" wind speed variable
*
* @return an optional {@link Integer} that, if present, represents the {@code Grib1_Parameter} value for the
* variable
*/
private static Optional<int[]> getGrib2VariableId(VariableSimpleIF variable) {
final Optional<int[]> result;
final Attribute idVariable = variable.findAttributeIgnoreCase("Grib2_Parameter");
if (idVariable != null) {
final Array grib2ParameterDisciplineCategoryAndNumber = idVariable.getValues();
if (grib2ParameterDisciplineCategoryAndNumber == null) {
result = Optional.empty();
} else {
final int[] resultArray = new int[(int) grib2ParameterDisciplineCategoryAndNumber.getSize()];
for (int i=0; i<resultArray.length; i++) {
resultArray[i] = grib2ParameterDisciplineCategoryAndNumber.getInt(i);
}
result = Optional.of(resultArray);
}
} else {
result = Optional.empty();
}
return result;
}
@Override
public String toString() {
return "Grib2VariableSpecification [disciplineCategoryNumberAndMoreOptionalSpecifiers="
+ Arrays.toString(disciplineCategoryNumberAndMoreOptionalSpecifiers) + "]";
}
}
@@ -7,14 +7,14 @@ import ucar.nc2.ft.FeatureDataset;
public class GribWindFieldFactoryImpl implements GribWindFieldFactory {
@Override
public GribWindField createGribWindField(FeatureDataset dataSet) {
public GribWindField createGribWindField(FeatureDataset... dataSets) {
final GribWindField result;
if (UVWindField.handles(dataSet)) {
result = new UVWindField(dataSet);
} else if (SpeedAndDirectionWindField.handles(dataSet)) {
result = new SpeedAndDirectionWindField(dataSet);
if (UVWindField.handles(dataSets)) {
result = new UVWindField(dataSets);
} else if (SpeedAndDirectionWindField.handles(dataSets)) {
result = new SpeedAndDirectionWindField(dataSets);
} else {
throw new IllegalArgumentException("Couldn't find a wind field implementation handling data set "+dataSet);
throw new IllegalArgumentException("Couldn't find a wind field implementation handling data set(s) "+dataSets);
}
return result;
}
@@ -18,37 +18,55 @@ import ucar.nc2.ft.FeatureDataset;
public class SpeedAndDirectionWindField extends AbstractGribWindFieldImpl {
private static final int WIND_DIRECTION_PARAMETER_ID = 31;
private static final int WIND_SPEED_PARAMETER_ID = 32;
private static final int WIND_DIRECTION_GRIB2_DISCIPLINE = 0;
private static final int WIND_DIRECTION_GRIB2_PARAMETER_CATEGORY = 2;
private static final int WIND_DIRECTION_GRIB2_PARAMETER_NUMBER = 0;
public SpeedAndDirectionWindField(FeatureDataset dataSet) {
super(dataSet, /* baseConfidence */ 0.5);
private static final int WIND_SPEED_PARAMETER_ID = 32;
private static final int WIND_SPEED_GRIB2_DISCIPLINE = 0;
private static final int WIND_SPEED_GRIB2_PARAMETER_CATEGORY = 2;
private static final int WIND_SPEED_GRIB2_PARAMETER_NUMBER = 1;
private static final VariableSpecification windDirectionVariableSpecification =
new CompositeVariableSpecification(new Grib1VariableSpecification(WIND_DIRECTION_PARAMETER_ID),
new Grib2VariableSpecification(new int[] { WIND_DIRECTION_GRIB2_DISCIPLINE, WIND_DIRECTION_GRIB2_PARAMETER_CATEGORY, WIND_DIRECTION_GRIB2_PARAMETER_NUMBER }));
private static final VariableSpecification windSpeedVariableSpecification =
new CompositeVariableSpecification(new Grib1VariableSpecification(WIND_SPEED_PARAMETER_ID),
new Grib2VariableSpecification(new int[] { WIND_SPEED_GRIB2_DISCIPLINE, WIND_SPEED_GRIB2_PARAMETER_CATEGORY, WIND_SPEED_GRIB2_PARAMETER_NUMBER }));
public SpeedAndDirectionWindField(FeatureDataset... dataSets) {
super(/* baseConfidence */ 0.5, dataSets);
}
@Override
public WindWithConfidence<TimePoint> getWind(TimePoint timePoint, Position position) throws IOException {
FeatureDataset dataSet = getDataSet();
final Wind wind;
final double confidence;
if (dataSet instanceof GridDataset) {
Triple<Double, TimePoint, Position> directionComponentInDegreesTrue = null;
Triple<Double, TimePoint, Position> speedComponentInMetersPerSecond = null;
for (final GridDatatype grid : ((GridDataset) dataSet).getGrids()) {
final Integer variableId = getVariableId(grid.getVariable()).orElse(-1);
if (variableId == WIND_DIRECTION_PARAMETER_ID) {
assert getUnit(grid.getVariable()).get().equals("m/s");
directionComponentInDegreesTrue = getValue(grid, timePoint, position);
} else if (variableId == WIND_SPEED_PARAMETER_ID) {
assert getUnit(grid.getVariable()).get().equals("m/s");
speedComponentInMetersPerSecond = getValue(grid, timePoint, position);
}
if (directionComponentInDegreesTrue != null && speedComponentInMetersPerSecond != null) {
break;
Triple<Double, TimePoint, Position> directionComponentInDegreesTrue = null;
Triple<Double, TimePoint, Position> speedComponentInMetersPerSecond = null;
for (final FeatureDataset dataSet : getDataSets()) {
if (dataSet instanceof GridDataset) {
for (final GridDatatype grid : ((GridDataset) dataSet).getGrids()) {
if (windDirectionVariableSpecification.matches(grid.getVariable())) {
assert isDegreesTrue(getUnit(grid.getVariable()).get());
directionComponentInDegreesTrue = getValue(grid, timePoint, position);
} else if (windSpeedVariableSpecification.matches(grid.getVariable())) {
assert isMetersPerSecond(getUnit(grid.getVariable()).get());
speedComponentInMetersPerSecond = getValue(grid, timePoint, position);
}
if (directionComponentInDegreesTrue != null && speedComponentInMetersPerSecond != null) {
break;
}
}
}
}
final Wind wind;
final double confidence;
if (directionComponentInDegreesTrue != null && speedComponentInMetersPerSecond != null) {
confidence = getTimeConfidence(timePoint, directionComponentInDegreesTrue.getB());
wind = new WindImpl(directionComponentInDegreesTrue.getC(), directionComponentInDegreesTrue.getB(),
new MeterPerSecondSpeedWithDegreeBearingImpl(speedComponentInMetersPerSecond.getA(),
new DegreeBearingImpl(directionComponentInDegreesTrue.getA())));
// we're getting the "from" direction from the GRIB file and need to convert to "to" here
new DegreeBearingImpl(directionComponentInDegreesTrue.getA()).reverse()));
} else {
wind = null;
confidence = 0;
@@ -60,8 +78,8 @@ public class SpeedAndDirectionWindField extends AbstractGribWindFieldImpl {
* Checks whether the data set has a wind speed variable (GRIB parameter #32) and a wind direction variable
* (GRIB parameter #31).
*/
public static boolean handles(FeatureDataset dataSet) {
return hasVariable(dataSet, WIND_DIRECTION_PARAMETER_ID) && hasVariable(dataSet, WIND_SPEED_PARAMETER_ID);
public static boolean handles(FeatureDataset... dataSets) {
return hasVariable(windDirectionVariableSpecification, dataSets) && hasVariable(windSpeedVariableSpecification, dataSets);
}
}
@@ -18,33 +18,50 @@ import ucar.nc2.ft.FeatureDataset;
public class UVWindField extends AbstractGribWindFieldImpl {
private static final int U_COMPONENT_OF_WIND_PARAMETER_ID = 33;
private static final int U_COMPONENT_OF_WIND_GRIB2_DISCIPLINE = 0;
private static final int U_COMPONENT_OF_WIND_GRIB2_PARAMETER_CATEGORY = 2;
private static final int U_COMPONENT_OF_WIND_GRIB2_PARAMETER_NUMBER = 2;
private static final int V_COMPONENT_OF_WIND_PARAMETER_ID = 34;
private static final int V_COMPONENT_OF_WIND_GRIB2_DISCIPLINE = 0;
private static final int V_COMPONENT_OF_WIND_GRIB2_PARAMETER_CATEGORY = 2;
private static final int V_COMPONENT_OF_WIND_GRIB2_PARAMETER_NUMBER = 3;
public UVWindField(FeatureDataset dataSet) {
super(dataSet, /* baseConfidence */ 0.5);
private static final VariableSpecification uComponentOfWindVariableSpecification =
new CompositeVariableSpecification(new Grib1VariableSpecification(U_COMPONENT_OF_WIND_PARAMETER_ID),
new Grib2VariableSpecification(new int[] { U_COMPONENT_OF_WIND_GRIB2_DISCIPLINE, U_COMPONENT_OF_WIND_GRIB2_PARAMETER_CATEGORY, U_COMPONENT_OF_WIND_GRIB2_PARAMETER_NUMBER }));
private static final VariableSpecification vComponentOfWindVariableSpecification =
new CompositeVariableSpecification(new Grib1VariableSpecification(V_COMPONENT_OF_WIND_PARAMETER_ID),
new Grib2VariableSpecification(new int[] { V_COMPONENT_OF_WIND_GRIB2_DISCIPLINE, V_COMPONENT_OF_WIND_GRIB2_PARAMETER_CATEGORY, V_COMPONENT_OF_WIND_GRIB2_PARAMETER_NUMBER }));
public UVWindField(FeatureDataset... dataSets) {
super(/* baseConfidence */ 0.5, dataSets);
}
@Override
public WindWithConfidence<TimePoint> getWind(TimePoint timePoint, Position position) throws IOException {
FeatureDataset dataSet = getDataSet();
final Wind wind;
final double confidence;
if (dataSet instanceof GridDataset) {
Triple<Double, TimePoint, Position> uComponentInMetersPerSecond = null;
Triple<Double, TimePoint, Position> vComponentInMetersPerSecond = null;
for (final GridDatatype grid : ((GridDataset) dataSet).getGrids()) {
final Integer variableId = getVariableId(grid.getVariable()).orElse(-1);
if (variableId == U_COMPONENT_OF_WIND_PARAMETER_ID) {
assert isMetersPerSecond(getUnit(grid.getVariable()).get());
uComponentInMetersPerSecond = getValue(grid, timePoint, position);
} else if (variableId == V_COMPONENT_OF_WIND_PARAMETER_ID) {
assert isMetersPerSecond(getUnit(grid.getVariable()).get());
vComponentInMetersPerSecond = getValue(grid, timePoint, position);
}
if (uComponentInMetersPerSecond != null && vComponentInMetersPerSecond != null) {
break;
Triple<Double, TimePoint, Position> uComponentInMetersPerSecond = null;
Triple<Double, TimePoint, Position> vComponentInMetersPerSecond = null;
for (final FeatureDataset dataSet : getDataSets()) {
if (dataSet instanceof GridDataset) {
for (final GridDatatype grid : ((GridDataset) dataSet).getGrids()) {
if (uComponentOfWindVariableSpecification.matches(grid.getVariable())) {
assert isMetersPerSecond(getUnit(grid.getVariable()).get());
uComponentInMetersPerSecond = getValue(grid, timePoint, position);
} else if (vComponentOfWindVariableSpecification.matches(grid.getVariable())) {
assert isMetersPerSecond(getUnit(grid.getVariable()).get());
vComponentInMetersPerSecond = getValue(grid, timePoint, position);
}
if (uComponentInMetersPerSecond != null && vComponentInMetersPerSecond != null) {
break;
}
}
}
}
if (uComponentInMetersPerSecond != null && vComponentInMetersPerSecond != null) {
confidence = getTimeConfidence(timePoint, uComponentInMetersPerSecond.getB());
final double atan2 = Math.atan2(uComponentInMetersPerSecond.getA(), vComponentInMetersPerSecond.getA());
wind = new WindImpl(uComponentInMetersPerSecond.getC(), uComponentInMetersPerSecond.getB(),
@@ -58,16 +75,11 @@ public class UVWindField extends AbstractGribWindFieldImpl {
return new WindWithConfidenceImpl<TimePoint>(wind, confidence*getBaseConfidence(), timePoint, /* useSpeed */ true);
}
private boolean isMetersPerSecond(String unit) {
final String spacelessUnit = unit.replaceAll(" ", "");
return spacelessUnit.equals("m/s") || spacelessUnit.equals("ms^-1");
}
/**
* Checks whether the data set has a u-component of wind (GRIB parameter #33) and a v-component of wind
* (GRIB parameter #34).
*/
public static boolean handles(FeatureDataset dataSet) {
return hasVariable(dataSet, U_COMPONENT_OF_WIND_PARAMETER_ID) && hasVariable(dataSet, V_COMPONENT_OF_WIND_PARAMETER_ID);
public static boolean handles(FeatureDataset... dataSets) {
return hasVariable(uComponentOfWindVariableSpecification, dataSets) && hasVariable(vComponentOfWindVariableSpecification, dataSets);
}
}
@@ -0,0 +1,45 @@
package com.sap.sailing.grib.impl;
import ucar.nc2.VariableSimpleIF;
import ucar.nc2.ft.FeatureDataset;
/**
* GRIB variables can be described by a GRIB1 "indicatorOfParameter" which is a single integer, such as {@code 31} for
* the wind direction (see http://apps.ecmwf.int/codes/grib/param-db?id=3031), or by a GRIB2 integer sequence starting with
* (discipline, parameterCategory, parameterNumber) such as (0, 2, 0) for the wind direction. An instance of a class
* implementing this interface is able to tell for a given {@link VariableSimpleIF} whether this specification matches
* the variable.
*
* @author Axel Uhl (D043530)
*
*/
public interface VariableSpecification {
/**
* Checks whether the given {@code variable} conforms to this variable specification.
*/
boolean matches(VariableSimpleIF variable);
default boolean appearsInAnyOf(FeatureDataset... dataSets) {
for (final FeatureDataset dataSet : dataSets) {
for (final VariableSimpleIF variable : dataSet.getDataVariables()) {
if (matches(variable)) {
return true;
}
}
}
return false;
}
/**
* Obtains the variable from the collection of {@code variables} that {@link #matches(VariableSimpleIF) matches}
* this specification. If no such variable exists in {@code variables}, {@code null} is returned.
*/
default VariableSimpleIF getVariable(Iterable<VariableSimpleIF> variables) {
for (final VariableSimpleIF variable : variables) {
if (matches(variable)) {
return variable;
}
}
return null;
}
}