mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-25 05:23:51 +00:00
Merge remote-tracking branch 'origin/master' into dataMining
This commit is contained in:
commit
62cbabcc14
157 files changed
+6321
-1855
No files matched your search
@@ -11,13 +11,15 @@ Export-Package:
|
||||
com.sap.sailing.polars.analysis,
|
||||
com.sap.sailing.polars.data,
|
||||
com.sap.sailing.polars.factory,
|
||||
com.sap.sailing.polars.regression
|
||||
com.sap.sailing.polars.regression,
|
||||
com.sap.sailing.polars.windestimation
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle:
|
||||
com.sap.sailing.domain.common;bundle-version="1.0.0",
|
||||
com.sap.sailing.domain,
|
||||
com.sap.sse.datamining,
|
||||
com.sap.sse.datamining.shared;bundle-version="1.0.0"
|
||||
com.sap.sse.datamining.shared;bundle-version="1.0.0",
|
||||
com.sap.sse.common
|
||||
Import-Package:
|
||||
com.sap.sailing.util,
|
||||
com.sap.sse.common
|
||||
Regular → Executable
+48
-38
@@ -17,18 +17,18 @@ import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.polars.analysis.PolarSheetAnalyzer;
|
||||
import com.sap.sailing.polars.data.PolarFix;
|
||||
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
|
||||
|
||||
/**
|
||||
* Public Facade interface allowing access to the polars of {@link BoatClass}es.
|
||||
* Public Facade interface granting clients access to the polar sheets of {@link BoatClass}es. A boat's "polar sheet"
|
||||
* (sometimes also referred to as a "VPP" (velocity prediction program)) makes a prediction how fast the boat will
|
||||
* sail at a given true wind angle and a given true wind speed.<p>
|
||||
*
|
||||
* It uses a {@link PolarSheetAnalyzer} for more advanced analysis. It's methods are facaded in this interface for
|
||||
* central access.
|
||||
*
|
||||
* The interesting methods for a user are {@link #getSpeed(BoatClass, Speed, Bearing, boolean)} if data for a specific angle is
|
||||
* This service uses a {@link PolarSheetAnalyzer} for more advanced analysis. Its methods are facaded in this interface for
|
||||
* central access.<p>
|
||||
* The interesting methods for a client are {@link #getSpeed(BoatClass, Speed, Bearing, boolean)} if data for a specific angle is
|
||||
* needed and {@link #getAverageSpeedWithBearing(BoatClass, Speed, LegType, Tack)}
|
||||
* which also returns the average angle for the provided parameters.
|
||||
* which also returns the average angle for the parameters provided.
|
||||
*
|
||||
* @author Frederik Petersen (D054528)
|
||||
*
|
||||
@@ -39,35 +39,37 @@ public interface PolarDataService {
|
||||
*
|
||||
* @param boatClass
|
||||
* @param windSpeed
|
||||
* @param bearingToTheWind
|
||||
* Boat's direction relative to the wind. either in -180 -> +180 or 0 -> 359 degrees
|
||||
* @param useLinearRegression if true uses lin. Regression for estimation in the wind interval, if false simple arithm. mean in interval
|
||||
* @param trueWindAngle
|
||||
* Boat's direction relative to the wind. either in -180 -> +180 or 0 -> 359 degrees interval. The true wind!
|
||||
* @return The speed the boat is moving at for the specified wind and bearing according to the polar diagram.
|
||||
* @throws NotEnoughDataHasBeenAddedException
|
||||
*/
|
||||
SpeedWithConfidence<Void> getSpeed(BoatClass boatClass, Speed windSpeed, Bearing bearingToTheWind, boolean useLinearRegression)
|
||||
SpeedWithConfidence<Void> getSpeed(BoatClass boatClass, Speed windSpeed, Bearing trueWindAngle)
|
||||
throws NotEnoughDataHasBeenAddedException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param boatClass
|
||||
* @param windSpeed
|
||||
* @param windSpeed
|
||||
* @param legType
|
||||
* Should be UpWind or DownWind, there is no information for other courses yet. Use getSpeed for the
|
||||
* desired angle to get rawer information on other courses for now.
|
||||
* Should be {@link LegType#UPWIND} or {@link LegType#DOWNWIND}, there is no information for other
|
||||
* courses yet. Use getSpeed for the desired angle to get rawer information on other courses for now.
|
||||
* @param tack
|
||||
* Polar data can vary depending on the tack the boat is on.
|
||||
* @return The estimated average speed of a boat for the supplied parameters with the estimated average bearing to
|
||||
* the wind and a confidence which consists of the confidences of the wind speed, and boat speed sources (50%)
|
||||
* the true wind and a confidence which consists of the confidences of the wind speed, and boat speed sources (50%)
|
||||
* and a confidence calculated using the amount of underlying fixes (50%). 0 <= confidence < 1<br/>
|
||||
* A value with zero confidence doesn't have any significance!<br/><br/>
|
||||
*
|
||||
* The bearing is somewhere between -179 to +180<br/><br/>
|
||||
*
|
||||
* Get the speed using returnValue.getObject()<br/><br/>
|
||||
*
|
||||
* A value with zero confidence doesn't have any significance!<br/>
|
||||
* <br/>
|
||||
*
|
||||
* The bearing is somewhere between -179 to +180<br/>
|
||||
* <br/>
|
||||
*
|
||||
* Get the speed using returnValue.getObject()<br/>
|
||||
* <br/>
|
||||
*
|
||||
* Returns null if the leg type is not up or downwind.
|
||||
*
|
||||
*
|
||||
* @throws NotEnoughDataHasBeenAddedException
|
||||
* If there is not enough data to supply a value with some kind of significance.
|
||||
*/
|
||||
@@ -76,7 +78,7 @@ public interface PolarDataService {
|
||||
|
||||
|
||||
/**
|
||||
* Generates a polar sheet for geven races and settings using the provided executor for the worker threads. This
|
||||
* Generates a polar sheet for given races and settings using the provided executor for the worker threads. This
|
||||
* method does not access a cache for now.
|
||||
*
|
||||
* @param trackedRaces
|
||||
@@ -90,16 +92,6 @@ public interface PolarDataService {
|
||||
PolarSheetsData generatePolarSheet(Set<TrackedRace> trackedRaces, PolarSheetGenerationSettings settings,
|
||||
Executor executor) throws InterruptedException, ExecutionException;
|
||||
|
||||
void newRaceFinishedTracking(TrackedRace trackedRace);
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* The {@link BoatClass} to obtain fixes for.
|
||||
* @return All raw polar fixes for the {@link BoatClass}. The implementation is responsible for deciding wether a
|
||||
* cache is used or not.
|
||||
*/
|
||||
Set<PolarFix> getPolarFixesForBoatClass(BoatClass key);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param boatClass
|
||||
@@ -115,6 +107,14 @@ public interface PolarDataService {
|
||||
*/
|
||||
Set<BoatClass> getAllBoatClassesWithPolarSheetsAvailable();
|
||||
|
||||
/**
|
||||
* To be called in an appropriate listener.
|
||||
* Starting point for fixes entering the backend polar data mining pipeline.
|
||||
*
|
||||
* @param fix
|
||||
* @param competitor
|
||||
* @param createdTrackedRace
|
||||
*/
|
||||
void competitorPositionChanged(GPSFixMoving fix, Competitor competitor, TrackedRace createdTrackedRace);
|
||||
|
||||
/**
|
||||
@@ -123,12 +123,22 @@ public interface PolarDataService {
|
||||
* @param windSpeed
|
||||
* @param startAngleInclusive between 0 and 359; smaller than (or equal to) endAngleExclusive
|
||||
* @param endAngleExclusive between 0 and 359; bigger than startAngleInclusive
|
||||
* @return array with datacount for all angles in the given area, else null
|
||||
* @return array with datacount for all angles in the given area, else -1
|
||||
*/
|
||||
Integer[] getDataCountsForWindSpeed(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive);
|
||||
int[] getDataCountsForWindSpeed(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive);
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> getAverageSpeedWithBearing(BoatClass boatClass, Speed windSpeed,
|
||||
LegType legType, Tack tack, boolean useLinReg) throws NotEnoughDataHasBeenAddedException;
|
||||
|
||||
/**
|
||||
* From a boat's speed over ground and assuming values for <code>boatClass</code>, the <code>tack</code> the boat is
|
||||
* currently sailing on, and the <code>legType</code>, this method estimates the true wind speed candidates at which
|
||||
* the boat may most likely have been sailing under these conditions.
|
||||
*
|
||||
* The confidence of the returned candidates is derived from the average confidence of the underlying fixes,
|
||||
* distance measures and the amount of underlying data.
|
||||
*
|
||||
* Multiple candidates are possible, because we cannot guarantee a reversible function (boatspeed over windspeed).
|
||||
*
|
||||
* @return set of wind candidates with confidence, empty set if no were found (due to insufficient underlying data)
|
||||
*/
|
||||
Set<SpeedWithBearingWithConfidence<Void>> getAverageTrueWindSpeedAndAngleCandidates(BoatClass boatClass, Speed speedOverGround, LegType legType, Tack tack);
|
||||
|
||||
}
|
||||
Regular → Executable
+4
-13
@@ -2,21 +2,12 @@ package com.sap.sailing.polars.analysis;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
|
||||
|
||||
public interface PolarSheetAnalyzer {
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> getAverageUpwindSpeedWithBearingOnStarboardTackFor(BoatClass boatClass,
|
||||
Speed windSpeed, boolean useLinReg) throws NotEnoughDataHasBeenAddedException;
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> getAverageDownwindSpeedWithBearingOnStarboardTackFor(BoatClass boatClass,
|
||||
Speed windSpeed, boolean useLinReg) throws NotEnoughDataHasBeenAddedException;
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> getAverageUpwindSpeedWithBearingOnPortTackFor(BoatClass boatClass,
|
||||
Speed windSpeed, boolean useLinReg) throws NotEnoughDataHasBeenAddedException;
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> getAverageDownwindSpeedWithBearingOnPortTackFor(BoatClass boatClass,
|
||||
Speed windSpeed, boolean useLinReg) throws NotEnoughDataHasBeenAddedException;
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> getAverageSpeedAndCourseOverGround(BoatClass boatClass,
|
||||
Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException;
|
||||
}
|
||||
Regular → Executable
+39
-68
@@ -1,16 +1,14 @@
|
||||
package com.sap.sailing.polars.analysis.impl;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
|
||||
import com.sap.sailing.domain.base.SpeedWithConfidence;
|
||||
import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl;
|
||||
import com.sap.sailing.domain.common.Bearing;
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.polars.PolarDataService;
|
||||
@@ -26,70 +24,46 @@ import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
|
||||
public class PolarSheetAnalyzerImpl implements PolarSheetAnalyzer {
|
||||
|
||||
private static final int MINIMUM_DATA_COUNT_FOR_ONE_ANGLE = 5;
|
||||
private PolarDataService polarDataService;
|
||||
private final PolarDataService polarDataService;
|
||||
|
||||
public PolarSheetAnalyzerImpl(PolarDataService polarDataService) {
|
||||
this.polarDataService = polarDataService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageUpwindSpeedWithBearingOnStarboardTackFor(BoatClass boatClass, Speed windSpeed, boolean useLinReg)
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageSpeedAndCourseOverGround(BoatClass boatClass, Speed windSpeed, LegType legType, Tack tack)
|
||||
throws NotEnoughDataHasBeenAddedException {
|
||||
int startAngleInclusive = 1;
|
||||
int endAngleExclusive = 90;
|
||||
final int startAngleInclusive;
|
||||
final int endAngleExclusive;
|
||||
if (legType == LegType.UPWIND && tack == Tack.STARBOARD) {
|
||||
startAngleInclusive = 0;
|
||||
endAngleExclusive = 90;
|
||||
} else if (legType == LegType.DOWNWIND && tack == Tack.STARBOARD) {
|
||||
startAngleInclusive = 90;
|
||||
endAngleExclusive = 180;
|
||||
} else if (legType == LegType.DOWNWIND && tack == Tack.PORT) {
|
||||
startAngleInclusive = 180;
|
||||
endAngleExclusive = 270;
|
||||
} else if (legType == LegType.UPWIND && tack == Tack.PORT) {
|
||||
startAngleInclusive = 270;
|
||||
endAngleExclusive = 360;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Leg type must be "+LegType.UPWIND.name()+
|
||||
" or "+LegType.DOWNWIND.name()+" but was "+legType.name());
|
||||
}
|
||||
SpeedWithBearingWithConfidence<Void> speedWithBearing = estimateAnglePeakAndAverageSpeed(boatClass, windSpeed, startAngleInclusive,
|
||||
endAngleExclusive, useLinReg);
|
||||
endAngleExclusive);
|
||||
return speedWithBearing;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageDownwindSpeedWithBearingOnStarboardTackFor(BoatClass boatClass, Speed windSpeed, boolean useLinReg)
|
||||
throws NotEnoughDataHasBeenAddedException {
|
||||
int startAngleInclusive = 91;
|
||||
int endAngleExclusive = 180;
|
||||
SpeedWithBearingWithConfidence<Void> speedWithBearing = estimateAnglePeakAndAverageSpeed(boatClass, windSpeed, startAngleInclusive,
|
||||
endAngleExclusive, useLinReg);
|
||||
return speedWithBearing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageDownwindSpeedWithBearingOnPortTackFor(BoatClass boatClass, Speed windSpeed, boolean useLinReg)
|
||||
throws NotEnoughDataHasBeenAddedException {
|
||||
int startAngleInclusive = 181;
|
||||
int endAngleExclusive = 270;
|
||||
SpeedWithBearingWithConfidence<Void> speedWithBearing = estimateAnglePeakAndAverageSpeed(boatClass, windSpeed, startAngleInclusive,
|
||||
endAngleExclusive, useLinReg);
|
||||
return speedWithBearing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageUpwindSpeedWithBearingOnPortTackFor(BoatClass boatClass, Speed windSpeed, boolean useLinReg)
|
||||
throws NotEnoughDataHasBeenAddedException {
|
||||
int startAngleInclusive = 271;
|
||||
int endAngleExclusive = 360;
|
||||
SpeedWithBearingWithConfidence<Void> speedWithBearing = estimateAnglePeakAndAverageSpeed(boatClass, windSpeed, startAngleInclusive,
|
||||
endAngleExclusive, useLinReg);
|
||||
return speedWithBearing;
|
||||
}
|
||||
|
||||
private SpeedWithBearingWithConfidence<Void> estimateAnglePeakAndAverageSpeed(BoatClass boatClass, Speed windSpeed,
|
||||
int startAngleInclusive, int endAngleExclusive, boolean useLinearRegression) throws NotEnoughDataHasBeenAddedException {
|
||||
Integer[] dataCountPerAngle = getDataCountArray(boatClass, windSpeed, startAngleInclusive, endAngleExclusive);
|
||||
|
||||
|
||||
|
||||
int startAngleInclusive, int endAngleExclusive) throws NotEnoughDataHasBeenAddedException {
|
||||
int[] dataCountPerAngle = getDataCountArray(boatClass, windSpeed, startAngleInclusive, endAngleExclusive);
|
||||
double estimatedPeak = estimatePeak(boatClass, windSpeed, startAngleInclusive, endAngleExclusive, dataCountPerAngle);
|
||||
|
||||
double convertedAngleIfOver180 = convertAngleIfNecessary(estimatedPeak);
|
||||
|
||||
SpeedWithConfidence<Void> averagedSpeedWithConfidence = estimateSpeed(boatClass, windSpeed,
|
||||
convertedAngleIfOver180, useLinearRegression);
|
||||
|
||||
SpeedWithConfidence<Void> averagedSpeedWithConfidence = estimateSpeed(boatClass, windSpeed, convertedAngleIfOver180);
|
||||
double originConfidence = averagedSpeedWithConfidence.getConfidence();
|
||||
|
||||
double overallConfidence = (originConfidence + calcDataCountConfidence(dataCountPerAngle, estimatedPeak)) / 2;
|
||||
|
||||
Bearing bearing = new DegreeBearingImpl(convertedAngleIfOver180);
|
||||
SpeedWithBearing speedWithBearing = new KnotSpeedWithBearingImpl(averagedSpeedWithConfidence.getObject().getKnots(), bearing);
|
||||
return new SpeedWithBearingWithConfidenceImpl<>(speedWithBearing, overallConfidence, null);
|
||||
@@ -104,14 +78,14 @@ public class PolarSheetAnalyzerImpl implements PolarSheetAnalyzer {
|
||||
* @param estimatedPeak
|
||||
* @return
|
||||
*/
|
||||
private double calcDataCountConfidence(Integer[] dataCountPerAngle, double estimatedPeak) {
|
||||
private double calcDataCountConfidence(int[] dataCountPerAngle, double estimatedPeak) {
|
||||
int dataCount = dataCountPerAngle[(int) Math.round(estimatedPeak)];
|
||||
double confidence = 1 - Math.pow(Math.E, - dataCount / 150);
|
||||
return confidence;
|
||||
}
|
||||
|
||||
private Integer[] getDataCountArray(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive) {
|
||||
Integer[] dataCountPerAngle = polarDataService.getDataCountsForWindSpeed(boatClass, windSpeed, startAngleInclusive, endAngleExclusive);
|
||||
private int[] getDataCountArray(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive) {
|
||||
int[] dataCountPerAngle = polarDataService.getDataCountsForWindSpeed(boatClass, windSpeed, startAngleInclusive, endAngleExclusive);
|
||||
return dataCountPerAngle;
|
||||
}
|
||||
|
||||
@@ -123,20 +97,21 @@ public class PolarSheetAnalyzerImpl implements PolarSheetAnalyzer {
|
||||
return convertedAngleIfOver180;
|
||||
}
|
||||
|
||||
private SpeedWithConfidence<Void> estimateSpeed(BoatClass boatClass, Speed windSpeed, double estimatedPeak,
|
||||
boolean useLinearRegression) throws NotEnoughDataHasBeenAddedException {
|
||||
private SpeedWithConfidence<Void> estimateSpeed(BoatClass boatClass, Speed windSpeed, double estimatedPeak) throws NotEnoughDataHasBeenAddedException {
|
||||
SpeedWithConfidence<Void> speed = polarDataService.getSpeed(boatClass, windSpeed, new DegreeBearingImpl(
|
||||
estimatedPeak), useLinearRegression);
|
||||
estimatedPeak));
|
||||
return speed;
|
||||
}
|
||||
|
||||
private double estimatePeak(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive, Integer[] dataCountPerAngle)
|
||||
private double estimatePeak(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive, int[] dataCountPerAngle)
|
||||
throws NotEnoughDataHasBeenAddedException {
|
||||
|
||||
// Find peak by averaging the angles that have at least 50% of the max datacount
|
||||
List<Integer> dataCountList = Arrays.asList(Arrays.copyOfRange(dataCountPerAngle, startAngleInclusive,
|
||||
endAngleExclusive));
|
||||
int maxDataCount = Collections.max(dataCountList);
|
||||
int maxDataCount = Integer.MIN_VALUE;
|
||||
for (int i = startAngleInclusive; i < endAngleExclusive; i++) {
|
||||
if (dataCountPerAngle[i] > maxDataCount) {
|
||||
maxDataCount = dataCountPerAngle[i];
|
||||
}
|
||||
}
|
||||
if (maxDataCount < MINIMUM_DATA_COUNT_FOR_ONE_ANGLE) {
|
||||
// The angle with the most data points doesn't have sufficient data, for the polar data to have any
|
||||
// significance.
|
||||
@@ -146,17 +121,13 @@ public class PolarSheetAnalyzerImpl implements PolarSheetAnalyzer {
|
||||
int sumOfAllUpperHalfAngles = 0;
|
||||
int numberOfAnglesAdded = 0;
|
||||
for (int i = startAngleInclusive; i < endAngleExclusive; i++) {
|
||||
Integer dataCount = dataCountPerAngle[i];
|
||||
int dataCount = dataCountPerAngle[i];
|
||||
if (dataCount > maxDataCount / 2) {
|
||||
sumOfAllUpperHalfAngles = sumOfAllUpperHalfAngles + i * dataCount;
|
||||
numberOfAnglesAdded = numberOfAnglesAdded + dataCount;
|
||||
}
|
||||
}
|
||||
|
||||
double estimatedPeak = (double) sumOfAllUpperHalfAngles / (double) numberOfAnglesAdded;
|
||||
return estimatedPeak;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
package com.sap.sailing.polars.caching;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.polars.data.PolarFix;
|
||||
|
||||
public class NoCacheEntryException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 5547683568438235668L;
|
||||
private final Set<TrackedRace> notCached;
|
||||
private final Set<PolarFix> resultList;
|
||||
|
||||
public NoCacheEntryException(Set<TrackedRace> notCached, Set<PolarFix> resultList) {
|
||||
this.notCached = notCached;
|
||||
this.resultList = resultList;
|
||||
}
|
||||
|
||||
public Set<TrackedRace> getNotCached() {
|
||||
return notCached;
|
||||
}
|
||||
|
||||
public Set<PolarFix> getCachedResultList() {
|
||||
return resultList;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package com.sap.sailing.polars.caching;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.polars.data.PolarFix;
|
||||
import com.sap.sailing.util.SmartFutureCache;
|
||||
|
||||
public class PolarFixCache extends
|
||||
SmartFutureCache<BoatClass, Map<RegattaAndRaceIdentifier, List<PolarFix>>, PolarFixCacheRaceInterval> {
|
||||
|
||||
private final List<PolarFixCacheUpdateDoneListener> cacheUpdateDoneListeners = new ArrayList<PolarFixCacheUpdateDoneListener>();
|
||||
|
||||
public PolarFixCache(Executor executor) {
|
||||
super(new PolarFixCacheUpdater(executor), "polarFixCache");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cache(BoatClass key, Map<RegattaAndRaceIdentifier, List<PolarFix>> value) {
|
||||
super.cache(key, value);
|
||||
synchronized (cacheUpdateDoneListeners) {
|
||||
for (PolarFixCacheUpdateDoneListener listener : cacheUpdateDoneListeners) {
|
||||
listener.cacheUpdateDoneForBoatClass(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener(PolarFixCacheUpdateDoneListener listener) {
|
||||
synchronized (cacheUpdateDoneListeners) {
|
||||
cacheUpdateDoneListeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(PolarFixCacheUpdateDoneListener listener) {
|
||||
synchronized (cacheUpdateDoneListeners) {
|
||||
if (cacheUpdateDoneListeners.contains(listener)) {
|
||||
cacheUpdateDoneListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Set<PolarFix> getFixesForTrackedRaces(Set<TrackedRace> trackedRaces) throws NoCacheEntryException {
|
||||
Set<PolarFix> resultList = new HashSet<PolarFix>();
|
||||
Set<TrackedRace> notCached = new HashSet<TrackedRace>();
|
||||
for (TrackedRace trackedRace : trackedRaces) {
|
||||
Map<RegattaAndRaceIdentifier, List<PolarFix>> result = get(trackedRace.getRace().getBoatClass(), false);
|
||||
List<PolarFix> resultForRace = result.get(trackedRace.getRaceIdentifier());
|
||||
if (resultForRace == null) {
|
||||
notCached.add(trackedRace);
|
||||
} else {
|
||||
resultList.addAll(resultForRace);
|
||||
}
|
||||
}
|
||||
if (notCached.size() > 0) {
|
||||
throw new NoCacheEntryException(notCached, resultList);
|
||||
}
|
||||
return resultList;
|
||||
}
|
||||
|
||||
}
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
package com.sap.sailing.polars.caching;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.polars.aggregation.PolarFixRaceInterval;
|
||||
import com.sap.sailing.util.SmartFutureCache.UpdateInterval;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
|
||||
public class PolarFixCacheRaceInterval implements UpdateInterval<PolarFixCacheRaceInterval>, PolarFixRaceInterval {
|
||||
|
||||
private final Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> competitorAndTimepointsForRace;
|
||||
|
||||
public PolarFixCacheRaceInterval(
|
||||
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> competitorAndTimepointsForRace) {
|
||||
this.competitorAndTimepointsForRace = competitorAndTimepointsForRace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> getCompetitorAndTimepointsForRace() {
|
||||
return competitorAndTimepointsForRace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PolarFixCacheRaceInterval join(PolarFixCacheRaceInterval otherUpdateInterval) {
|
||||
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> joinedInterval = new HashMap<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>>();
|
||||
Set<TrackedRace> racesContainedInEitherMap = new HashSet<TrackedRace>();
|
||||
for (TrackedRace race : competitorAndTimepointsForRace.keySet()) {
|
||||
racesContainedInEitherMap.add(race);
|
||||
}
|
||||
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> otherUpdateIntervalInExplicitForm = otherUpdateInterval
|
||||
.getCompetitorAndTimepointsForRace();
|
||||
for (TrackedRace race : otherUpdateIntervalInExplicitForm.keySet()) {
|
||||
racesContainedInEitherMap.add(race);
|
||||
}
|
||||
|
||||
for (TrackedRace race : racesContainedInEitherMap) {
|
||||
HashMap<Competitor, Pair<TimePoint, TimePoint>> joinedMap = new HashMap<Competitor, Pair<TimePoint, TimePoint>>();
|
||||
Map<Competitor, Pair<TimePoint, TimePoint>> mapOfTimpointCompetitorPairs = competitorAndTimepointsForRace
|
||||
.get(race);
|
||||
if (mapOfTimpointCompetitorPairs != null) {
|
||||
joinedMap.putAll(mapOfTimpointCompetitorPairs);
|
||||
}
|
||||
Map<Competitor, Pair<TimePoint, TimePoint>> mapOfOtherTimpointCompetitorPairs = otherUpdateIntervalInExplicitForm
|
||||
.get(race);
|
||||
if (mapOfOtherTimpointCompetitorPairs != null) {
|
||||
mergeMap(joinedMap, mapOfOtherTimpointCompetitorPairs);
|
||||
}
|
||||
joinedInterval.put(race, joinedMap);
|
||||
}
|
||||
return new PolarFixCacheRaceInterval(joinedInterval);
|
||||
}
|
||||
|
||||
private void mergeMap(HashMap<Competitor, Pair<TimePoint, TimePoint>> joinedMap,
|
||||
Map<Competitor, Pair<TimePoint, TimePoint>> mapOfOtherTimpointCompetitorPairs) {
|
||||
for (Entry<Competitor, Pair<TimePoint, TimePoint>> otherEntry : mapOfOtherTimpointCompetitorPairs.entrySet()) {
|
||||
|
||||
Competitor competitor = otherEntry.getKey();
|
||||
Pair<TimePoint, TimePoint> otherValue = otherEntry.getValue();
|
||||
Pair<TimePoint, TimePoint> existingValue = joinedMap.get(competitor);
|
||||
if (existingValue != null) {
|
||||
Pair<TimePoint, TimePoint> joinedTimepointInterval = joinTimepointIntervals(existingValue, otherValue);
|
||||
joinedMap.put(competitor, joinedTimepointInterval);
|
||||
} else {
|
||||
joinedMap.put(competitor, otherValue);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method ignores a possible gap between the two intervals.
|
||||
*
|
||||
* @param existingValue
|
||||
* @param otherValue
|
||||
* @return
|
||||
*/
|
||||
private Pair<TimePoint, TimePoint> joinTimepointIntervals(Pair<TimePoint, TimePoint> existingValue,
|
||||
Pair<TimePoint, TimePoint> otherValue) {
|
||||
TimePoint startExisting = existingValue.getA();
|
||||
TimePoint endExisting = existingValue.getB();
|
||||
TimePoint startOther = otherValue.getA();
|
||||
TimePoint endOther = otherValue.getB();
|
||||
|
||||
TimePoint startNew = startExisting.before(startOther) ? startExisting : startOther;
|
||||
TimePoint endNew = endExisting.after(endOther) ? endExisting : endOther;
|
||||
|
||||
return new Pair<TimePoint, TimePoint>(startNew, endNew);
|
||||
}
|
||||
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package com.sap.sailing.polars.caching;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
|
||||
public interface PolarFixCacheUpdateDoneListener {
|
||||
|
||||
void cacheUpdateDoneForBoatClass(BoatClass key);
|
||||
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
package com.sap.sailing.polars.caching;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.impl.PolarSheetGenerationSettingsImpl;
|
||||
import com.sap.sailing.polars.aggregation.PolarFixAggregator;
|
||||
import com.sap.sailing.polars.data.PolarFix;
|
||||
import com.sap.sailing.util.SmartFutureCache.CacheUpdater;
|
||||
|
||||
public class PolarFixCacheUpdater implements
|
||||
CacheUpdater<BoatClass, Map<RegattaAndRaceIdentifier, List<PolarFix>>, PolarFixCacheRaceInterval> {
|
||||
|
||||
private final Executor executor;
|
||||
|
||||
public PolarFixCacheUpdater(Executor executor) {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<RegattaAndRaceIdentifier, List<PolarFix>> computeCacheUpdate(BoatClass key,
|
||||
PolarFixCacheRaceInterval updateInterval) throws Exception {
|
||||
PolarFixAggregator aggregator = new PolarFixAggregator(updateInterval,
|
||||
PolarSheetGenerationSettingsImpl.createStandardPolarSettings(), executor);
|
||||
Thread.sleep(updateInterval.getCompetitorAndTimepointsForRace().keySet().iterator().next()
|
||||
.getMillisecondsOverWhichToAverageSpeed() / 2);
|
||||
aggregator.startPolarFixAggregation();
|
||||
|
||||
return aggregator.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<RegattaAndRaceIdentifier, List<PolarFix>> provideNewCacheValue(BoatClass key,
|
||||
Map<RegattaAndRaceIdentifier, List<PolarFix>> oldCacheValue,
|
||||
Map<RegattaAndRaceIdentifier, List<PolarFix>> computedCacheUpdate, PolarFixCacheRaceInterval updateInterval) {
|
||||
Map<RegattaAndRaceIdentifier, List<PolarFix>> newCacheValue = new HashMap<RegattaAndRaceIdentifier, List<PolarFix>>();
|
||||
if (oldCacheValue != null) {
|
||||
newCacheValue.putAll(oldCacheValue);
|
||||
}
|
||||
for (Entry<RegattaAndRaceIdentifier, List<PolarFix>> newEntry : computedCacheUpdate.entrySet()) {
|
||||
List<PolarFix> oldList = newCacheValue.get(newEntry.getKey());
|
||||
if (oldList != null) {
|
||||
oldList.addAll(newEntry.getValue());
|
||||
} else {
|
||||
newCacheValue.put(newEntry.getKey(), newEntry.getValue());
|
||||
}
|
||||
}
|
||||
return newCacheValue;
|
||||
}
|
||||
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
package com.sap.sailing.polars.caching;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.common.PolarSheetsData;
|
||||
import com.sap.sailing.polars.PolarDataService;
|
||||
import com.sap.sailing.util.SmartFutureCache;
|
||||
|
||||
public class PolarSheetPerBoatClassCache extends
|
||||
SmartFutureCache<BoatClass, PolarSheetsData, com.sap.sailing.util.SmartFutureCache.EmptyUpdateInterval>
|
||||
implements PolarFixCacheUpdateDoneListener {
|
||||
|
||||
public PolarSheetPerBoatClassCache(PolarDataService polarDataService) {
|
||||
super(new PolarSheetPerBoatClassCacheUpdater(polarDataService), "PolarSheetPerBoatClassCache");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cacheUpdateDoneForBoatClass(BoatClass key) {
|
||||
triggerUpdate(key, new EmptyUpdateInterval());
|
||||
}
|
||||
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
package com.sap.sailing.polars.caching;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
|
||||
import com.sap.sailing.domain.common.PolarSheetsData;
|
||||
import com.sap.sailing.domain.common.impl.PolarSheetGenerationSettingsImpl;
|
||||
import com.sap.sailing.polars.PolarDataService;
|
||||
import com.sap.sailing.polars.generation.PolarSheetGenerator;
|
||||
import com.sap.sailing.util.SmartFutureCache.CacheUpdater;
|
||||
import com.sap.sailing.util.SmartFutureCache.EmptyUpdateInterval;
|
||||
|
||||
public class PolarSheetPerBoatClassCacheUpdater implements
|
||||
CacheUpdater<BoatClass, PolarSheetsData, EmptyUpdateInterval> {
|
||||
|
||||
private final PolarDataService polarDataService;
|
||||
|
||||
public PolarSheetPerBoatClassCacheUpdater(PolarDataService polarDataService) {
|
||||
this.polarDataService = polarDataService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PolarSheetsData computeCacheUpdate(BoatClass key, EmptyUpdateInterval updateInterval) throws Exception {
|
||||
PolarSheetGenerationSettings settings = PolarSheetGenerationSettingsImpl.createStandardPolarSettings();
|
||||
PolarSheetGenerator generator = new PolarSheetGenerator(polarDataService.getPolarFixesForBoatClass(key),
|
||||
settings);
|
||||
PolarSheetsData generationResult = generator.generate();
|
||||
return generationResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PolarSheetsData provideNewCacheValue(BoatClass key, PolarSheetsData oldCacheValue,
|
||||
PolarSheetsData computedCacheUpdate, EmptyUpdateInterval updateInterval) {
|
||||
return computedCacheUpdate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,7 +9,7 @@ public interface PolarFix {
|
||||
|
||||
public abstract Speed getWindSpeed();
|
||||
|
||||
public abstract double getAngleToWind();
|
||||
public abstract double getTrueWindAngleDeg();
|
||||
|
||||
public abstract String getGaugeIdString();
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ public class PolarFixImpl implements PolarFix {
|
||||
|
||||
private SpeedWithBearing boatSpeed;
|
||||
private Speed windSpeed;
|
||||
private double angleToWind;
|
||||
private double trueWindAngleDeg;
|
||||
private String gaugeIdString;
|
||||
private String dayString;
|
||||
|
||||
@@ -53,7 +53,7 @@ public class PolarFixImpl implements PolarFix {
|
||||
windEstimated = windSpeed; // maybe no upwind start; need to default to measured wind speed/direction
|
||||
}
|
||||
Bearing windBearing = windEstimated.getFrom();
|
||||
angleToWind = bearing.getDifferenceTo(windBearing).getDegrees();
|
||||
trueWindAngleDeg = bearing.getDifferenceTo(windBearing).getDegrees();
|
||||
}
|
||||
|
||||
private String createDayString(TrackedRace race) {
|
||||
@@ -105,8 +105,8 @@ public class PolarFixImpl implements PolarFix {
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getAngleToWind() {
|
||||
return angleToWind;
|
||||
public double getTrueWindAngleDeg() {
|
||||
return trueWindAngleDeg;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+9
-9
@@ -12,7 +12,7 @@ import com.sap.sailing.domain.common.PolarSheetsData;
|
||||
import com.sap.sailing.domain.common.PolarSheetsHistogramData;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.impl.PolarSheetsDataImpl;
|
||||
import com.sap.sailing.domain.common.impl.WindSteppingWithMaxDistance;
|
||||
import com.sap.sailing.domain.common.impl.WindSpeedSteppingWithMaxDistance;
|
||||
import com.sap.sailing.polars.data.BoatAndWindSpeedWithOriginInfo;
|
||||
import com.sap.sailing.polars.data.DataPointWithOriginInfo;
|
||||
import com.sap.sailing.polars.data.PolarFix;
|
||||
@@ -23,7 +23,7 @@ public class PolarSheetGenerator {
|
||||
|
||||
private final List<List<BoatAndWindSpeedWithOriginInfo>> polarData;
|
||||
|
||||
private WindSteppingWithMaxDistance stepping;
|
||||
private WindSpeedSteppingWithMaxDistance stepping;
|
||||
|
||||
private PolarSheetGenerationSettings settings;
|
||||
|
||||
@@ -32,7 +32,7 @@ public class PolarSheetGenerator {
|
||||
public PolarSheetGenerator(Set<PolarFix> fixes, PolarSheetGenerationSettings settings) {
|
||||
this.settings = settings;
|
||||
this.fixes = fixes;
|
||||
stepping = settings.getWindStepping();
|
||||
stepping = settings.getWindSpeedStepping();
|
||||
polarData = initializePolarDataContainer();
|
||||
}
|
||||
|
||||
@@ -46,16 +46,16 @@ public class PolarSheetGenerator {
|
||||
|
||||
|
||||
private void addPolarFix(PolarFix polarFix) {
|
||||
long roundedAngle = Math.round(polarFix.getAngleToWind());
|
||||
long roundedAngleDeg = Math.round(polarFix.getTrueWindAngleDeg());
|
||||
Speed boatSpeed = polarFix.getBoatSpeed();
|
||||
Speed windSpeed = polarFix.getWindSpeed();
|
||||
int angle = (int) roundedAngle;
|
||||
if (angle < 0) {
|
||||
angle = (360 + angle);
|
||||
int angleDeg = (int) roundedAngleDeg;
|
||||
if (angleDeg < 0) {
|
||||
angleDeg = (360 + angleDeg);
|
||||
}
|
||||
BoatAndWindSpeedWithOriginInfo speeds = new BoatAndWindSpeedWithOriginInfoImpl(boatSpeed, windSpeed,
|
||||
polarFix.getGaugeIdString(), polarFix.getDayString());
|
||||
polarData.get(angle).add(speeds);
|
||||
polarData.get(angleDeg).add(speeds);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,7 +270,7 @@ public class PolarSheetGenerator {
|
||||
return polarData;
|
||||
}
|
||||
|
||||
public WindSteppingWithMaxDistance getStepping() {
|
||||
public WindSpeedSteppingWithMaxDistance getStepping() {
|
||||
return stepping;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.sap.sailing.polars.impl;
|
||||
|
||||
public class CubicEquation {
|
||||
|
||||
private double a;
|
||||
private double b;
|
||||
private double c;
|
||||
private double d;
|
||||
|
||||
public CubicEquation(double a, double b, double c, double d) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.c = c;
|
||||
this.d = d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Following: http://www.1728.org/cubic2.htm
|
||||
* !The complex roots are not returned!
|
||||
*/
|
||||
public double[] solve() {
|
||||
|
||||
double f = ((3 * c) / a - (b * b) / (a * a)) / 3;
|
||||
double g = ((2 * b * b * b) / (a * a * a) - (9 * b * c) / (a * a) + (27 * d) / a) / 27;
|
||||
double h = (g*g) / 4 + (f*f*f) / 27;
|
||||
|
||||
double[] result;
|
||||
|
||||
if (f == 0 && g == 0 && h == 0) {
|
||||
// All roots equal and real
|
||||
result = new double[1];
|
||||
result[0] = - Math.cbrt(d/a);
|
||||
} else if (h > 0) {
|
||||
// Only one root is real
|
||||
double r = -(g/2) + Math.sqrt(h);
|
||||
double s = Math.cbrt(r);
|
||||
double t = -(g/2) - Math.sqrt(h);
|
||||
double u = Math.cbrt(t);
|
||||
result = new double[1];
|
||||
result[0] = (s + u) - (b / (3 * a));
|
||||
//!!The complex roots are not returned
|
||||
} else {
|
||||
// All three roots are real and not equal
|
||||
double i = Math.sqrt((g * g) / 4 - h);
|
||||
double j = Math.cbrt(i);
|
||||
double k = Math.acos(-(g/(2*i)));
|
||||
double l = -1 * j;
|
||||
double m = Math.cos(k / 3);
|
||||
double n = Math.sqrt(3) * Math.sin(k/3);
|
||||
double p = - (b/(3*a));
|
||||
result = new double[3];
|
||||
result[0] = 2 * j * Math.cos(k / 3) - b / (3 * a);
|
||||
result[1] = l * (m + n) + p;
|
||||
result[2] = l * (m - n) + p;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
Regular → Executable
+47
-93
@@ -1,8 +1,6 @@
|
||||
package com.sap.sailing.polars.impl;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -11,22 +9,20 @@ import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
|
||||
import com.sap.sailing.domain.base.SpeedWithConfidence;
|
||||
import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl;
|
||||
import com.sap.sailing.domain.common.Bearing;
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
|
||||
import com.sap.sailing.domain.common.PolarSheetsData;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.polars.PolarDataService;
|
||||
import com.sap.sailing.polars.aggregation.PolarFixAggregator;
|
||||
import com.sap.sailing.polars.aggregation.SimplePolarFixRaceInterval;
|
||||
import com.sap.sailing.polars.analysis.PolarSheetAnalyzer;
|
||||
import com.sap.sailing.polars.analysis.impl.PolarSheetAnalyzerImpl;
|
||||
import com.sap.sailing.polars.caching.PolarFixCache;
|
||||
import com.sap.sailing.polars.caching.PolarSheetPerBoatClassCache;
|
||||
import com.sap.sailing.polars.data.PolarFix;
|
||||
import com.sap.sailing.polars.generation.PolarSheetGenerator;
|
||||
import com.sap.sailing.polars.mining.PolarDataMiner;
|
||||
@@ -35,7 +31,7 @@ import com.sap.sailing.util.SmartFutureCache;
|
||||
|
||||
/**
|
||||
* Uses two chained {@link SmartFutureCache}s. One to store {@link PolarFix}es extracted from {@link TrackedRace}s and
|
||||
* the other one for storing one polar sheet per boat class. This allows quick access to desired measures like optimal
|
||||
* the other one for storing one polar sheet per boat class. This enables quick access to desired measures like optimal
|
||||
* beat angles.
|
||||
*
|
||||
* @author Frederik Petersen (D054528)
|
||||
@@ -43,51 +39,61 @@ import com.sap.sailing.util.SmartFutureCache;
|
||||
*/
|
||||
public class PolarDataServiceImpl implements PolarDataService {
|
||||
|
||||
private final PolarFixCache polarFixCache;
|
||||
private final PolarSheetPerBoatClassCache polarSheetPerBoatClassCache;
|
||||
|
||||
private final PolarSheetAnalyzer polarSheetAnalyzer;
|
||||
|
||||
private final PolarDataMiner polarDataMiner;
|
||||
|
||||
|
||||
|
||||
|
||||
public PolarDataServiceImpl(Executor executor) {
|
||||
this.polarFixCache = new PolarFixCache(executor);
|
||||
this.polarSheetPerBoatClassCache = new PolarSheetPerBoatClassCache(this);
|
||||
polarFixCache.addListener(polarSheetPerBoatClassCache);
|
||||
this.polarSheetAnalyzer = new PolarSheetAnalyzerImpl(this);
|
||||
this.polarDataMiner = new PolarDataMiner();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeedWithConfidence<Void> getSpeed(BoatClass boatClass, Speed windSpeed, Bearing bearingToTheWind,
|
||||
boolean useLinearRegression) throws NotEnoughDataHasBeenAddedException {
|
||||
return polarDataMiner.estimateBoatSpeed(boatClass, windSpeed, bearingToTheWind, useLinearRegression);
|
||||
public SpeedWithConfidence<Void> getSpeed(BoatClass boatClass, Speed windSpeed, Bearing trueWindAngle) throws NotEnoughDataHasBeenAddedException {
|
||||
return polarDataMiner.estimateBoatSpeed(boatClass, windSpeed, trueWindAngle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageSpeedWithBearing(BoatClass boatClass, Speed windSpeed,
|
||||
LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException {
|
||||
return getAverageSpeedWithBearing(boatClass, windSpeed, legType, tack, true);
|
||||
public Set<SpeedWithBearingWithConfidence<Void>> getAverageTrueWindSpeedAndAngleCandidates(BoatClass boatClass,
|
||||
Speed speedOverGround, LegType legType, Tack tack) {
|
||||
|
||||
// The following is an estimation function. It only serves as a stub. It's the same for all boatclasses and returns
|
||||
// default maneuver angles. This is NOT final.
|
||||
// The function is able to return boat speed values for windspeed alues between 5kn and 25kn , which are some kind of realistic
|
||||
// for sailing boats. They are taken from the 505 polars we gathered in the races until now.
|
||||
// TODO keep a cache of sampling points over which to fit a function for every boat class and then return the
|
||||
// corresponding angle
|
||||
|
||||
Set<SpeedWithBearingWithConfidence<Void>> resultSet = new HashSet<>();
|
||||
int tackFactor = (tack.equals(Tack.PORT)) ? -1 : 1;
|
||||
if (legType.equals(LegType.UPWIND)) {
|
||||
CubicEquation upWindEquation = new CubicEquation(0.0002, -0.0245, 0.7602, -0.0463-speedOverGround.getKnots());
|
||||
int angle = 49 * tackFactor;
|
||||
solveAndAddResults(resultSet, upWindEquation, angle);
|
||||
|
||||
|
||||
} else if (legType.equals(LegType.DOWNWIND)) {
|
||||
CubicEquation downWindEquation = new CubicEquation(0.0003, -0.0373, 1.5213, -2.1309-speedOverGround.getKnots());
|
||||
int angle = 30 * tackFactor;
|
||||
solveAndAddResults(resultSet, downWindEquation, angle);
|
||||
}
|
||||
return resultSet;
|
||||
//return polarDataMiner.estimateTrueWindSpeedAndAngleCandidates(boatClass, speedOverGround, legType, tack);
|
||||
}
|
||||
|
||||
|
||||
private void solveAndAddResults(Set<SpeedWithBearingWithConfidence<Void>> result, CubicEquation upWindEquation, int angle) {
|
||||
double[] windSpeedCandidates = upWindEquation.solve();
|
||||
for (int i = 0; i < windSpeedCandidates.length; i++) {
|
||||
double windSpeedCandidateInKnots = windSpeedCandidates[i] > 0 ? windSpeedCandidates[i] : 0;
|
||||
result.add(new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(
|
||||
windSpeedCandidateInKnots, new DegreeBearingImpl(angle)), 0.00001, null));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageSpeedWithBearing(BoatClass boatClass,
|
||||
Speed windSpeed, LegType legType, Tack tack, boolean useLinReg) throws NotEnoughDataHasBeenAddedException {
|
||||
SpeedWithBearingWithConfidence<Void> speedWithBearing = null;
|
||||
if (tack.equals(Tack.STARBOARD)) {
|
||||
if (legType.equals(LegType.UPWIND)) {
|
||||
speedWithBearing = polarSheetAnalyzer.getAverageUpwindSpeedWithBearingOnStarboardTackFor(boatClass, windSpeed, useLinReg);
|
||||
} else if (legType.equals(LegType.DOWNWIND)) {
|
||||
speedWithBearing = polarSheetAnalyzer.getAverageDownwindSpeedWithBearingOnStarboardTackFor(boatClass, windSpeed, useLinReg);
|
||||
}
|
||||
} else if (tack.equals(Tack.PORT)) {
|
||||
if (legType.equals(LegType.UPWIND)) {
|
||||
return polarSheetAnalyzer.getAverageUpwindSpeedWithBearingOnPortTackFor(boatClass, windSpeed, useLinReg);
|
||||
} else if (legType.equals(LegType.DOWNWIND)) {
|
||||
return polarSheetAnalyzer.getAverageDownwindSpeedWithBearingOnPortTackFor(boatClass, windSpeed, useLinReg);
|
||||
}
|
||||
}
|
||||
return speedWithBearing;
|
||||
Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException {
|
||||
return polarDataMiner.getAverageSpeedAndCourseOverGround(boatClass, windSpeed, legType, tack);
|
||||
}
|
||||
|
||||
|
||||
@@ -95,85 +101,33 @@ public class PolarDataServiceImpl implements PolarDataService {
|
||||
public PolarSheetsData generatePolarSheet(Set<TrackedRace> trackedRaces, PolarSheetGenerationSettings settings,
|
||||
Executor executor) throws InterruptedException, ExecutionException {
|
||||
Set<PolarFix> fixes;
|
||||
// If settings are default, look into cache
|
||||
// if (settings.areDefault()) {
|
||||
// try {
|
||||
// fixes = polarFixCache.getFixesForTrackedRaces(trackedRaces);
|
||||
// } catch (NoCacheEntryException e) {
|
||||
// // If there is no cache entry for at least one of the races: Aggregate for non-cached races.
|
||||
// fixes = e.getCachedResultList();
|
||||
// PolarFixAggregator aggregator = new PolarFixAggregator(
|
||||
// new SimplePolarFixRaceInterval(e.getNotCached()),
|
||||
// settings, executor);
|
||||
// aggregator.startPolarFixAggregation();
|
||||
// fixes.addAll(aggregator.getAggregationResultAsSingleList());
|
||||
// }
|
||||
// } else {
|
||||
PolarFixAggregator aggregator = new PolarFixAggregator(new SimplePolarFixRaceInterval(trackedRaces), settings,
|
||||
executor);
|
||||
aggregator.startPolarFixAggregation();
|
||||
fixes = aggregator.getAggregationResultAsSingleList();
|
||||
// }
|
||||
PolarSheetGenerator generator = new PolarSheetGenerator(fixes, settings);
|
||||
return generator.generate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newRaceFinishedTracking(TrackedRace trackedRace) {
|
||||
// TODO add a similar listening method, that deletes all cached data on removal of race
|
||||
// HashSet<TrackedRace> set = new HashSet<TrackedRace>();
|
||||
// set.add(trackedRace);
|
||||
// polarFixCache.triggerUpdate(trackedRace.getRace().getBoatClass(), new PolarFixCacheRaceInterval(set));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PolarFix> getPolarFixesForBoatClass(BoatClass key) {
|
||||
Map<RegattaAndRaceIdentifier, List<PolarFix>> fixesInMap = polarFixCache.get(key, false);
|
||||
Set<PolarFix> resultSet = new HashSet<PolarFix>();
|
||||
for (List<PolarFix> value : fixesInMap.values()) {
|
||||
resultSet.addAll(value);
|
||||
}
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PolarSheetsData getPolarSheetForBoatClass(BoatClass boatClass) {
|
||||
return polarDataMiner.createFullSheetForBoatClass(boatClass);
|
||||
//return polarSheetPerBoatClassCache.get(boatClass, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<BoatClass> getAllBoatClassesWithPolarSheetsAvailable() {
|
||||
return polarDataMiner.getAvailableBoatClasses();
|
||||
//return polarSheetPerBoatClassCache.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void competitorPositionChanged(final GPSFixMoving fix, final Competitor competitor,
|
||||
final TrackedRace createdTrackedRace) {
|
||||
polarDataMiner.addFix(fix, competitor, createdTrackedRace);
|
||||
// TODO build datamining pipeline for the cache
|
||||
|
||||
// PolarFixCacheRaceInterval interval = new PolarFixCacheRaceInterval(createIntervalSpecification(
|
||||
// createdTrackedRace, competitor, timePoint));
|
||||
// polarFixCache.triggerUpdate(createdTrackedRace.getRace().getBoatClass(), interval);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer[] getDataCountsForWindSpeed(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive) {
|
||||
public int[] getDataCountsForWindSpeed(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive) {
|
||||
return polarDataMiner.getDataCountsForWindSpeed(boatClass, windSpeed, startAngleInclusive, endAngleExclusive);
|
||||
}
|
||||
|
||||
// private Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> createIntervalSpecification(
|
||||
// TrackedRace createdTrackedRace, Competitor competitor, TimePoint timePoint) {
|
||||
// HashMap<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> result = new HashMap<TrackedRace,
|
||||
// Map<Competitor, Pair<TimePoint, TimePoint>>>();
|
||||
// HashMap<Competitor, Pair<TimePoint, TimePoint>> competitorMap = new HashMap<Competitor, Pair<TimePoint,
|
||||
// TimePoint>>();
|
||||
// competitorMap.put(competitor, new Pair<TimePoint, TimePoint>(timePoint, timePoint));
|
||||
// result.put(createdTrackedRace, competitorMap);
|
||||
// return result;
|
||||
// }
|
||||
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.sap.sailing.polars.mining;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sse.datamining.data.Cluster;
|
||||
import com.sap.sse.datamining.data.ClusterGroup;
|
||||
|
||||
public class AverageAngleContainer {
|
||||
|
||||
private ClusterGroup<Speed> speedClusterGroup;
|
||||
|
||||
private static class DataCountAngleSumAndAverage {
|
||||
private int dataCount;
|
||||
private double angleSumDeg;
|
||||
private double averageAngleDeg;
|
||||
protected DataCountAngleSumAndAverage() {
|
||||
super();
|
||||
this.dataCount = 0;
|
||||
this.angleSumDeg = 0.0;
|
||||
this.averageAngleDeg = 0.0;
|
||||
}
|
||||
public double getAverageAngleDeg() {
|
||||
return averageAngleDeg;
|
||||
}
|
||||
public synchronized void add(double angleRad) {
|
||||
dataCount++;
|
||||
angleSumDeg += angleRad;
|
||||
averageAngleDeg = angleSumDeg / (double) dataCount;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<BoatClass, Map<Cluster<Speed>, DataCountAngleSumAndAverage>> dataCountAndAngleSumMap;
|
||||
|
||||
public AverageAngleContainer(ClusterGroup<Speed> speedClusterGroup) {
|
||||
this.speedClusterGroup = speedClusterGroup;
|
||||
dataCountAndAngleSumMap = new HashMap<>();
|
||||
}
|
||||
|
||||
public void addFix(BoatClass boatClass, Speed windSpeed, int roundedAngleDeg) {
|
||||
Cluster<Speed> windCluster = speedClusterGroup.getClusterFor(windSpeed);
|
||||
if (!dataCountAndAngleSumMap.containsKey(boatClass)) {
|
||||
dataCountAndAngleSumMap.put(boatClass, new HashMap<>());
|
||||
}
|
||||
Map<Cluster<Speed>, DataCountAngleSumAndAverage> boatClassMap = dataCountAndAngleSumMap.get(boatClass);
|
||||
DataCountAngleSumAndAverage triple = boatClassMap.get(windCluster);
|
||||
if (triple == null) {
|
||||
triple = new DataCountAngleSumAndAverage();
|
||||
boatClassMap.put(windCluster, triple);
|
||||
}
|
||||
triple.add(roundedAngleDeg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>null</code> if no entry exists for the <code>windSpeed</code> requested; a valid angle in degrees
|
||||
* otherwise, representing the average angle of this cluster
|
||||
*/
|
||||
Double getAverageAngleDeg(BoatClass boatClass, Speed windSpeed) {
|
||||
Double result = null;
|
||||
if (dataCountAndAngleSumMap.containsKey(boatClass)) {
|
||||
Map<Cluster<Speed>, DataCountAngleSumAndAverage> boatClassMap = dataCountAndAngleSumMap.get(boatClass);
|
||||
Cluster<Speed> windCluster = speedClusterGroup.getClusterFor(windSpeed);
|
||||
if (boatClassMap.containsKey(windCluster)) {
|
||||
DataCountAngleSumAndAverage triple = boatClassMap.get(windCluster);
|
||||
result = triple.getAverageAngleDeg();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
+7
-7
@@ -56,9 +56,9 @@ public class GPSFixMovingWithPolarContext implements PolarClusterKey {
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoundedAngleToTheWind getRoundedAngleToTheWind() {
|
||||
public RoundedTrueWindAngle getRoundedTrueWindAngle() {
|
||||
final BearingWithConfidence<Integer> angleToTheWind = getAngleToTheWind();
|
||||
return angleToTheWind == null ? null : new RoundedAngleToTheWind(angleToTheWind.getObject());
|
||||
return angleToTheWind == null ? null : new RoundedTrueWindAngle(angleToTheWind.getObject());
|
||||
}
|
||||
|
||||
public BearingWithConfidence<Integer> getAngleToTheWind() {
|
||||
@@ -91,11 +91,6 @@ public class GPSFixMovingWithPolarContext implements PolarClusterKey {
|
||||
// use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero
|
||||
track.getMillisecondsOverWhichToAverageSpeed()/2, /* minimumConfidence */ 0.00000001));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoatClass getBoatClass() {
|
||||
return race.getRace().getBoatClass();
|
||||
}
|
||||
|
||||
private Set<WindSource> collectWindSourcesToIgnoreForBearing() {
|
||||
Set<WindSource> windSourcesToExclude = new HashSet<WindSource>();
|
||||
@@ -148,4 +143,9 @@ public class GPSFixMovingWithPolarContext implements PolarClusterKey {
|
||||
return windSourcesToExclude;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoatClass getBoatClass() {
|
||||
return race.getRace().getBoatClass();
|
||||
}
|
||||
|
||||
}
|
||||
Regular → Executable
+125
-29
@@ -1,7 +1,9 @@
|
||||
package com.sap.sailing.polars.mining;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
@@ -9,15 +11,23 @@ import java.util.logging.Logger;
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
|
||||
import com.sap.sailing.domain.base.SpeedWithConfidence;
|
||||
import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl;
|
||||
import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl;
|
||||
import com.sap.sailing.domain.common.Bearing;
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.domain.common.confidence.BearingWithConfidence;
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.domain.tracking.WindWithConfidence;
|
||||
import com.sap.sailing.polars.analysis.PolarSheetAnalyzer;
|
||||
import com.sap.sailing.polars.regression.BoatSpeedEstimator;
|
||||
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
|
||||
import com.sap.sailing.polars.regression.impl.WindSpeedAndAngleEstimator;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.datamining.AdditionalResultDataBuilder;
|
||||
@@ -28,18 +38,28 @@ import com.sap.sse.datamining.factories.GroupKeyFactory;
|
||||
import com.sap.sse.datamining.impl.components.GroupedDataEntry;
|
||||
import com.sap.sse.datamining.shared.GroupKey;
|
||||
|
||||
public class IncrementalRegressionProcessor implements Processor<GroupedDataEntry<GPSFixMovingWithPolarContext>,Void> {
|
||||
|
||||
public class IncrementalRegressionProcessor implements Processor<GroupedDataEntry<GPSFixMovingWithPolarContext>, Void>,
|
||||
PolarSheetAnalyzer {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(IncrementalRegressionProcessor.class.getName());
|
||||
|
||||
private final Map<GroupKey, BoatSpeedEstimator> boatSpeedEstimators = new HashMap<GroupKey, BoatSpeedEstimator>();
|
||||
|
||||
private final ClusterGroup<Speed> speedClusterGroup;
|
||||
|
||||
|
||||
private final Set<BoatClass> availableBoatClasses = new HashSet<>();
|
||||
|
||||
private final Map<Pair<LegType, Tack>, AverageAngleContainer> averageAngleContainers;
|
||||
|
||||
public IncrementalRegressionProcessor(ClusterGroup<Speed> speedClusterGroup) {
|
||||
this.speedClusterGroup = speedClusterGroup;
|
||||
averageAngleContainers = new HashMap<>();
|
||||
for (LegType legType : new LegType[] { LegType.UPWIND, LegType.DOWNWIND }) {
|
||||
for (Tack tack : new Tack[] { Tack.PORT, Tack.STARBOARD }) {
|
||||
averageAngleContainers.put(new Pair<LegType, Tack>(legType, tack),
|
||||
new AverageAngleContainer(speedClusterGroup));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,34 +67,50 @@ public class IncrementalRegressionProcessor implements Processor<GroupedDataEntr
|
||||
GroupKey key = element.getKey();
|
||||
BoatSpeedEstimator boatSpeedEstimator;
|
||||
synchronized (boatSpeedEstimators) {
|
||||
if (!boatSpeedEstimators.containsKey(key)) {
|
||||
boatSpeedEstimators.put(key, new BoatSpeedEstimator());
|
||||
boatSpeedEstimator = boatSpeedEstimators.get(key);
|
||||
if (boatSpeedEstimator == null) {
|
||||
boatSpeedEstimator = new BoatSpeedEstimator();
|
||||
boatSpeedEstimators.put(key, boatSpeedEstimator);
|
||||
availableBoatClasses.add(element.getDataEntry().getBoatClass());
|
||||
}
|
||||
|
||||
boatSpeedEstimator = boatSpeedEstimators.get(key);
|
||||
}
|
||||
GPSFixMovingWithPolarContext fix = element.getDataEntry();
|
||||
|
||||
BearingWithConfidence<Integer> angleToTheWind = fix.getAngleToTheWind();
|
||||
|
||||
WindWithConfidence<Pair<Position, TimePoint>> windSpeed = fix.getWindSpeed();
|
||||
|
||||
SpeedWithBearingWithConfidence<TimePoint> boatSpeedWithConfidence = fix.getBoatSpeed();
|
||||
|
||||
|
||||
//Only add GPS data if speeds and angles are not null, else do nothing!
|
||||
// Only add GPS data if speeds and angles are not null, else do nothing!
|
||||
if (angleToTheWind != null && windSpeed != null && boatSpeedWithConfidence != null) {
|
||||
|
||||
fillAverageAngleContainer(fix, element, windSpeed);
|
||||
WindWithConfidence<Pair<Position, TimePoint>> windWithConfidenceForSpeed = windSpeed;
|
||||
|
||||
double confidenceForWindSpeed = windWithConfidenceForSpeed.getConfidence();
|
||||
double confidenceForWindBearing = angleToTheWind.getConfidence();
|
||||
double confidenceForBoatSpeed = boatSpeedWithConfidence.getConfidence();
|
||||
double averagedConfidence = (confidenceForBoatSpeed + confidenceForWindBearing + confidenceForWindSpeed) / 3;
|
||||
boatSpeedEstimator.addData(windWithConfidenceForSpeed.getObject().getKnots(), angleToTheWind.getObject()
|
||||
.getDegrees(), boatSpeedWithConfidence.getObject().getKnots(), averagedConfidence);
|
||||
}
|
||||
boatSpeedEstimator.addData(boatSpeedWithConfidence.getObject(), averagedConfidence);
|
||||
}
|
||||
}
|
||||
|
||||
private void fillAverageAngleContainer(GPSFixMovingWithPolarContext fix,
|
||||
GroupedDataEntry<GPSFixMovingWithPolarContext> element,
|
||||
WindWithConfidence<Pair<Position, TimePoint>> windSpeed) {
|
||||
final int roundedAngleDeg = fix.getRoundedTrueWindAngle().getAngleDeg();
|
||||
final LegType legType;
|
||||
final Tack tack;
|
||||
if (roundedAngleDeg < -90) {
|
||||
legType = LegType.DOWNWIND;
|
||||
tack = Tack.PORT;
|
||||
} else if (roundedAngleDeg < 0) {
|
||||
legType = LegType.UPWIND;
|
||||
tack = Tack.PORT;
|
||||
} else if (roundedAngleDeg > 90) {
|
||||
legType = LegType.DOWNWIND;
|
||||
tack = Tack.STARBOARD;
|
||||
} else { // roundedAngleDeg > 0 && roundedAngleDeg <= 90
|
||||
legType = LegType.UPWIND;
|
||||
tack = Tack.STARBOARD;
|
||||
}
|
||||
averageAngleContainers.get(new Pair<>(legType, tack)).addFix(element.getDataEntry().getBoatClass(),
|
||||
windSpeed.getObject(), roundedAngleDeg);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,14 +129,28 @@ public class IncrementalRegressionProcessor implements Processor<GroupedDataEntr
|
||||
return null;
|
||||
}
|
||||
|
||||
public Pair<SpeedWithConfidence<Void>, Integer> estimateBoatSpeed(final BoatClass boatClass, final Speed windSpeed,
|
||||
final Bearing angleToTheWind, boolean useLinearRegression)
|
||||
throws NotEnoughDataHasBeenAddedException {
|
||||
int dataCount = 0;
|
||||
public static class SpeedWithConfidenceAndDataCount {
|
||||
private final SpeedWithConfidence<Void> speedWithConfidence;
|
||||
private final int dataCount;
|
||||
protected SpeedWithConfidenceAndDataCount(SpeedWithConfidence<Void> speedWithConfidence, int dataCount) {
|
||||
super();
|
||||
this.speedWithConfidence = speedWithConfidence;
|
||||
this.dataCount = dataCount;
|
||||
}
|
||||
public SpeedWithConfidence<Void> getSpeedWithConfidence() {
|
||||
return speedWithConfidence;
|
||||
}
|
||||
public int getDataCount() {
|
||||
return dataCount;
|
||||
}
|
||||
}
|
||||
|
||||
SpeedWithConfidenceAndDataCount estimateBoatSpeed(final BoatClass boatClass, final Speed windSpeed,
|
||||
final Bearing trueWindAngle) throws NotEnoughDataHasBeenAddedException {
|
||||
PolarClusterKey key = new PolarClusterKey() {
|
||||
@Override
|
||||
public RoundedAngleToTheWind getRoundedAngleToTheWind() {
|
||||
return new RoundedAngleToTheWind(angleToTheWind);
|
||||
public RoundedTrueWindAngle getRoundedTrueWindAngle() {
|
||||
return new RoundedTrueWindAngle(trueWindAngle);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -125,12 +175,38 @@ public class IncrementalRegressionProcessor implements Processor<GroupedDataEntr
|
||||
if (boatSpeedEstimator == null) {
|
||||
throw new NotEnoughDataHasBeenAddedException();
|
||||
}
|
||||
KnotSpeedImpl speedWithoutConfidence = new KnotSpeedImpl(boatSpeedEstimator.estimateSpeed(windSpeed.getKnots(),
|
||||
angleToTheWind.getDegrees(), useLinearRegression));
|
||||
dataCount = boatSpeedEstimator.getDataCount();
|
||||
Speed speedWithoutConfidence = boatSpeedEstimator.estimateSpeed(windSpeed.getKnots(), trueWindAngle.getDegrees());
|
||||
final int dataCount = boatSpeedEstimator.getDataCount();
|
||||
double confidence = boatSpeedEstimator.getConfidence();
|
||||
return new Pair<SpeedWithConfidence<Void>, Integer>(new SpeedWithConfidenceImpl<Void>(
|
||||
speedWithoutConfidence, confidence, null), dataCount);
|
||||
return new SpeedWithConfidenceAndDataCount(new SpeedWithConfidenceImpl<Void>(speedWithoutConfidence, confidence, null), dataCount);
|
||||
}
|
||||
|
||||
Set<SpeedWithBearingWithConfidence<Void>> estimateTrueWindSpeedAndAngleCandidates(BoatClass boatClass,
|
||||
Speed speedOverGround, LegType legType, Tack tack) {
|
||||
List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed = obtainPolarSamplingPoints(
|
||||
boatClass, legType, tack);
|
||||
|
||||
WindSpeedAndAngleEstimator windSpeedAndAngleEstimator = new WindSpeedAndAngleEstimator(averageBoatSpeedAndCourseForWindSpeed);
|
||||
return windSpeedAndAngleEstimator.getAverageTrueWindSpeedAndAngleCandidates(speedOverGround);
|
||||
}
|
||||
|
||||
private List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> obtainPolarSamplingPoints(BoatClass boatClass,
|
||||
LegType legType, Tack tack) {
|
||||
List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed = new ArrayList<>();
|
||||
for (int i = 0; i < 60; i++) {
|
||||
Speed windSpeed = new KnotSpeedImpl(i * 0.5);
|
||||
SpeedWithBearingWithConfidence<Void> averageSpeedAndCourseOverGround;
|
||||
try {
|
||||
averageSpeedAndCourseOverGround = getAverageSpeedAndCourseOverGround(
|
||||
boatClass, windSpeed, legType, tack);
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(windSpeed,
|
||||
averageSpeedAndCourseOverGround));
|
||||
} catch (NotEnoughDataHasBeenAddedException e) {
|
||||
// ignore this, since there is not enough data for this windspeed.
|
||||
}
|
||||
|
||||
}
|
||||
return averageBoatSpeedAndCourseForWindSpeed;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -155,4 +231,24 @@ public class IncrementalRegressionProcessor implements Processor<GroupedDataEntr
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageSpeedAndCourseOverGround(BoatClass boatClass,
|
||||
Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException {
|
||||
Double averageAngle = averageAngleContainers.get(new Pair<>(legType, tack)).getAverageAngleDeg(
|
||||
boatClass, windSpeed);
|
||||
return estimateSpeedForAverageAngle(boatClass, windSpeed, averageAngle);
|
||||
}
|
||||
|
||||
private SpeedWithBearingWithConfidence<Void> estimateSpeedForAverageAngle(BoatClass boatClass, Speed windSpeed,
|
||||
Double averageAngle) throws NotEnoughDataHasBeenAddedException {
|
||||
if (averageAngle == null) {
|
||||
throw new NotEnoughDataHasBeenAddedException();
|
||||
}
|
||||
DegreeBearingImpl angleToTheWind = new DegreeBearingImpl(averageAngle);
|
||||
SpeedWithConfidence<Void> estimatedSpeed = estimateBoatSpeed(boatClass, windSpeed, angleToTheWind).getSpeedWithConfidence();
|
||||
SpeedWithBearing speedWithBearing = new KnotSpeedWithBearingImpl(estimatedSpeed.getObject().getKnots(),
|
||||
angleToTheWind);
|
||||
return new SpeedWithBearingWithConfidenceImpl<Void>(speedWithBearing, estimatedSpeed.getConfidence(), null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,7 +12,7 @@ public interface PolarClusterKey {
|
||||
* @return degrees boat to windDirection in DEG
|
||||
*/
|
||||
@Dimension(messageKey = "angleToWind")
|
||||
RoundedAngleToTheWind getRoundedAngleToTheWind();
|
||||
RoundedTrueWindAngle getRoundedTrueWindAngle();
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
+2
-2
@@ -12,8 +12,8 @@ public class PolarDataDimensionCollectionFactory {
|
||||
public static Collection<Function<?>> getClusterKeyDimensions() throws NoSuchMethodException {
|
||||
Collection<Function<?>> dimensions = new ArrayList<>();
|
||||
FunctionFactory functionFactory = new FunctionFactory();
|
||||
Function<RoundedAngleToTheWind> angleFunction = functionFactory
|
||||
.createMethodWrappingFunction(PolarClusterKey.class.getMethod("getRoundedAngleToTheWind",
|
||||
Function<RoundedTrueWindAngle> angleFunction = functionFactory
|
||||
.createMethodWrappingFunction(PolarClusterKey.class.getMethod("getRoundedTrueWindAngle",
|
||||
new Class<?>[0]));
|
||||
Function<WindSpeedLevel> windSpeedFunction = functionFactory.createMethodWrappingFunction(PolarClusterKey.class
|
||||
.getMethod("getWindSpeedCluster", new Class<?>[0]));
|
||||
|
||||
Regular → Executable
+34
-40
@@ -12,23 +12,27 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
|
||||
import com.sap.sailing.domain.base.SpeedWithConfidence;
|
||||
import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl;
|
||||
import com.sap.sailing.domain.common.Bearing;
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
|
||||
import com.sap.sailing.domain.common.PolarSheetsData;
|
||||
import com.sap.sailing.domain.common.PolarSheetsHistogramData;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
|
||||
import com.sap.sailing.domain.common.impl.PolarSheetGenerationSettingsImpl;
|
||||
import com.sap.sailing.domain.common.impl.PolarSheetsDataImpl;
|
||||
import com.sap.sailing.domain.common.impl.PolarSheetsHistogramDataImpl;
|
||||
import com.sap.sailing.domain.common.impl.WindSteppingWithMaxDistance;
|
||||
import com.sap.sailing.domain.common.impl.WindSpeedSteppingWithMaxDistance;
|
||||
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.polars.analysis.PolarSheetAnalyzer;
|
||||
import com.sap.sailing.polars.mining.IncrementalRegressionProcessor.SpeedWithConfidenceAndDataCount;
|
||||
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.datamining.components.Processor;
|
||||
import com.sap.sse.datamining.data.ClusterGroup;
|
||||
import com.sap.sse.datamining.functions.Function;
|
||||
@@ -37,9 +41,9 @@ import com.sap.sse.datamining.impl.components.GroupedDataEntry;
|
||||
import com.sap.sse.datamining.impl.components.ParallelFilteringProcessor;
|
||||
import com.sap.sse.datamining.impl.components.ParallelMultiDimensionsValueNestingGroupingProcessor;
|
||||
|
||||
public class PolarDataMiner {
|
||||
public class PolarDataMiner implements PolarSheetAnalyzer {
|
||||
|
||||
private static final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3);
|
||||
private static final int THREAD_POOL_SIZE = Math.min(Runtime.getRuntime().availableProcessors(), 3);
|
||||
private static final ThreadPoolExecutor executor = createExecutor();
|
||||
private final PolarSheetGenerationSettings backendPolarSheetGenerationSettings;
|
||||
|
||||
@@ -62,11 +66,9 @@ public class PolarDataMiner {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void setUpWorkflow() throws ClassCastException, NoSuchMethodException,
|
||||
SecurityException {
|
||||
WindSteppingWithMaxDistance stepping = backendPolarSheetGenerationSettings.getWindStepping();
|
||||
WindSpeedSteppingWithMaxDistance stepping = backendPolarSheetGenerationSettings.getWindSpeedStepping();
|
||||
final ClusterGroup<Speed> speedClusterGroup = SpeedClusterGroupFromWindSteppingCreator
|
||||
.createSpeedClusterGroupFrom(stepping);
|
||||
incrementalRegressionProcessor = new IncrementalRegressionProcessor(speedClusterGroup);
|
||||
@@ -113,24 +115,25 @@ public class PolarDataMiner {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param boatClass
|
||||
* @param windSpeed
|
||||
* @param angleToTheWind
|
||||
* @param trueWindAngle
|
||||
* @param useLinearRegression if true uses lin. regression in the wind interval, otherwise arithm. mean
|
||||
* @return
|
||||
* @throws NotEnoughDataHasBeenAddedException
|
||||
*/
|
||||
public SpeedWithConfidence<Void> estimateBoatSpeed(BoatClass boatClass, Speed windSpeed, Bearing angleToTheWind,
|
||||
boolean useLinearRegression)
|
||||
public SpeedWithConfidence<Void> estimateBoatSpeed(BoatClass boatClass, Speed windSpeed, Bearing trueWindAngle)
|
||||
throws NotEnoughDataHasBeenAddedException {
|
||||
return incrementalRegressionProcessor.estimateBoatSpeed(boatClass, windSpeed, angleToTheWind, useLinearRegression).getA();
|
||||
return incrementalRegressionProcessor.estimateBoatSpeed(boatClass, windSpeed, trueWindAngle).getSpeedWithConfidence();
|
||||
}
|
||||
|
||||
public Set<SpeedWithBearingWithConfidence<Void>> estimateTrueWindSpeedAndAngleCandidates(BoatClass boatClass,
|
||||
Speed speedOverGround, LegType legType, Tack tack) {
|
||||
return incrementalRegressionProcessor.estimateTrueWindSpeedAndAngleCandidates(boatClass, speedOverGround, legType, tack);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public PolarSheetsData createFullSheetForBoatClass(BoatClass boatClass) {
|
||||
Double[] defaultWindSpeeds = backendPolarSheetGenerationSettings.getWindStepping().getRawStepping();
|
||||
double[] defaultWindSpeeds = backendPolarSheetGenerationSettings.getWindSpeedStepping().getRawStepping();
|
||||
Number[][] averagedPolarDataByWindSpeed = new Number[defaultWindSpeeds.length][360];
|
||||
|
||||
Map<Integer, Integer[]> dataCountPerAngleForWindspeed = new HashMap<>();
|
||||
@@ -140,26 +143,25 @@ public class PolarDataMiner {
|
||||
|
||||
for (int windIndex = 0; windIndex < defaultWindSpeeds.length; windIndex++) {
|
||||
Double windSpeed = defaultWindSpeeds[windIndex];
|
||||
|
||||
Integer[] perAngle = new Integer[360];
|
||||
Map<Integer, PolarSheetsHistogramData> perWindSpeed = new HashMap<>();
|
||||
for (int angle = 0; angle < 360; angle++) {
|
||||
Pair<SpeedWithConfidence<Void>, Integer> speedWithConfidenceAndDataCount;
|
||||
SpeedWithConfidenceAndDataCount speedWithConfidenceAndDataCount;
|
||||
try {
|
||||
int convertedAngle = convertAngleIfNecessary(angle);
|
||||
speedWithConfidenceAndDataCount = incrementalRegressionProcessor.estimateBoatSpeed(boatClass,
|
||||
new KnotSpeedImpl(windSpeed), new DegreeBearingImpl(convertedAngle), true);
|
||||
new KnotSpeedImpl(windSpeed), new DegreeBearingImpl(convertedAngle));
|
||||
} catch (NotEnoughDataHasBeenAddedException e) {
|
||||
// No data so put in a 0 speed with 0 confidence
|
||||
speedWithConfidenceAndDataCount = new Pair<SpeedWithConfidence<Void>, Integer>(
|
||||
speedWithConfidenceAndDataCount = new SpeedWithConfidenceAndDataCount(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(0), 0, null), 0);
|
||||
}
|
||||
|
||||
averagedPolarDataByWindSpeed[windIndex][angle] = speedWithConfidenceAndDataCount.getA().getObject().getKnots();
|
||||
int dataCount = speedWithConfidenceAndDataCount.getB();
|
||||
averagedPolarDataByWindSpeed[windIndex][angle] = speedWithConfidenceAndDataCount.getSpeedWithConfidence().getObject().getKnots();
|
||||
int dataCount = speedWithConfidenceAndDataCount.getDataCount();
|
||||
|
||||
totalDataCount = totalDataCount + dataCount;
|
||||
//FIXME hard coded
|
||||
// FIXME hard coded
|
||||
double coefficiantOfVariation = 0.8;
|
||||
double confidenceMeasure = 0.5;
|
||||
|
||||
@@ -170,15 +172,11 @@ public class PolarDataMiner {
|
||||
histogramDataMap.put(windIndex, perWindSpeed);
|
||||
dataCountPerAngleForWindspeed.put(windIndex, perAngle);
|
||||
}
|
||||
|
||||
|
||||
PolarSheetsData data = new PolarSheetsDataImpl(averagedPolarDataByWindSpeed, totalDataCount,
|
||||
dataCountPerAngleForWindspeed, backendPolarSheetGenerationSettings.getWindStepping(), histogramDataMap);
|
||||
dataCountPerAngleForWindspeed, backendPolarSheetGenerationSettings.getWindSpeedStepping(), histogramDataMap);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private int convertAngleIfNecessary(int angle) {
|
||||
int convertedAngle = angle;
|
||||
if (angle > 180) {
|
||||
@@ -187,8 +185,6 @@ public class PolarDataMiner {
|
||||
return convertedAngle;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private PolarSheetsHistogramDataImpl createEmptyHistogramData(Integer[] perAngle, int angle, int dataCount,
|
||||
double coefficiantOfVariation, double confidenceMeasure) {
|
||||
perAngle[angle] = dataCount;
|
||||
@@ -197,38 +193,36 @@ public class PolarDataMiner {
|
||||
Map<String, Integer[]> yValuesByGaugeIds = new HashMap<>();
|
||||
Map<String, Integer[]> yValuesByDay = new HashMap<>();
|
||||
Map<String, Integer[]> yValuesByDayAndGaugeId = new HashMap<>();
|
||||
|
||||
|
||||
PolarSheetsHistogramDataImpl polarSheetsHistogramDataImpl = new PolarSheetsHistogramDataImpl(angle, xValues, yValues, yValuesByGaugeIds,
|
||||
yValuesByDay, yValuesByDayAndGaugeId, dataCount, coefficiantOfVariation);
|
||||
|
||||
polarSheetsHistogramDataImpl.setConfidenceMeasure(confidenceMeasure);
|
||||
return polarSheetsHistogramDataImpl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Set<BoatClass> getAvailableBoatClasses() {
|
||||
return incrementalRegressionProcessor.getAvailableBoatClasses();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Integer[] getDataCountsForWindSpeed(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive) {
|
||||
Integer[] dataCounts = new Integer[360];
|
||||
public int[] getDataCountsForWindSpeed(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive) {
|
||||
int[] dataCounts = new int[360];
|
||||
for (int angle = 0; angle < 360; angle++) {
|
||||
if (angle >= startAngleInclusive && angle < endAngleExclusive) {
|
||||
try {
|
||||
dataCounts[angle] = incrementalRegressionProcessor.estimateBoatSpeed(boatClass, windSpeed,
|
||||
new DegreeBearingImpl(convertAngleIfNecessary(angle)), true).getB();
|
||||
new DegreeBearingImpl(convertAngleIfNecessary(angle))).getDataCount();
|
||||
} catch (NotEnoughDataHasBeenAddedException e) {
|
||||
dataCounts[angle] = 0;
|
||||
}
|
||||
} else {
|
||||
dataCounts[angle] = null;
|
||||
dataCounts[angle] = -1;
|
||||
}
|
||||
}
|
||||
return dataCounts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageSpeedAndCourseOverGround(BoatClass boatClass,
|
||||
Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException {
|
||||
return incrementalRegressionProcessor.getAverageSpeedAndCourseOverGround(boatClass, windSpeed, legType, tack);
|
||||
}
|
||||
}
|
||||
+12
-8
@@ -2,24 +2,24 @@ package com.sap.sailing.polars.mining;
|
||||
|
||||
import com.sap.sailing.domain.common.Bearing;
|
||||
|
||||
public class RoundedAngleToTheWind {
|
||||
public class RoundedTrueWindAngle {
|
||||
|
||||
private final int angle;
|
||||
private final int angleDeg;
|
||||
|
||||
public RoundedAngleToTheWind(Bearing angleToTheWind) {
|
||||
this.angle = (int) Math.round(angleToTheWind.getDegrees());
|
||||
public RoundedTrueWindAngle(Bearing angleToTheWind) {
|
||||
this.angleDeg = (int) Math.round(angleToTheWind.getDegrees());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "" + angle;
|
||||
return "" + getAngleDeg();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + angle;
|
||||
result = prime * result + getAngleDeg();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -31,10 +31,14 @@ public class RoundedAngleToTheWind {
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
RoundedAngleToTheWind other = (RoundedAngleToTheWind) obj;
|
||||
if (angle != other.angle)
|
||||
RoundedTrueWindAngle other = (RoundedTrueWindAngle) obj;
|
||||
if (getAngleDeg() != other.getAngleDeg())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getAngleDeg() {
|
||||
return angleDeg;
|
||||
}
|
||||
|
||||
}
|
||||
+3
-3
@@ -1,15 +1,15 @@
|
||||
package com.sap.sailing.polars.mining;
|
||||
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.impl.WindSteppingWithMaxDistance;
|
||||
import com.sap.sailing.domain.common.impl.WindSpeedSteppingWithMaxDistance;
|
||||
import com.sap.sailing.polars.clusters.SpeedClusterGroup;
|
||||
import com.sap.sse.datamining.data.ClusterGroup;
|
||||
|
||||
public class SpeedClusterGroupFromWindSteppingCreator {
|
||||
|
||||
public static ClusterGroup<Speed> createSpeedClusterGroupFrom(WindSteppingWithMaxDistance windStepping) {
|
||||
public static ClusterGroup<Speed> createSpeedClusterGroupFrom(WindSpeedSteppingWithMaxDistance windStepping) {
|
||||
double maxDistance = windStepping.getMaxDistance();
|
||||
Double[] rawIntegerStepping = windStepping.getRawStepping();
|
||||
double[] rawIntegerStepping = windStepping.getRawStepping();
|
||||
double[] rawDoubleLevelMids = new double[rawIntegerStepping.length];
|
||||
for (int i = 0; i < rawIntegerStepping.length; i++) {
|
||||
rawDoubleLevelMids[i] = rawIntegerStepping[i];
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package com.sap.sailing.polars.mining;
|
||||
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.WindStepping;
|
||||
import com.sap.sailing.domain.common.WindSpeedStepping;
|
||||
|
||||
public class WindSpeedLevel {
|
||||
|
||||
private final int level;
|
||||
private final WindStepping stepping;
|
||||
private final WindSpeedStepping stepping;
|
||||
|
||||
public WindSpeedLevel(Speed windSpeed, WindStepping stepping) {
|
||||
public WindSpeedLevel(Speed windSpeed, WindSpeedStepping stepping) {
|
||||
this.level = stepping.getLevelIndexForValue(windSpeed.getKnots());
|
||||
this.stepping = stepping;
|
||||
}
|
||||
|
||||
+31
-26
@@ -1,31 +1,36 @@
|
||||
package com.sap.sailing.polars.regression;
|
||||
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
|
||||
import com.sap.sailing.polars.mining.IncrementalRegressionProcessor;
|
||||
import com.sap.sailing.polars.regression.impl.IncrementalLeastSquaresProcessor;
|
||||
import com.sap.sailing.util.impl.LockUtil;
|
||||
import com.sap.sailing.util.impl.NamedReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* Combines two linear regression processors. One with the angle to the wind as the x axis and the other with the wind
|
||||
* speed on the x axis. Both y axis represent the boat speed.
|
||||
* Supplies incremental arithmetic mean for confidence and speed.
|
||||
* <p>
|
||||
*
|
||||
* {@link #estimateSpeed(double, double)} returns the average of the estimation of both regressions.
|
||||
* {@link #estimateSpeed(double, double)} returns the average of the speed.
|
||||
* <p>
|
||||
*
|
||||
* Note that this class should only be used for a small interval on a polar sheet. The
|
||||
* Note that this class should only be used for a small wind interval on a polar sheet. The
|
||||
* {@link IncrementalRegressionProcessor} is one example for that. It has one instance of the {@link BoatSpeedEstimator}
|
||||
* for each wind speed level and rounded angle combination.
|
||||
* for each wind boatclass, speed level and rounded angle combination.
|
||||
*
|
||||
* @author Frederik Petersen (D054528)
|
||||
*
|
||||
*/
|
||||
public class BoatSpeedEstimator {
|
||||
public class BoatSpeedEstimator implements Serializable {
|
||||
|
||||
private IncrementalLinearRegressionProcessor angleRegression = new IncrementalLeastSquaresProcessor();
|
||||
private static final long serialVersionUID = -254184914347332658L;
|
||||
|
||||
private IncrementalLinearRegressionProcessor windSpeedRegression = new IncrementalLeastSquaresProcessor();
|
||||
|
||||
private NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock(getClass().getName(), true);
|
||||
private double speedSumInKnots = 0;
|
||||
|
||||
private transient NamedReentrantReadWriteLock lock = createLock();
|
||||
|
||||
private double confidenceSum = 0;
|
||||
|
||||
private int dataCount = 0;
|
||||
@@ -33,30 +38,22 @@ public class BoatSpeedEstimator {
|
||||
/**
|
||||
* @param useLinearRegression if false mean of wind interval is used, otherwise linear regression
|
||||
*/
|
||||
public double estimateSpeed(double windSpeed, double angleToTheWind, boolean useLinearRegression) throws NotEnoughDataHasBeenAddedException {
|
||||
public Speed estimateSpeed(double windSpeed, double angleToTheWind) throws NotEnoughDataHasBeenAddedException {
|
||||
LockUtil.lockForRead(lock);
|
||||
double result;
|
||||
if (dataCount < 1) {
|
||||
throw new NotEnoughDataHasBeenAddedException();
|
||||
}
|
||||
try {
|
||||
if (useLinearRegression) {
|
||||
double angleEstimated = angleRegression.getEstimatedY(angleToTheWind);
|
||||
double windSpeedEstimated = windSpeedRegression.getEstimatedY(windSpeed);
|
||||
|
||||
result = (angleEstimated + windSpeedEstimated) / 2.0;
|
||||
} else {
|
||||
//Return average for that wind interval
|
||||
result = windSpeedRegression.getMeanOfY();
|
||||
}
|
||||
return new KnotSpeedImpl(speedSumInKnots / dataCount);
|
||||
} finally {
|
||||
LockUtil.unlockAfterRead(lock);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void addData(double windSpeed, double angleToTheWind, double boatSpeed, double confidence) {
|
||||
public void addData(Speed boatSpeed, double confidence) {
|
||||
LockUtil.lockForWrite(lock);
|
||||
try {
|
||||
angleRegression.addMeasuredPoint(angleToTheWind, boatSpeed);
|
||||
windSpeedRegression.addMeasuredPoint(windSpeed, boatSpeed);
|
||||
speedSumInKnots = speedSumInKnots + boatSpeed.getKnots();
|
||||
dataCount++;
|
||||
confidenceSum = confidenceSum + confidence;
|
||||
} finally {
|
||||
@@ -71,5 +68,13 @@ public class BoatSpeedEstimator {
|
||||
public double getConfidence() {
|
||||
return confidenceSum / dataCount;
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream ois) {
|
||||
lock = createLock();
|
||||
}
|
||||
|
||||
private NamedReentrantReadWriteLock createLock() {
|
||||
return new NamedReentrantReadWriteLock(getClass().getName(), true);
|
||||
}
|
||||
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
package com.sap.sailing.polars.regression.impl;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
|
||||
import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
|
||||
public class WindSpeedAndAngleEstimator {
|
||||
|
||||
private static final double DEFAULT_MAX_DISTANCE_TO_SAMPLING_POINT = 0.5;
|
||||
private final List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed;
|
||||
private final double maxDistanceToSamplingPointFunctionInKnots;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param averageBoatSpeedAndCourseForWindSpeed
|
||||
* List of sampling points. Needs to be sorted by windspeed (A of the Pair object) low to high
|
||||
*/
|
||||
public WindSpeedAndAngleEstimator(
|
||||
List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed) {
|
||||
this(averageBoatSpeedAndCourseForWindSpeed, DEFAULT_MAX_DISTANCE_TO_SAMPLING_POINT);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param averageBoatSpeedAndCourseForWindSpeed
|
||||
* List of sampling points. Needs to be sorted by windspeed (A of the Pair object) low to high
|
||||
* @param maxDistanceToSamplingPointFunctionInKnots
|
||||
* If there is only a sampling point on one side, what's the maximal distance to consider valid when
|
||||
* comparing?
|
||||
*/
|
||||
public WindSpeedAndAngleEstimator(
|
||||
List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed,
|
||||
double maxDistanceToSamplingPointFunctionInKnots) {
|
||||
this.averageBoatSpeedAndCourseForWindSpeed = averageBoatSpeedAndCourseForWindSpeed;
|
||||
this.maxDistanceToSamplingPointFunctionInKnots = maxDistanceToSamplingPointFunctionInKnots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks at all entries in {@link #dataCountAndAngleSumMap} and determines the wind speed and true wind angle at
|
||||
* which the boat has most likely been sailing to achieve the <code>boatSpeed</code> provided.
|
||||
*
|
||||
* BE ADVISED: The result candidate with the highest confidence, which is returned here, is not necessarily the
|
||||
* candidate which is the most likely to be correct. It really depends on the quality and quantity of gathered polar
|
||||
* data.
|
||||
*
|
||||
* Use {@link #getAverageTrueWindSpeedAndAngleCandidates(Speed)} to get all candidates.
|
||||
*
|
||||
* @return null if none was found, otherwise candidate with highest confidence, found in the list returned by
|
||||
* {@link #getAverageTrueWindSpeedAndAngleCandidates(Speed)}
|
||||
*/
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageTrueWindSpeedAndAngle(Speed boatSpeed) {
|
||||
Set<SpeedWithBearingWithConfidence<Void>> resultCandidates = getAverageTrueWindSpeedAndAngleCandidates(boatSpeed);
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> resultCandidateWithHighestConfidence = null;
|
||||
for (SpeedWithBearingWithConfidence<Void> resultCandidate : resultCandidates) {
|
||||
if (resultCandidateWithHighestConfidence == null
|
||||
|| resultCandidate.getConfidence() > resultCandidateWithHighestConfidence.getConfidence()) {
|
||||
resultCandidateWithHighestConfidence = resultCandidate;
|
||||
}
|
||||
}
|
||||
return resultCandidateWithHighestConfidence;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uses the constructor-supplied sampling points to find wind and course candidates for the supplied boatSpeed.
|
||||
*
|
||||
* @return empty set if the underlying data was not sufficient to estimate wind. Otherwise a set of candidates
|
||||
* with windspeed course of the boat relative to the wind and a confidence which was derived from the
|
||||
* confidences of underlying fixes, the amount of underlying fixes and the distance between the input
|
||||
* boatSpeed and the sampling points supplied by the gathered polar data.
|
||||
*/
|
||||
public Set<SpeedWithBearingWithConfidence<Void>> getAverageTrueWindSpeedAndAngleCandidates(Speed boatSpeed) {
|
||||
double requestedBoatSpeedInKnots = boatSpeed.getKnots();
|
||||
Pair<Speed, SpeedWithBearingWithConfidence<Void>> last = null;
|
||||
Set<SpeedWithBearingWithConfidence<Void>> resultCandidates = new HashSet<>();
|
||||
Set<SpeedWithBearingWithConfidence<Void>> resultCandidatesWithDistance = new HashSet<>();
|
||||
for (Pair<Speed, SpeedWithBearingWithConfidence<Void>> averageSpeedForWindSpeed : averageBoatSpeedAndCourseForWindSpeed) {
|
||||
Speed windSpeed = averageSpeedForWindSpeed.getA();
|
||||
SpeedWithBearingWithConfidence<Void> averageBoatSpeedWithCourse = averageSpeedForWindSpeed.getB();
|
||||
double currentBoatSpeedInKnots = averageBoatSpeedWithCourse.getObject().getKnots();
|
||||
if (last != null) {
|
||||
double lastBoatSpeedInKnots = last.getB().getObject().getKnots();
|
||||
|
||||
if (lastBoatSpeedInKnots <= requestedBoatSpeedInKnots
|
||||
&& currentBoatSpeedInKnots >= requestedBoatSpeedInKnots) {
|
||||
SpeedWithBearingWithConfidenceImpl<Void> resultCandidate = createWeightedResultCandidateForLowerLast(
|
||||
requestedBoatSpeedInKnots, last, windSpeed, averageBoatSpeedWithCourse,
|
||||
lastBoatSpeedInKnots, currentBoatSpeedInKnots);
|
||||
resultCandidates.add(resultCandidate);
|
||||
} else if (lastBoatSpeedInKnots >= requestedBoatSpeedInKnots
|
||||
&& currentBoatSpeedInKnots <= requestedBoatSpeedInKnots) {
|
||||
SpeedWithBearingWithConfidenceImpl<Void> resultCandidate = createWeightedResultCandidateForHigherLast(
|
||||
requestedBoatSpeedInKnots, last, windSpeed, averageBoatSpeedWithCourse,
|
||||
lastBoatSpeedInKnots, currentBoatSpeedInKnots);
|
||||
resultCandidates.add(resultCandidate);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* This will be run when there was no candidate found in between the two sampling points (or we are at the
|
||||
* beginning) and add a Candidate if the requested point was maxDistanceToSamplingPointFunctionInKnots or
|
||||
* less away from the last regarded sampling point. The candidate (if valid) is then added to the
|
||||
* resultCandidatesWithDistance set. Confidences are halved and the set is only used if the standard result
|
||||
* set is empty in the end
|
||||
*/
|
||||
if (resultCandidates.size() == 0 && last != null) {
|
||||
addResultCandidateIfDistanceValid(requestedBoatSpeedInKnots, resultCandidatesWithDistance, last.getA(),
|
||||
last.getB());
|
||||
}
|
||||
last = averageSpeedForWindSpeed;
|
||||
}
|
||||
// check last sampling point for distance match and return withDistance set if standard set is empty
|
||||
if (last != null && resultCandidates.size() == 0) {
|
||||
addResultCandidateIfDistanceValid(requestedBoatSpeedInKnots, resultCandidatesWithDistance, last.getA(),
|
||||
last.getB());
|
||||
resultCandidates = resultCandidatesWithDistance;
|
||||
}
|
||||
return resultCandidates;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Confidence will be halved in situations where there the requested speed does not lie between any sampling points.
|
||||
* The speed and angle values are equal to the sampling point values.
|
||||
*/
|
||||
private void addResultCandidateIfDistanceValid(double requestedBoatSpeedInKnots,
|
||||
Set<SpeedWithBearingWithConfidence<Void>> resultCandidates, Speed windSpeed,
|
||||
SpeedWithBearingWithConfidence<Void> averageBoatSpeedWithCourse) {
|
||||
if (Math.abs(averageBoatSpeedWithCourse.getObject().getKnots() - requestedBoatSpeedInKnots) <= maxDistanceToSamplingPointFunctionInKnots) {
|
||||
SpeedWithBearingWithConfidence<Void> resultCandidate = new SpeedWithBearingWithConfidenceImpl<Void>(
|
||||
new KnotSpeedWithBearingImpl(windSpeed.getKnots(), new DegreeBearingImpl(averageBoatSpeedWithCourse
|
||||
.getObject().getBearing().getDegrees())), averageBoatSpeedWithCourse.getConfidence() * 0.5,
|
||||
null);
|
||||
resultCandidates.add(resultCandidate);
|
||||
}
|
||||
}
|
||||
|
||||
private SpeedWithBearingWithConfidenceImpl<Void> createWeightedResultCandidateForHigherLast(
|
||||
double requestedBoatSpeedInKnots, Pair<Speed, SpeedWithBearingWithConfidence<Void>> last, Speed windSpeed,
|
||||
SpeedWithBearingWithConfidence<Void> averageBoatSpeedWithCourse, double lastBoatSpeedInKnots,
|
||||
double currentBoatSpeedInKnots) {
|
||||
double differenceBetweenBothSpeedsInKnots = lastBoatSpeedInKnots - currentBoatSpeedInKnots;
|
||||
double ratio;
|
||||
if (differenceBetweenBothSpeedsInKnots < 0.00001) {
|
||||
//Both sampling points have the same ratio. Use confidence to determine the ratio
|
||||
ratio = last.getB().getConfidence() / (averageBoatSpeedWithCourse.getConfidence() + last.getB().getConfidence());
|
||||
} else {
|
||||
double differenceBetweenRequestedAndLower = requestedBoatSpeedInKnots - currentBoatSpeedInKnots;
|
||||
ratio = differenceBetweenRequestedAndLower / differenceBetweenBothSpeedsInKnots;
|
||||
}
|
||||
double weightedAverageWindSpeedInKnots = (( 1 - ratio) * windSpeed.getKnots())
|
||||
+ (ratio * last.getA().getKnots());
|
||||
double currentCourseInDeg = averageBoatSpeedWithCourse.getObject().getBearing()
|
||||
.getDegrees();
|
||||
double lastCourseInDeg = last.getB().getObject().getBearing().getDegrees();
|
||||
double weightedAverageAngleInDeg = (( 1 - ratio) * currentCourseInDeg) + (ratio * lastCourseInDeg);
|
||||
double weightedAverageConfidence = (( 1 - ratio) * averageBoatSpeedWithCourse.getConfidence())
|
||||
+ (ratio * last.getB().getConfidence());
|
||||
SpeedWithBearingWithConfidenceImpl<Void> resultCandidate = new SpeedWithBearingWithConfidenceImpl<Void>(
|
||||
new KnotSpeedWithBearingImpl(weightedAverageWindSpeedInKnots, new DegreeBearingImpl(
|
||||
weightedAverageAngleInDeg)), weightedAverageConfidence, null);
|
||||
return resultCandidate;
|
||||
}
|
||||
|
||||
private SpeedWithBearingWithConfidenceImpl<Void> createWeightedResultCandidateForLowerLast(
|
||||
double requestedBoatSpeedInKnots, Pair<Speed, SpeedWithBearingWithConfidence<Void>> last, Speed windSpeed,
|
||||
SpeedWithBearingWithConfidence<Void> averageBoatSpeedWithCourse, double lastBoatSpeedInKnots,
|
||||
double currentBoatSpeedInKnots) {
|
||||
double differenceBetweenBothSpeedsInKnots = currentBoatSpeedInKnots - lastBoatSpeedInKnots;
|
||||
double ratio;
|
||||
if (differenceBetweenBothSpeedsInKnots < 0.00001) {
|
||||
// Both sampling points have the same ratio. Use confidence to determine the ratio
|
||||
ratio = averageBoatSpeedWithCourse.getConfidence()
|
||||
/ (averageBoatSpeedWithCourse.getConfidence() + last.getB().getConfidence());
|
||||
} else {
|
||||
double differenceBetweenRequestedAndLower = requestedBoatSpeedInKnots - lastBoatSpeedInKnots;
|
||||
ratio = differenceBetweenRequestedAndLower / differenceBetweenBothSpeedsInKnots;
|
||||
}
|
||||
double weightedAverageWindSpeedInKnots = (ratio * windSpeed.getKnots())
|
||||
+ ((1 - ratio) * last.getA().getKnots());
|
||||
double currentCourseInDeg = averageBoatSpeedWithCourse.getObject().getBearing()
|
||||
.getDegrees();
|
||||
double lastCourseInDeg = last.getB().getObject().getBearing().getDegrees();
|
||||
double weightedAverageAngleInDeg = (ratio * currentCourseInDeg) + ((1 - ratio) * lastCourseInDeg);
|
||||
double weightedAverageConfidence = (ratio * averageBoatSpeedWithCourse.getConfidence())
|
||||
+ ((1 - ratio) * last.getB().getConfidence());
|
||||
SpeedWithBearingWithConfidenceImpl<Void> resultCandidate = new SpeedWithBearingWithConfidenceImpl<Void>(
|
||||
new KnotSpeedWithBearingImpl(weightedAverageWindSpeedInKnots, new DegreeBearingImpl(
|
||||
weightedAverageAngleInDeg)), weightedAverageConfidence, null);
|
||||
return resultCandidate;
|
||||
}
|
||||
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package com.sap.sailing.polars.windestimation;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.domain.tracking.Maneuver;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.impl.WindTrackImpl;
|
||||
import com.sap.sailing.polars.PolarDataService;
|
||||
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
|
||||
/**
|
||||
* Implements a wind estimation based on maneuver classifications.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public class ManeuverBasedWindEstimationTrackImpl extends WindTrackImpl {
|
||||
private static final long serialVersionUID = -764156498143531475L;
|
||||
|
||||
/**
|
||||
* Using a fairly low base confidence for this estimation wind track. It shall only serve as a
|
||||
* default and should be superseded by other wind sources easily.
|
||||
*/
|
||||
private static final double DEFAULT_BASE_CONFIDENCE = 0.01;
|
||||
|
||||
private final PolarDataService polarService;
|
||||
|
||||
private final TrackedRace trackedRace;
|
||||
|
||||
public ManeuverBasedWindEstimationTrackImpl(PolarDataService polarService, TrackedRace trackedRace, long millisecondsOverWhichToAverage)
|
||||
throws NotEnoughDataHasBeenAddedException {
|
||||
super(millisecondsOverWhichToAverage, DEFAULT_BASE_CONFIDENCE, /* useSpeed */ false,
|
||||
/* nameForReadWriteLock */ ManeuverBasedWindEstimationTrackImpl.class.getName());
|
||||
this.polarService = polarService;
|
||||
this.trackedRace = trackedRace;
|
||||
analyzeRace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all the race's maneuvers and tries to find the tacks and jibes. For each such maneuver identified, a wind fix
|
||||
* will be created based on the average COG into and out of the maneuver.
|
||||
*/
|
||||
private void analyzeRace() throws NotEnoughDataHasBeenAddedException {
|
||||
final Map<Maneuver, BoatClass> maneuvers = getAllManeuvers();
|
||||
for (final Entry<Maneuver, BoatClass> maneuverAndBoatClass : maneuvers.entrySet()) {
|
||||
// Now for each maneuver's starting speed (speed into the maneuver) get the approximated
|
||||
// wind speed and direction assuming average upwind / downwind performance based on the polar service.
|
||||
// TODO continue here, asking the polarService for the wind speed based on boat speed, leg type and tack
|
||||
for (LegType legType : new LegType[] { LegType.UPWIND, LegType.DOWNWIND }) {
|
||||
for (Tack tack : new Tack[] { Tack.PORT, Tack.STARBOARD }) {
|
||||
polarService.getAverageTrueWindSpeedAndAngleCandidates(maneuverAndBoatClass.getValue(),
|
||||
maneuverAndBoatClass.getKey().getSpeedWithBearingBefore(),
|
||||
legType, tack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Maneuver, BoatClass> getAllManeuvers() {
|
||||
Map<Maneuver, BoatClass> maneuvers = new HashMap<>();
|
||||
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
|
||||
final BoatClass boatClass = competitor.getBoat().getBoatClass();
|
||||
final TimePoint from = trackedRace.getStartOfRace() == null ? trackedRace.getStartOfTracking()
|
||||
: trackedRace.getStartOfRace();
|
||||
final TimePoint to = trackedRace.getEndOfRace() == null ? trackedRace.getEndOfTracking() == null ?
|
||||
MillisecondsTimePoint.now() : trackedRace.getEndOfTracking() : trackedRace.getEndOfRace();
|
||||
for (Maneuver maneuver : trackedRace.getManeuvers(competitor, from, to, /* waitForLatest */ false)) {
|
||||
maneuvers.put(maneuver, boatClass);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableMap(maneuvers);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user