preparing for multi-file upload for both Expedition and GRIB; introduced MappingIterable and MappingIterator

moved wind import servlet package structure a little

Change-Id: I9b573d4c91191933383c87397ac8584d9463b4bb
This commit is contained in:
Axel Uhl
2017-02-08 09:52:25 +01:00
parent 0f4cb97efa
commit 07edd7ffb1
20 changed files with 613 additions and 161 deletions
+3 -81
View File
@@ -7,86 +7,8 @@ Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ClassPath: .,
lib/netcdfAll-4.6.8.jar
Export-Package: ucar.atd.dorade,
ucar.coord,
ucar.httpservices,
ucar.ma2,
ucar.nc2,
ucar.nc2.constants,
ucar.nc2.dataset,
ucar.nc2.dataset.conv,
ucar.nc2.dataset.transform,
ucar.nc2.dods,
ucar.nc2.dt,
ucar.nc2.dt.grid,
ucar.nc2.dt.grid.gis,
ucar.nc2.dt.image.image,
ucar.nc2.dt.point,
ucar.nc2.dt.point.decode,
ucar.nc2.dt.radial,
ucar.nc2.dt.trajectory,
ucar.nc2.ft,
ucar.nc2.ft.coordsys,
ucar.nc2.ft.fmrc,
ucar.nc2.ft.point,
ucar.nc2.ft.point.bufr,
ucar.nc2.ft.point.collection,
ucar.nc2.ft.point.remote,
ucar.nc2.ft.point.standard,
ucar.nc2.ft.point.standard.plug,
ucar.nc2.ft.point.writer,
ucar.nc2.ft.radial,
ucar.nc2.ft.scan,
ucar.nc2.ft.swath,
ucar.nc2.ft2,
ucar.nc2.geotiff,
ucar.nc2.grib,
ucar.nc2.grib.collection,
ucar.nc2.grib.grib1,
ucar.nc2.grib.grib1.tables,
ucar.nc2.grib.grib2,
ucar.nc2.grib.grib2.table,
ucar.nc2.grib.writer,
ucar.nc2.iosp,
ucar.nc2.iosp.bufr,
ucar.nc2.iosp.bufr.tables,
ucar.nc2.iosp.bufr.writer,
ucar.nc2.iosp.cinrad,
ucar.nc2.iosp.dmsp,
ucar.nc2.iosp.dorade,
ucar.nc2.iosp.fysat,
ucar.nc2.iosp.fysat.util,
ucar.nc2.iosp.gini,
ucar.nc2.iosp.grads,
ucar.nc2.iosp.hdf4,
ucar.nc2.iosp.hdf5,
ucar.nc2.iosp.misc,
ucar.nc2.iosp.netcdf3,
ucar.nc2.iosp.netcdf4,
ucar.nc2.iosp.nexrad2,
ucar.nc2.iosp.nids,
ucar.nc2.iosp.noaa,
ucar.nc2.iosp.nowrad,
ucar.nc2.iosp.shapefile,
ucar.nc2.iosp.sigmet,
ucar.nc2.iosp.uamiv,
ucar.nc2.iosp.uf,
ucar.nc2.jni.netcdf,
ucar.nc2.ncml,
ucar.nc2.rewrite,
ucar.nc2.stream,
ucar.nc2.thredds,
ucar.nc2.time,
ucar.nc2.units,
ucar.nc2.util,
ucar.nc2.util.cache,
ucar.nc2.util.log,
ucar.nc2.util.net,
ucar.nc2.util.rc,
ucar.nc2.util.reflect,
ucar.nc2.util.xml,
ucar.nc2.wmo,
ucar.nc2.write
Export-Package: com.sap.sailing.grib
Require-Bundle: com.sap.sailing.domain.common,
com.sap.sailing.domain,
com.sap.sse.common
com.sap.sse.common,
com.sap.sse
@@ -4,6 +4,7 @@ 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;
@@ -39,4 +40,6 @@ public interface GribWindField {
* The time range covered by the underlying GRIB data.
*/
TimeRange getTimeRange();
Iterable<Wind> getAllWindFixes();
}
@@ -1,5 +1,13 @@
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;
@@ -9,13 +17,18 @@ import ucar.nc2.ft.FeatureDataset;
*
* Example Usage:
* <pre>
* final Formatter errorLog = new Formatter(System.err);
* 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)
*
*/
@@ -23,4 +36,41 @@ 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);
}
@@ -1,8 +1,10 @@
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;
@@ -227,12 +229,35 @@ public abstract class AbstractGribWindFieldImpl implements GribWindField {
return getNearestNonNaNValueWithPosition(coordinateSystem, grid, timeIndex, zIndex, yIndex, xIndex, distance+1);
}
private double getValue(GridDatatype grid, int timeIndex, int zIndex, final int x, final int y) throws IOException {
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;
}
@FunctionalInterface
static interface ValueForCoordinateProvider<T> {
T getValue(int timeIndex, int x, int y, TimePoint timePoint, Position position);
}
protected <T> Iterable<T> foreach(GridDatatype grid, ValueForCoordinateProvider<T> provider) {
final List<T> result = new ArrayList<>();
final GridCoordSystem coordinateSystem = grid.getCoordinateSystem();
final int timeDimLength = grid.getTimeDimension().getLength();
final int xDimLength = grid.getXDimension().getLength();
final int yDimLength = grid.getYDimension().getLength();
for (int t=0; t<timeDimLength; t++) {
final TimePoint timePoint = toTimePoint(coordinateSystem.getTimeAxis1D().getCalendarDate(t));
for (int x=0; x<xDimLength; x++) {
for (int y=0; y<yDimLength; y++) {
final Position position = toPosition(coordinateSystem.getLatLon(x, y));
result.add(provider.getValue(t, x, y, timePoint, position));
}
}
}
return result;
}
protected Optional<String> getUnit(VariableDS variable) {
final Optional<String> result;
final ucar.nc2.Attribute attribute = variable.findAttribute("units");
@@ -1,11 +1,76 @@
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();
synchronized (GribWindFieldFactoryImpl.this) {
filesToCleanWhenGribWindFieldNoLongerUsed.remove(ref);
finished = filesToCleanWhenGribWindFieldNoLongerUsed.isEmpty();
if (finished) {
fileSystemCleaner = null;
}
}
} catch (InterruptedException e) {
logger.log(Level.WARNING, "Interrupted while waiting for weak reference, giving up", e);
finished = true;
}
}
}, "GRIB directory cleaner");
fileSystemCleaner.setDaemon(true);
}
}
}
@Override
public GribWindField createGribWindField(FeatureDataset... dataSets) {
final GribWindField result;
@@ -18,4 +83,86 @@ public class GribWindFieldFactoryImpl implements GribWindFieldFactory {
}
return result;
}
@Override
public GribWindField createGribWindField(Iterable<String> locations) throws IOException {
return createGribWindField(logger, DEFAULT_ERROR_LOG_LEVEL, locations);
}
@Override
public GribWindField createGribWindField(Logger logger, Level level, Iterable<String> locations) throws IOException {
final Formatter errorLog = createLogFormatter(logger, level);
return createGribWindField(errorLog, locations);
}
@Override
public GribWindField createGribWindField(Formatter errorLog, Iterable<String> locations) throws IOException {
final List<FeatureDataset> dataSets = new ArrayList<>();
for (final String location : locations) {
FeatureDataset dataSet = FeatureDatasetFactoryManager.open(FeatureType.ANY, location, /* task */ null, errorLog);
dataSets.add(dataSet);
}
return createGribWindField(dataSets.toArray(new FeatureDataset[0]));
}
@Override
public GribWindField createGribWindFieldFromFiles(Iterable<File> files) throws IOException {
return createGribWindFieldFromFiles(logger, DEFAULT_ERROR_LOG_LEVEL, files);
}
@Override
public GribWindField createGribWindFieldFromFiles(Logger logger, Level level, Iterable<File> files)
throws IOException {
return createGribWindFieldFromFiles(createLogFormatter(logger, level), files);
}
@Override
public GribWindField createGribWindFieldFromFiles(Formatter errorLog, Iterable<File> files) throws IOException {
return createGribWindField(errorLog, new MappingIterable<>(files,
f->{ try { return f.getCanonicalPath(); } catch (Exception e) { logger.log(Level.SEVERE, "Error obtaining GRIB file path", e); return null; } }));
}
@Override
public GribWindField createGribWindFieldFromStreams(Map<InputStream, String> streamsAndFilenames) throws IOException {
return createGribWindFieldFromStreams(logger, DEFAULT_ERROR_LOG_LEVEL, streamsAndFilenames);
}
@Override
public GribWindField createGribWindFieldFromStreams(Logger logger, Level level, Map<InputStream, String> streamsAndFilenames)
throws IOException {
return createGribWindFieldFromStreams(createLogFormatter(logger, level), streamsAndFilenames);
}
@Override
public GribWindField createGribWindFieldFromStreams(Formatter errorLog, Map<InputStream, String> streamsAndFilenames)
throws IOException {
final List<File> files = new ArrayList<>();
for (final Entry<InputStream, String> e : streamsAndFilenames.entrySet()) {
files.add(copyStreamToFile(e.getKey(), e.getValue()));
}
final GribWindField result = createGribWindFieldFromFiles(errorLog, files);
for (final File file : files) {
removeDirectoryWhenWindFieldNoLongerStronglyReferenced(result, file.getParentFile());
}
return result;
}
/**
* Copies the contents of the input stream {@code s} into a temporary directory that is created solely for this
* purpose. The {@code filename} will be used for the name of the file. The {@link File} object representing the
* file written is returned so that it and its containing directory can be removed when the contents of the stream
* are no longer needed.
*/
private File copyStreamToFile(InputStream s, String filename) throws IOException {
Path tempDir = Files.createTempDirectory("gribcache");
Path filePath = tempDir.resolve(filename);
Files.copy(s, filePath);
return filePath.toFile();
}
@Override
public Formatter createLogFormatter(Logger logger, Level level) {
return new Formatter(new LoggerAppender(level, logger));
}
}
@@ -1,6 +1,11 @@
package com.sap.sailing.grib.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Wind;
@@ -17,6 +22,7 @@ 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;
@@ -63,16 +69,23 @@ public class SpeedAndDirectionWindField extends AbstractGribWindFieldImpl {
final double confidence;
if (directionComponentInDegreesTrue != null && speedComponentInMetersPerSecond != null) {
confidence = getTimeConfidence(timePoint, directionComponentInDegreesTrue.getB());
wind = new WindImpl(directionComponentInDegreesTrue.getC(), directionComponentInDegreesTrue.getB(),
new MeterPerSecondSpeedWithDegreeBearingImpl(speedComponentInMetersPerSecond.getA(),
// we're getting the "from" direction from the GRIB file and need to convert to "to" here
new DegreeBearingImpl(directionComponentInDegreesTrue.getA()).reverse()));
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
@@ -82,4 +95,48 @@ public class SpeedAndDirectionWindField extends AbstractGribWindFieldImpl {
return hasVariable(windDirectionVariableSpecification, dataSets) && hasVariable(windSpeedVariableSpecification, dataSets);
}
@Override
public Iterable<Wind> getAllWindFixes() {
final List<Wind> result = new ArrayList<>();
for (final FeatureDataset dataSet : getDataSets()) {
GridDatatype directionGrid = null;
GridDatatype speedGrid = null;
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 finalDirectionGrid = directionGrid;
final GridDatatype finalSpeedGrid = speedGrid;
for (final Wind wind : foreach(directionGrid, (int timeIndex, int x, int y, TimePoint timePoint, Position position)->{
try {
final Wind wind;
double speedInMetersPerSecond = getValue(finalSpeedGrid, timeIndex, /* zIndex */ 0, x, y);
double trueDirectionFromInDeg = getValue(finalDirectionGrid, timeIndex, /* zIndex */ 0, x, y);
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,6 +1,11 @@
package com.sap.sailing.grib.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Wind;
@@ -17,6 +22,7 @@ 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;
@@ -63,11 +69,8 @@ public class UVWindField extends AbstractGribWindFieldImpl {
}
if (uComponentInMetersPerSecond != null && vComponentInMetersPerSecond != null) {
confidence = getTimeConfidence(timePoint, uComponentInMetersPerSecond.getB());
final double atan2 = Math.atan2(uComponentInMetersPerSecond.getA(), vComponentInMetersPerSecond.getA());
wind = new WindImpl(uComponentInMetersPerSecond.getC(), uComponentInMetersPerSecond.getB(),
new MeterPerSecondSpeedWithDegreeBearingImpl(Math.sqrt(uComponentInMetersPerSecond.getA()*uComponentInMetersPerSecond.getA()+
vComponentInMetersPerSecond.getA()*vComponentInMetersPerSecond.getA()),
new RadianBearingImpl(atan2>0 ? atan2 : 2*Math.PI+atan2)));
wind = createWindFixFromUAndV(uComponentInMetersPerSecond.getC(), uComponentInMetersPerSecond.getB(),
uComponentInMetersPerSecond.getA(), vComponentInMetersPerSecond.getA());
} else {
wind = null;
confidence = 0;
@@ -82,4 +85,58 @@ public class UVWindField extends AbstractGribWindFieldImpl {
public static boolean handles(FeatureDataset... dataSets) {
return hasVariable(uComponentOfWindVariableSpecification, dataSets) && hasVariable(vComponentOfWindVariableSpecification, dataSets);
}
@Override
public Iterable<Wind> getAllWindFixes() {
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 finalUGrid = uGrid;
final GridDatatype finalVGrid = vGrid;
for (final Wind wind : foreach(uGrid, (int timeIndex, int x, int y, TimePoint timePoint, Position position)->{
try {
final Wind wind;
double uComponentInMetersPerSecond = getValue(finalUGrid, timeIndex, /* zIndex */ 0, x, y);
double vComponentInMetersPerSecond = getValue(finalVGrid, timeIndex, /* zIndex */ 0, x, y);
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)));
}
}