mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-20 04:35:32 +00:00
added an implementation for a UVWindField that seems to successfully produce Wind objects
Change-Id: Iba2cf94d9b247cd8411a22d5c58843d648ce806b
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
+162
-4
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user