normalized line endings

This commit is contained in:
Steffen Schaefer
2017-05-16 09:42:02 +02:00
parent 8cf9053d37
commit 9123771f34
2780 changed files with 301735 additions and 301735 deletions
+12 -12
View File
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry exported="true" kind="lib" path="lib/netcdfAll-4.6.8.jar" sourcepath="C:/data/java/thredds/cdm/src/main/java">
<attributes>
<attribute name="javadoc_location" value="https://www.unidata.ucar.edu/software/thredds/v4.3/netcdf-java/v4.3/javadoc/"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry exported="true" kind="lib" path="lib/netcdfAll-4.6.8.jar" sourcepath="C:/data/java/thredds/cdm/src/main/java">
<attributes>
<attribute name="javadoc_location" value="https://www.unidata.ucar.edu/software/thredds/v4.3/netcdf-java/v4.3/javadoc/"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+12 -12
View File
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.sap.sailing.grib</artifactId>
<packaging>eclipse-plugin</packaging>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.sap.sailing.grib</artifactId>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,45 +1,45 @@
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;
import com.sap.sse.common.TimeRange;
/**
* A wind field that is based on some GRIB data and can be queried using a {@link TimePoint} and a {@link Position}.
*
* @author Axel Uhl (D043530)
*
*/
public interface GribWindField {
/**
* @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;
/**
* The time range covered by the underlying GRIB data.
*/
TimeRange getTimeRange();
Iterable<Wind> getAllWindFixes() throws IOException;
}
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;
import com.sap.sse.common.TimeRange;
/**
* A wind field that is based on some GRIB data and can be queried using a {@link TimePoint} and a {@link Position}.
*
* @author Axel Uhl (D043530)
*
*/
public interface GribWindField {
/**
* @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;
/**
* The time range covered by the underlying GRIB data.
*/
TimeRange getTimeRange();
Iterable<Wind> getAllWindFixes() throws IOException;
}
@@ -1,82 +1,82 @@
package com.sap.sailing.grib;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Formatter;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.grib.impl.GribWindFieldFactoryImpl;
import ucar.nc2.ft.FeatureDataset;
/**
* Factory interface used to create objects of type {@link GribWindField} based on a {@link FeatureDataset}.
*
* Example Usage:
* <pre>
* final Formatter errorLog = createLogFormatter(logger, Level.INFO);
* final ucar.nc2.util.CancelTask task = null;
* final String location = "resources/globalMarineNetCroatia.grb.bz2"; // or could be some URI / URL from where to retrieve it
* final FeatureDataset dataSet = FeatureDatasetFactoryManager.open(FeatureType.ANY, location, task, errorLog);
* final GribWindField windField = GribWindFieldFactory.INSTANCE.createGribWindField(dataSet);
* </pre>
*
* Or:
* <pre>
* final GribWindField windField = GribWindFieldFactory.INSTANCE.createGribWindField(new File("/this/is/the/path/to/my/grib/file.grb"));
* </pre>
*
* @author Axel Uhl (D043530)
*
*/
public interface GribWindFieldFactory {
GribWindFieldFactory INSTANCE = new GribWindFieldFactoryImpl();
GribWindField createGribWindField(FeatureDataset... dataSet);
GribWindField createGribWindField(Iterable<String> locations) throws IOException;
GribWindField createGribWindField(Logger logger, Level level, Iterable<String> locations) throws IOException;
GribWindField createGribWindField(Formatter errorLog, Iterable<String> locations) throws IOException;
GribWindField createGribWindFieldFromFiles(Iterable<File> files) throws IOException;
GribWindField createGribWindFieldFromFiles(Formatter errorLog, Iterable<File> files) throws IOException;
GribWindField createGribWindFieldFromFiles(Logger logger, Level level, Iterable<File> files) throws IOException;
/**
* Requires that the $TMP part of the file system can be accessed for writing because the files will temporarily be
* stored there.
*/
GribWindField createGribWindFieldFromStreams(Map<InputStream, String> inputStreamsAndFilenames) throws IOException;
/**
* Requires that the $TMP part of the file system can be accessed for writing because the files will temporarily be
* stored there.
*/
GribWindField createGribWindFieldFromStreams(Formatter errorLog, Map<InputStream, String> inputStreamsAndFilenames) throws IOException;
/**
* Requires that the $TMP part of the file system can be accessed for writing because the files will temporarily be
* stored there.
*/
GribWindField createGribWindFieldFromStreams(Logger logger, Level level, Map<InputStream, String> inputStreamsAndFilenames) throws IOException;
/**
* Creates a {@link Formatter} that writes its messages to the {@code logger} using the log {@code level} provided.
* Use this method to create formatters for the {@code createGribWindField...} methods that require one.
*/
Formatter createLogFormatter(Logger logger, Level level);
/**
* To be called when the bundle shuts down; all resources can be released now, and all persistent file
* caches can be cleaned.
*/
void shutdown();
}
package com.sap.sailing.grib;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Formatter;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.grib.impl.GribWindFieldFactoryImpl;
import ucar.nc2.ft.FeatureDataset;
/**
* Factory interface used to create objects of type {@link GribWindField} based on a {@link FeatureDataset}.
*
* Example Usage:
* <pre>
* final Formatter errorLog = createLogFormatter(logger, Level.INFO);
* final ucar.nc2.util.CancelTask task = null;
* final String location = "resources/globalMarineNetCroatia.grb.bz2"; // or could be some URI / URL from where to retrieve it
* final FeatureDataset dataSet = FeatureDatasetFactoryManager.open(FeatureType.ANY, location, task, errorLog);
* final GribWindField windField = GribWindFieldFactory.INSTANCE.createGribWindField(dataSet);
* </pre>
*
* Or:
* <pre>
* final GribWindField windField = GribWindFieldFactory.INSTANCE.createGribWindField(new File("/this/is/the/path/to/my/grib/file.grb"));
* </pre>
*
* @author Axel Uhl (D043530)
*
*/
public interface GribWindFieldFactory {
GribWindFieldFactory INSTANCE = new GribWindFieldFactoryImpl();
GribWindField createGribWindField(FeatureDataset... dataSet);
GribWindField createGribWindField(Iterable<String> locations) throws IOException;
GribWindField createGribWindField(Logger logger, Level level, Iterable<String> locations) throws IOException;
GribWindField createGribWindField(Formatter errorLog, Iterable<String> locations) throws IOException;
GribWindField createGribWindFieldFromFiles(Iterable<File> files) throws IOException;
GribWindField createGribWindFieldFromFiles(Formatter errorLog, Iterable<File> files) throws IOException;
GribWindField createGribWindFieldFromFiles(Logger logger, Level level, Iterable<File> files) throws IOException;
/**
* Requires that the $TMP part of the file system can be accessed for writing because the files will temporarily be
* stored there.
*/
GribWindField createGribWindFieldFromStreams(Map<InputStream, String> inputStreamsAndFilenames) throws IOException;
/**
* Requires that the $TMP part of the file system can be accessed for writing because the files will temporarily be
* stored there.
*/
GribWindField createGribWindFieldFromStreams(Formatter errorLog, Map<InputStream, String> inputStreamsAndFilenames) throws IOException;
/**
* Requires that the $TMP part of the file system can be accessed for writing because the files will temporarily be
* stored there.
*/
GribWindField createGribWindFieldFromStreams(Logger logger, Level level, Map<InputStream, String> inputStreamsAndFilenames) throws IOException;
/**
* Creates a {@link Formatter} that writes its messages to the {@code logger} using the log {@code level} provided.
* Use this method to create formatters for the {@code createGribWindField...} methods that require one.
*/
Formatter createLogFormatter(Logger logger, Level level);
/**
* To be called when the bundle shuts down; all resources can be released now, and all persistent file
* caches can be cleaned.
*/
void shutdown();
}
@@ -1,301 +1,301 @@
package com.sap.sailing.grib.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.StreamSupport;
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.Pair;
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.ma2.Index;
import ucar.nc2.Dimension;
import ucar.nc2.dataset.VariableDS;
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
* "Wind speed" parameter #32, or the "u-component of wind" parameter #33 and the "v-component of wind"
* parameter #34. If the GRIB source has wind for more than one vertical layer/level (z-axis), only the
* first one is used.
*
* @author Axel Uhl (d043530)
*
*/
public abstract class AbstractGribWindFieldImpl implements GribWindField {
private final FeatureDataset[] dataSets;
private final Weigher<TimePoint> timeConfidenceWeigher;
/**
* 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(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(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() {
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
public 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.startsAtOrAfter(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 StreamSupport.stream(Arrays.asList(getDataSets()).spliterator(), /* parallel */ false).map(dataSet->toTimePoint(dataSet.getCalendarDateStart())).min(Comparator.naturalOrder()).get();
}
private TimePoint getEndTime() {
return StreamSupport.stream(Arrays.asList(getDataSets()).spliterator(), /* parallel */ false).map(dataSet->toTimePoint(dataSet.getCalendarDateEnd())).max(Comparator.naturalOrder()).get();
}
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[] getDataSets() {
return dataSets;
}
/**
* 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();
}
Pair<Double, Position> nearestNonNaNValueWithPosition = getNearestNonNaNValueWithPosition(
coordinateSystem, grid, timeIndex, /* zIndex */ 0, xy[1], xy[0], /* distance */ 0);
return new Triple<Double, TimePoint, Position>(nearestNonNaNValueWithPosition.getA(), responseTimePoint, nearestNonNaNValueWithPosition.getB());
}
/**
* Tries to find a non-{@code NaN} value starting at a given "distance" from a grid point. "Distance" here means the
* following: a grid point (x, y) has distance d from a grid point (a, b) if and only if {@code Math.abs(a-x)==d &&
* Math.abs(b-y)<=d || Math.abs(a-x)<=d && Math.abs(b-y)==d}. These points are intersected with the bounds as
* defined by the dimensions and then probed until a non-{@code NaN} value is found. The position for the grid point
* where such a value was found is then returned. If no such point is found with the exact distance, the next higher
* distance is probed recursively. If no such point exists within the bounds of the grid,
* {@code new Pair<>(NaN, null)} will be returned.
*/
private Pair<Double, Position> getNearestNonNaNValueWithPosition(GridCoordSystem coordinateSystem,
GridDatatype grid, int timeIndex, int zIndex, int yIndex, int xIndex, int distance) throws IOException {
final int minX = Math.max(0, xIndex-distance);
final int maxX = Math.min(grid.getXDimension().getLength()-1, xIndex+distance);
final int minY = Math.max(0, yIndex-distance);
final int maxY = Math.min(grid.getYDimension().getLength()-1, yIndex+distance);
if (minX>maxX || minY>maxY) { // empty range
return new Pair<>(Double.NaN, null);
}
for (int x=minX; x<=maxX; x++) {
for (int y = minY; y <= maxY; y += Math.max(1, maxY - minY)) { // scan upper and lower row; increment even if single-element range to trigger condition
final double valueAsFloat = getValue(grid, timeIndex, zIndex, x, y);
if (!Double.isNaN(valueAsFloat)) {
final Position responsePosition = toPosition(coordinateSystem.getLatLon(x, y));
return new Pair<>(valueAsFloat, responsePosition);
}
}
}
for (int y=minY+1; y<maxY; y++) {
for (int x = minX; x <= maxX; x += Math.max(1, maxX - minX)) { // scan left and right border without top/bottom elements
final double valueAsFloat = getValue(grid, timeIndex, zIndex, x, y);
if (!Double.isNaN(valueAsFloat)) {
final Position responsePosition = toPosition(coordinateSystem.getLatLon(x, y));
return new Pair<>(valueAsFloat, responsePosition);
}
}
}
// nothing found so far; tail-recurse for next greater distance
return getNearestNonNaNValueWithPosition(coordinateSystem, grid, timeIndex, zIndex, yIndex, xIndex, distance+1);
}
protected double getValue(GridDatatype grid, int timeIndex, int zIndex, final int x, final int y) throws IOException {
final Array arrayForTimePointBefore = grid.readDataSlice(timeIndex, zIndex, y, x);
final double valueAsFloat = (double) arrayForTimePointBefore.getFloat(0);
return valueAsFloat;
}
/**
* Instead of using {@link GridDatatype#readDataSlice(int, int, int, int)} which really reads from the file(s),
* use an already read {@link Array} of mass data here.
*/
protected double getValue(Array gridData, int zIndex, final int x, final int y) throws IOException {
final double valueAsFloat;
if (gridData.getRank() == 3) { // includes z dimension
valueAsFloat = gridData.getDouble(Index.factory(new int[] { zIndex, y, x }));
} else {
valueAsFloat = gridData.getDouble(Index.factory(new int[] { y, x }));
}
return valueAsFloat;
}
@FunctionalInterface
static interface ValueForCoordinateProvider<T> {
T getValue(Array gridData, int timeIndex, Index index, TimePoint timePoint, Position position);
}
protected <T> Iterable<T> foreach(GridDatatype grid, ValueForCoordinateProvider<T> provider) throws IOException {
final List<T> result = new ArrayList<>();
final GridCoordSystem coordinateSystem = grid.getCoordinateSystem();
final int timeDimLength = grid.getTimeDimension().getLength();
for (int t=0; t<timeDimLength; t++) {
final TimePoint timePoint = toTimePoint(coordinateSystem.getTimeAxis1D().getCalendarDate(t));
final Array gridData = grid.readVolumeData(t);
final long arraySize = gridData.getSize();
final Index index = gridData.getIndex();
for (long i=0; i<arraySize; i++) {
final Position position = toPosition(coordinateSystem.getLatLon(/* x */ index.getCurrentCounter()[index.getCurrentCounter().length-1],
/* y */ index.getCurrentCounter()[index.getCurrentCounter().length-2]));
result.add(provider.getValue(gridData, t, index, timePoint, position));
index.incr();
}
}
return result;
}
protected 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;
}
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();
}
}
package com.sap.sailing.grib.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.StreamSupport;
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.Pair;
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.ma2.Index;
import ucar.nc2.Dimension;
import ucar.nc2.dataset.VariableDS;
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
* "Wind speed" parameter #32, or the "u-component of wind" parameter #33 and the "v-component of wind"
* parameter #34. If the GRIB source has wind for more than one vertical layer/level (z-axis), only the
* first one is used.
*
* @author Axel Uhl (d043530)
*
*/
public abstract class AbstractGribWindFieldImpl implements GribWindField {
private final FeatureDataset[] dataSets;
private final Weigher<TimePoint> timeConfidenceWeigher;
/**
* 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(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(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() {
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
public 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.startsAtOrAfter(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 StreamSupport.stream(Arrays.asList(getDataSets()).spliterator(), /* parallel */ false).map(dataSet->toTimePoint(dataSet.getCalendarDateStart())).min(Comparator.naturalOrder()).get();
}
private TimePoint getEndTime() {
return StreamSupport.stream(Arrays.asList(getDataSets()).spliterator(), /* parallel */ false).map(dataSet->toTimePoint(dataSet.getCalendarDateEnd())).max(Comparator.naturalOrder()).get();
}
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[] getDataSets() {
return dataSets;
}
/**
* 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();
}
Pair<Double, Position> nearestNonNaNValueWithPosition = getNearestNonNaNValueWithPosition(
coordinateSystem, grid, timeIndex, /* zIndex */ 0, xy[1], xy[0], /* distance */ 0);
return new Triple<Double, TimePoint, Position>(nearestNonNaNValueWithPosition.getA(), responseTimePoint, nearestNonNaNValueWithPosition.getB());
}
/**
* Tries to find a non-{@code NaN} value starting at a given "distance" from a grid point. "Distance" here means the
* following: a grid point (x, y) has distance d from a grid point (a, b) if and only if {@code Math.abs(a-x)==d &&
* Math.abs(b-y)<=d || Math.abs(a-x)<=d && Math.abs(b-y)==d}. These points are intersected with the bounds as
* defined by the dimensions and then probed until a non-{@code NaN} value is found. The position for the grid point
* where such a value was found is then returned. If no such point is found with the exact distance, the next higher
* distance is probed recursively. If no such point exists within the bounds of the grid,
* {@code new Pair<>(NaN, null)} will be returned.
*/
private Pair<Double, Position> getNearestNonNaNValueWithPosition(GridCoordSystem coordinateSystem,
GridDatatype grid, int timeIndex, int zIndex, int yIndex, int xIndex, int distance) throws IOException {
final int minX = Math.max(0, xIndex-distance);
final int maxX = Math.min(grid.getXDimension().getLength()-1, xIndex+distance);
final int minY = Math.max(0, yIndex-distance);
final int maxY = Math.min(grid.getYDimension().getLength()-1, yIndex+distance);
if (minX>maxX || minY>maxY) { // empty range
return new Pair<>(Double.NaN, null);
}
for (int x=minX; x<=maxX; x++) {
for (int y = minY; y <= maxY; y += Math.max(1, maxY - minY)) { // scan upper and lower row; increment even if single-element range to trigger condition
final double valueAsFloat = getValue(grid, timeIndex, zIndex, x, y);
if (!Double.isNaN(valueAsFloat)) {
final Position responsePosition = toPosition(coordinateSystem.getLatLon(x, y));
return new Pair<>(valueAsFloat, responsePosition);
}
}
}
for (int y=minY+1; y<maxY; y++) {
for (int x = minX; x <= maxX; x += Math.max(1, maxX - minX)) { // scan left and right border without top/bottom elements
final double valueAsFloat = getValue(grid, timeIndex, zIndex, x, y);
if (!Double.isNaN(valueAsFloat)) {
final Position responsePosition = toPosition(coordinateSystem.getLatLon(x, y));
return new Pair<>(valueAsFloat, responsePosition);
}
}
}
// nothing found so far; tail-recurse for next greater distance
return getNearestNonNaNValueWithPosition(coordinateSystem, grid, timeIndex, zIndex, yIndex, xIndex, distance+1);
}
protected double getValue(GridDatatype grid, int timeIndex, int zIndex, final int x, final int y) throws IOException {
final Array arrayForTimePointBefore = grid.readDataSlice(timeIndex, zIndex, y, x);
final double valueAsFloat = (double) arrayForTimePointBefore.getFloat(0);
return valueAsFloat;
}
/**
* Instead of using {@link GridDatatype#readDataSlice(int, int, int, int)} which really reads from the file(s),
* use an already read {@link Array} of mass data here.
*/
protected double getValue(Array gridData, int zIndex, final int x, final int y) throws IOException {
final double valueAsFloat;
if (gridData.getRank() == 3) { // includes z dimension
valueAsFloat = gridData.getDouble(Index.factory(new int[] { zIndex, y, x }));
} else {
valueAsFloat = gridData.getDouble(Index.factory(new int[] { y, x }));
}
return valueAsFloat;
}
@FunctionalInterface
static interface ValueForCoordinateProvider<T> {
T getValue(Array gridData, int timeIndex, Index index, TimePoint timePoint, Position position);
}
protected <T> Iterable<T> foreach(GridDatatype grid, ValueForCoordinateProvider<T> provider) throws IOException {
final List<T> result = new ArrayList<>();
final GridCoordSystem coordinateSystem = grid.getCoordinateSystem();
final int timeDimLength = grid.getTimeDimension().getLength();
for (int t=0; t<timeDimLength; t++) {
final TimePoint timePoint = toTimePoint(coordinateSystem.getTimeAxis1D().getCalendarDate(t));
final Array gridData = grid.readVolumeData(t);
final long arraySize = gridData.getSize();
final Index index = gridData.getIndex();
for (long i=0; i<arraySize; i++) {
final Position position = toPosition(coordinateSystem.getLatLon(/* x */ index.getCurrentCounter()[index.getCurrentCounter().length-1],
/* y */ index.getCurrentCounter()[index.getCurrentCounter().length-2]));
result.add(provider.getValue(gridData, t, index, timePoint, position));
index.incr();
}
}
return result;
}
protected 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;
}
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();
}
}
@@ -1,21 +1,21 @@
package com.sap.sailing.grib.impl;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import com.sap.sailing.grib.GribWindFieldFactory;
public class Activator implements BundleActivator {
private GribWindFieldFactory factory;
@Override
public void start(BundleContext context) throws Exception {
factory = GribWindFieldFactoryImpl.INSTANCE;
}
@Override
public void stop(BundleContext context) throws Exception {
factory.shutdown();
}
}
package com.sap.sailing.grib.impl;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import com.sap.sailing.grib.GribWindFieldFactory;
public class Activator implements BundleActivator {
private GribWindFieldFactory factory;
@Override
public void start(BundleContext context) throws Exception {
factory = GribWindFieldFactoryImpl.INSTANCE;
}
@Override
public void stop(BundleContext context) throws Exception {
factory.shutdown();
}
}
@@ -1,30 +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)
+ "]";
}
}
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)
+ "]";
}
}
@@ -1,44 +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 + "]";
}
}
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 + "]";
}
}
@@ -1,63 +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) + "]";
}
}
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) + "]";
}
}
@@ -1,201 +1,201 @@
package com.sap.sailing.grib.impl;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.grib.GribWindField;
import com.sap.sailing.grib.GribWindFieldFactory;
import com.sap.sse.common.util.MappingIterable;
import com.sap.sse.util.LoggerAppender;
import ucar.nc2.constants.FeatureType;
import ucar.nc2.ft.FeatureDataset;
import ucar.nc2.ft.FeatureDatasetFactoryManager;
public class GribWindFieldFactoryImpl implements GribWindFieldFactory {
private static final Logger logger = Logger.getLogger(GribWindFieldFactoryImpl.class.getName());
private static final Level DEFAULT_ERROR_LOG_LEVEL = Level.INFO;
/**
* Weak references inserted into {@link #filesToCleanWhenGribWindFieldNoLongerUsed} must be registered with this
* queue. When such a registration happens, a {@link #fileSystemCleaner} thread must be started while holding this
* object's monitor (synchronized). The thread is then responsible for observing the queue and cleaning the
* directories. When, while holding the monitor, the {@link #filesToCleanWhenGribWindFieldNoLongerUsed} map becomes
* empty, the thread terminates and sets the {@link #fileSystemCleaner} field to {@code null} so that a new thread
* must be created the next time a reference is entered into the map.
*/
private final ReferenceQueue<GribWindField> referenceQueue = new ReferenceQueue<>();
private Thread fileSystemCleaner;
private final Map<WeakReference<GribWindField>, File> filesToCleanWhenGribWindFieldNoLongerUsed = new HashMap<>();
private void removeDirectoryWhenWindFieldNoLongerStronglyReferenced(GribWindField windField, File directoryToRemove) {
assert directoryToRemove.isDirectory();
synchronized (this) {
final boolean needToStartThread = filesToCleanWhenGribWindFieldNoLongerUsed.isEmpty();
filesToCleanWhenGribWindFieldNoLongerUsed.put(new WeakReference<GribWindField>(windField, referenceQueue), directoryToRemove);
if (needToStartThread && fileSystemCleaner == null) {
fileSystemCleaner = new Thread(()->{
boolean finished = false;
while (!finished) {
try {
final Reference<? extends GribWindField> ref = referenceQueue.remove();
finished = cleanup(ref);
} catch (InterruptedException e) {
logger.log(Level.WARNING, "Interrupted while waiting for weak reference, giving up", e);
finished = true;
}
}
}, "GRIB directory cleaner");
fileSystemCleaner.setDaemon(true);
fileSystemCleaner.start();
}
}
}
private boolean cleanup(final Reference<? extends GribWindField> ref) {
boolean finished;
final File dir;
synchronized (GribWindFieldFactoryImpl.this) {
dir = filesToCleanWhenGribWindFieldNoLongerUsed.remove(ref);
finished = filesToCleanWhenGribWindFieldNoLongerUsed.isEmpty();
if (finished) {
fileSystemCleaner = null;
}
}
rm_rf(dir);
return finished;
}
@Override
public synchronized void shutdown() {
// clone key set because cleanup will remove the reference from filesToCleanWhenGribWindFieldNoLongerUsed
// which would lead to a ConcurrentModificationException otherwise.
for (final WeakReference<GribWindField> ref : new ArrayList<>(filesToCleanWhenGribWindFieldNoLongerUsed.keySet())) {
cleanup(ref);
}
}
/**
* Removes directory and all its contents
*/
private void rm_rf(File dir) {
assert dir.isDirectory();
for (final File f : dir.listFiles(f->true)) {
if (f.isDirectory()) {
rm_rf(f);
} else {
f.delete();
}
}
dir.delete();
}
@Override
public GribWindField createGribWindField(FeatureDataset... dataSets) {
final GribWindField result;
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(s) "+dataSets);
}
return result;
}
@Override
public GribWindField createGribWindField(Iterable<String> locations) throws IOException {
return createGribWindField(logger, DEFAULT_ERROR_LOG_LEVEL, locations);
}
@Override
public GribWindField createGribWindField(Logger logger, Level level, Iterable<String> locations) throws IOException {
final Formatter errorLog = createLogFormatter(logger, level);
return createGribWindField(errorLog, locations);
}
@Override
public GribWindField createGribWindField(Formatter errorLog, Iterable<String> locations) throws IOException {
final List<FeatureDataset> dataSets = new ArrayList<>();
for (final String location : locations) {
FeatureDataset dataSet = FeatureDatasetFactoryManager.open(FeatureType.ANY, location, /* task */ null, errorLog);
dataSets.add(dataSet);
}
return createGribWindField(dataSets.toArray(new FeatureDataset[0]));
}
@Override
public GribWindField createGribWindFieldFromFiles(Iterable<File> files) throws IOException {
return createGribWindFieldFromFiles(logger, DEFAULT_ERROR_LOG_LEVEL, files);
}
@Override
public GribWindField createGribWindFieldFromFiles(Logger logger, Level level, Iterable<File> files)
throws IOException {
return createGribWindFieldFromFiles(createLogFormatter(logger, level), files);
}
@Override
public GribWindField createGribWindFieldFromFiles(Formatter errorLog, Iterable<File> files) throws IOException {
return createGribWindField(errorLog, new MappingIterable<>(files,
f->{ try { return f.getCanonicalPath(); } catch (Exception e) { logger.log(Level.SEVERE, "Error obtaining GRIB file path", e); return null; } }));
}
@Override
public GribWindField createGribWindFieldFromStreams(Map<InputStream, String> streamsAndFilenames) throws IOException {
return createGribWindFieldFromStreams(logger, DEFAULT_ERROR_LOG_LEVEL, streamsAndFilenames);
}
@Override
public GribWindField createGribWindFieldFromStreams(Logger logger, Level level, Map<InputStream, String> streamsAndFilenames)
throws IOException {
return createGribWindFieldFromStreams(createLogFormatter(logger, level), streamsAndFilenames);
}
@Override
public GribWindField createGribWindFieldFromStreams(Formatter errorLog, Map<InputStream, String> streamsAndFilenames)
throws IOException {
final List<File> files = new ArrayList<>();
for (final Entry<InputStream, String> e : streamsAndFilenames.entrySet()) {
files.add(copyStreamToFile(e.getKey(), e.getValue()));
}
final GribWindField result = createGribWindFieldFromFiles(errorLog, files);
for (final File file : files) {
removeDirectoryWhenWindFieldNoLongerStronglyReferenced(result, file.getParentFile());
}
return result;
}
/**
* Copies the contents of the input stream {@code s} into a temporary directory that is created solely for this
* purpose. The {@code filename} will be used for the name of the file. The {@link File} object representing the
* file written is returned so that it and its containing directory can be removed when the contents of the stream
* are no longer needed.
*/
private File copyStreamToFile(InputStream s, String filename) throws IOException {
Path tempDir = Files.createTempDirectory("gribcache");
Path filePath = tempDir.resolve(filename);
Files.copy(s, filePath);
return filePath.toFile();
}
@Override
public Formatter createLogFormatter(Logger logger, Level level) {
return new Formatter(new LoggerAppender(level, logger));
}
}
package com.sap.sailing.grib.impl;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.grib.GribWindField;
import com.sap.sailing.grib.GribWindFieldFactory;
import com.sap.sse.common.util.MappingIterable;
import com.sap.sse.util.LoggerAppender;
import ucar.nc2.constants.FeatureType;
import ucar.nc2.ft.FeatureDataset;
import ucar.nc2.ft.FeatureDatasetFactoryManager;
public class GribWindFieldFactoryImpl implements GribWindFieldFactory {
private static final Logger logger = Logger.getLogger(GribWindFieldFactoryImpl.class.getName());
private static final Level DEFAULT_ERROR_LOG_LEVEL = Level.INFO;
/**
* Weak references inserted into {@link #filesToCleanWhenGribWindFieldNoLongerUsed} must be registered with this
* queue. When such a registration happens, a {@link #fileSystemCleaner} thread must be started while holding this
* object's monitor (synchronized). The thread is then responsible for observing the queue and cleaning the
* directories. When, while holding the monitor, the {@link #filesToCleanWhenGribWindFieldNoLongerUsed} map becomes
* empty, the thread terminates and sets the {@link #fileSystemCleaner} field to {@code null} so that a new thread
* must be created the next time a reference is entered into the map.
*/
private final ReferenceQueue<GribWindField> referenceQueue = new ReferenceQueue<>();
private Thread fileSystemCleaner;
private final Map<WeakReference<GribWindField>, File> filesToCleanWhenGribWindFieldNoLongerUsed = new HashMap<>();
private void removeDirectoryWhenWindFieldNoLongerStronglyReferenced(GribWindField windField, File directoryToRemove) {
assert directoryToRemove.isDirectory();
synchronized (this) {
final boolean needToStartThread = filesToCleanWhenGribWindFieldNoLongerUsed.isEmpty();
filesToCleanWhenGribWindFieldNoLongerUsed.put(new WeakReference<GribWindField>(windField, referenceQueue), directoryToRemove);
if (needToStartThread && fileSystemCleaner == null) {
fileSystemCleaner = new Thread(()->{
boolean finished = false;
while (!finished) {
try {
final Reference<? extends GribWindField> ref = referenceQueue.remove();
finished = cleanup(ref);
} catch (InterruptedException e) {
logger.log(Level.WARNING, "Interrupted while waiting for weak reference, giving up", e);
finished = true;
}
}
}, "GRIB directory cleaner");
fileSystemCleaner.setDaemon(true);
fileSystemCleaner.start();
}
}
}
private boolean cleanup(final Reference<? extends GribWindField> ref) {
boolean finished;
final File dir;
synchronized (GribWindFieldFactoryImpl.this) {
dir = filesToCleanWhenGribWindFieldNoLongerUsed.remove(ref);
finished = filesToCleanWhenGribWindFieldNoLongerUsed.isEmpty();
if (finished) {
fileSystemCleaner = null;
}
}
rm_rf(dir);
return finished;
}
@Override
public synchronized void shutdown() {
// clone key set because cleanup will remove the reference from filesToCleanWhenGribWindFieldNoLongerUsed
// which would lead to a ConcurrentModificationException otherwise.
for (final WeakReference<GribWindField> ref : new ArrayList<>(filesToCleanWhenGribWindFieldNoLongerUsed.keySet())) {
cleanup(ref);
}
}
/**
* Removes directory and all its contents
*/
private void rm_rf(File dir) {
assert dir.isDirectory();
for (final File f : dir.listFiles(f->true)) {
if (f.isDirectory()) {
rm_rf(f);
} else {
f.delete();
}
}
dir.delete();
}
@Override
public GribWindField createGribWindField(FeatureDataset... dataSets) {
final GribWindField result;
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(s) "+dataSets);
}
return result;
}
@Override
public GribWindField createGribWindField(Iterable<String> locations) throws IOException {
return createGribWindField(logger, DEFAULT_ERROR_LOG_LEVEL, locations);
}
@Override
public GribWindField createGribWindField(Logger logger, Level level, Iterable<String> locations) throws IOException {
final Formatter errorLog = createLogFormatter(logger, level);
return createGribWindField(errorLog, locations);
}
@Override
public GribWindField createGribWindField(Formatter errorLog, Iterable<String> locations) throws IOException {
final List<FeatureDataset> dataSets = new ArrayList<>();
for (final String location : locations) {
FeatureDataset dataSet = FeatureDatasetFactoryManager.open(FeatureType.ANY, location, /* task */ null, errorLog);
dataSets.add(dataSet);
}
return createGribWindField(dataSets.toArray(new FeatureDataset[0]));
}
@Override
public GribWindField createGribWindFieldFromFiles(Iterable<File> files) throws IOException {
return createGribWindFieldFromFiles(logger, DEFAULT_ERROR_LOG_LEVEL, files);
}
@Override
public GribWindField createGribWindFieldFromFiles(Logger logger, Level level, Iterable<File> files)
throws IOException {
return createGribWindFieldFromFiles(createLogFormatter(logger, level), files);
}
@Override
public GribWindField createGribWindFieldFromFiles(Formatter errorLog, Iterable<File> files) throws IOException {
return createGribWindField(errorLog, new MappingIterable<>(files,
f->{ try { return f.getCanonicalPath(); } catch (Exception e) { logger.log(Level.SEVERE, "Error obtaining GRIB file path", e); return null; } }));
}
@Override
public GribWindField createGribWindFieldFromStreams(Map<InputStream, String> streamsAndFilenames) throws IOException {
return createGribWindFieldFromStreams(logger, DEFAULT_ERROR_LOG_LEVEL, streamsAndFilenames);
}
@Override
public GribWindField createGribWindFieldFromStreams(Logger logger, Level level, Map<InputStream, String> streamsAndFilenames)
throws IOException {
return createGribWindFieldFromStreams(createLogFormatter(logger, level), streamsAndFilenames);
}
@Override
public GribWindField createGribWindFieldFromStreams(Formatter errorLog, Map<InputStream, String> streamsAndFilenames)
throws IOException {
final List<File> files = new ArrayList<>();
for (final Entry<InputStream, String> e : streamsAndFilenames.entrySet()) {
files.add(copyStreamToFile(e.getKey(), e.getValue()));
}
final GribWindField result = createGribWindFieldFromFiles(errorLog, files);
for (final File file : files) {
removeDirectoryWhenWindFieldNoLongerStronglyReferenced(result, file.getParentFile());
}
return result;
}
/**
* Copies the contents of the input stream {@code s} into a temporary directory that is created solely for this
* purpose. The {@code filename} will be used for the name of the file. The {@link File} object representing the
* file written is returned so that it and its containing directory can be removed when the contents of the stream
* are no longer needed.
*/
private File copyStreamToFile(InputStream s, String filename) throws IOException {
Path tempDir = Files.createTempDirectory("gribcache");
Path filePath = tempDir.resolve(filename);
Files.copy(s, filePath);
return filePath.toFile();
}
@Override
public Formatter createLogFormatter(Logger logger, Level level) {
return new Formatter(new LoggerAppender(level, logger));
}
}
@@ -1,151 +1,151 @@
package com.sap.sailing.grib.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.MeterPerSecondSpeedWithDegreeBearingImpl;
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.ma2.Array;
import ucar.ma2.Index;
import ucar.nc2.dt.GridDatatype;
import ucar.nc2.dt.grid.GridDataset;
import ucar.nc2.ft.FeatureDataset;
public class SpeedAndDirectionWindField extends AbstractGribWindFieldImpl {
private static final Logger logger = Logger.getLogger(SpeedAndDirectionWindField.class.getName());
private static final int WIND_DIRECTION_PARAMETER_ID = 31;
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;
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 {
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 = createWindFixFromDirectionAndSpeed(directionComponentInDegreesTrue.getC(), directionComponentInDegreesTrue.getB(),
speedComponentInMetersPerSecond.getA(),
// we're getting the "from" direction from the GRIB file and need to convert to "to" here
directionComponentInDegreesTrue.getA());
} else {
wind = null;
confidence = 0;
}
return new WindWithConfidenceImpl<TimePoint>(wind, confidence*getBaseConfidence(), timePoint, /* useSpeed */ true);
}
private Wind createWindFixFromDirectionAndSpeed(Position position, TimePoint timePoint, double speedInMetersPerSecond, double fromTrueDirectionInDeg) {
return new WindImpl(position, timePoint,
new MeterPerSecondSpeedWithDegreeBearingImpl(speedInMetersPerSecond,
// we're getting the "from" direction from the GRIB file and need to convert to "to" here
new DegreeBearingImpl(fromTrueDirectionInDeg).reverse()));
}
/**
* 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... dataSets) {
return hasVariable(windDirectionVariableSpecification, dataSets) && hasVariable(windSpeedVariableSpecification, dataSets);
}
@Override
public Iterable<Wind> getAllWindFixes() throws IOException {
final List<Wind> result = new ArrayList<>();
GridDatatype directionGrid = null;
GridDatatype speedGrid = null;
for (final FeatureDataset dataSet : getDataSets()) {
for (Iterator<GridDatatype> i=((GridDataset) dataSet).getGrids().iterator(); i.hasNext() && (directionGrid==null || speedGrid==null); ) {
final GridDatatype grid = i.next();
if (windDirectionVariableSpecification.matches(grid.getVariable())) {
assert isDegreesTrue(getUnit(grid.getVariable()).get());
directionGrid = grid;
} else if (windSpeedVariableSpecification.matches(grid.getVariable())) {
assert isMetersPerSecond(getUnit(grid.getVariable()).get());
speedGrid = grid;
}
}
}
if (directionGrid != null && speedGrid != null) {
final GridDatatype finalSpeedGrid = speedGrid;
final Map<Integer, Array> speedGridDataCache = new HashMap<>();
for (final Wind wind : foreach(directionGrid, (Array directionGridData, int timeIndex, Index index, TimePoint timePoint, Position position)->{
try {
final Wind wind;
Array speedGridData = speedGridDataCache.get(timeIndex);
if (speedGridData == null) {
speedGridData = finalSpeedGrid.readVolumeData(timeIndex);
speedGridDataCache.put(timeIndex, speedGridData);
}
double speedInMetersPerSecond = speedGridData.getDouble(index);
double trueDirectionFromInDeg = directionGridData.getDouble(index);
if (!Double.isNaN(speedInMetersPerSecond) && !Double.isNaN(trueDirectionFromInDeg)) {
wind = createWindFixFromDirectionAndSpeed(position, timePoint, speedInMetersPerSecond, trueDirectionFromInDeg);
} else {
wind = null;
}
return wind;
} catch (Exception e) {
logger.log(Level.INFO, "Exception trying to compute wind from speed and direction", e);
return null;
}
})) {
if (wind != null) {
result.add(wind);
}
};
}
return result;
}
}
package com.sap.sailing.grib.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.MeterPerSecondSpeedWithDegreeBearingImpl;
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.ma2.Array;
import ucar.ma2.Index;
import ucar.nc2.dt.GridDatatype;
import ucar.nc2.dt.grid.GridDataset;
import ucar.nc2.ft.FeatureDataset;
public class SpeedAndDirectionWindField extends AbstractGribWindFieldImpl {
private static final Logger logger = Logger.getLogger(SpeedAndDirectionWindField.class.getName());
private static final int WIND_DIRECTION_PARAMETER_ID = 31;
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;
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 {
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 = createWindFixFromDirectionAndSpeed(directionComponentInDegreesTrue.getC(), directionComponentInDegreesTrue.getB(),
speedComponentInMetersPerSecond.getA(),
// we're getting the "from" direction from the GRIB file and need to convert to "to" here
directionComponentInDegreesTrue.getA());
} else {
wind = null;
confidence = 0;
}
return new WindWithConfidenceImpl<TimePoint>(wind, confidence*getBaseConfidence(), timePoint, /* useSpeed */ true);
}
private Wind createWindFixFromDirectionAndSpeed(Position position, TimePoint timePoint, double speedInMetersPerSecond, double fromTrueDirectionInDeg) {
return new WindImpl(position, timePoint,
new MeterPerSecondSpeedWithDegreeBearingImpl(speedInMetersPerSecond,
// we're getting the "from" direction from the GRIB file and need to convert to "to" here
new DegreeBearingImpl(fromTrueDirectionInDeg).reverse()));
}
/**
* 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... dataSets) {
return hasVariable(windDirectionVariableSpecification, dataSets) && hasVariable(windSpeedVariableSpecification, dataSets);
}
@Override
public Iterable<Wind> getAllWindFixes() throws IOException {
final List<Wind> result = new ArrayList<>();
GridDatatype directionGrid = null;
GridDatatype speedGrid = null;
for (final FeatureDataset dataSet : getDataSets()) {
for (Iterator<GridDatatype> i=((GridDataset) dataSet).getGrids().iterator(); i.hasNext() && (directionGrid==null || speedGrid==null); ) {
final GridDatatype grid = i.next();
if (windDirectionVariableSpecification.matches(grid.getVariable())) {
assert isDegreesTrue(getUnit(grid.getVariable()).get());
directionGrid = grid;
} else if (windSpeedVariableSpecification.matches(grid.getVariable())) {
assert isMetersPerSecond(getUnit(grid.getVariable()).get());
speedGrid = grid;
}
}
}
if (directionGrid != null && speedGrid != null) {
final GridDatatype finalSpeedGrid = speedGrid;
final Map<Integer, Array> speedGridDataCache = new HashMap<>();
for (final Wind wind : foreach(directionGrid, (Array directionGridData, int timeIndex, Index index, TimePoint timePoint, Position position)->{
try {
final Wind wind;
Array speedGridData = speedGridDataCache.get(timeIndex);
if (speedGridData == null) {
speedGridData = finalSpeedGrid.readVolumeData(timeIndex);
speedGridDataCache.put(timeIndex, speedGridData);
}
double speedInMetersPerSecond = speedGridData.getDouble(index);
double trueDirectionFromInDeg = directionGridData.getDouble(index);
if (!Double.isNaN(speedInMetersPerSecond) && !Double.isNaN(trueDirectionFromInDeg)) {
wind = createWindFixFromDirectionAndSpeed(position, timePoint, speedInMetersPerSecond, trueDirectionFromInDeg);
} else {
wind = null;
}
return wind;
} catch (Exception e) {
logger.log(Level.INFO, "Exception trying to compute wind from speed and direction", e);
return null;
}
})) {
if (wind != null) {
result.add(wind);
}
};
}
return result;
}
}
@@ -1,153 +1,153 @@
package com.sap.sailing.grib.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
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.ma2.Array;
import ucar.ma2.Index;
import ucar.nc2.dt.GridDatatype;
import ucar.nc2.dt.grid.GridDataset;
import ucar.nc2.ft.FeatureDataset;
public class UVWindField extends AbstractGribWindFieldImpl {
private static final Logger logger = Logger.getLogger(UVWindField.class.getName());
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;
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 {
final Wind wind;
final double confidence;
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());
wind = createWindFixFromUAndV(uComponentInMetersPerSecond.getC(), uComponentInMetersPerSecond.getB(),
uComponentInMetersPerSecond.getA(), vComponentInMetersPerSecond.getA());
} else {
wind = null;
confidence = 0;
}
return new WindWithConfidenceImpl<TimePoint>(wind, confidence*getBaseConfidence(), timePoint, /* useSpeed */ true);
}
/**
* 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... dataSets) {
return hasVariable(uComponentOfWindVariableSpecification, dataSets) && hasVariable(vComponentOfWindVariableSpecification, dataSets);
}
@Override
public Iterable<Wind> getAllWindFixes() throws IOException {
final List<Wind> result = new ArrayList<>();
for (final FeatureDataset dataSet : getDataSets()) {
GridDatatype uGrid = null;
GridDatatype vGrid = null;
for (Iterator<GridDatatype> i=((GridDataset) dataSet).getGrids().iterator(); i.hasNext() && (uGrid==null || vGrid==null); ) {
final GridDatatype grid = i.next();
if (uComponentOfWindVariableSpecification.matches(grid.getVariable())) {
assert isMetersPerSecond(getUnit(grid.getVariable()).get());
uGrid = grid;
} else if (vComponentOfWindVariableSpecification.matches(grid.getVariable())) {
assert isMetersPerSecond(getUnit(grid.getVariable()).get());
vGrid = grid;
}
}
if (uGrid != null && vGrid != null) {
final GridDatatype finalVGrid = vGrid;
final Map<Integer, Array> vGridDataCache = new HashMap<>();
for (final Wind wind : foreach(uGrid, (Array uGridData, int timeIndex, Index index, TimePoint timePoint, Position position)->{
assert Arrays.equals(index.getShape(), uGridData.getShape());
try {
final Wind wind;
Array vGridData = vGridDataCache.get(timeIndex);
if (vGridData == null) {
vGridData = finalVGrid.readVolumeData(timeIndex);
vGridDataCache.put(timeIndex, vGridData);
}
double uComponentInMetersPerSecond = uGridData.getDouble(index);
double vComponentInMetersPerSecond = vGridData.getDouble(index);
if (!Double.isNaN(uComponentInMetersPerSecond) && !Double.isNaN(vComponentInMetersPerSecond)) {
wind = createWindFixFromUAndV(position, timePoint, uComponentInMetersPerSecond, vComponentInMetersPerSecond);
} else {
wind = null;
}
return wind;
} catch (Exception e) {
logger.log(Level.INFO, "Exception trying to compute wind from speed and direction", e);
return null;
}
})) {
if (wind != null) {
result.add(wind);
}
};
}
}
return result;
}
private Wind createWindFixFromUAndV(Position position, TimePoint timePoint, double uComponentInMetersPerSecond,
double vComponentInMetersPerSecond) {
final double atan2 = Math.atan2(uComponentInMetersPerSecond, vComponentInMetersPerSecond);
return new WindImpl(position, timePoint,
new MeterPerSecondSpeedWithDegreeBearingImpl(
Math.sqrt(uComponentInMetersPerSecond * uComponentInMetersPerSecond
+ vComponentInMetersPerSecond * vComponentInMetersPerSecond),
new RadianBearingImpl(atan2 > 0 ? atan2 : 2 * Math.PI + atan2)));
}
}
package com.sap.sailing.grib.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
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.ma2.Array;
import ucar.ma2.Index;
import ucar.nc2.dt.GridDatatype;
import ucar.nc2.dt.grid.GridDataset;
import ucar.nc2.ft.FeatureDataset;
public class UVWindField extends AbstractGribWindFieldImpl {
private static final Logger logger = Logger.getLogger(UVWindField.class.getName());
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;
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 {
final Wind wind;
final double confidence;
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());
wind = createWindFixFromUAndV(uComponentInMetersPerSecond.getC(), uComponentInMetersPerSecond.getB(),
uComponentInMetersPerSecond.getA(), vComponentInMetersPerSecond.getA());
} else {
wind = null;
confidence = 0;
}
return new WindWithConfidenceImpl<TimePoint>(wind, confidence*getBaseConfidence(), timePoint, /* useSpeed */ true);
}
/**
* 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... dataSets) {
return hasVariable(uComponentOfWindVariableSpecification, dataSets) && hasVariable(vComponentOfWindVariableSpecification, dataSets);
}
@Override
public Iterable<Wind> getAllWindFixes() throws IOException {
final List<Wind> result = new ArrayList<>();
for (final FeatureDataset dataSet : getDataSets()) {
GridDatatype uGrid = null;
GridDatatype vGrid = null;
for (Iterator<GridDatatype> i=((GridDataset) dataSet).getGrids().iterator(); i.hasNext() && (uGrid==null || vGrid==null); ) {
final GridDatatype grid = i.next();
if (uComponentOfWindVariableSpecification.matches(grid.getVariable())) {
assert isMetersPerSecond(getUnit(grid.getVariable()).get());
uGrid = grid;
} else if (vComponentOfWindVariableSpecification.matches(grid.getVariable())) {
assert isMetersPerSecond(getUnit(grid.getVariable()).get());
vGrid = grid;
}
}
if (uGrid != null && vGrid != null) {
final GridDatatype finalVGrid = vGrid;
final Map<Integer, Array> vGridDataCache = new HashMap<>();
for (final Wind wind : foreach(uGrid, (Array uGridData, int timeIndex, Index index, TimePoint timePoint, Position position)->{
assert Arrays.equals(index.getShape(), uGridData.getShape());
try {
final Wind wind;
Array vGridData = vGridDataCache.get(timeIndex);
if (vGridData == null) {
vGridData = finalVGrid.readVolumeData(timeIndex);
vGridDataCache.put(timeIndex, vGridData);
}
double uComponentInMetersPerSecond = uGridData.getDouble(index);
double vComponentInMetersPerSecond = vGridData.getDouble(index);
if (!Double.isNaN(uComponentInMetersPerSecond) && !Double.isNaN(vComponentInMetersPerSecond)) {
wind = createWindFixFromUAndV(position, timePoint, uComponentInMetersPerSecond, vComponentInMetersPerSecond);
} else {
wind = null;
}
return wind;
} catch (Exception e) {
logger.log(Level.INFO, "Exception trying to compute wind from speed and direction", e);
return null;
}
})) {
if (wind != null) {
result.add(wind);
}
};
}
}
return result;
}
private Wind createWindFixFromUAndV(Position position, TimePoint timePoint, double uComponentInMetersPerSecond,
double vComponentInMetersPerSecond) {
final double atan2 = Math.atan2(uComponentInMetersPerSecond, vComponentInMetersPerSecond);
return new WindImpl(position, timePoint,
new MeterPerSecondSpeedWithDegreeBearingImpl(
Math.sqrt(uComponentInMetersPerSecond * uComponentInMetersPerSecond
+ vComponentInMetersPerSecond * vComponentInMetersPerSecond),
new RadianBearingImpl(atan2 > 0 ? atan2 : 2 * Math.PI + atan2)));
}
}
@@ -1,45 +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;
}
}
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;
}
}