mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-20 04:35:32 +00:00
normalized line endings
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
+301
-301
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+30
-30
@@ -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)
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
|
||||
+44
-44
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
|
||||
+63
-63
@@ -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) + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+201
-201
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+151
-151
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
+45
-45
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user