added an implementation for a UVWindField that seems to successfully produce Wind objects

Change-Id: Iba2cf94d9b247cd8411a22d5c58843d648ce806b
This commit is contained in:
Axel Uhl
2017-01-20 18:24:55 +01:00
parent f09c7e4de1
commit a37ff48e94
8 changed files with 287 additions and 17 deletions
@@ -103,4 +103,6 @@ Export-Package: ucar.atd.dorade,
ucar.nc2.wmo,
ucar.nc2.write,
ucar.netcdf
Require-Bundle: com.sap.sailing.domain.common
Require-Bundle: com.sap.sailing.domain.common,
com.sap.sailing.domain,
com.sap.sse.common
@@ -1,8 +1,30 @@
package com.sap.sailing.grib;
import java.io.IOException;
import com.sap.sailing.domain.common.Bounds;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sse.common.TimePoint;
public interface GribWindField {
Wind getWind(Position position);
/**
* @return the geographical bounds within which there is wind data in this field
*/
Bounds getBounds();
/**
* @param timePoint
* at which time to query the wind; assuming that the wind field may have wind data for several time
* points, the data from the closest time point is used. Time and space distance as well as the general
* reliability of the service of the underlying GRIB file are combined into the confidence value of the
* wind vector returned.
* @param position
* a position that is {@link Bounds#contains(Position) contained} in the {@link #getBounds() bounds} of
* this field
* @return the wind data valid at the requested time point and position, with a confidence that expresses the
* general confidence in the GRIB source as well as the time-based confidence. For example, if asking
* further into the future than the source knows, confidence is reduced.
*/
WindWithConfidence<TimePoint> getWind(TimePoint timePoint, Position position) throws IOException;
}
@@ -1,11 +1,33 @@
package com.sap.sailing.grib.impl;
import com.sap.sailing.grib.GribWindField;
import java.io.IOException;
import java.util.Optional;
import com.sap.sailing.domain.common.Bounds;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.confidence.Weigher;
import com.sap.sailing.domain.common.impl.BoundsImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.confidence.ConfidenceFactory;
import com.sap.sailing.grib.GribWindField;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.Util.Triple;
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.dt.GridCoordSystem;
import ucar.nc2.dt.GridDatatype;
import ucar.nc2.dt.grid.GridDataset;
import ucar.nc2.ft.FeatureDataset;
import ucar.nc2.time.CalendarDate;
import ucar.unidata.geoloc.LatLonPoint;
import ucar.unidata.geoloc.LatLonRect;
/**
* Wraps a {@link GridDataset GRIB data set} that either has the "Wind direction" parameter #31 and the
@@ -19,21 +41,157 @@ import ucar.nc2.ft.FeatureDataset;
public abstract class AbstractGribWindFieldImpl implements GribWindField {
private final FeatureDataset dataSet;
public AbstractGribWindFieldImpl(FeatureDataset dataSet) {
private final Weigher<TimePoint> timeConfidenceWeigher;
/**
* The base confidence of the wind data coming from the underlying {@link #dataSet}, 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;
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()) {
final Attribute idVariable = variable.findAttributeIgnoreCase("Grib1_Parameter");
if (idVariable != null && idVariable.getNumericValue().intValue() == variableId) {
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 double getBaseConfidence() {
return baseConfidence;
}
@Override
public Bounds getBounds() {
return toBounds(getDataSet().getBoundingBox());
}
protected TimeRange getTimeRange() {
return new TimeRangeImpl(getStartTime(), getEndTime());
}
/**
* Based on the time difference to the {@link #getTimeRange() time range} of this GRIB data set computes
* a confidence value. If within the time range, the confidence value is 1. Outside the time range a
* weigher is used that reduces the confidence accordingly, like for other wind sources.
*/
protected double getTimeConfidence(TimePoint timePoint) {
final double result;
final TimeRange timeRange = getTimeRange();
if (timeRange.includes(timePoint)) {
result = 1.0;
} else if (timeRange.startsAfter(timePoint)) {
result = timeConfidenceWeigher.getConfidence(timeRange.from(), timePoint);
} else {
assert timeRange.endsBefore(timePoint);
result = timeConfidenceWeigher.getConfidence(timeRange.to(), timePoint);
}
return result;
}
protected double getTimeConfidence(TimePoint request, TimePoint fact) {
return timeConfidenceWeigher.getConfidence(request, fact);
}
protected TimePoint toTimePoint(CalendarDate calendarDate) {
return new MillisecondsTimePoint(calendarDate.getMillis());
}
protected TimePoint getStartTime() {
return toTimePoint(getDataSet().getCalendarDateStart());
}
private TimePoint getEndTime() {
return toTimePoint(getDataSet().getCalendarDateEnd());
}
private Bounds toBounds(LatLonRect boundingBox) {
return new BoundsImpl(toPosition(boundingBox.getLowerLeftPoint()), toPosition(boundingBox.getUpperRightPoint()));
}
private Position toPosition(LatLonPoint point) {
return new DegreePosition(point.getLatitude(), point.getLongitude());
}
protected FeatureDataset getDataSet() {
return dataSet;
}
/**
* Looks up a value in {@code grid} for the given time point and position.
*
* @param timePoint
* may be outside of {@link #getTimeRange()}; in any case, the value time-wise closes to the time point
* requested is chosen
* @param position
* may be outside of {@link #getBounds()}; in any case, the value at the position closest to
* {@code position} will be chosen
* @return a non-interpolated, non-extrapolated value taken at the grid point that is time-wise and position-wise
* closes to those co-ordinates requested; as a triple whose first component is the value itself, the second
* component is the grid time point at which the value was taken, and the third component is the grid
* position where the value was obtained
*/
protected Triple<Double, TimePoint, Position> getValue(GridDatatype grid, TimePoint timePoint, Position position) throws IOException {
GridCoordSystem coordinateSystem = grid.getCoordinateSystem();
int[] xy = coordinateSystem.findXYindexFromLatLon(position.getLatDeg(), position.getLngDeg(), new int[2]);
Dimension timeDimension = grid.getTimeDimension();
final int timeIndex;
final TimePoint responseTimePoint;
if (timeDimension != null) {
final int numberOfTimepointsInGrid = timeDimension.getLength();
if (numberOfTimepointsInGrid == 1) {
timeIndex = 0;
responseTimePoint = getStartTime();
} else {
final Duration timeBetweenSamples = getTimeRange().getDuration().divide(numberOfTimepointsInGrid);
final Duration requestedDurationAfterStart = getStartTime().until(timePoint);
final double steps = requestedDurationAfterStart.divide(timeBetweenSamples);
if (steps < 0) {
timeIndex = 0;
responseTimePoint = getStartTime();
} else if (steps > numberOfTimepointsInGrid-1) {
timeIndex = numberOfTimepointsInGrid-1;
responseTimePoint = getEndTime();
} else {
timeIndex = (int) Math.round(steps);
responseTimePoint = getStartTime().plus(timeBetweenSamples.times(timeIndex));
}
}
} else {
timeIndex = -1; // time index doesn't matter if no time dimension exists for the grid
responseTimePoint = getStartTime();
}
final Array arrayForTimePointBefore = grid.readDataSlice(timeIndex, /* z_index */ 0, xy[1], xy[0]);
final Position responsePosition = toPosition(coordinateSystem.getLatLon(xy[0], xy[1]));
return new Triple<Double, TimePoint, Position>((double) arrayForTimePointBefore.getFloat(0), responseTimePoint, responsePosition);
}
}
@@ -1,18 +1,19 @@
package com.sap.sailing.grib.impl;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sse.common.TimePoint;
import ucar.nc2.ft.FeatureDataset;
public class SpeedAndDirectionWindField extends AbstractGribWindFieldImpl {
public SpeedAndDirectionWindField(FeatureDataset dataSet) {
super(dataSet);
super(dataSet, /* baseConfidence */ 0.5);
}
@Override
public Wind getWind(Position position) {
public WindWithConfidence<TimePoint> getWind(TimePoint timePoint, Position position) {
// TODO Auto-generated method stub
return null;
}
@@ -1,20 +1,74 @@
package com.sap.sailing.grib.impl;
import java.io.IOException;
import java.util.Optional;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.impl.MeterPerSecondSpeedWithDegreeBearingImpl;
import com.sap.sailing.domain.common.impl.RadianBearingImpl;
import com.sap.sailing.domain.common.impl.WindImpl;
import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sailing.domain.tracking.impl.WindWithConfidenceImpl;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Triple;
import ucar.nc2.dataset.VariableDS;
import ucar.nc2.dt.GridDatatype;
import ucar.nc2.dt.grid.GridDataset;
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 V_COMPONENT_OF_WIND_PARAMETER_ID = 34;
public UVWindField(FeatureDataset dataSet) {
super(dataSet);
super(dataSet, /* baseConfidence */ 0.5);
}
@Override
public Wind getWind(Position position) {
// TODO Auto-generated method stub
return null;
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 getUnit(grid.getVariable()).get().equals("m/s");
uComponentInMetersPerSecond = getValue(grid, timePoint, position);
} else if (variableId == V_COMPONENT_OF_WIND_PARAMETER_ID) {
assert getUnit(grid.getVariable()).get().equals("m/s");
vComponentInMetersPerSecond = getValue(grid, timePoint, position);
}
if (uComponentInMetersPerSecond != null && vComponentInMetersPerSecond != null) {
break;
}
}
confidence = getTimeConfidence(timePoint, uComponentInMetersPerSecond.getB());
final double atan2 = Math.atan2(uComponentInMetersPerSecond.getA(), vComponentInMetersPerSecond.getA());
wind = new WindImpl(uComponentInMetersPerSecond.getC(), uComponentInMetersPerSecond.getB(),
new MeterPerSecondSpeedWithDegreeBearingImpl(Math.sqrt(uComponentInMetersPerSecond.getA()*uComponentInMetersPerSecond.getA()+
vComponentInMetersPerSecond.getA()*vComponentInMetersPerSecond.getA()),
new RadianBearingImpl(atan2>0 ? atan2 : 2*Math.PI+atan2).reverse()));
} else {
wind = null;
confidence = 0;
}
return new WindWithConfidenceImpl<TimePoint>(wind, confidence*getBaseConfidence(), timePoint, /* useSpeed */ true);
}
private Optional<String> getUnit(VariableDS variable) {
final Optional<String> result;
final ucar.nc2.Attribute attribute = variable.findAttribute("units");
if (attribute != null) {
result = Optional.of(attribute.getStringValue());
} else {
result = Optional.empty();
}
return result;
}
/**
@@ -22,6 +76,6 @@ public class UVWindField extends AbstractGribWindFieldImpl {
* (GRIB parameter #34).
*/
public static boolean handles(FeatureDataset dataSet) {
return hasVariable(dataSet, 33) && hasVariable(dataSet, 34);
return hasVariable(dataSet, U_COMPONENT_OF_WIND_PARAMETER_ID) && hasVariable(dataSet, V_COMPONENT_OF_WIND_PARAMETER_ID);
}
}