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
+8
-3
@@ -13,8 +13,8 @@ public interface BearingChangeAnalyzer {
|
||||
BearingChangeAnalyzer INSTANCE = new BearingChangeAnalyzerImpl();
|
||||
|
||||
/**
|
||||
* Determines whether during a maneuver a certain course was reached and crossed during a maneuver. When used with
|
||||
* the wind direction, this can tell whether a boat tacked or jibed.
|
||||
* Determines whether during a maneuver a certain course was reached and crossed. When used with the wind direction,
|
||||
* this can tell whether a boat tacked or jibed.
|
||||
*
|
||||
* @param totalCourseChangeInDegrees
|
||||
* tells how far and to which direction the course was changed. Positive values mean a change to
|
||||
@@ -25,7 +25,12 @@ public interface BearingChangeAnalyzer {
|
||||
* positive then the competitor passed the 000deg course, whereas had the
|
||||
* <code>totalCourseChangeInDegrees</code> been negative and less than 370deg then the competitor would
|
||||
* have passed the 180deg course but not the 000deg course.
|
||||
* @return the number of times that the course identified by
|
||||
* <code>wasThisCourseReachedAndCrossedDuringManeuver</code> was crossed; <code>0</code> means it was not
|
||||
* crossed; any positive number tells how many times it was crossed, otherwise. Numbers greater than
|
||||
* <code>1</code> require <code>totalCourseChangeInDegrees</code> to be greater than 360 degrees.
|
||||
*
|
||||
*/
|
||||
boolean didPass(Bearing courseBeforeManeuver, double totalCourseChangeInDegrees,
|
||||
int didPass(Bearing courseBeforeManeuver, double totalCourseChangeInDegrees,
|
||||
Bearing courseAfterManeuver, Bearing wasThisCourseReachedAndCrossedDuringManeuver);
|
||||
}
|
||||
+4
-3
@@ -18,6 +18,7 @@ public enum BoatClassMasterdata {
|
||||
DRAGON_INT ("Dragon Int.", true, 8.89, 1.96, BoatHullType.MONOHULL, true, "Drachen", "Dragon"),
|
||||
EXTREME_40 ("Extreme 40", false, 12.2, 6.60, BoatHullType.CATAMARAN, true, "Extreme-40", "Extreme40", "ESS40"),
|
||||
D_35 ("D35", false, 10.81, 6.89, BoatHullType.CATAMARAN, false),
|
||||
ELLIOTT_6M ("Elliott 6m", true, 6.0, 2.35, BoatHullType.MONOHULL, true, "Elliott6m"),
|
||||
EUROPE_INT ("Europe Int.", true, 3.35, 1.35, BoatHullType.MONOHULL, false, "Europe"),
|
||||
F_18 ("Formula 18", true, 6.85, 2.25, BoatHullType.CATAMARAN, true, "F18", "F-18"),
|
||||
FARR_30 ("Farr 30", true, 9.42, 3.08, BoatHullType.MONOHULL, true, "F30", "F-30", "Farr-30"),
|
||||
@@ -45,7 +46,7 @@ public enum BoatClassMasterdata {
|
||||
PLATU_25 ("Platu 25", true, 7.53, 2.62, BoatHullType.MONOHULL, true, "Platu", "Platu-25", "PLA", "B25"),
|
||||
RS_X ("RS:X", true, 2.86, 0.93, BoatHullType.SURFERBOARD, false, "RS-X", "RSX", "RS:X"),
|
||||
SONAR ("Sonar", true, 7.01, 2.39, BoatHullType.MONOHULL, true),
|
||||
STAR ("Star", true, 6.92, 1.74, BoatHullType.MONOHULL, false),
|
||||
STAR ("Star", true, 6.92, 1.74, BoatHullType.MONOHULL, false, "STR"),
|
||||
STREAMLINE ("Streamline", true, 7.15, 2.55, BoatHullType.MONOHULL, true),
|
||||
SWAN_45 ("Swan 45", true, 13.83, 3.91, BoatHullType.MONOHULL, true, "Swan", "Swan-45"),
|
||||
TORNADO ("Tornado Catamaran", true, 6.10, 3.02, BoatHullType.CATAMARAN, true, "Tornado", "Tornado Cat"),
|
||||
@@ -103,8 +104,8 @@ public enum BoatClassMasterdata {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String unifyBoatClassName(String boatClassName) {
|
||||
return boatClassName.toUpperCase().replaceAll("\\s+","");
|
||||
public static String unifyBoatClassName(String boatClassName) {
|
||||
return boatClassName == null ? null : boatClassName.toUpperCase().replaceAll("\\s+","");
|
||||
}
|
||||
|
||||
public Distance getHullLength() {
|
||||
|
||||
+3
-3
@@ -2,9 +2,9 @@ package com.sap.sailing.domain.common;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.sap.sailing.domain.common.impl.WindSteppingWithMaxDistance;
|
||||
import com.sap.sailing.domain.common.impl.WindSpeedSteppingWithMaxDistance;
|
||||
|
||||
public interface PolarSheetGenerationSettings extends Serializable{
|
||||
public interface PolarSheetGenerationSettings extends Serializable {
|
||||
|
||||
Integer getMinimumDataCountPerGraph();
|
||||
|
||||
@@ -26,7 +26,7 @@ public interface PolarSheetGenerationSettings extends Serializable{
|
||||
|
||||
boolean useOnlyEstimatedForWindDirection();
|
||||
|
||||
WindSteppingWithMaxDistance getWindStepping();
|
||||
WindSpeedSteppingWithMaxDistance getWindSpeedStepping();
|
||||
|
||||
boolean splitByWindgauges();
|
||||
|
||||
|
||||
+8
-2
@@ -11,8 +11,14 @@ public interface PolarSheetsData extends Serializable {
|
||||
|
||||
Integer[] getDataCountPerAngleForWindspeed(int windIndex);
|
||||
|
||||
WindStepping getStepping();
|
||||
WindSpeedStepping getStepping();
|
||||
|
||||
Map<Integer, Map<Integer,PolarSheetsHistogramData>> getHistogramDataMap();
|
||||
/**
|
||||
* @return key is a zero-based index into the wind speed range list that uses the {@link #getStepping() stepping
|
||||
* configured}; value is a map whose keys are true wind angles in degrees in the range 0 <= angle <
|
||||
* 360 and whose values are all the data points collected for this
|
||||
*
|
||||
*/
|
||||
Map<Integer, Map<Integer, PolarSheetsHistogramData>> getHistogramDataMap();
|
||||
|
||||
}
|
||||
+3
-9
@@ -6,15 +6,9 @@ import java.util.List;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
|
||||
public interface PolarSheetsXYDiagramData extends Serializable {
|
||||
|
||||
List<Pair<Double, Double>> getPointsForAverageSpeedMovingAverage(Tack tack, LegType legType);
|
||||
|
||||
List<Pair<Double, Double>> getPointsForUpwindStarboardAverageSpeed();
|
||||
|
||||
List<Pair<Double, Double>> getPointsForUpwindStarboardAverageAngle();
|
||||
|
||||
List<Pair<Double, Double>> getPointsForUpwindStarboardAverageAngleMovingAverage();
|
||||
|
||||
List<Pair<Double, Double>> getPointsForUpwindStarboardAverageSpeedMovingAverage();
|
||||
|
||||
List<Pair<Double, Double>> getPointsForUpwindStarboardAverageConfidence();
|
||||
List<Pair<Double, Double>> getPointsForAverageConfidence(Tack tack, LegType legType);
|
||||
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import com.sap.sse.datamining.shared.Unit;
|
||||
import com.sap.sse.datamining.shared.annotations.Statistic;
|
||||
|
||||
/**
|
||||
* A speed, convertable in various units of measure. Can be negative.
|
||||
* A speed, convertible in various units of measure. Can be negative.
|
||||
*
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
|
||||
+2
-2
@@ -2,13 +2,13 @@ package com.sap.sailing.domain.common;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public interface WindStepping extends Serializable{
|
||||
public interface WindSpeedStepping extends Serializable {
|
||||
|
||||
public abstract int getLevelIndexForValue(double speed);
|
||||
|
||||
public abstract Double getSteppedValueForValue(double speed);
|
||||
|
||||
Double[] getRawStepping();
|
||||
double[] getRawStepping();
|
||||
|
||||
public abstract int getLevelIndexFloorForValue(double speed);
|
||||
|
||||
+4
-4
@@ -6,14 +6,14 @@ import com.sap.sailing.domain.common.BearingChangeAnalyzer;
|
||||
public class BearingChangeAnalyzerImpl implements BearingChangeAnalyzer {
|
||||
|
||||
@Override
|
||||
public boolean didPass(Bearing courseBeforeManeuver, double totalCourseChangeInDegrees, Bearing courseAfterManeuver,
|
||||
public int didPass(Bearing courseBeforeManeuver, double totalCourseChangeInDegrees, Bearing courseAfterManeuver,
|
||||
Bearing wasThisCourseReachedAndCrossedDuringManeuver) {
|
||||
final boolean result;
|
||||
final int result;
|
||||
Bearing diffToCourseInQuestion = courseBeforeManeuver.getDifferenceTo(wasThisCourseReachedAndCrossedDuringManeuver);
|
||||
if (Math.signum(diffToCourseInQuestion.getDegrees()) == Math.signum(totalCourseChangeInDegrees)) {
|
||||
// simple case: the actual course change was greater than what was required in that direction to
|
||||
// reach the course in question
|
||||
result = Math.abs(diffToCourseInQuestion.getDegrees()) <= Math.abs(totalCourseChangeInDegrees);
|
||||
result = (int) ((Math.abs(totalCourseChangeInDegrees) + 360. - Math.abs(diffToCourseInQuestion.getDegrees())) / 360.);
|
||||
} else {
|
||||
// more complicated: the smallest possible course change required may have been to a different direction than the
|
||||
// actual course change. The course in question may still have been reached and crossed in the actual course
|
||||
@@ -24,7 +24,7 @@ public class BearingChangeAnalyzerImpl implements BearingChangeAnalyzer {
|
||||
// Approach: revert the course change required; revert 005deg to 355deg and check again with this reverted course change.
|
||||
Bearing revertedDiffToCourseInQuestion = new DegreeBearingImpl(-360 * Math.signum(diffToCourseInQuestion
|
||||
.getDegrees())+diffToCourseInQuestion.getDegrees());
|
||||
result = Math.abs(revertedDiffToCourseInQuestion.getDegrees()) <= Math.abs(totalCourseChangeInDegrees);
|
||||
result = (int) ((Math.abs(totalCourseChangeInDegrees) + 360. - Math.abs(revertedDiffToCourseInQuestion.getDegrees())) / 360.);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+11
-8
@@ -8,8 +8,8 @@ import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
|
||||
public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSettings {
|
||||
|
||||
public static PolarSheetGenerationSettings createStandardPolarSettings() {
|
||||
Double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. };
|
||||
WindSteppingWithMaxDistance windStepping = new WindSteppingWithMaxDistance(levels, 2.5);
|
||||
double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. };
|
||||
WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 2.5);
|
||||
return new PolarSheetGenerationSettingsImpl(50, 0.1, 20, 20, 0.1, true, true, 2, 0.05, true, windStepping,
|
||||
false);
|
||||
}
|
||||
@@ -19,9 +19,12 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet
|
||||
for (double levelValue = 0.5; levelValue < 35; levelValue = levelValue + 0.5) {
|
||||
levelList.add(levelValue);
|
||||
}
|
||||
Double[] levels = levelList.toArray(new Double[levelList.size()]);
|
||||
|
||||
WindSteppingWithMaxDistance windStepping = new WindSteppingWithMaxDistance(levels, 0.5);
|
||||
double[] levels = new double[levelList.size()];
|
||||
int i=0;
|
||||
for (Double level : levelList) {
|
||||
levels[i++] = level;
|
||||
}
|
||||
WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 0.5);
|
||||
return new PolarSheetGenerationSettingsImpl(50, 0.1, 20, 20, 0.1, true, true, 2, 0.05, true, windStepping,
|
||||
false);
|
||||
}
|
||||
@@ -37,7 +40,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet
|
||||
private double outlierDetectionNeighboorhoodRadius;
|
||||
private double outlierMinimumNeighboorhoodPct;
|
||||
private boolean useOnlyEstimationForWindDirection;
|
||||
private WindSteppingWithMaxDistance windStepping;
|
||||
private WindSpeedSteppingWithMaxDistance windStepping;
|
||||
private boolean splitByWindGauges;
|
||||
|
||||
//GWT
|
||||
@@ -47,7 +50,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet
|
||||
Integer minimumDataCountPerAngle, Integer numberOfHistogramColumns, double minimumConfidenceMeasure,
|
||||
boolean useOnlyWindGaugesForWindSpeed, boolean shouldRemoveOutliers,
|
||||
double outlierDetectionNeighboorhoodRadius, double outlierMinimumNeighboorhoodPct,
|
||||
boolean useOnlyEstimationForWindDirection, WindSteppingWithMaxDistance windStepping,
|
||||
boolean useOnlyEstimationForWindDirection, WindSpeedSteppingWithMaxDistance windStepping,
|
||||
boolean splitByWindGauges) {
|
||||
this.minimumDataCountPerGraph = minimumDataCountPerGraph;
|
||||
this.minimumWindConfidence = minimumWindConfidence;
|
||||
@@ -114,7 +117,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet
|
||||
}
|
||||
|
||||
@Override
|
||||
public WindSteppingWithMaxDistance getWindStepping() {
|
||||
public WindSpeedSteppingWithMaxDistance getWindSpeedStepping() {
|
||||
return windStepping;
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -4,7 +4,7 @@ import java.util.Map;
|
||||
|
||||
import com.sap.sailing.domain.common.PolarSheetsData;
|
||||
import com.sap.sailing.domain.common.PolarSheetsHistogramData;
|
||||
import com.sap.sailing.domain.common.WindStepping;
|
||||
import com.sap.sailing.domain.common.WindSpeedStepping;
|
||||
|
||||
public class PolarSheetsDataImpl implements PolarSheetsData {
|
||||
|
||||
@@ -14,7 +14,7 @@ public class PolarSheetsDataImpl implements PolarSheetsData {
|
||||
|
||||
private int dataCount;
|
||||
|
||||
private WindStepping stepping;
|
||||
private WindSpeedStepping stepping;
|
||||
|
||||
private Map<Integer,Integer[]> dataCountPerAngleForWindspeed;
|
||||
|
||||
@@ -24,7 +24,7 @@ public class PolarSheetsDataImpl implements PolarSheetsData {
|
||||
PolarSheetsDataImpl() {};
|
||||
|
||||
public PolarSheetsDataImpl(Number[][] averagedPolarDataByWindSpeed, int dataCount,
|
||||
Map<Integer, Integer[]> dataCountPerAngleForWindspeed, WindStepping stepping,
|
||||
Map<Integer, Integer[]> dataCountPerAngleForWindspeed, WindSpeedStepping stepping,
|
||||
Map<Integer, Map<Integer, PolarSheetsHistogramData>> histogramDataMap) {
|
||||
this.averagedPolarDataByWindSpeed = averagedPolarDataByWindSpeed;
|
||||
this.dataCount = dataCount;
|
||||
@@ -51,7 +51,7 @@ public class PolarSheetsDataImpl implements PolarSheetsData {
|
||||
}
|
||||
|
||||
@Override
|
||||
public WindStepping getStepping() {
|
||||
public WindSpeedStepping getStepping() {
|
||||
return stepping;
|
||||
}
|
||||
|
||||
|
||||
+40
-36
@@ -1,57 +1,61 @@
|
||||
package com.sap.sailing.domain.common.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.PolarSheetsXYDiagramData;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
|
||||
public class PolarSheetsXYDiagramDataImpl implements PolarSheetsXYDiagramData {
|
||||
|
||||
|
||||
private static final long serialVersionUID = 778667444303004468L;
|
||||
|
||||
PolarSheetsXYDiagramDataImpl() {}
|
||||
PolarSheetsXYDiagramDataImpl() {
|
||||
}
|
||||
|
||||
private List<Pair<Double, Double>> pointsForUpwindStarboardAverageAngle;
|
||||
private List<Pair<Double, Double>> pointsForUpwindStarboardAverageSpeed;
|
||||
private List<Pair<Double, Double>> pointsForUpwindStarboardAverageAngleMovingAverage;
|
||||
private List<Pair<Double, Double>> pointsForUpwindStarboardAverageSpeedMovingAverage;
|
||||
private List<Pair<Double, Double>> pointsForUpwindStarboardAverageConfidence;
|
||||
private Map<Pair<LegType, Tack>, List<Pair<Double, Double>>> pointsForAverageSpeedMovingAverage;
|
||||
|
||||
public PolarSheetsXYDiagramDataImpl(List<Pair<Double, Double>> pointsForUpwindStarboardAverageAngle,
|
||||
List<Pair<Double, Double>> pointsForUpwindStarboardAverageSpeed,
|
||||
List<Pair<Double, Double>> pointsForUpwindStarboardAverageAngleMovingAverage,
|
||||
List<Pair<Double, Double>> pointsForUpwindStarboardAverageSpeedMovingAverage,
|
||||
List<Pair<Double, Double>> pointsForUpwindStarboardAverageConfidence) {
|
||||
this.pointsForUpwindStarboardAverageAngle = pointsForUpwindStarboardAverageAngle;
|
||||
this.pointsForUpwindStarboardAverageSpeed = pointsForUpwindStarboardAverageSpeed;
|
||||
this.pointsForUpwindStarboardAverageAngleMovingAverage = pointsForUpwindStarboardAverageAngleMovingAverage;
|
||||
this.pointsForUpwindStarboardAverageSpeedMovingAverage = pointsForUpwindStarboardAverageSpeedMovingAverage;
|
||||
this.pointsForUpwindStarboardAverageConfidence = pointsForUpwindStarboardAverageConfidence;
|
||||
private Map<Pair<LegType, Tack>, List<Pair<Double, Double>>> pointsForAverageConfidence;
|
||||
|
||||
public PolarSheetsXYDiagramDataImpl(List<Pair<Double, Double>> pointsForUpwindStarboardAverageSpeedMovingAverage,
|
||||
List<Pair<Double, Double>> pointsForUpwindStarboardAverageConfidence,
|
||||
List<Pair<Double, Double>> pointsForUpwindPortAverageSpeedMovingAverage,
|
||||
List<Pair<Double, Double>> pointsForUpwindPortAverageConfidence,
|
||||
List<Pair<Double, Double>> pointsForDownwindStarboardAverageSpeedMovingAverage,
|
||||
List<Pair<Double, Double>> pointsForDownwindStarboardAverageConfidence,
|
||||
List<Pair<Double, Double>> pointsForDownwindPortAverageSpeedMovingAverage,
|
||||
List<Pair<Double, Double>> pointsForDownwindPortAverageConfidence) {
|
||||
pointsForAverageSpeedMovingAverage = new HashMap<Pair<LegType, Tack>, List<Pair<Double, Double>>>();
|
||||
pointsForAverageConfidence = new HashMap<Pair<LegType, Tack>, List<Pair<Double, Double>>>();
|
||||
pointsForAverageSpeedMovingAverage.put(new Pair<LegType, Tack>(LegType.UPWIND, Tack.STARBOARD),
|
||||
pointsForUpwindStarboardAverageSpeedMovingAverage);
|
||||
pointsForAverageConfidence.put(new Pair<LegType, Tack>(LegType.UPWIND, Tack.STARBOARD),
|
||||
pointsForUpwindStarboardAverageConfidence);
|
||||
pointsForAverageSpeedMovingAverage.put(new Pair<LegType, Tack>(LegType.UPWIND, Tack.PORT),
|
||||
pointsForUpwindPortAverageSpeedMovingAverage);
|
||||
pointsForAverageConfidence.put(new Pair<LegType, Tack>(LegType.UPWIND, Tack.PORT),
|
||||
pointsForUpwindPortAverageConfidence);
|
||||
pointsForAverageSpeedMovingAverage.put(new Pair<LegType, Tack>(LegType.DOWNWIND, Tack.STARBOARD),
|
||||
pointsForDownwindStarboardAverageSpeedMovingAverage);
|
||||
pointsForAverageConfidence.put(new Pair<LegType, Tack>(LegType.DOWNWIND, Tack.STARBOARD),
|
||||
pointsForDownwindStarboardAverageConfidence);
|
||||
pointsForAverageSpeedMovingAverage.put(new Pair<LegType, Tack>(LegType.DOWNWIND, Tack.PORT),
|
||||
pointsForDownwindPortAverageSpeedMovingAverage);
|
||||
pointsForAverageConfidence.put(new Pair<LegType, Tack>(LegType.DOWNWIND, Tack.PORT),
|
||||
pointsForDownwindPortAverageConfidence);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<Double, Double>> getPointsForUpwindStarboardAverageAngle() {
|
||||
return pointsForUpwindStarboardAverageAngle;
|
||||
public List<Pair<Double, Double>> getPointsForAverageSpeedMovingAverage(Tack tack, LegType legType) {
|
||||
return pointsForAverageSpeedMovingAverage.get(new Pair<LegType, Tack>(legType, tack));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<Double, Double>> getPointsForUpwindStarboardAverageSpeed() {
|
||||
return pointsForUpwindStarboardAverageSpeed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<Double, Double>> getPointsForUpwindStarboardAverageAngleMovingAverage() {
|
||||
return pointsForUpwindStarboardAverageAngleMovingAverage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<Double, Double>> getPointsForUpwindStarboardAverageSpeedMovingAverage() {
|
||||
return pointsForUpwindStarboardAverageSpeedMovingAverage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<Double, Double>> getPointsForUpwindStarboardAverageConfidence() {
|
||||
return pointsForUpwindStarboardAverageConfidence;
|
||||
public List<Pair<Double, Double>> getPointsForAverageConfidence(Tack tack, LegType legType) {
|
||||
return pointsForAverageConfidence.get(new Pair<LegType, Tack>(legType, tack));
|
||||
}
|
||||
|
||||
}
|
||||
+6
-3
@@ -40,9 +40,12 @@ public class RGBColor extends AbstractColor {
|
||||
b = Integer.parseInt(htmlColor.substring(4, 6), 16);
|
||||
break;
|
||||
case 3:
|
||||
r = Integer.parseInt(htmlColor.substring(0, 1), 16);
|
||||
g = Integer.parseInt(htmlColor.substring(1, 2), 16);
|
||||
b = Integer.parseInt(htmlColor.substring(2, 3), 16);
|
||||
final int r0 = Integer.parseInt(htmlColor.substring(0, 1), 16);
|
||||
r = 16*r0 + r0;
|
||||
final int g0 = Integer.parseInt(htmlColor.substring(1, 2), 16);
|
||||
g = 16*g0 + g0;
|
||||
final int b0 = Integer.parseInt(htmlColor.substring(2, 3), 16);
|
||||
b = 16*b0 + b0;
|
||||
break;
|
||||
case 1:
|
||||
r = g = b = Integer.parseInt(htmlColor.substring(0, 1), 16);
|
||||
|
||||
+7
-7
@@ -2,18 +2,18 @@ package com.sap.sailing.domain.common.impl;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.sap.sailing.domain.common.WindStepping;
|
||||
import com.sap.sailing.domain.common.WindSpeedStepping;
|
||||
|
||||
public class WindSteppingImpl implements WindStepping {
|
||||
public class WindSpeedSteppingImpl implements WindSpeedStepping {
|
||||
|
||||
|
||||
//For GWT Serialization
|
||||
protected WindSteppingImpl() {};
|
||||
protected WindSpeedSteppingImpl() {};
|
||||
|
||||
private static final long serialVersionUID = 2215693490331489508L;
|
||||
protected Double[] levels;
|
||||
protected double[] levels;
|
||||
|
||||
public WindSteppingImpl(Double[] levels) {
|
||||
public WindSpeedSteppingImpl(double[] levels) {
|
||||
this.levels = levels;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public class WindSteppingImpl implements WindStepping {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double[] getRawStepping() {
|
||||
public double[] getRawStepping() {
|
||||
return levels;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ public class WindSteppingImpl implements WindStepping {
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
WindSteppingImpl other = (WindSteppingImpl) obj;
|
||||
WindSpeedSteppingImpl other = (WindSpeedSteppingImpl) obj;
|
||||
if (!Arrays.equals(levels, other.levels))
|
||||
return false;
|
||||
return true;
|
||||
+4
-4
@@ -1,17 +1,17 @@
|
||||
package com.sap.sailing.domain.common.impl;
|
||||
|
||||
|
||||
public class WindSteppingWithMaxDistance extends WindSteppingImpl {
|
||||
public class WindSpeedSteppingWithMaxDistance extends WindSpeedSteppingImpl {
|
||||
|
||||
private static final long serialVersionUID = -2207840179212727591L;
|
||||
private double maxDistance;
|
||||
|
||||
//For GWT Serialization
|
||||
WindSteppingWithMaxDistance() {
|
||||
WindSpeedSteppingWithMaxDistance() {
|
||||
super();
|
||||
};
|
||||
|
||||
public WindSteppingWithMaxDistance(Double[] levels, double maxDistance) {
|
||||
public WindSpeedSteppingWithMaxDistance(double[] levels, double maxDistance) {
|
||||
super(levels);
|
||||
this.maxDistance = maxDistance;
|
||||
}
|
||||
@@ -58,7 +58,7 @@ public class WindSteppingWithMaxDistance extends WindSteppingImpl {
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
WindSteppingWithMaxDistance other = (WindSteppingWithMaxDistance) obj;
|
||||
WindSpeedSteppingWithMaxDistance other = (WindSpeedSteppingWithMaxDistance) obj;
|
||||
if (Double.doubleToLongBits(maxDistance) != Double.doubleToLongBits(other.maxDistance))
|
||||
return false;
|
||||
return true;
|
||||
+10
-5
@@ -12,17 +12,22 @@ import com.sap.sailing.domain.abstractlog.shared.events.impl.BaseRegisterCompeti
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
|
||||
public class RaceLogRegisterCompetitorEventImpl extends BaseRegisterCompetitorEventImpl<RaceLogEventVisitor>
|
||||
implements RaceLogRegisterCompetitorEvent {
|
||||
public class RaceLogRegisterCompetitorEventImpl extends BaseRegisterCompetitorEventImpl<RaceLogEventVisitor> implements
|
||||
RaceLogRegisterCompetitorEvent {
|
||||
private static final long serialVersionUID = -5114645637316367845L;
|
||||
private final RaceLogEventData raceLogEventData;
|
||||
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code competitor} is null
|
||||
*/
|
||||
public RaceLogRegisterCompetitorEventImpl(TimePoint createdAt, AbstractLogEventAuthor author,
|
||||
TimePoint logicalTimePoint, Serializable id, int passId, Competitor competitor) {
|
||||
TimePoint logicalTimePoint, Serializable id, int passId, Competitor competitor)
|
||||
throws IllegalArgumentException {
|
||||
super(createdAt, author, logicalTimePoint, id, competitor);
|
||||
this.raceLogEventData = new RaceLogEventDataImpl(null, passId);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getPassId() {
|
||||
return raceLogEventData.getPassId();
|
||||
|
||||
+7
-3
@@ -10,11 +10,15 @@ import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
|
||||
public class RegattaLogRegisterCompetitorEventImpl extends BaseRegisterCompetitorEventImpl<RegattaLogEventVisitor>
|
||||
implements RegattaLogRegisterCompetitorEvent {
|
||||
implements RegattaLogRegisterCompetitorEvent {
|
||||
private static final long serialVersionUID = -5114645637316367845L;
|
||||
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code competitor} is null
|
||||
*/
|
||||
public RegattaLogRegisterCompetitorEventImpl(TimePoint createdAt, AbstractLogEventAuthor author,
|
||||
TimePoint logicalTimePoint, Serializable id, Competitor competitor) {
|
||||
TimePoint logicalTimePoint, Serializable id, Competitor competitor) throws IllegalArgumentException {
|
||||
super(createdAt, author, logicalTimePoint, id, competitor);
|
||||
}
|
||||
|
||||
|
||||
+13
-6
@@ -8,21 +8,28 @@ import com.sap.sailing.domain.abstractlog.shared.events.RegisterCompetitorEvent;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
|
||||
public abstract class BaseRegisterCompetitorEventImpl<VisitorT> extends AbstractLogEventImpl<VisitorT>
|
||||
implements RegisterCompetitorEvent<VisitorT> {
|
||||
public abstract class BaseRegisterCompetitorEventImpl<VisitorT> extends AbstractLogEventImpl<VisitorT> implements
|
||||
RegisterCompetitorEvent<VisitorT> {
|
||||
private static final long serialVersionUID = -30864810737555657L;
|
||||
private final Competitor competitor;
|
||||
|
||||
public BaseRegisterCompetitorEventImpl(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint,
|
||||
Serializable pId, Competitor competitor) {
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code competitor} is null
|
||||
*/
|
||||
public BaseRegisterCompetitorEventImpl(TimePoint createdAt, AbstractLogEventAuthor author,
|
||||
TimePoint logicalTimePoint, Serializable pId, Competitor competitor) throws IllegalArgumentException {
|
||||
super(createdAt, author, logicalTimePoint, pId);
|
||||
if (competitor == null) {
|
||||
throw new IllegalArgumentException("Competitor may not be null");
|
||||
}
|
||||
this.competitor = competitor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Competitor getCompetitor() {
|
||||
return competitor;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortInfo() {
|
||||
|
||||
+13
-4
@@ -270,18 +270,27 @@ public class SharedDomainFactoryImpl implements SharedDomainFactory {
|
||||
|
||||
@Override
|
||||
public BoatClass getOrCreateBoatClass(String name, boolean typicallyStartsUpwind) {
|
||||
final String unifiedBoatClassName = BoatClassMasterdata.unifyBoatClassName(name);
|
||||
synchronized (boatClassCache) {
|
||||
BoatClass result = boatClassCache.get(name);
|
||||
if (result == null && name != null) {
|
||||
if (result == null) {
|
||||
result = boatClassCache.get(unifiedBoatClassName);
|
||||
}
|
||||
if (result == null && unifiedBoatClassName != null) {
|
||||
BoatClassMasterdata boatClassMasterdata = BoatClassMasterdata.resolveBoatClass(name);
|
||||
if (boatClassMasterdata != null) {
|
||||
result = new BoatClassImpl(name, boatClassMasterdata);
|
||||
result = new BoatClassImpl(boatClassMasterdata.getDisplayName(), boatClassMasterdata);
|
||||
boatClassCache.put(name, result);
|
||||
boatClassCache.put(unifiedBoatClassName, result);
|
||||
boatClassCache.put(result.getName(), result);
|
||||
for (String alternativeName : boatClassMasterdata.getAlternativeNames()) {
|
||||
boatClassCache.put(alternativeName, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
result = new BoatClassImpl(name, typicallyStartsUpwind);
|
||||
boatClassCache.put(name, result);
|
||||
result = new BoatClassImpl(unifiedBoatClassName, typicallyStartsUpwind);
|
||||
boatClassCache.put(unifiedBoatClassName, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+1
-1
@@ -102,7 +102,7 @@ public class SimpleDomainFactoryTest {
|
||||
}
|
||||
});
|
||||
assertNotNull(raceDefinition.getBoatClass());
|
||||
assertEquals("Unknown", raceDefinition.getBoatClass().getName());
|
||||
assertEquals("UNKNOWN", raceDefinition.getBoatClass().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
BIN
Binary file not shown.
+2127
File diff suppressed because it is too large.
Load diff
+47
-24
@@ -1,7 +1,6 @@
|
||||
package com.sap.sailing.domain.common.test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -13,69 +12,93 @@ public class BearingChangeAnalyzerTest {
|
||||
|
||||
@Test
|
||||
public void testSimpleNonCrossingZeroDegreesCase() {
|
||||
assertTrue(bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), 10, new DegreeBearingImpl(15),
|
||||
assertEquals(1, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), 10, new DegreeBearingImpl(15),
|
||||
new DegreeBearingImpl(10)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNegatigeSimpleNonCrossingZeroDegreesCase() {
|
||||
assertFalse(bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), 10, new DegreeBearingImpl(15),
|
||||
public void testDoubleNonCrossingZeroDegreesCase() {
|
||||
assertEquals(2, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), 370, new DegreeBearingImpl(15),
|
||||
new DegreeBearingImpl(10)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNegativeDoubleNonCrossingZeroDegreesCase() {
|
||||
assertEquals(1, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), 370, new DegreeBearingImpl(15),
|
||||
new DegreeBearingImpl(16)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNegativeSimpleNonCrossingZeroDegreesCase() {
|
||||
assertEquals(0, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), 10, new DegreeBearingImpl(15),
|
||||
new DegreeBearingImpl(17)));
|
||||
assertFalse(bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), 10, new DegreeBearingImpl(15),
|
||||
assertEquals(0, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), 10, new DegreeBearingImpl(15),
|
||||
new DegreeBearingImpl(3)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForwardCrossingZeroDegreesCase() {
|
||||
assertTrue(bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), 10, new DegreeBearingImpl(5),
|
||||
assertEquals(1, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), 10, new DegreeBearingImpl(5),
|
||||
new DegreeBearingImpl(0)));
|
||||
assertTrue(bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), 10, new DegreeBearingImpl(5),
|
||||
assertEquals(1, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), 10, new DegreeBearingImpl(5),
|
||||
new DegreeBearingImpl(359)));
|
||||
assertTrue(bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), 10, new DegreeBearingImpl(5),
|
||||
assertEquals(1, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), 10, new DegreeBearingImpl(5),
|
||||
new DegreeBearingImpl(1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNegatigeForwardCrossingZeroDegreesCase() {
|
||||
assertFalse(bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), 10, new DegreeBearingImpl(5),
|
||||
public void testNegativeForwardCrossingZeroDegreesCase() {
|
||||
assertEquals(0, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), 10, new DegreeBearingImpl(5),
|
||||
new DegreeBearingImpl(354)));
|
||||
assertFalse(bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), 10, new DegreeBearingImpl(5),
|
||||
assertEquals(0, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), 10, new DegreeBearingImpl(5),
|
||||
new DegreeBearingImpl(6)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackwardNonCrossingZeroDegreesCase() {
|
||||
assertTrue(bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), -350, new DegreeBearingImpl(5),
|
||||
assertEquals(1, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), -350, new DegreeBearingImpl(5),
|
||||
new DegreeBearingImpl(180)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNegatigeBackwardNonCrossingZeroDegreesCase() {
|
||||
assertFalse(bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), -350, new DegreeBearingImpl(5),
|
||||
public void testDoubleBackwardNonCrossingZeroDegreesCase() {
|
||||
assertEquals(2, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), -710, new DegreeBearingImpl(5),
|
||||
new DegreeBearingImpl(180)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNegativeDoubleBackwardNonCrossingZeroDegreesCase() {
|
||||
assertEquals(1, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), -710, new DegreeBearingImpl(5),
|
||||
new DegreeBearingImpl(3)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNegativeBackwardNonCrossingZeroDegreesCase() {
|
||||
assertEquals(0, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), -350, new DegreeBearingImpl(5),
|
||||
new DegreeBearingImpl(359)));
|
||||
assertFalse(bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), -350, new DegreeBearingImpl(5),
|
||||
assertEquals(0, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), -350, new DegreeBearingImpl(5),
|
||||
new DegreeBearingImpl(0)));
|
||||
assertFalse(bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), -350, new DegreeBearingImpl(5),
|
||||
assertEquals(0, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(355), -350, new DegreeBearingImpl(5),
|
||||
new DegreeBearingImpl(1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackwardCrossingZeroDegreesCase() {
|
||||
assertTrue(bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), -10, new DegreeBearingImpl(355),
|
||||
assertEquals(1, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), -10, new DegreeBearingImpl(355),
|
||||
new DegreeBearingImpl(0)));
|
||||
assertTrue(bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), -10, new DegreeBearingImpl(355),
|
||||
assertEquals(1, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), -10, new DegreeBearingImpl(355),
|
||||
new DegreeBearingImpl(359)));
|
||||
assertTrue(bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), -10, new DegreeBearingImpl(355),
|
||||
assertEquals(1, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), -10, new DegreeBearingImpl(355),
|
||||
new DegreeBearingImpl(1)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNegatigeBackwardCrossingZeroDegreesCase() {
|
||||
assertFalse(bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), -10, new DegreeBearingImpl(355),
|
||||
public void testNegativeBackwardCrossingZeroDegreesCase() {
|
||||
assertEquals(0, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), -10, new DegreeBearingImpl(355),
|
||||
new DegreeBearingImpl(180)));
|
||||
assertFalse(bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), -10, new DegreeBearingImpl(355),
|
||||
assertEquals(0, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), -10, new DegreeBearingImpl(355),
|
||||
new DegreeBearingImpl(354)));
|
||||
assertFalse(bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), -10, new DegreeBearingImpl(355),
|
||||
assertEquals(0, bearingChangeAnalyzer.didPass(new DegreeBearingImpl(5), -10, new DegreeBearingImpl(355),
|
||||
new DegreeBearingImpl(6)));
|
||||
}
|
||||
}
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
package com.sap.sailing.domain.common.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.common.impl.RGBColor;
|
||||
|
||||
public class RGBColorTest {
|
||||
/**
|
||||
* Tests http://www.w3.org/TR/css3-color/#colorunits section 4.2.1 for three-digit color values. The specification
|
||||
* reads:
|
||||
* "The three-digit RGB notation (#rgb) is converted into six-digit form (#rrggbb) by replicating digits, not by adding zeros. For example, #fb0 expands to #ffbb00."
|
||||
*/
|
||||
@Test
|
||||
public void testThreeDigitColor() {
|
||||
final RGBColor threeDigit = new RGBColor("#08f");
|
||||
final RGBColor sixDigit = new RGBColor("#0088ff");
|
||||
assertEquals(sixDigit, threeDigit);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHtmlConversion() {
|
||||
assertEquals("#0088FF", new RGBColor(0, 136, 255).getAsHtml());
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -97,7 +97,7 @@ public class MultipleClassesInRegattaTest {
|
||||
new DummyTrackedRegattaRegistry());
|
||||
|
||||
assertEquals("STG", kiwotest1.getRegatta().getBoatClass().getName());
|
||||
assertEquals("505", kiwotest2.getRegatta().getBoatClass().getName());
|
||||
assertEquals("5O5", kiwotest2.getRegatta().getBoatClass().getName());
|
||||
assertEquals("49er", kiwotest3.getRegatta().getBoatClass().getName());
|
||||
assertEquals("STG", weym470may112014_2.getRegatta().getBoatClass().getName());
|
||||
assertNotSame(kiwotest1.getRegatta(), kiwotest2.getRegatta());
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package com.sap.sailing.domain.test;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.common.ManeuverType;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.WindSourceType;
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.WindSourceImpl;
|
||||
import com.sap.sailing.domain.tracking.Maneuver;
|
||||
import com.sap.sailing.domain.tracking.impl.WindImpl;
|
||||
import com.sap.sailing.domain.tractracadapter.ReceiverType;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
import com.tractrac.model.lib.api.event.CreateModelException;
|
||||
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
|
||||
|
||||
/**
|
||||
* This is a test for bug 2009 (see http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2009).
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public class PhilippBuhlsDoublePenaltyCircleAtKielerWoche2014Test extends AbstractManeuverDetectionTestCase {
|
||||
public PhilippBuhlsDoublePenaltyCircleAtKielerWoche2014Test() throws MalformedURLException, URISyntaxException {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getExpectedEventName() {
|
||||
// don't worry about the missing "r" at the end of "Kiele"; this is what we're getting from TracTrac
|
||||
return "Kiele Woche 2014 - Olympic Week";
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws URISyntaxException, IOException, InterruptedException, ParseException, SubscriberInitializationException, CreateModelException {
|
||||
super.setUp();
|
||||
URI storedUri = new URI("file:///"+new File("resources/event_20140619_KieleWoche-R1_Blue_Laser.mtb").getCanonicalPath().replace('\\', '/'));
|
||||
super.setUp(
|
||||
new URL("file:///" + new File("resources/event_20140619_KieleWoche-R1_Blue_Laser.txt").getCanonicalPath()),
|
||||
/* liveUri */null, /* storedUri */storedUri, new ReceiverType[] { ReceiverType.MARKPASSINGS,
|
||||
ReceiverType.MARKPOSITIONS, ReceiverType.RACECOURSE, ReceiverType.RAWPOSITIONS });
|
||||
getTrackedRace().recordWind(
|
||||
new WindImpl(/* position */null, new MillisecondsTimePoint(dateFormat.parse("21/06/2014-13:03:35")),
|
||||
new KnotSpeedWithBearingImpl(18, new DegreeBearingImpl(296))),
|
||||
new WindSourceImpl(WindSourceType.WEB));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that Philipp Buhl is having two penalty circles detected in the time between 13:03:18+0200 and 13:03:47+0200
|
||||
*/
|
||||
@Test
|
||||
public void testDoublePenaltyForPhilippAndTobiasAndMaximAndDharmender() throws ParseException, NoWindException {
|
||||
assertTwoPenalties("Philipp Buhl", "06/21/2014-13:03:18", "06/21/2014-13:03:47", "06/21/2014-13:03:24", "06/21/2014-13:03:37");
|
||||
assertTwoPenalties("Dharmender SINGH", "06/21/2014-12:51:40", "06/21/2014-12:52:40", "06/21/2014-12:51:55", "06/21/2014-12:52:12");
|
||||
// note the typo in Tobias's name; this is how we get it from TracTrac...
|
||||
assertTwoPenalties("Tolbias SCHADEWALDT", "06/21/2014-12:46:50", "06/21/2014-12:47:30", "06/21/2014-12:47:07", "06/21/2014-12:47:15");
|
||||
assertTwoPenalties("Maxim NIKOLAEV", "06/21/2014-12:49:22", "06/21/2014-12:50:13", "06/21/2014-12:49:32", "06/21/2014-12:49:48");
|
||||
}
|
||||
|
||||
private void assertTwoPenalties(String competitorName, final String from, final String to,
|
||||
final String firstPenalty, final String secondPenalty) throws ParseException, NoWindException {
|
||||
Competitor competitor = getCompetitorByName(competitorName);
|
||||
Date fromDate = dateFormat.parse(from);
|
||||
Date toDate = dateFormat.parse(to);
|
||||
assertNotNull(fromDate);
|
||||
assertNotNull(toDate);
|
||||
assertNotNull(competitor);
|
||||
List<Maneuver> maneuvers = getTrackedRace().getManeuvers(competitor, new MillisecondsTimePoint(fromDate),
|
||||
new MillisecondsTimePoint(toDate), /* waitForLatest */ true);
|
||||
maneuversInvalid = new ArrayList<Maneuver>(maneuvers);
|
||||
for (Maneuver maneuver : maneuvers) {
|
||||
if (maneuver.getType() == ManeuverType.PENALTY_CIRCLE) {
|
||||
assertTrue(Math.abs(maneuver.getDirectionChangeInDegrees()) < 700); // the second penalty has to count for its own
|
||||
}
|
||||
}
|
||||
assertManeuver(maneuvers, ManeuverType.PENALTY_CIRCLE, new MillisecondsTimePoint(dateFormat.parse(firstPenalty)), 5000);
|
||||
assertManeuver(maneuvers, ManeuverType.PENALTY_CIRCLE, new MillisecondsTimePoint(dateFormat.parse(secondPenalty)), 5000);
|
||||
assertAllManeuversOfTypesDetected(Collections.singletonList(ManeuverType.PENALTY_CIRCLE), maneuversInvalid);
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -4,16 +4,16 @@ import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.common.WindStepping;
|
||||
import com.sap.sailing.domain.common.impl.WindSteppingImpl;
|
||||
import com.sap.sailing.domain.common.impl.WindSteppingWithMaxDistance;
|
||||
import com.sap.sailing.domain.common.WindSpeedStepping;
|
||||
import com.sap.sailing.domain.common.impl.WindSpeedSteppingImpl;
|
||||
import com.sap.sailing.domain.common.impl.WindSpeedSteppingWithMaxDistance;
|
||||
|
||||
public class WindSteppingTest {
|
||||
|
||||
@Test
|
||||
public void testStepping() {
|
||||
Double[] levels = {2.,4.};
|
||||
WindStepping stepping = new WindSteppingImpl(levels);
|
||||
double[] levels = {2.,4.};
|
||||
WindSpeedStepping stepping = new WindSpeedSteppingImpl(levels);
|
||||
Assert.assertEquals(0, stepping.getLevelIndexForValue(1));
|
||||
Assert.assertEquals(1, stepping.getLevelIndexForValue(5));
|
||||
Assert.assertEquals(1, stepping.getLevelIndexForValue(3));
|
||||
@@ -23,8 +23,8 @@ public class WindSteppingTest {
|
||||
|
||||
@Test
|
||||
public void testSteppingWithMaxDistance() {
|
||||
Double[] levels = {2.,4.};
|
||||
WindStepping stepping = new WindSteppingWithMaxDistance(levels, 1.0);
|
||||
double[] levels = {2.,4.};
|
||||
WindSpeedStepping stepping = new WindSpeedSteppingWithMaxDistance(levels, 1.0);
|
||||
Assert.assertEquals(0, stepping.getLevelIndexForValue(1));
|
||||
Assert.assertEquals(1, stepping.getLevelIndexForValue(5));
|
||||
Assert.assertEquals(-1, stepping.getLevelIndexForValue(5.01));
|
||||
|
||||
+1
-1
@@ -351,7 +351,7 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Maneuver> getManeuvers(Competitor competitor, TimePoint from, TimePoint to, boolean waitForLatest) throws NoWindException {
|
||||
public List<Maneuver> getManeuvers(Competitor competitor, TimePoint from, TimePoint to, boolean waitForLatest) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
@@ -32,6 +34,7 @@ import com.sap.sse.common.Util;
|
||||
|
||||
public class EventImpl extends EventBaseImpl implements Event {
|
||||
private static final long serialVersionUID = 855135446595485715L;
|
||||
private static final Logger logger = Logger.getLogger(EventImpl.class.getName());
|
||||
|
||||
private final Set<Regatta> regattas;
|
||||
|
||||
@@ -125,6 +128,9 @@ public class EventImpl extends EventBaseImpl implements Event {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
logger.log(Level.SEVERE, "Stale image link "+imageURL+" for event "+getName()+". Please double-check and replace or remove.", ioe);
|
||||
throw ioe;
|
||||
} finally {
|
||||
if (in != null) {
|
||||
in.close();
|
||||
|
||||
+61
-39
@@ -1199,12 +1199,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null,
|
||||
fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race);
|
||||
}
|
||||
FutureTask<List<CompetitorDTO>> task = new FutureTask<List<CompetitorDTO>>(new Callable<List<CompetitorDTO>>() {
|
||||
@Override
|
||||
public List<CompetitorDTO> call() throws Exception {
|
||||
return baseDomainFactory.getCompetitorDTOList(AbstractSimpleLeaderboardImpl.this.getCompetitorsFromBestToWorst(raceColumn, timePoint));
|
||||
}
|
||||
});
|
||||
FutureTask<List<CompetitorDTO>> task = new FutureTask<List<CompetitorDTO>>(
|
||||
() -> baseDomainFactory.getCompetitorDTOList(AbstractSimpleLeaderboardImpl.this.getCompetitorsFromBestToWorst(raceColumn, timePoint)));
|
||||
executor.execute(task);
|
||||
competitorsFromBestToWorstTasks.put(raceColumn, task);
|
||||
}
|
||||
@@ -1236,8 +1232,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
result.discardThresholds = null;
|
||||
}
|
||||
// Computing the competitor leg ranks is expensive, especially in live mode, in case new events keep
|
||||
// invalidating the ranks cache in TrackedLegImpl. Then problem then is that the sorting based on wind data is repeated for
|
||||
// each competitor, leading to square effort. We therefore need to compute the leg ranks for those race where leg
|
||||
// invalidating the ranks cache in TrackedLegImpl. The problem then is that the sorting based on wind data is repeated for
|
||||
// each competitor, leading to square effort. We therefore need to compute the leg ranks for those races where leg
|
||||
// details are requested only once and pass them into getLeaderboardEntryDTO
|
||||
final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache = new HashMap<Leg, LinkedHashMap<Competitor, Integer>>();
|
||||
for (final RaceColumn raceColumn : this.getRaceColumns()) {
|
||||
@@ -1273,35 +1269,29 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
Map<String, Future<LeaderboardEntryDTO>> futuresForColumnName = new HashMap<String, Future<LeaderboardEntryDTO>>();
|
||||
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
|
||||
for (final RaceColumn raceColumn : this.getRaceColumns()) {
|
||||
RunnableFuture<LeaderboardEntryDTO> future = new FutureTask<LeaderboardEntryDTO>(
|
||||
new Callable<LeaderboardEntryDTO>() {
|
||||
@Override
|
||||
public LeaderboardEntryDTO call() {
|
||||
try {
|
||||
Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint, discardedRaceColumns);
|
||||
return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint,
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails != null
|
||||
&& namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn
|
||||
.getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory,
|
||||
fillNetPointsUncorrected, cache);
|
||||
} catch (NoWindException e) {
|
||||
logger.info("Exception trying to compute leaderboard entry for competitor "
|
||||
+ competitor.getName() + " in race column " + raceColumn.getName() + ": "
|
||||
+ e.getMessage());
|
||||
logger.throwing(AbstractSimpleLeaderboardImpl.class.getName(),
|
||||
"computeLeaderboardByName.future.call()", e);
|
||||
throw new NoWindError(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
RunnableFuture<LeaderboardEntryDTO> future = new FutureTask<LeaderboardEntryDTO>(() -> {
|
||||
try {
|
||||
Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint, discardedRaceColumns);
|
||||
return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint,
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails != null
|
||||
&& namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn
|
||||
.getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory,
|
||||
fillNetPointsUncorrected, cache);
|
||||
} catch (NoWindException e) {
|
||||
logger.info("Exception trying to compute leaderboard entry for competitor "
|
||||
+ competitor.getName() + " in race column " + raceColumn.getName() + ": "
|
||||
+ e.getMessage());
|
||||
logger.throwing(AbstractSimpleLeaderboardImpl.class.getName(),
|
||||
"computeLeaderboardByName.future.call()", e);
|
||||
throw new NoWindError(e);
|
||||
}
|
||||
});
|
||||
executor.execute(future);
|
||||
futuresForColumnName.put(raceColumn.getName(), future);
|
||||
}
|
||||
for (Map.Entry<String, Future<LeaderboardEntryDTO>> raceColumnNameAndFuture : futuresForColumnName
|
||||
.entrySet()) {
|
||||
for (Map.Entry<String, Future<LeaderboardEntryDTO>> raceColumnNameAndFuture : futuresForColumnName.entrySet()) {
|
||||
try {
|
||||
row.fieldsByRaceColumnName.put(raceColumnNameAndFuture.getKey(), raceColumnNameAndFuture.getValue()
|
||||
.get());
|
||||
row.fieldsByRaceColumnName.put(raceColumnNameAndFuture.getKey(), raceColumnNameAndFuture.getValue().get());
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ExecutionException e) {
|
||||
@@ -1427,7 +1417,11 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
.getEstimatedSpeed(competitorStartTime);
|
||||
entryDTO.speedOverGroundAtPassingStartWaypointInKnots = competitorSpeedWhenPassingStart == null ? null
|
||||
: competitorSpeedWhenPassingStart.getKnots();
|
||||
entryDTO.startTack = trackedRace.getTack(competitor, competitorStartTime);
|
||||
try {
|
||||
entryDTO.startTack = trackedRace.getTack(competitor, competitorStartTime);
|
||||
} catch (NoWindException nwe) {
|
||||
entryDTO.startTack = null; // leave empty in case no wind information is available
|
||||
}
|
||||
Distance distanceFromStarboardSideOfStartLineWhenPassingStart = trackedRace
|
||||
.getDistanceFromStarboardSideOfStartLineWhenPassingStart(competitor);
|
||||
entryDTO.distanceToStarboardSideOfStartLineInMeters = distanceFromStarboardSideOfStartLineWhenPassingStart == null ? null
|
||||
@@ -1576,10 +1570,22 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
}
|
||||
final Distance windwardDistanceToOverallLeader = trackedRace == null ? null : trackedRace
|
||||
.getWindwardDistanceToOverallLeader(competitor, timePoint, WindPositionMode.LEG_MIDDLE);
|
||||
final Distance averageAbsoluteCrossTrackError = trackedRace == null ? null : trackedRace.getAverageAbsoluteCrossTrackError(
|
||||
Distance averageAbsoluteCrossTrackError;
|
||||
try {
|
||||
averageAbsoluteCrossTrackError = trackedRace == null ? null : trackedRace.getAverageAbsoluteCrossTrackError(
|
||||
competitor, timePoint, waitForLatestAnalyses, cache);
|
||||
final Distance averageSignedCrossTrackError = trackedRace == null ? null : trackedRace.getAverageSignedCrossTrackError(
|
||||
} catch (NoWindException nwe) {
|
||||
// without wind information, use null meaning "unknown"
|
||||
averageAbsoluteCrossTrackError = null;
|
||||
}
|
||||
Distance averageSignedCrossTrackError;
|
||||
try {
|
||||
averageSignedCrossTrackError = trackedRace == null ? null : trackedRace.getAverageSignedCrossTrackError(
|
||||
competitor, timePoint, waitForLatestAnalyses, cache);
|
||||
} catch (NoWindException nwe) {
|
||||
// without wind information, use null meaning "unknown"
|
||||
averageSignedCrossTrackError = null;
|
||||
}
|
||||
return new RaceDetails(legDetails, windwardDistanceToOverallLeader, averageAbsoluteCrossTrackError, averageSignedCrossTrackError);
|
||||
} finally {
|
||||
course.unlockAfterRead();
|
||||
@@ -1595,12 +1601,28 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
result = null;
|
||||
} else {
|
||||
result = new LegEntryDTO();
|
||||
result.legType = trackedLeg.getTrackedLeg().getLegType(timePoint);
|
||||
try {
|
||||
result.legType = trackedLeg.getTrackedLeg().getLegType(timePoint);
|
||||
} catch (NoWindException nwe) {
|
||||
result.legType = null; // can't determine leg type without wind data
|
||||
}
|
||||
final Speed averageSpeedOverGround = trackedLeg.getAverageSpeedOverGround(timePoint);
|
||||
result.averageSpeedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots();
|
||||
final Distance averageAbsoluteCrossTrackError = trackedLeg.getAverageAbsoluteCrossTrackError(timePoint, waitForLatestAnalyses);
|
||||
Distance averageAbsoluteCrossTrackError;
|
||||
try {
|
||||
averageAbsoluteCrossTrackError = trackedLeg.getAverageAbsoluteCrossTrackError(timePoint, waitForLatestAnalyses);
|
||||
} catch (NoWindException nwe) {
|
||||
// leave averageAbsoluteCrossTrackError as null, meaning "unknown"
|
||||
averageAbsoluteCrossTrackError = null;
|
||||
}
|
||||
result.averageAbsoluteCrossTrackErrorInMeters = averageAbsoluteCrossTrackError == null ? null : averageAbsoluteCrossTrackError.getMeters();
|
||||
final Distance averageSignedCrossTrackError = trackedLeg.getAverageSignedCrossTrackError(timePoint, waitForLatestAnalyses);
|
||||
Distance averageSignedCrossTrackError;
|
||||
try {
|
||||
averageSignedCrossTrackError = trackedLeg.getAverageSignedCrossTrackError(timePoint, waitForLatestAnalyses);
|
||||
} catch (NoWindException nwe) {
|
||||
// leave averageSignedCrossTrackError as null, meaning "unknown"
|
||||
averageSignedCrossTrackError = null;
|
||||
}
|
||||
result.averageSignedCrossTrackErrorInMeters = averageSignedCrossTrackError == null ? null : averageSignedCrossTrackError.getMeters();
|
||||
Double speedOverGroundInKnots;
|
||||
if (trackedLeg.hasFinishedLeg(timePoint)) {
|
||||
|
||||
@@ -4,5 +4,12 @@ import com.sap.sailing.domain.base.Timed;
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
|
||||
public interface GPSFix extends Positioned, Timed, WithValidityCache, WithEstimatedSpeedCache {
|
||||
/**
|
||||
* Reaches <code>to</code> at <code>to</code>'s {@link GPSFix#getTimePoint() time point} starting at this fix,
|
||||
* traveling on a straight great circle segment.
|
||||
*
|
||||
* @return the speed over ground and course over ground required to reach <code>to</code> starting at this fix,
|
||||
* traveling along a great circle segment
|
||||
*/
|
||||
SpeedWithBearing getSpeedAndBearingRequiredToReach(GPSFix to);
|
||||
}
|
||||
@@ -92,15 +92,14 @@ public interface TrackedLeg extends Serializable {
|
||||
* for leg types, wind data and leg bearings can be passed to save the effort of redundant calculations of these
|
||||
* values.
|
||||
*/
|
||||
Distance getAbsoluteWindwardDistance(Position pos1, Position pos2, TimePoint at, WindPositionMode windPositionMode,
|
||||
WindLegTypeAndLegBearingCache cache) throws NoWindException;
|
||||
Distance getAbsoluteWindwardDistance(Position pos1, Position pos2, TimePoint at, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache);
|
||||
|
||||
/**
|
||||
* Same as {@link #getAbsoluteWindwardDistance(Position, Position, TimePoint, WindPositionMode)}, but this method considers the leg's
|
||||
* direction and will return a negative distance if <code>pos1</code> is already "ahead" of <code>pos2</code> in the
|
||||
* leg's direction, or a positive distance otherwise.
|
||||
*/
|
||||
Distance getWindwardDistance(Position pos1, Position pos2, TimePoint at, WindPositionMode windPositionMode) throws NoWindException;
|
||||
Distance getWindwardDistance(Position pos1, Position pos2, TimePoint at, WindPositionMode windPositionMode);
|
||||
|
||||
/**
|
||||
* The middle (traveling half the length) of the course middle line, connecting the center of gravity of the leg's start
|
||||
|
||||
+6
-12
@@ -42,22 +42,17 @@ public interface TrackedLegOfCompetitor extends Serializable {
|
||||
/**
|
||||
* Estimates how much the competitor still has to go to the end waypoint of this leg, projected onto the wind
|
||||
* direction. If the competitor already finished this leg, a zero, non-<code>null</code> distance will result.
|
||||
* If the competitor hasn't started the leg yet, the full leg distance is returned.
|
||||
*
|
||||
* @throws NoWindException
|
||||
* thrown in case no wind direction is known
|
||||
* If the competitor hasn't started the leg yet, the full leg distance is returned. For reaching legs or when
|
||||
* no wind information is available, the projection onto the leg's direction will be used instead of wind
|
||||
* projection.
|
||||
*/
|
||||
Distance getWindwardDistanceToGo(TimePoint timePoint, WindPositionMode windPositionMode) throws NoWindException;
|
||||
Distance getWindwardDistanceToGo(TimePoint timePoint, WindPositionMode windPositionMode);
|
||||
|
||||
/**
|
||||
* Same as {@link #getWindwardDistanceToGo(TimePoint, WindPositionMode)}, only that a cache for wind and leg
|
||||
* type / bearing can be passed.
|
||||
*
|
||||
* @throws NoWindException
|
||||
* thrown in case no wind direction is known
|
||||
*/
|
||||
Distance getWindwardDistanceToGo(TimePoint timePoint, WindPositionMode legMiddle,
|
||||
WindLegTypeAndLegBearingCache cache) throws NoWindException;
|
||||
Distance getWindwardDistanceToGo(TimePoint timePoint, WindPositionMode legMiddle, WindLegTypeAndLegBearingCache cache);
|
||||
|
||||
/**
|
||||
* Computes an approximation for the average velocity made good (windward / leeward speed) of this leg's competitor at
|
||||
@@ -188,8 +183,7 @@ public interface TrackedLegOfCompetitor extends Serializable {
|
||||
* Same as {@link #getVelocityMadeGood(TimePoint, WindPositionMode)}, only that a cache for wind data and leg type and bearing
|
||||
* is passed.
|
||||
*/
|
||||
Speed getVelocityMadeGood(TimePoint at, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache)
|
||||
throws NoWindException;
|
||||
Speed getVelocityMadeGood(TimePoint at, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache);
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -416,7 +416,7 @@ public interface TrackedRace extends Serializable {
|
||||
* the approximation is at most <code>maxDistance</code>. The approximation's fixes are original fixes from
|
||||
* the competitor's {@link GPSFixTrack track}.
|
||||
*/
|
||||
List<GPSFixMoving> approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to);
|
||||
Iterable<GPSFixMoving> approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to);
|
||||
|
||||
/**
|
||||
* @return a non-<code>null</code> but perhaps empty list of the maneuvers that <code>competitor</code> performed in
|
||||
@@ -424,8 +424,7 @@ public interface TrackedRace extends Serializable {
|
||||
* result is taken from the cache straight away (<code>waitForLatest==false</code>) or, if a re-calculation
|
||||
* for the <code>key</code> is still ongoing, the result of that ongoing re-calculation is returned.
|
||||
*/
|
||||
List<Maneuver> getManeuvers(Competitor competitor, TimePoint from, TimePoint to, boolean waitForLatest)
|
||||
throws NoWindException;
|
||||
List<Maneuver> getManeuvers(Competitor competitor, TimePoint from, TimePoint to, boolean waitForLatest);
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if this race is known to start with an {@link LegType#UPWIND upwind} leg. If this is
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import com.sap.sse.common.TimePoint;
|
||||
|
||||
/**
|
||||
* This trivial "cache" implementation doesn't cache and may be used as a default for those cases where only few
|
||||
* calculations are to be done and creating the caching structure would cost spend cycles than simply doing the
|
||||
* calculations are to be done and creating the caching structure would cost more cycles than simply doing the
|
||||
* calculation.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
|
||||
+1
@@ -791,6 +791,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl {
|
||||
super(TrackBasedEstimationWindTrackImpl.this, trackedRace, from, to, /* resolution in milliseconds */ 1000l);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Wind getWind(Position p, TimePoint timePoint) {
|
||||
final WindWithConfidence<TimePoint> estimatedWindDirectionWithConfidence = getWindWithConfidence(timePoint);
|
||||
return estimatedWindDirectionWithConfidence == null ? null : estimatedWindDirectionWithConfidence.getObject();
|
||||
|
||||
+24
-19
@@ -325,7 +325,7 @@ public class TrackedLegImpl implements TrackedLeg {
|
||||
|
||||
@Override
|
||||
public Distance getAbsoluteWindwardDistance(Position pos1, Position pos2, TimePoint at,
|
||||
WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) throws NoWindException {
|
||||
WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) {
|
||||
final Distance preResult = getWindwardDistance(pos1, pos2, at, windPositionMode);
|
||||
final Distance result;
|
||||
if (preResult.getMeters() >= 0) {
|
||||
@@ -337,30 +337,35 @@ public class TrackedLegImpl implements TrackedLeg {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Distance getWindwardDistance(Position pos1, Position pos2, TimePoint at, WindPositionMode windPositionMode) throws NoWindException {
|
||||
public Distance getWindwardDistance(Position pos1, Position pos2, TimePoint at, WindPositionMode windPositionMode) {
|
||||
return getWindwardDistance(pos1, pos2, at, windPositionMode, new NoCachingWindLegTypeAndLegBearingCache());
|
||||
}
|
||||
|
||||
Distance getWindwardDistance(final Position pos1, final Position pos2, TimePoint at, WindPositionMode windPositionMode,
|
||||
WindLegTypeAndLegBearingCache cache) throws NoWindException {
|
||||
LegType legType = cache.getLegType(this, at);
|
||||
if (legType != LegType.REACHING) { // upwind or downwind
|
||||
final Position effectivePosition = getEffectiveWindPosition(
|
||||
new Callable<Position>() { @Override public Position call() {
|
||||
return pos1.translateGreatCircle(pos1.getBearingGreatCircle(pos2), pos1.getDistance(pos2).scale(0.5));
|
||||
}}, at,
|
||||
windPositionMode);
|
||||
Wind wind = getTrackedRace().getWind(effectivePosition, at);
|
||||
if (wind == null) {
|
||||
return pos2.alongTrackDistance(pos1, cache.getLegBearing(this, at));
|
||||
Distance getWindwardDistance(final Position pos1, final Position pos2, TimePoint at, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) {
|
||||
Distance result;
|
||||
try {
|
||||
final LegType legType = cache.getLegType(this, at);
|
||||
if (legType != LegType.REACHING) { // upwind or downwind
|
||||
final Position effectivePosition = getEffectiveWindPosition(
|
||||
() -> pos1.translateGreatCircle(pos1.getBearingGreatCircle(pos2), pos1.getDistance(pos2).scale(0.5)),
|
||||
at, windPositionMode);
|
||||
Wind wind = getTrackedRace().getWind(effectivePosition, at);
|
||||
if (wind == null) {
|
||||
result = pos2.alongTrackDistance(pos1, cache.getLegBearing(this, at));
|
||||
} else {
|
||||
Position projectionToLineThroughPos2 = pos1.projectToLineThrough(pos2, wind.getBearing());
|
||||
result = pos2.alongTrackDistance(projectionToLineThroughPos2,
|
||||
legType == LegType.UPWIND ? wind.getFrom() : wind.getBearing());
|
||||
}
|
||||
} else {
|
||||
Position projectionToLineThroughPos2 = pos1.projectToLineThrough(pos2, wind.getBearing());
|
||||
return pos2.alongTrackDistance(projectionToLineThroughPos2, legType == LegType.UPWIND ? wind.getFrom() : wind.getBearing());
|
||||
// reaching leg, return distance projected onto leg's bearing
|
||||
result = pos2.alongTrackDistance(pos1, cache.getLegBearing(this, at));
|
||||
}
|
||||
} else {
|
||||
// reaching leg, return distance projected onto leg's bearing
|
||||
return pos2.alongTrackDistance(pos1, cache.getLegBearing(this, at));
|
||||
} catch (NoWindException e) {
|
||||
// no wind information; use along-track distance as fallback
|
||||
result = pos2.alongTrackDistance(pos1, cache.getLegBearing(this, at));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
+25
-24
@@ -6,7 +6,6 @@ import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Leg;
|
||||
@@ -175,8 +174,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
|
||||
|
||||
|
||||
@Override
|
||||
public Distance getWindwardDistanceToGo(TimePoint timePoint, WindPositionMode windPositionMode,
|
||||
WindLegTypeAndLegBearingCache cache) throws NoWindException {
|
||||
public Distance getWindwardDistanceToGo(TimePoint timePoint, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) {
|
||||
if (hasFinishedLeg(timePoint)) {
|
||||
return Distance.NULL;
|
||||
} else {
|
||||
@@ -192,7 +190,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Distance getWindwardDistanceToGo(TimePoint timePoint, WindPositionMode windPositionMode) throws NoWindException {
|
||||
public Distance getWindwardDistanceToGo(TimePoint timePoint, WindPositionMode windPositionMode) {
|
||||
return getWindwardDistanceToGo(timePoint, windPositionMode, new NoCachingWindLegTypeAndLegBearingCache());
|
||||
}
|
||||
|
||||
@@ -202,12 +200,11 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
|
||||
* bearing, and the distance from the projection to the <code>mark</code> is returned. Otherwise, it is assumed that
|
||||
* the leg is neither an upwind nor a downwind leg, and hence the true distance to <code>mark</code> is returned. A
|
||||
* cache for wind and leg type / bearing can be passed to avoid their redundant calculation during a single
|
||||
* round-trip.
|
||||
* round-trip.<p>
|
||||
*
|
||||
* @throws NoWindException
|
||||
* If no wind information is available, again the true geometrical distance to <code>mark</code> is returned.
|
||||
*/
|
||||
private Distance getWindwardDistanceTo(Mark mark, TimePoint at, WindPositionMode windPositionMode,
|
||||
WindLegTypeAndLegBearingCache cache) throws NoWindException {
|
||||
private Distance getWindwardDistanceTo(Mark mark, TimePoint at, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) {
|
||||
Position estimatedPosition = getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(at, false);
|
||||
if (!hasStartedLeg(at) || estimatedPosition == null) {
|
||||
// covers the case with no fixes for this leg yet, also if the mark passing has already been received
|
||||
@@ -233,26 +230,30 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
|
||||
* @throws NoWindException in case the wind direction is not known
|
||||
*/
|
||||
private SpeedWithBearing getWindwardSpeed(SpeedWithBearing speed, final TimePoint at, WindPositionMode windPositionMode,
|
||||
WindLegTypeAndLegBearingCache cache) throws NoWindException {
|
||||
WindLegTypeAndLegBearingCache cache) {
|
||||
SpeedWithBearing result = null;
|
||||
if (speed != null) {
|
||||
Bearing projectToBearing;
|
||||
if (cache.getLegType(getTrackedLeg(), at) != LegType.REACHING) {
|
||||
final Wind wind;
|
||||
if (windPositionMode == WindPositionMode.EXACT) {
|
||||
wind = cache.getWind(getTrackedRace(), getCompetitor(), at);
|
||||
try {
|
||||
if (cache.getLegType(getTrackedLeg(), at) != LegType.REACHING) {
|
||||
final Wind wind;
|
||||
if (windPositionMode == WindPositionMode.EXACT) {
|
||||
wind = cache.getWind(getTrackedRace(), getCompetitor(), at);
|
||||
} else {
|
||||
wind = getTrackedRace().getWind(
|
||||
getTrackedLeg().getEffectiveWindPosition(
|
||||
() -> getTrackedRace().getTrack(getCompetitor())
|
||||
.getEstimatedPosition(at, false), at, windPositionMode), at);
|
||||
}
|
||||
if (wind == null) {
|
||||
throw new NoWindException("Need at least wind direction to determine windward speed");
|
||||
}
|
||||
projectToBearing = wind.getBearing();
|
||||
} else {
|
||||
wind = getTrackedRace().getWind(
|
||||
getTrackedLeg().getEffectiveWindPosition(
|
||||
new Callable<Position>() { @Override public Position call() {
|
||||
return getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(at, false);
|
||||
}}, at, windPositionMode), at);
|
||||
projectToBearing = cache.getLegBearing(getTrackedLeg(), at);
|
||||
}
|
||||
if (wind == null) {
|
||||
throw new NoWindException("Need at least wind direction to determine windward speed");
|
||||
}
|
||||
projectToBearing = wind.getBearing();
|
||||
} else {
|
||||
} catch (NoWindException nwe) {
|
||||
// as fallback in the absence of wind information, project to leg bearing
|
||||
projectToBearing = cache.getLegBearing(getTrackedLeg(), at);
|
||||
}
|
||||
double cos = Math.cos(speed.getBearing().getRadians() - projectToBearing.getRadians());
|
||||
@@ -608,7 +609,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Speed getVelocityMadeGood(TimePoint at, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) throws NoWindException {
|
||||
public Speed getVelocityMadeGood(TimePoint at, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) {
|
||||
if (hasStartedLeg(at)) {
|
||||
TimePoint timePoint;
|
||||
if (hasFinishedLeg(at)) {
|
||||
|
||||
+197
-106
@@ -77,6 +77,8 @@ import com.sap.sailing.domain.common.confidence.impl.BearingWithConfidenceImpl;
|
||||
import com.sap.sailing.domain.common.confidence.impl.HyperbolicTimeDifferenceWeigher;
|
||||
import com.sap.sailing.domain.common.confidence.impl.PositionAndTimePointWeigher;
|
||||
import com.sap.sailing.domain.common.impl.CentralAngleDistance;
|
||||
import com.sap.sailing.domain.common.impl.CourseChangeImpl;
|
||||
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.common.impl.NauticalMileDistance;
|
||||
@@ -2066,7 +2068,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GPSFixMoving> approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to) {
|
||||
public Iterable<GPSFixMoving> approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to) {
|
||||
DouglasPeucker<Competitor, GPSFixMoving> douglasPeucker = new DouglasPeucker<Competitor, GPSFixMoving>(
|
||||
getTrack(competitor));
|
||||
return douglasPeucker.approximate(maxDistance, from, to);
|
||||
@@ -2195,11 +2197,11 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
* @return an empty list if no maneuver is detected for <code>competitor</code> between <code>from</code> and
|
||||
* <code>to</code>, or else the list of maneuvers detected.
|
||||
*/
|
||||
private List<Maneuver> detectManeuvers(Competitor competitor, List<GPSFixMoving> approximatingFixesToAnalyze,
|
||||
private List<Maneuver> detectManeuvers(Competitor competitor, Iterable<GPSFixMoving> approximatingFixesToAnalyze,
|
||||
boolean ignoreMarkPassings, TimePoint earliestManeuverStart, TimePoint latestManeuverEnd)
|
||||
throws NoWindException {
|
||||
List<Maneuver> result = new ArrayList<Maneuver>();
|
||||
if (approximatingFixesToAnalyze.size() > 2) {
|
||||
if (Util.size(approximatingFixesToAnalyze) > 2) {
|
||||
List<com.sap.sse.common.Util.Pair<GPSFixMoving, CourseChange>> courseChangeSequenceInSameDirection = new ArrayList<com.sap.sse.common.Util.Pair<GPSFixMoving, CourseChange>>();
|
||||
Iterator<GPSFixMoving> approximationPointsIter = approximatingFixesToAnalyze.iterator();
|
||||
GPSFixMoving previous = approximationPointsIter.next();
|
||||
@@ -2208,15 +2210,24 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
SpeedWithBearing speedWithBearingOnApproximationFromPreviousToCurrent = previous
|
||||
.getSpeedAndBearingRequiredToReach(current);
|
||||
SpeedWithBearing speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges = speedWithBearingOnApproximationFromPreviousToCurrent;
|
||||
SpeedWithBearing speedWithBearingOnApproximationFromCurrentToNext; // will certainly be assigned because
|
||||
// iter's collection's size > 2
|
||||
SpeedWithBearing speedWithBearingOnApproximationFromCurrentToNext; // will certainly be assigned because iter's collection's size > 2
|
||||
do {
|
||||
GPSFixMoving next = approximationPointsIter.next();
|
||||
// traveling on great circle segments from one approximation point to the next
|
||||
speedWithBearingOnApproximationFromCurrentToNext = current.getSpeedAndBearingRequiredToReach(next);
|
||||
// compute course change on "approximation track"
|
||||
// FIXME bug 2009: when a maneuver (particularly a penalty circle) is executed at high turn rates, approximations may lead to turns >180deg, hence inferred to turn the wrong way; need to loop across the non-approximated fixes here!
|
||||
CourseChange courseChange = speedWithBearingOnApproximationFromPreviousToCurrent
|
||||
.getCourseChangeRequiredToReach(speedWithBearingOnApproximationFromCurrentToNext);
|
||||
Bearing courseChangeOnOriginalFixes = getCourseChange(competitor, previous.getTimePoint(), next.getTimePoint());
|
||||
// check for the case where the course change between the approximation fixes may have been >180deg by comparing the direction
|
||||
// of the course change on the approximation points with the direction of the course change during the same time range on the
|
||||
// original fixes (see also bug 2009):
|
||||
if (Math.abs(courseChangeOnOriginalFixes.getDegrees()) > 180 &&
|
||||
Math.signum(courseChange.getCourseChangeInDegrees()) != Math.signum(courseChangeOnOriginalFixes.getDegrees())) {
|
||||
courseChange = new CourseChangeImpl(-Math.signum(courseChange.getCourseChangeInDegrees())*(360.0-Math.abs(courseChange.getCourseChangeInDegrees())),
|
||||
courseChange.getSpeedChangeInKnots());
|
||||
}
|
||||
com.sap.sse.common.Util.Pair<GPSFixMoving, CourseChange> courseChangeAtFix = new com.sap.sse.common.Util.Pair<GPSFixMoving, CourseChange>(current,
|
||||
courseChange);
|
||||
if (!courseChangeSequenceInSameDirection.isEmpty()
|
||||
@@ -2245,6 +2256,34 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* On <code>competitor</code>'s track iterates the fixes starting after <code>startExclusive</code> until
|
||||
* <code>endExclusive</code> or any later fix has been reached and sums up the direction change as a "bearing." A
|
||||
* negative sign means a direction change to port, a positive sign means a direction change to starboard.
|
||||
*/
|
||||
private Bearing getCourseChange(Competitor competitor, TimePoint startExclusive, TimePoint endExclusive) {
|
||||
Bearing directionChangeInDegrees = new DegreeBearingImpl(0);
|
||||
GPSFixTrack<Competitor, GPSFixMoving> track = getTrack(competitor);
|
||||
track.lockForRead();
|
||||
try {
|
||||
GPSFixMoving previous = null;
|
||||
GPSFixMoving fix = null;
|
||||
for (Iterator<GPSFixMoving> i=track.getFixesIterator(startExclusive, /* inclusive */ false);
|
||||
i.hasNext() && (previous == null || !previous.getTimePoint().after(endExclusive));
|
||||
previous = fix) {
|
||||
fix = i.next();
|
||||
if (previous != null) {
|
||||
directionChangeInDegrees = new DegreeBearingImpl(directionChangeInDegrees.getDegrees()
|
||||
+ previous.getSpeed().getBearing().getDifferenceTo(fix.getSpeed().getBearing())
|
||||
.getDegrees());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
track.unlockAfterRead();
|
||||
}
|
||||
return directionChangeInDegrees;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches results from {@link #maneuverCache}. The cache is updated asynchronously after relevant updates have been
|
||||
* received (see {@link #triggerManeuverCacheRecalculation(Competitor)} and
|
||||
@@ -2258,8 +2297,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
* <code>competitor</code>, reduced to the interval requested, will be returned.
|
||||
*/
|
||||
@Override
|
||||
public List<Maneuver> getManeuvers(Competitor competitor, TimePoint from, TimePoint to, boolean waitForLatest)
|
||||
throws NoWindException {
|
||||
public List<Maneuver> getManeuvers(Competitor competitor, TimePoint from, TimePoint to, boolean waitForLatest) {
|
||||
com.sap.sse.common.Util.Triple<TimePoint, TimePoint, List<Maneuver>> allManeuvers = maneuverCache.get(competitor, waitForLatest);
|
||||
List<Maneuver> result;
|
||||
if (allManeuvers == null) {
|
||||
@@ -2284,12 +2322,13 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
/**
|
||||
* Groups the {@link CourseChange} sequence into groups where the times of the fixes at which the course changes
|
||||
* took place are no further apart than {@link #getApproximateManeuverDurationInMilliseconds()} milliseconds or
|
||||
* where the distances of those course changes are less than two hull lengths apart. For those, a single
|
||||
* where the distances of those course changes are less than three hull lengths apart. For those, a single
|
||||
* {@link Maneuver} object is created and added to the resulting list. The maneuver sums up the direction changes of
|
||||
* the individual {@link CourseChange} objects. This can result in direction changes of more than 180 degrees in one
|
||||
* direction which may, e.g., represent a penalty circle or a mark rounding maneuver. As the maneuver's time point,
|
||||
* the average time point of the course changes that went into the maneuver construction is used.
|
||||
* <p>
|
||||
*
|
||||
* @param speedWithBearingOnApproximationAtBeginning
|
||||
* the speed/bearing before the first approximating fix passed in
|
||||
* <code>courseChangeSequenceInSameDirection</code>
|
||||
@@ -2322,11 +2361,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
List<com.sap.sse.common.Util.Pair<GPSFixMoving, CourseChange>> group = new ArrayList<com.sap.sse.common.Util.Pair<GPSFixMoving, CourseChange>>();
|
||||
if (!courseChangeSequenceInSameDirection.isEmpty()) {
|
||||
Distance threeHullLengths = competitor.getBoat().getBoatClass().getHullLength().scale(3);
|
||||
SpeedWithBearing beforeGroupOnApproximation = speedWithBearingOnApproximationAtBeginning; // speed/bearing
|
||||
// before group
|
||||
SpeedWithBearing beforeCurrentCourseChangeOnApproximation = beforeGroupOnApproximation; // speed/bearing
|
||||
// before current
|
||||
// course change
|
||||
SpeedWithBearing beforeGroupOnApproximation = speedWithBearingOnApproximationAtBeginning; // speed/bearing before group
|
||||
SpeedWithBearing beforeCurrentCourseChangeOnApproximation = beforeGroupOnApproximation; // speed/bearing before current course change
|
||||
Iterator<com.sap.sse.common.Util.Pair<GPSFixMoving, CourseChange>> iter = courseChangeSequenceInSameDirection.iterator();
|
||||
double totalCourseChangeInDegrees = 0.0;
|
||||
long totalMilliseconds = 0l;
|
||||
@@ -2382,8 +2418,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
TimePoint maneuverTimePoint = computeManeuverTimepoint(competitor, timePointBeforeManeuver, timePointAfterManeuver);
|
||||
final GPSFixTrack<Competitor, GPSFixMoving> competitorTrack = getTrack(competitor);
|
||||
Position maneuverPosition = competitorTrack.getEstimatedPosition(maneuverTimePoint, /* extrapolate */false);
|
||||
Tack tackAfterManeuver = getTack(maneuverPosition, timePointAfterManeuver,
|
||||
speedWithBearingOnApproximationAtEnd.getBearing());
|
||||
final Wind wind = getWind(maneuverPosition, maneuverTimePoint);
|
||||
final Tack tackAfterManeuver = wind == null ? null : getTack(maneuverPosition, timePointAfterManeuver, speedWithBearingOnApproximationAtEnd.getBearing());
|
||||
ManeuverType maneuverType;
|
||||
Distance maneuverLoss = null;
|
||||
// the TrackedLegOfCompetitor variables may be null, e.g., in case the time points are before or after the race
|
||||
@@ -2412,18 +2448,15 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
} else {
|
||||
markPassingTimePoint = null;
|
||||
}
|
||||
final Wind wind = getWind(maneuverPosition, maneuverTimePoint);
|
||||
final SpeedWithBearing estimatedSpeedBeforeManeuver = competitorTrack.getEstimatedSpeed(timePointBeforeManeuver);
|
||||
final SpeedWithBearing estimatedSpeedAfterManeuver = competitorTrack.getEstimatedSpeed(timePointAfterManeuver);
|
||||
if (wind != null && estimatedSpeedBeforeManeuver != null && estimatedSpeedAfterManeuver != null) {
|
||||
if (estimatedSpeedBeforeManeuver != null && estimatedSpeedAfterManeuver != null) {
|
||||
BearingChangeAnalyzer bearingChangeAnalyzer = BearingChangeAnalyzer.INSTANCE;
|
||||
final Bearing courseBeforeManeuver = estimatedSpeedBeforeManeuver.getBearing();
|
||||
final Bearing courseAfterManeuver = estimatedSpeedAfterManeuver.getBearing();
|
||||
boolean jibed = bearingChangeAnalyzer.didPass(courseBeforeManeuver, totalCourseChangeInDegrees,
|
||||
courseAfterManeuver, wind.getBearing());
|
||||
boolean tacked = bearingChangeAnalyzer.didPass(courseBeforeManeuver, totalCourseChangeInDegrees,
|
||||
courseAfterManeuver, wind.getFrom());
|
||||
if (markPassingTimePoint != null && (tacked || jibed)) {
|
||||
int numberOfJibes = wind == null ? 0 : bearingChangeAnalyzer.didPass(courseBeforeManeuver, totalCourseChangeInDegrees, courseAfterManeuver, wind.getBearing());
|
||||
int numberOfTacks = wind == null ? 0 : bearingChangeAnalyzer.didPass(courseBeforeManeuver, totalCourseChangeInDegrees, courseAfterManeuver, wind.getFrom());
|
||||
if (markPassingTimePoint != null && (numberOfTacks + numberOfJibes > 0)) {
|
||||
// In case of a mark passing we need to split the maneuver analysis into the phase before and after
|
||||
// the mark passing. First of all, this is important to identify the correct maneuver time point for
|
||||
// each tack and jibe, second it is essential to call a penalty which is only the case if the tack and
|
||||
@@ -2434,50 +2467,112 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
result.addAll(detectManeuvers(competitor, timePointBeforeManeuver, markPassingTimePoint.minus(1), /* ignoreMarkPassings */ true));
|
||||
result.addAll(detectManeuvers(competitor, markPassingTimePoint.plus(1), timePointAfterManeuver, /* ignoreMarkPassings */ true));
|
||||
} else {
|
||||
// Either there was no mark passing, or the mark passing was not accompanied by a tack or a jibe
|
||||
if (tacked && jibed && markPassingTimePoint == null) {
|
||||
// Either there was no mark passing, or the mark passing was not accompanied by a tack or a jibe.
|
||||
// For the first tack/jibe combination (they must alternate because course changes to in the same direction and
|
||||
// the wind is considered sufficiently stable to not allow for two successive tacks or two successive jibes)
|
||||
// we create a PENALTY_CIRCLE maneuver and recurse for the time interval after the first penalty circle has completed.
|
||||
if (numberOfTacks>0 && numberOfJibes>0 && markPassingTimePoint == null) {
|
||||
TimePointAndTotalCourseChangeInDegrees firstPenaltyCircleCompletedAt = getTimePointOfCompletionOfFirstPenaltyCircle(courseBeforeManeuver, group, wind);
|
||||
maneuverType = ManeuverType.PENALTY_CIRCLE;
|
||||
if (legBeforeManeuver != null) {
|
||||
maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, maneuverTimePoint,
|
||||
timePointAfterManeuver);
|
||||
maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, maneuverTimePoint, firstPenaltyCircleCompletedAt.getTimePoint());
|
||||
}
|
||||
} else if (tacked) {
|
||||
maneuverType = ManeuverType.TACK;
|
||||
if (legBeforeManeuver != null) {
|
||||
maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, maneuverTimePoint,
|
||||
timePointAfterManeuver);
|
||||
}
|
||||
} else if (jibed) {
|
||||
maneuverType = ManeuverType.JIBE;
|
||||
if (legBeforeManeuver != null) {
|
||||
maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver, maneuverTimePoint,
|
||||
timePointAfterManeuver);
|
||||
TimePoint penaltyTimePoint = computeManeuverTimepoint(competitor, timePointBeforeManeuver, firstPenaltyCircleCompletedAt.getTimePoint());
|
||||
Position penaltyPosition = competitorTrack.getEstimatedPosition(penaltyTimePoint, /* extrapolate */ false);
|
||||
final Maneuver maneuver = new ManeuverImpl(maneuverType, tackAfterManeuver, penaltyPosition,
|
||||
penaltyTimePoint, speedWithBearingOnApproximationAtBeginning,
|
||||
competitorTrack.getEstimatedSpeed(penaltyTimePoint), firstPenaltyCircleCompletedAt.getTotalCourseChangeInDegrees(), maneuverLoss);
|
||||
result.add(maneuver);
|
||||
// after we've "consumed" one tack and one jibe, recursively find more maneuvers if tacks and/or jibes remain
|
||||
if (numberOfTacks>1 || numberOfJibes>1) {
|
||||
result.addAll(detectManeuvers(competitor, firstPenaltyCircleCompletedAt.getTimePoint(), timePointAfterManeuver, /* ignoreMarkPassings */ true));
|
||||
}
|
||||
} else {
|
||||
// heading up or bearing away
|
||||
Bearing windBearing = wind.getBearing();
|
||||
Bearing toWindBeforeManeuver = windBearing
|
||||
.getDifferenceTo(speedWithBearingOnApproximationAtBeginning.getBearing());
|
||||
Bearing toWindAfterManeuver = windBearing.getDifferenceTo(speedWithBearingOnApproximationAtEnd
|
||||
.getBearing());
|
||||
maneuverType = Math.abs(toWindBeforeManeuver.getDegrees()) < Math.abs(toWindAfterManeuver
|
||||
.getDegrees()) ? ManeuverType.HEAD_UP : ManeuverType.BEAR_AWAY;
|
||||
if (numberOfTacks > 0) {
|
||||
maneuverType = ManeuverType.TACK;
|
||||
if (legBeforeManeuver != null) {
|
||||
maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver,
|
||||
maneuverTimePoint, timePointAfterManeuver);
|
||||
}
|
||||
} else if (numberOfJibes > 0) {
|
||||
maneuverType = ManeuverType.JIBE;
|
||||
if (legBeforeManeuver != null) {
|
||||
maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver,
|
||||
maneuverTimePoint, timePointAfterManeuver);
|
||||
}
|
||||
} else {
|
||||
if (wind != null) {
|
||||
// heading up or bearing away
|
||||
Bearing windBearing = wind.getBearing();
|
||||
Bearing toWindBeforeManeuver = windBearing
|
||||
.getDifferenceTo(speedWithBearingOnApproximationAtBeginning.getBearing());
|
||||
Bearing toWindAfterManeuver = windBearing
|
||||
.getDifferenceTo(speedWithBearingOnApproximationAtEnd.getBearing());
|
||||
maneuverType = Math.abs(toWindBeforeManeuver.getDegrees()) < Math.abs(toWindAfterManeuver
|
||||
.getDegrees()) ? ManeuverType.HEAD_UP : ManeuverType.BEAR_AWAY;
|
||||
} else {
|
||||
// no wind information; marking as UNKNOWN
|
||||
maneuverType = ManeuverType.UNKNOWN;
|
||||
if (legBeforeManeuver != null) {
|
||||
maneuverLoss = legBeforeManeuver.getManeuverLoss(timePointBeforeManeuver,
|
||||
maneuverTimePoint, timePointAfterManeuver);
|
||||
}
|
||||
}
|
||||
}
|
||||
final Maneuver maneuver = new ManeuverImpl(maneuverType, tackAfterManeuver, maneuverPosition,
|
||||
maneuverTimePoint, speedWithBearingOnApproximationAtBeginning,
|
||||
speedWithBearingOnApproximationAtEnd, totalCourseChangeInDegrees, maneuverLoss);
|
||||
result.add(maneuver);
|
||||
}
|
||||
final Maneuver maneuver = new ManeuverImpl(maneuverType, tackAfterManeuver, maneuverPosition,
|
||||
maneuverTimePoint, speedWithBearingOnApproximationAtBeginning,
|
||||
speedWithBearingOnApproximationAtEnd, totalCourseChangeInDegrees, maneuverLoss);
|
||||
result.add(maneuver);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class TimePointAndTotalCourseChangeInDegrees {
|
||||
private final TimePoint timePoint;
|
||||
private final double totalCourseChangeInDegrees;
|
||||
protected TimePointAndTotalCourseChangeInDegrees(TimePoint timePoint, double totalCourseChangeInDegrees) {
|
||||
super();
|
||||
this.timePoint = timePoint;
|
||||
this.totalCourseChangeInDegrees = totalCourseChangeInDegrees;
|
||||
}
|
||||
public TimePoint getTimePoint() {
|
||||
return timePoint;
|
||||
}
|
||||
public double getTotalCourseChangeInDegrees() {
|
||||
return totalCourseChangeInDegrees;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starting at <code>timePointBeforeManeuver</code>, and assuming that the group of <code>approximatedFixesAndCourseChanges</code>
|
||||
* contains at least a tack and a jibe, finds the earliest approximated fix's time point at which a tack and a jibe have been
|
||||
* completed.
|
||||
*/
|
||||
private TimePointAndTotalCourseChangeInDegrees getTimePointOfCompletionOfFirstPenaltyCircle(Bearing courseBeforeManeuver, Iterable<Pair<GPSFixMoving, CourseChange>> approximatedFixesAndCourseChanges, Wind wind) {
|
||||
double totalCourseChangeInDegrees = 0;
|
||||
TimePoint timePoint = null;
|
||||
BearingChangeAnalyzer bearingChangeAnalyzer = BearingChangeAnalyzer.INSTANCE;
|
||||
Bearing newCourse = courseBeforeManeuver;
|
||||
for (Pair<GPSFixMoving, CourseChange> fixAndCourseChange : approximatedFixesAndCourseChanges) {
|
||||
totalCourseChangeInDegrees += fixAndCourseChange.getB().getCourseChangeInDegrees();
|
||||
newCourse = newCourse.add(new DegreeBearingImpl(fixAndCourseChange.getB().getCourseChangeInDegrees()));
|
||||
int numberOfJibes = bearingChangeAnalyzer.didPass(courseBeforeManeuver, totalCourseChangeInDegrees, newCourse, wind.getBearing());
|
||||
int numberOfTacks = bearingChangeAnalyzer.didPass(courseBeforeManeuver, totalCourseChangeInDegrees, newCourse, wind.getFrom());
|
||||
if (numberOfJibes > 0 && numberOfTacks > 0) {
|
||||
timePoint = fixAndCourseChange.getA().getTimePoint();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new TimePointAndTotalCourseChangeInDegrees(timePoint, totalCourseChangeInDegrees);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the maneuver time point as the time point along between maneuver start and end where the competitor's
|
||||
* track has greatest change in course.
|
||||
*/
|
||||
private TimePoint computeManeuverTimepoint(Competitor competitor, TimePoint timePointBeforeManeuver,
|
||||
TimePoint timePointAfterManeuver) {
|
||||
private TimePoint computeManeuverTimepoint(Competitor competitor, TimePoint timePointBeforeManeuver, TimePoint timePointAfterManeuver) {
|
||||
TimePoint result = timePointBeforeManeuver;
|
||||
GPSFixTrack<Competitor, GPSFixMoving> track = getTrack(competitor);
|
||||
GPSFixMoving lastFix = null;
|
||||
@@ -2922,63 +3017,59 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
LineMarksWithPositions marksAndPositions = getLineMarksAndPositions(timePoint, waypoint);
|
||||
LineDetails result = null;
|
||||
if (marksAndPositions != null) {
|
||||
try {
|
||||
final TrackedLeg legDeterminingDirection = getLegDeterminingDirectionInWhichToPassWaypoint(waypoint);
|
||||
final Mark portMarkWhileApproachingLine = marksAndPositions.getPortMarkWhileApproachingLine();
|
||||
final Mark starboardMarkWhileApproachingLine = marksAndPositions.getStarboardMarkWhileApproachingLine();
|
||||
final Position portMarkPositionWhileApproachingLine = marksAndPositions
|
||||
.getPortMarkPositionWhileApproachingLine();
|
||||
final Position starboardMarkPositionWhileApproachingLine = marksAndPositions
|
||||
.getStarboardMarkPositionWhileApproachingLine();
|
||||
final Bearing differenceToCombinedWind;
|
||||
final NauticalSide advantageousSideWhileApproachingLine;
|
||||
final Distance distanceAdvantage;
|
||||
Wind combinedWind = getWind(starboardMarkPositionWhileApproachingLine, timePoint);
|
||||
if (combinedWind != null) {
|
||||
differenceToCombinedWind = portMarkPositionWhileApproachingLine.getBearingGreatCircle(
|
||||
starboardMarkPositionWhileApproachingLine).getDifferenceTo(combinedWind.getFrom());
|
||||
Distance windwardDistanceFromFirstToSecondMark;
|
||||
windwardDistanceFromFirstToSecondMark = legDeterminingDirection.getWindwardDistance(
|
||||
portMarkPositionWhileApproachingLine, starboardMarkPositionWhileApproachingLine, timePoint,
|
||||
WindPositionMode.EXACT);
|
||||
final Position worseMarkPosition;
|
||||
final Position betterMarkPosition;
|
||||
final int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(waypoint);
|
||||
final boolean isStartLine = indexOfWaypoint == 0;
|
||||
if ((isStartLine && windwardDistanceFromFirstToSecondMark.getMeters() > 0)
|
||||
|| (!isStartLine && windwardDistanceFromFirstToSecondMark.getMeters() < 0)) {
|
||||
// first mark is worse than second mark
|
||||
worseMarkPosition = portMarkPositionWhileApproachingLine;
|
||||
betterMarkPosition = starboardMarkPositionWhileApproachingLine;
|
||||
} else {
|
||||
// second mark is worse than first mark
|
||||
worseMarkPosition = starboardMarkPositionWhileApproachingLine;
|
||||
betterMarkPosition = portMarkPositionWhileApproachingLine;
|
||||
}
|
||||
if (windwardDistanceFromFirstToSecondMark.getMeters() >= 0) {
|
||||
distanceAdvantage = windwardDistanceFromFirstToSecondMark;
|
||||
} else {
|
||||
distanceAdvantage = new CentralAngleDistance(
|
||||
-windwardDistanceFromFirstToSecondMark.getCentralAngleRad());
|
||||
}
|
||||
if (betterMarkPosition.crossTrackError(worseMarkPosition,
|
||||
legDeterminingDirection.getLegBearing(timePoint)).getCentralAngleRad() > 0) {
|
||||
advantageousSideWhileApproachingLine = NauticalSide.STARBOARD;
|
||||
} else {
|
||||
advantageousSideWhileApproachingLine = NauticalSide.PORT;
|
||||
}
|
||||
} else { // no wind information
|
||||
differenceToCombinedWind = null;
|
||||
advantageousSideWhileApproachingLine = null;
|
||||
distanceAdvantage = null;
|
||||
final TrackedLeg legDeterminingDirection = getLegDeterminingDirectionInWhichToPassWaypoint(waypoint);
|
||||
final Mark portMarkWhileApproachingLine = marksAndPositions.getPortMarkWhileApproachingLine();
|
||||
final Mark starboardMarkWhileApproachingLine = marksAndPositions.getStarboardMarkWhileApproachingLine();
|
||||
final Position portMarkPositionWhileApproachingLine = marksAndPositions
|
||||
.getPortMarkPositionWhileApproachingLine();
|
||||
final Position starboardMarkPositionWhileApproachingLine = marksAndPositions
|
||||
.getStarboardMarkPositionWhileApproachingLine();
|
||||
final Bearing differenceToCombinedWind;
|
||||
final NauticalSide advantageousSideWhileApproachingLine;
|
||||
final Distance distanceAdvantage;
|
||||
Wind combinedWind = getWind(starboardMarkPositionWhileApproachingLine, timePoint);
|
||||
if (combinedWind != null) {
|
||||
differenceToCombinedWind = portMarkPositionWhileApproachingLine.getBearingGreatCircle(
|
||||
starboardMarkPositionWhileApproachingLine).getDifferenceTo(combinedWind.getFrom());
|
||||
Distance windwardDistanceFromFirstToSecondMark;
|
||||
windwardDistanceFromFirstToSecondMark = legDeterminingDirection.getWindwardDistance(
|
||||
portMarkPositionWhileApproachingLine, starboardMarkPositionWhileApproachingLine, timePoint,
|
||||
WindPositionMode.EXACT);
|
||||
final Position worseMarkPosition;
|
||||
final Position betterMarkPosition;
|
||||
final int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(waypoint);
|
||||
final boolean isStartLine = indexOfWaypoint == 0;
|
||||
if ((isStartLine && windwardDistanceFromFirstToSecondMark.getMeters() > 0)
|
||||
|| (!isStartLine && windwardDistanceFromFirstToSecondMark.getMeters() < 0)) {
|
||||
// first mark is worse than second mark
|
||||
worseMarkPosition = portMarkPositionWhileApproachingLine;
|
||||
betterMarkPosition = starboardMarkPositionWhileApproachingLine;
|
||||
} else {
|
||||
// second mark is worse than first mark
|
||||
worseMarkPosition = starboardMarkPositionWhileApproachingLine;
|
||||
betterMarkPosition = portMarkPositionWhileApproachingLine;
|
||||
}
|
||||
result = new LineDetailsImpl(timePoint, waypoint,
|
||||
portMarkPositionWhileApproachingLine.getDistance(starboardMarkPositionWhileApproachingLine),
|
||||
differenceToCombinedWind, advantageousSideWhileApproachingLine, distanceAdvantage,
|
||||
portMarkWhileApproachingLine, starboardMarkWhileApproachingLine);
|
||||
} catch (NoWindException e) {
|
||||
// result remains null;
|
||||
if (windwardDistanceFromFirstToSecondMark.getMeters() >= 0) {
|
||||
distanceAdvantage = windwardDistanceFromFirstToSecondMark;
|
||||
} else {
|
||||
distanceAdvantage = new CentralAngleDistance(
|
||||
-windwardDistanceFromFirstToSecondMark.getCentralAngleRad());
|
||||
}
|
||||
if (betterMarkPosition.crossTrackError(worseMarkPosition,
|
||||
legDeterminingDirection.getLegBearing(timePoint)).getCentralAngleRad() > 0) {
|
||||
advantageousSideWhileApproachingLine = NauticalSide.STARBOARD;
|
||||
} else {
|
||||
advantageousSideWhileApproachingLine = NauticalSide.PORT;
|
||||
}
|
||||
} else { // no wind information
|
||||
differenceToCombinedWind = null;
|
||||
advantageousSideWhileApproachingLine = null;
|
||||
distanceAdvantage = null;
|
||||
}
|
||||
result = new LineDetailsImpl(timePoint, waypoint,
|
||||
portMarkPositionWhileApproachingLine.getDistance(starboardMarkPositionWhileApproachingLine),
|
||||
differenceToCombinedWind, advantageousSideWhileApproachingLine, distanceAdvantage,
|
||||
portMarkWhileApproachingLine, starboardMarkWhileApproachingLine);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+10
-6
@@ -17,12 +17,16 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
|
||||
/**
|
||||
* Emulates a collection of {@link Wind} fixes for a {@link TrackedRace}. Subclasses have to specify a resolution and a
|
||||
* way to compute a wind fix for a given time point. If not constrained by a {@link #from} and/or a {@link #to} time
|
||||
* point, an equidistant time field is assumed, starting at {@link TrackedRace#getStart()} and leading up to
|
||||
* {@link TrackedRace#getTimePointOfNewestEvent()}. If {@link TrackedRace#getStart()} returns <code>null</code>,
|
||||
* {@link Long#MAX_VALUE} is used as the {@link #from} time point, pushing the start to the more or less infinite future
|
||||
* ("end of the universe"). If no event was received yet and hence {@link TrackedRace#getTimePointOfNewestEvent()}
|
||||
* returns <code>null</code>, the {@link #to} end is assumed to be the beginning of the epoch (1970-01-01T00:00:00).
|
||||
* way to compute a wind fix for a given time point in the form of an implementation of the
|
||||
* {@link #getWind(Position, TimePoint)} method.
|
||||
* <p>
|
||||
*
|
||||
* If not constrained by a {@link #from} and/or a {@link #to} time point, an equidistant time field is assumed, starting
|
||||
* at {@link TrackedRace#getStart()} and leading up to {@link TrackedRace#getTimePointOfNewestEvent()}. If
|
||||
* {@link TrackedRace#getStart()} returns <code>null</code>, {@link Long#MAX_VALUE} is used as the {@link #from} time
|
||||
* point, pushing the start to the more or less infinite future ("end of the universe"). If no event was received yet
|
||||
* and hence {@link TrackedRace#getTimePointOfNewestEvent()} returns <code>null</code>, the {@link #to} end is assumed
|
||||
* to be the beginning of the epoch (1970-01-01T00:00:00).
|
||||
*
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
|
||||
+3
-3
@@ -38,7 +38,7 @@ import com.sap.sailing.domain.common.impl.DegreePosition;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.PolarSheetGenerationSettingsImpl;
|
||||
import com.sap.sailing.domain.common.impl.WindSourceImpl;
|
||||
import com.sap.sailing.domain.common.impl.WindSteppingWithMaxDistance;
|
||||
import com.sap.sailing.domain.common.impl.WindSpeedSteppingWithMaxDistance;
|
||||
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
|
||||
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
|
||||
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
||||
@@ -66,8 +66,8 @@ public class PolarSheetGenerationServiceTest {
|
||||
|
||||
SailingService service = new MockSailingServiceForPolarSheetGeneration();
|
||||
|
||||
Double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. };
|
||||
WindSteppingWithMaxDistance windStepping = new WindSteppingWithMaxDistance(levels, 2.0);
|
||||
double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. };
|
||||
WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 2.0);
|
||||
PolarSheetGenerationSettings settings = new PolarSheetGenerationSettingsImpl(1, 0, 1, 20, 0, false, true, 5,
|
||||
0.05, false, windStepping, false);
|
||||
|
||||
|
||||
@@ -130,8 +130,9 @@
|
||||
<module>com.sap.sailing.gwt.ui.RegattaOverview</module>
|
||||
<module>com.sap.sailing.gwt.ui.PolarSheets</module>
|
||||
</modules>
|
||||
<style>PRETTY</style>
|
||||
<gen>${basedir}/.generated</gen>
|
||||
<localWorkers>7</localWorkers>
|
||||
<localWorkers>15</localWorkers>
|
||||
<webappDirectory>${basedir}</webappDirectory>
|
||||
<warSourceDirectory>${basedir}/src/main/resources</warSourceDirectory>
|
||||
<!-- Uncomment the following to obtain details split-point reports
|
||||
|
||||
+19
-19
@@ -59,27 +59,27 @@ public class GetLeaderboardByNameAction implements AsyncAction<LeaderboardDTO> {
|
||||
public void execute(final AsyncCallback<LeaderboardDTO> callback) {
|
||||
final long clientTimeWhenRequestWasSent = System.currentTimeMillis();
|
||||
sailingService.getLeaderboardByName(leaderboardName, date, namesOfRacesForWhichToLoadLegDetails, addOverallDetails,
|
||||
previousLeaderboard==null?null:previousLeaderboard.getId(), fillNetPointsUncorrected,
|
||||
new AsyncCallback<IncrementalOrFullLeaderboardDTO>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
callback.onFailure(caught);
|
||||
}
|
||||
previousLeaderboard==null?null:previousLeaderboard.getId(), fillNetPointsUncorrected,
|
||||
new AsyncCallback<IncrementalOrFullLeaderboardDTO>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
callback.onFailure(caught);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(IncrementalOrFullLeaderboardDTO result) {
|
||||
final long clientTimeWhenResponseWasReceived = System.currentTimeMillis();
|
||||
if (result == null) {
|
||||
errorReporter.reportError(stringMessages.errorTryingToObtainLeaderboardContents(leaderboardName), /* silent */ true);
|
||||
} else {
|
||||
if (timerToAdjustOffetIn != null) {
|
||||
timerToAdjustOffetIn.adjustClientServerOffset(clientTimeWhenRequestWasSent,
|
||||
result.getCurrentServerTime(), clientTimeWhenResponseWasReceived);
|
||||
}
|
||||
LeaderboardDTO leaderboardDTOResult = result.getLeaderboardDTO(previousLeaderboard);
|
||||
callback.onSuccess(leaderboardDTOResult);
|
||||
}
|
||||
@Override
|
||||
public void onSuccess(IncrementalOrFullLeaderboardDTO result) {
|
||||
final long clientTimeWhenResponseWasReceived = System.currentTimeMillis();
|
||||
if (result == null) {
|
||||
errorReporter.reportError(stringMessages.errorTryingToObtainLeaderboardContents(leaderboardName), /* silent */ true);
|
||||
} else {
|
||||
if (timerToAdjustOffetIn != null) {
|
||||
timerToAdjustOffetIn.adjustClientServerOffset(clientTimeWhenRequestWasSent,
|
||||
result.getCurrentServerTime(), clientTimeWhenResponseWasReceived);
|
||||
}
|
||||
LeaderboardDTO leaderboardDTOResult = result.getLeaderboardDTO(previousLeaderboard);
|
||||
callback.onSuccess(leaderboardDTOResult);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.sap.sailing.gwt.ui.actions;
|
||||
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMap;
|
||||
import com.sap.sse.gwt.client.async.AsyncAction;
|
||||
|
||||
/**
|
||||
* An asynchronous action to determine whether a suitable polar diagram is available for the race shown on {@link RaceMap}
|
||||
*
|
||||
* @author Christopher Ronnewinkel (D036654)
|
||||
*
|
||||
*/
|
||||
public class GetPolarAction implements AsyncAction<Boolean> {
|
||||
private final SailingServiceAsync sailingService;
|
||||
private final RegattaAndRaceIdentifier raceIdentifier;
|
||||
|
||||
public GetPolarAction(SailingServiceAsync sailingService, RegattaAndRaceIdentifier raceIdentifier) {
|
||||
this.sailingService = sailingService;
|
||||
this.raceIdentifier = raceIdentifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(AsyncCallback<Boolean> callback) {
|
||||
sailingService.getPolarResults(raceIdentifier, callback);
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -9,9 +9,11 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
import com.google.gwt.user.client.Window;
|
||||
import com.google.gwt.user.client.ui.ListBox;
|
||||
import com.sap.sailing.domain.common.BoatClassMasterdata;
|
||||
import com.sap.sailing.gwt.ui.client.AbstractRegattaPanel;
|
||||
import com.sap.sailing.gwt.ui.client.RaceSelectionProvider;
|
||||
import com.sap.sailing.gwt.ui.client.RegattaRefresher;
|
||||
@@ -163,7 +165,11 @@ public abstract class AbstractEventManagementPanel extends AbstractRegattaPanel
|
||||
}
|
||||
|
||||
private boolean checkBoatClassMatch(Iterable<String> boatClassNames, RegattaDTO selectedRegatta) {
|
||||
return Util.contains(boatClassNames, selectedRegatta.boatClass.getName());
|
||||
Set<BoatClassMasterdata> boatClassNamesMappedToMasterData = new HashSet<>();
|
||||
for (String boatClassName : boatClassNames) {
|
||||
boatClassNamesMappedToMasterData.add(BoatClassMasterdata.resolveBoatClass(boatClassName));
|
||||
}
|
||||
return boatClassNamesMappedToMasterData.contains(BoatClassMasterdata.resolveBoatClass(selectedRegatta.boatClass.getName()));
|
||||
}
|
||||
|
||||
}
|
||||
+1
-2
@@ -572,8 +572,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel {
|
||||
tractracPassword, new MarkedAsyncCallback<Void>(new AsyncCallback<Void>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
reportError("Error trying to register races " + selectedRaces + " for tracking: "
|
||||
+ caught.getMessage() + ". Check live/stored URI syntax.");
|
||||
reportError(stringMessages.errorTryingToRegisterRacesForTracking(selectedRaces.toString(), caught.getMessage()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
@@ -137,6 +137,8 @@ public interface SailingService extends RemoteService {
|
||||
WindInfoForRaceDTO getAveragedWindInfo(RegattaAndRaceIdentifier raceIdentifier, Date from, long millisecondsStepWidth,
|
||||
int numberOfFixes, double latDeg, double lngDeg, Collection<String> windSources) throws NoWindException;
|
||||
|
||||
Boolean getPolarResults(RegattaAndRaceIdentifier raceIdentifier);
|
||||
|
||||
SimulatorResultsDTO getSimulatorResults(RegattaAndRaceIdentifier raceIdentifier, Date from, Date prevStartTime);
|
||||
|
||||
CompactRaceMapDataDTO getRaceMapData(RegattaAndRaceIdentifier raceIdentifier, Date date, Map<String, Date> fromPerCompetitorIdAsString,
|
||||
|
||||
+2
@@ -183,6 +183,8 @@ public interface SailingServiceAsync extends BuildVersionRetriever {
|
||||
int numberOfFixes, Collection<String> windSourceTypeNames, boolean onlyUpToNewestEvent,
|
||||
boolean includeCombinedWindForAllLegMiddles, AsyncCallback<WindInfoForRaceDTO> callback);
|
||||
|
||||
void getPolarResults(RegattaAndRaceIdentifier raceIdentifier, AsyncCallback<Boolean> callback);
|
||||
|
||||
void getSimulatorResults(RegattaAndRaceIdentifier raceIdentifier, Date from, Date prevStartTime, AsyncCallback<SimulatorResultsDTO> callback);
|
||||
|
||||
void setWind(RegattaAndRaceIdentifier raceIdentifier, WindDTO wind, AsyncCallback<Void> callback);
|
||||
|
||||
+2
@@ -1132,4 +1132,6 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages {
|
||||
String noFleetsDefined();
|
||||
String successfullyCreatedRegattas();
|
||||
String active();
|
||||
String errorTryingToRegisterRacesForTracking(String raceNames, String message);
|
||||
String errorDeterminingPolarAvailability(String raceName, String message);
|
||||
}
|
||||
+3
-1
@@ -1114,4 +1114,6 @@ errorTryingToCreateNewEvent=Error trying to create new event {0}: {1}
|
||||
createNewEvent=Create New Event
|
||||
noFleetsDefined=No fleets defined.
|
||||
successfullyCreatedRegattas=Successfully created regattas
|
||||
active=Active
|
||||
active=Active
|
||||
errorTryingToRegisterRacesForTracking=Error trying to register races {0} for tracking: {1}. Check live/stored URI syntax.
|
||||
errorDeterminingPolarAvailability=Error determining availability of polar / VPP data for race {0}: {1}
|
||||
+3
-1
@@ -1103,4 +1103,6 @@ errorTryingToCreateNewEvent=Fehler beim Anlegen einer neuen Veranstaltung {0}: {
|
||||
createNewEvent=Neue Veranstaltung anlegen
|
||||
noFleetsDefined=Keine Gruppen definiert.
|
||||
successfullyCreatedRegattas=Regatten erfolgreich angelegt
|
||||
active=Aktiv
|
||||
active=Aktiv
|
||||
errorTryingToRegisterRacesForTracking=Fehler beim Versuch, die Wettfahrten {0} zum Tracking zu registrieren: {1}. Live/stored URI syntax prüfen.
|
||||
errorDeterminingPolarAvailability=Fehler bei der Verfügbarkeitsprüfung für Polardaten für Wettfahrt {0}: {1}
|
||||
+3
-1
@@ -1051,4 +1051,6 @@ errorTryingToCreateNewEvent=Error trying to create new event {0}: {1}
|
||||
createNewEvent=Create New Event
|
||||
noFleetsDefined=No fleets defined.
|
||||
successfullyCreatedRegattas=Successfully created regattas
|
||||
active=Active
|
||||
active=Active
|
||||
errorTryingToRegisterRacesForTracking=Error trying to register races {0} for tracking: {1}. Check live/stored URI syntax.
|
||||
errorDeterminingPolarAvailability=Error determining availability of polar / VPP data for race {0}: {1}
|
||||
+73
-12
@@ -81,6 +81,7 @@ import com.sap.sailing.domain.common.impl.BoundsImpl;
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.RGBColor;
|
||||
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
|
||||
import com.sap.sailing.gwt.ui.actions.GetPolarAction;
|
||||
import com.sap.sailing.gwt.ui.actions.GetRaceMapDataAction;
|
||||
import com.sap.sailing.gwt.ui.actions.GetWindInfoAction;
|
||||
import com.sap.sailing.gwt.ui.client.ClientResources;
|
||||
@@ -123,6 +124,7 @@ import com.sap.sse.common.filter.Filter;
|
||||
import com.sap.sse.common.filter.FilterSet;
|
||||
import com.sap.sse.gwt.client.ErrorReporter;
|
||||
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
|
||||
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
|
||||
import com.sap.sse.gwt.client.player.TimeListener;
|
||||
import com.sap.sse.gwt.client.player.Timer;
|
||||
import com.sap.sse.gwt.client.player.Timer.PlayModes;
|
||||
@@ -309,6 +311,15 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
private WindStreamletsRaceboardOverlay streamletOverlay;
|
||||
private final boolean showViewStreamlets;
|
||||
private final boolean showViewSimulation;
|
||||
|
||||
private static final String GET_POLAR_CATEGORY = "getPolar";
|
||||
|
||||
/**
|
||||
* Tells about the availability of polar / VPP data for this race. If available, the simulation feature can be
|
||||
* offered to the user.
|
||||
*/
|
||||
private boolean hasPolar;
|
||||
|
||||
private final RegattaAndRaceIdentifier raceIdentifier;
|
||||
|
||||
public RaceMap(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor,
|
||||
@@ -338,6 +349,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
isMapInitialized = false;
|
||||
this.showViewStreamlets = showViewStreamlets;
|
||||
this.showViewSimulation = showViewSimulation;
|
||||
this.hasPolar = false;
|
||||
headerPanel = new FlowPanel();
|
||||
headerPanel.setStyleName("RaceMap-HeaderPanel");
|
||||
panelForLeftHeaderLabels = new AbsolutePanel();
|
||||
@@ -375,7 +387,10 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
// simplify road display
|
||||
mapTypeStyles[2] = GoogleMapStyleHelper.createSimplifiedStyle(MapTypeStyleFeatureType.ROAD);
|
||||
// set water color
|
||||
mapTypeStyles[3] = GoogleMapStyleHelper.createColorStyle(MapTypeStyleFeatureType.WATER, new RGBColor(0, 136, 255), -35, -34);
|
||||
// To play with the styles, check out http://gmaps-samples-v3.googlecode.com/svn/trunk/styledmaps/wizard/index.html.
|
||||
// To convert an RGB color into the strange hue/saturation/lightness model used by the Google Map use
|
||||
// http://software.stadtwerk.org/google_maps_colorizr/#water/all/123456/.
|
||||
mapTypeStyles[3] = GoogleMapStyleHelper.createColorStyle(MapTypeStyleFeatureType.WATER, new RGBColor(0, 136, 255), 0, -70);
|
||||
|
||||
MapTypeControlOptions mapTypeControlOptions = MapTypeControlOptions.newInstance();
|
||||
mapTypeControlOptions.setPosition(ControlPosition.BOTTOM_RIGHT);
|
||||
@@ -470,9 +485,12 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
}
|
||||
|
||||
if (showViewSimulation) {
|
||||
// determine availability of polar diagram
|
||||
setHasPolar();
|
||||
// initialize simulation canvas
|
||||
simulationOverlay = new RaceSimulationOverlay(getMap(), /* zIndex */ 0, timer, raceIdentifier, sailingService, stringMessages, asyncActionsExecutor);
|
||||
simulationOverlay = new RaceSimulationOverlay(getMap(), /* zIndex */ 0, raceIdentifier, sailingService, stringMessages, asyncActionsExecutor);
|
||||
simulationOverlay.addToMap();
|
||||
simulationOverlay.setVisible(false);
|
||||
}
|
||||
|
||||
createHeaderPanel(map);
|
||||
@@ -486,6 +504,25 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
|
||||
LoadApi.go(onLoad, loadLibraries, sensor, "key="+GoogleMapAPIKey.V3_APIKey);
|
||||
}
|
||||
|
||||
private void setHasPolar() {
|
||||
GetPolarAction getPolar = new GetPolarAction(sailingService, raceIdentifier);
|
||||
asyncActionsExecutor.execute(getPolar, GET_POLAR_CATEGORY,
|
||||
new MarkedAsyncCallback<>(new AsyncCallback<Boolean>() {
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
errorReporter.reportError(stringMessages.errorDeterminingPolarAvailability(
|
||||
raceIdentifier.getRaceName(), caught.getMessage()), /* silent */ true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Boolean result) {
|
||||
// store results
|
||||
hasPolar = result.booleanValue();
|
||||
}
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a header panel where additional information can be displayed by using
|
||||
@@ -710,6 +747,9 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
public void onSuccess(RaceMapDataDTO raceMapDataDTO) {
|
||||
if (map != null && raceMapDataDTO != null) {
|
||||
quickRanks = raceMapDataDTO.quickRanks;
|
||||
if (showViewSimulation && settings.isShowSimulationOverlay()) {
|
||||
simulationOverlay.updateLeg(getCurrentLeg(), newTime, /* clearCanvas */ false);
|
||||
}
|
||||
// process response only if not received out of order
|
||||
if (startedProcessingRequestID < requestID) {
|
||||
startedProcessingRequestID = requestID;
|
||||
@@ -735,22 +775,26 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
showAdvantageLine(competitorsToShow, newTime);
|
||||
|
||||
// Rezoom the map
|
||||
// TODO make this a loop across the LatLngBoundsCalculators, pulling them from a collection updated in updateSettings
|
||||
Bounds zoomToBounds = null;
|
||||
if (!settings.getZoomSettings().containsZoomType(ZoomTypes.NONE)) { // Auto zoom if setting is not manual
|
||||
Bounds bounds = settings.getZoomSettings().getNewBounds(RaceMap.this);
|
||||
zoomMapToNewBounds(bounds);
|
||||
mapFirstZoomDone = true;
|
||||
} else if (!mapFirstZoomDone) { // Zoom once to the marks
|
||||
zoomMapToNewBounds(new CourseMarksBoundsCalculator().calculateNewBounds(RaceMap.this));
|
||||
mapFirstZoomDone = true;
|
||||
zoomToBounds = settings.getZoomSettings().getNewBounds(RaceMap.this);
|
||||
if (zoomToBounds == null && !mapFirstZoomDone) {
|
||||
zoomToBounds = getDefaultZoomBounds(); // the user-specified zoom couldn't find what it was looking for; try defaults once
|
||||
}
|
||||
} else if (!mapFirstZoomDone) { // Zoom once to the marks if marks exist
|
||||
zoomToBounds = new CourseMarksBoundsCalculator().calculateNewBounds(RaceMap.this);
|
||||
if (zoomToBounds == null) {
|
||||
zoomToBounds = getDefaultZoomBounds(); // use default zoom, e.g.,
|
||||
}
|
||||
/*
|
||||
* Reset the mapZoomedOrPannedSinceLastRaceSelection: In spite of the fact that
|
||||
* the map was just zoomed to the bounds of the marks, it was not a zoom or pan
|
||||
* triggered by the user. As a consequence the
|
||||
* mapZoomedOrPannedSinceLastRaceSelection option has to reset again.
|
||||
*/
|
||||
// TODO bug 494: consider initial user-specific zoom settings
|
||||
}
|
||||
zoomMapToNewBounds(zoomToBounds);
|
||||
mapFirstZoomDone = true;
|
||||
}
|
||||
} else {
|
||||
lastTimeChangeBeforeInitialization = newTime;
|
||||
@@ -1918,7 +1962,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
|
||||
@Override
|
||||
public SettingsDialogComponent<RaceMapSettings> getSettingsDialogComponent() {
|
||||
return new RaceMapSettingsDialogComponent(settings, stringMessages, this.showViewSimulation);
|
||||
return new RaceMapSettingsDialogComponent(settings, stringMessages, this.showViewSimulation && this.hasPolar);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1981,7 +2025,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
if (newSettings.isShowSimulationOverlay() != settings.isShowSimulationOverlay()) {
|
||||
settings.setShowSimulationOverlay(newSettings.isShowSimulationOverlay());
|
||||
simulationOverlay.setVisible(newSettings.isShowSimulationOverlay());
|
||||
simulationOverlay.timeChanged(timer.getTime(), null);
|
||||
simulationOverlay.updateLeg(getCurrentLeg(), timer.getTime(), true);
|
||||
}
|
||||
if (requiredRedraw) {
|
||||
redraw();
|
||||
@@ -2186,6 +2230,16 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int getCurrentLeg() {
|
||||
com.sap.sse.common.Util.Pair<Integer, CompetitorDTO> leaderWithLeg = this
|
||||
.getLeadingVisibleCompetitorWithOneBasedLegNumber(getCompetitorsToShow());
|
||||
if (leaderWithLeg == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return leaderWithLeg.getA();
|
||||
}
|
||||
}
|
||||
|
||||
private Image createSAPLogo() {
|
||||
ImageResource sapLogoResource = resources.sapLogoOverlay();
|
||||
@@ -2204,4 +2258,11 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
|
||||
public String getDependentCssClassName() {
|
||||
return "raceMap";
|
||||
}
|
||||
|
||||
/**
|
||||
* The default zoom bounds are defined by the boats
|
||||
*/
|
||||
private Bounds getDefaultZoomBounds() {
|
||||
return new BoatsBoundsCalculator().calculateNewBounds(RaceMap.this);
|
||||
}
|
||||
}
|
||||
+23
-42
@@ -2,6 +2,7 @@ package com.sap.sailing.gwt.ui.client.shared.racemap;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.maps.client.MapWidget;
|
||||
@@ -37,50 +38,30 @@ public class RaceMapImageManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the map overlay icons
|
||||
* The method can only be called after the map is loaded
|
||||
* Loads the map overlay icons The method can only be called after the map is loaded. The {@link #maneuverIconsForTypeAndTargetTack} map
|
||||
* is filled for all combinations of {@link ManeuverType maneuver types} and {@link Tack tacks} including the value <code>null</code> representing
|
||||
* the unknown tack.
|
||||
*/
|
||||
public void loadMapIcons(MapWidget map) {
|
||||
if(map != null) {
|
||||
Marker tackToStarboardIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=T|71bf44|000000", 10, 33);
|
||||
maneuverIconsForTypeAndTargetTack.put(new Util.Pair<ManeuverType, Tack>(ManeuverType.TACK, Tack.STARBOARD), tackToStarboardIcon);
|
||||
|
||||
Marker tackToPortIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=T|d95252|000000", 10, 33);
|
||||
maneuverIconsForTypeAndTargetTack.put(new Util.Pair<ManeuverType, Tack>(ManeuverType.TACK, Tack.PORT), tackToPortIcon);
|
||||
|
||||
Marker jibeToStarboardIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=J|71bf44|000000", 10, 33);
|
||||
maneuverIconsForTypeAndTargetTack.put(new Util.Pair<ManeuverType, Tack>(ManeuverType.JIBE, Tack.STARBOARD), jibeToStarboardIcon);
|
||||
|
||||
Marker jibeToPortIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=J|d95252|000000", 10, 33);
|
||||
maneuverIconsForTypeAndTargetTack.put(new Util.Pair<ManeuverType, Tack>(ManeuverType.JIBE, Tack.PORT), jibeToPortIcon);
|
||||
|
||||
Marker headUpOnStarboardIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=H|71bf44|000000", 10, 33);
|
||||
maneuverIconsForTypeAndTargetTack.put(new Util.Pair<ManeuverType, Tack>(ManeuverType.HEAD_UP, Tack.STARBOARD), headUpOnStarboardIcon);
|
||||
|
||||
Marker headUpOnPortIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=H|d95252|000000", 10, 33);
|
||||
maneuverIconsForTypeAndTargetTack.put(new Util.Pair<ManeuverType, Tack>(ManeuverType.HEAD_UP, Tack.PORT), headUpOnPortIcon);
|
||||
|
||||
Marker bearAwayOnStarboardIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=B|71bf44|000000", 10, 33);
|
||||
maneuverIconsForTypeAndTargetTack.put(new Util.Pair<ManeuverType, Tack>(ManeuverType.BEAR_AWAY, Tack.STARBOARD), bearAwayOnStarboardIcon);
|
||||
|
||||
Marker bearAwayOnPortIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=B|d95252|000000", 10, 33);
|
||||
maneuverIconsForTypeAndTargetTack.put(new Util.Pair<ManeuverType, Tack>(ManeuverType.BEAR_AWAY, Tack.PORT), bearAwayOnPortIcon);
|
||||
|
||||
Marker markPassingToStarboardIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=M|71bf44|000000", 10, 33);
|
||||
maneuverIconsForTypeAndTargetTack.put(new Util.Pair<ManeuverType, Tack>(ManeuverType.MARK_PASSING, Tack.STARBOARD), markPassingToStarboardIcon);
|
||||
|
||||
Marker markPassingToPortIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=M|d95252|000000", 10, 33);
|
||||
maneuverIconsForTypeAndTargetTack.put(new Util.Pair<ManeuverType, Tack>(ManeuverType.MARK_PASSING, Tack.PORT), markPassingToPortIcon);
|
||||
|
||||
Marker unknownManeuverIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=?|FFFFFF|000000", 10, 33);
|
||||
maneuverIconsForTypeAndTargetTack.put(new Util.Pair<ManeuverType, Tack>(ManeuverType.UNKNOWN, Tack.STARBOARD), unknownManeuverIcon);
|
||||
maneuverIconsForTypeAndTargetTack.put(new Util.Pair<ManeuverType, Tack>(ManeuverType.UNKNOWN, Tack.PORT), unknownManeuverIcon);
|
||||
|
||||
Marker penaltyCircleToStarboardIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=P|71bf44|000000", 10, 33);
|
||||
maneuverIconsForTypeAndTargetTack.put(new Util.Pair<ManeuverType, Tack>(ManeuverType.PENALTY_CIRCLE, Tack.STARBOARD), penaltyCircleToStarboardIcon);
|
||||
|
||||
Marker penaltyCircleToPortIcon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=P|d95252|000000", 10, 33);
|
||||
maneuverIconsForTypeAndTargetTack.put(new Util.Pair<ManeuverType, Tack>(ManeuverType.PENALTY_CIRCLE, Tack.PORT), penaltyCircleToPortIcon);
|
||||
if (map != null) {
|
||||
final Map<ManeuverType, Character> maneuverLetter = new HashMap<>();
|
||||
maneuverLetter.put(ManeuverType.TACK, 'T');
|
||||
maneuverLetter.put(ManeuverType.JIBE, 'J');
|
||||
maneuverLetter.put(ManeuverType.HEAD_UP, 'H');
|
||||
maneuverLetter.put(ManeuverType.BEAR_AWAY, 'B');
|
||||
maneuverLetter.put(ManeuverType.MARK_PASSING, 'M');
|
||||
maneuverLetter.put(ManeuverType.PENALTY_CIRCLE, 'P');
|
||||
maneuverLetter.put(ManeuverType.UNKNOWN, '?');
|
||||
final Map<Tack, String> tackColor = new HashMap<>();
|
||||
tackColor.put(Tack.STARBOARD, "71bf44");
|
||||
tackColor.put(Tack.PORT, "d95252");
|
||||
tackColor.put(null, "FFFFFF");
|
||||
for (Entry<ManeuverType, Character> m : maneuverLetter.entrySet()) {
|
||||
for (Entry<Tack, String> t : tackColor.entrySet()) {
|
||||
final Marker icon = createMarker("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld="+m.getValue()+"|"+t.getValue()+"|000000", 10, 33);
|
||||
maneuverIconsForTypeAndTargetTack.put(new Util.Pair<ManeuverType, Tack>(m.getKey(), t.getKey()), icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-9
@@ -1302,13 +1302,15 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
|
||||
LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName());
|
||||
if (fieldsForRace != null && fieldsForRace.legDetails != null) {
|
||||
int lastLegIndex = fieldsForRace.legDetails.size() - 1;
|
||||
LegEntryDTO lastLegDetail = fieldsForRace.legDetails.get(lastLegIndex);
|
||||
// competitor may be in leg prior to the one the leader is in; find competitors current leg
|
||||
while (lastLegDetail == null && lastLegIndex > 0) {
|
||||
lastLegDetail = fieldsForRace.legDetails.get(--lastLegIndex);
|
||||
}
|
||||
if (lastLegDetail != null) {
|
||||
result = lastLegDetail.currentSpeedOverGroundInKnots;
|
||||
if (lastLegIndex >= 0) {
|
||||
LegEntryDTO lastLegDetail = fieldsForRace.legDetails.get(lastLegIndex);
|
||||
// competitor may be in leg prior to the one the leader is in; find competitors current leg
|
||||
while (lastLegDetail == null && lastLegIndex > 0) {
|
||||
lastLegDetail = fieldsForRace.legDetails.get(--lastLegIndex);
|
||||
}
|
||||
if (lastLegDetail != null) {
|
||||
result = lastLegDetail.currentSpeedOverGroundInKnots;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -2247,7 +2249,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
|
||||
leaderboardTable.sortColumn(columnToSortFor, columnToSortFor.getPreferredSortingOrder().isAscending());
|
||||
}
|
||||
|
||||
if(!isEmbedded) {
|
||||
if (!isEmbedded) {
|
||||
scoreCorrectionCommentLabel.setText(leaderboard.getComment() != null ? leaderboard.getComment() : "");
|
||||
if (leaderboard.getTimePointOfLastCorrectionsValidity() != null) {
|
||||
Date lastCorrectionDate = leaderboard.getTimePointOfLastCorrectionsValidity();
|
||||
@@ -2663,8 +2665,13 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
|
||||
return textRaceColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be expandable, a race column needs to have one or more tracked races associated and needs to have at least
|
||||
* some GPS data available. Wind data is not required for expandability because several metrics can reasonably be
|
||||
* determined even without reliable wind information.
|
||||
*/
|
||||
private boolean shallExpandRaceColumn(RaceColumnDTO raceColumnDTO) {
|
||||
return showRaceDetails && raceColumnDTO.hasTrackedRaces() && raceColumnDTO.hasGPSData() && raceColumnDTO.hasWindData();
|
||||
return showRaceDetails && raceColumnDTO.hasTrackedRaces() && raceColumnDTO.hasGPSData();
|
||||
}
|
||||
|
||||
private void removeUnusedRaceColumns(LeaderboardDTO leaderboard) {
|
||||
|
||||
+45
-15
@@ -1,19 +1,22 @@
|
||||
package com.sap.sailing.gwt.ui.polarsheets;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.moxieapps.gwt.highcharts.client.AxisTitle;
|
||||
import org.moxieapps.gwt.highcharts.client.Chart;
|
||||
import org.moxieapps.gwt.highcharts.client.Legend;
|
||||
import org.moxieapps.gwt.highcharts.client.Point;
|
||||
import org.moxieapps.gwt.highcharts.client.Series;
|
||||
import org.moxieapps.gwt.highcharts.client.Series.Type;
|
||||
import org.moxieapps.gwt.highcharts.client.labels.XAxisLabels;
|
||||
import org.moxieapps.gwt.highcharts.client.plotOptions.AreaPlotOptions;
|
||||
import org.moxieapps.gwt.highcharts.client.plotOptions.Marker;
|
||||
import org.moxieapps.gwt.highcharts.client.plotOptions.PlotOptions.Stacking;
|
||||
|
||||
import com.google.gwt.dom.client.Style.Unit;
|
||||
import com.google.gwt.user.client.Timer;
|
||||
@@ -34,11 +37,14 @@ public class AngleOverDataSizeHistogramPanel extends DockLayoutPanel {
|
||||
private final StringMessages stringMessages;
|
||||
|
||||
private final Map<String, Set<Series>> seriesForId;
|
||||
|
||||
private final Map<String, Series> seriesForName;
|
||||
|
||||
public AngleOverDataSizeHistogramPanel(StringMessages stringMessages) {
|
||||
super(Unit.PX);
|
||||
|
||||
this.seriesForId = new HashMap<>();
|
||||
this.seriesForName = new HashMap<>();
|
||||
this.stringMessages = stringMessages;
|
||||
setSize("100%", "100%");
|
||||
|
||||
@@ -53,44 +59,51 @@ public class AngleOverDataSizeHistogramPanel extends DockLayoutPanel {
|
||||
histogramChart.getYAxis().setMin(0).setAxisTitle(new AxisTitle().setText(stringMessages.numberOfDataPoints()));
|
||||
histogramChart.getXAxis().setLabels(new XAxisLabels().setRotation(-90f).setY(10))
|
||||
.setAxisTitle(new AxisTitle().setText(stringMessages.beatAngle()));
|
||||
histogramChart.setAreaPlotOptions(new AreaPlotOptions().setStacking(Stacking.NORMAL).setLineColor("#666666")
|
||||
histogramChart.setAreaPlotOptions(new AreaPlotOptions().setLineColor("#666666")
|
||||
.setLineWidth(1).setMarker(new Marker().setLineWidth(1).setLineColor("#666666")));
|
||||
histogramChart.setLegend(new Legend().setEnabled(false));
|
||||
return histogramChart;
|
||||
}
|
||||
|
||||
public void addData(Map<Integer, PolarSheetsHistogramData> histogramDataPerAngle, String seriesId, String actualSeriesName) {
|
||||
public void addData(Map<Integer, PolarSheetsHistogramData> histogramDataPerAngle, String seriesId, String actualSeriesName, boolean setVisible) {
|
||||
chart.setTitle("");
|
||||
Series series = chart.createSeries();
|
||||
|
||||
Point[] points = toPoints(histogramDataPerAngle);
|
||||
series.setPoints(points);
|
||||
series.setPoints(points, false);
|
||||
series.setName(actualSeriesName);
|
||||
chart.addSeries(series);
|
||||
chart.addSeries(series, false, false);
|
||||
series.setVisible(setVisible, false);
|
||||
|
||||
if (!seriesForId.containsKey(seriesId)) {
|
||||
seriesForId.put(seriesId, new HashSet<Series>());
|
||||
}
|
||||
Set<Series> seriesSet = seriesForId.get(seriesId);
|
||||
seriesSet.add(series);
|
||||
seriesForName.put(actualSeriesName, series);
|
||||
}
|
||||
|
||||
private Point[] toPoints(Map<Integer, PolarSheetsHistogramData> histogramDataPerAngle) {
|
||||
Number[] xValues = new Number[360];
|
||||
Number[] yValues = new Number[360];
|
||||
List<Number> xValues = new ArrayList<>();
|
||||
List<Number> yValues = new ArrayList<>();
|
||||
for (int i = 0; i < 360; i++) {
|
||||
xValues[i] = i;
|
||||
yValues[i] = histogramDataPerAngle.get(i).getDataCount();
|
||||
if (histogramDataPerAngle.get(i) != null) {
|
||||
int convertedAngle = i > 180 ? i - 360 : i;
|
||||
xValues.add(convertedAngle);
|
||||
yValues.add(histogramDataPerAngle.get(i).getDataCount());
|
||||
}
|
||||
}
|
||||
|
||||
return toPoints(xValues, yValues);
|
||||
}
|
||||
|
||||
public Point[] toPoints(Number[] xValues, Number[] yValues) {
|
||||
Point[] points = new Point[xValues.length];
|
||||
for (int i = 0; i < xValues.length; i++) {
|
||||
points[i] = new Point(xValues[i], yValues[i]);
|
||||
private Point[] toPoints(Iterable<Number> xValues, Iterable<Number> yValues) {
|
||||
List<Point> points = new ArrayList<>();
|
||||
Iterator<Number> x=xValues.iterator();
|
||||
Iterator<Number> y=yValues.iterator();
|
||||
while (x.hasNext()) {
|
||||
points.add(new Point(x.next(), y.next()));
|
||||
}
|
||||
return points;
|
||||
return points.toArray(new Point[points.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -115,6 +128,7 @@ public class AngleOverDataSizeHistogramPanel extends DockLayoutPanel {
|
||||
public void removeSeries(String seriesId) {
|
||||
for (Series series : seriesForId.get(seriesId)) {
|
||||
chart.removeSeries(series);
|
||||
seriesForName.remove(series.getName());
|
||||
}
|
||||
seriesForId.remove(seriesId);
|
||||
}
|
||||
@@ -123,6 +137,22 @@ public class AngleOverDataSizeHistogramPanel extends DockLayoutPanel {
|
||||
chart.removeAllSeries();
|
||||
seriesForId.clear();
|
||||
}
|
||||
|
||||
public void redrawChart() {
|
||||
chart.redraw();
|
||||
}
|
||||
|
||||
public void showSeries(String name) {
|
||||
if (seriesForName.containsKey(name)) {
|
||||
seriesForName.get(name).show();
|
||||
}
|
||||
}
|
||||
|
||||
public void hideSeries(String name) {
|
||||
if (seriesForName.containsKey(name)) {
|
||||
seriesForName.get(name).hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+3
-3
@@ -31,7 +31,7 @@ public class PolarSheetGenerationSettingsDialogComponent implements SettingsDial
|
||||
private DoubleBox outlierRadiusBox;
|
||||
private DoubleBox outlierNeighborhoodPctBox;
|
||||
private CheckBox useOnlyEstimationForWindDirectionBox;
|
||||
private WindSteppingConfiguratorPanel windSteppingBox;
|
||||
private WindSpeedSteppingConfiguratorPanel windSteppingBox;
|
||||
private DoubleBox windSteppingMaxDistanceBox;
|
||||
private CheckBox splitByWindGaugesBox;
|
||||
|
||||
@@ -113,12 +113,12 @@ public class PolarSheetGenerationSettingsDialogComponent implements SettingsDial
|
||||
numberOfHistogramColumnsBox = dialog.createIntegerBox(settings.getNumberOfHistogramColumns(), 3);
|
||||
grid.setWidget(10, 1, numberOfHistogramColumnsBox);
|
||||
grid.setWidget(11, 0, new Label(stringMessages.polarSheetWindSteppingInKnots() + ":"));
|
||||
windSteppingBox = new WindSteppingConfiguratorPanel(settings.getWindStepping());
|
||||
windSteppingBox = new WindSpeedSteppingConfiguratorPanel(settings.getWindSpeedStepping());
|
||||
grid.setWidget(11, 1, windSteppingBox);
|
||||
Label windSteppingMaxDistanceLabel = new Label(stringMessages.polarSheetWindSteppingMaxDistance() + ":");
|
||||
windSteppingMaxDistanceLabel.setTitle(stringMessages.polarSheetWindSteppingMaxDistanceTooltip());
|
||||
grid.setWidget(12, 0, windSteppingMaxDistanceLabel);
|
||||
windSteppingMaxDistanceBox = dialog.createDoubleBox(settings.getWindStepping().getMaxDistance(), 6);
|
||||
windSteppingMaxDistanceBox = dialog.createDoubleBox(settings.getWindSpeedStepping().getMaxDistance(), 6);
|
||||
grid.setWidget(12, 1, windSteppingMaxDistanceBox);
|
||||
grid.setWidget(13, 0, new Label(stringMessages.pleaseSeeToolTips()));
|
||||
|
||||
|
||||
+68
-43
@@ -10,12 +10,14 @@ import java.util.Map.Entry;
|
||||
|
||||
import org.moxieapps.gwt.highcharts.client.BaseChart;
|
||||
import org.moxieapps.gwt.highcharts.client.Chart;
|
||||
import org.moxieapps.gwt.highcharts.client.Color;
|
||||
import org.moxieapps.gwt.highcharts.client.Point;
|
||||
import org.moxieapps.gwt.highcharts.client.Series;
|
||||
import org.moxieapps.gwt.highcharts.client.events.PointSelectEventHandler;
|
||||
import org.moxieapps.gwt.highcharts.client.events.SeriesHideEvent;
|
||||
import org.moxieapps.gwt.highcharts.client.events.SeriesHideEventHandler;
|
||||
import org.moxieapps.gwt.highcharts.client.events.SeriesShowEvent;
|
||||
import org.moxieapps.gwt.highcharts.client.events.SeriesShowEventHandler;
|
||||
import org.moxieapps.gwt.highcharts.client.plotOptions.LinePlotOptions;
|
||||
import org.moxieapps.gwt.highcharts.client.plotOptions.Marker;
|
||||
import org.moxieapps.gwt.highcharts.client.plotOptions.SeriesPlotOptions;
|
||||
|
||||
import com.google.gwt.dom.client.Style.Unit;
|
||||
@@ -44,18 +46,50 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
|
||||
private final Map<String,PolarSheetsData> polarSheetsDataMap;
|
||||
|
||||
private final AngleOverDataSizeHistogramPanel angleOverDataSizeHistogramPanel;
|
||||
private final SeriesPlotOptions seriesPlotOptions;
|
||||
|
||||
public PolarSheetsChartPanel(StringMessages stringMessages, AngleOverDataSizeHistogramPanel angleOverDataSizeHistogramPanel) {
|
||||
super(Unit.PCT);
|
||||
seriesPlotOptions = new SeriesPlotOptions();
|
||||
this.stringMessages = stringMessages;
|
||||
this.angleOverDataSizeHistogramPanel = angleOverDataSizeHistogramPanel;
|
||||
polarSheetsDataMap = new HashMap<String, PolarSheetsData>();
|
||||
setSize("100%", "100%");
|
||||
chart = createPolarSheetChart();
|
||||
setSeriesShowHandler(createSeriesShowEventHandler());
|
||||
setSeriesHideHandler(createSeriesHideEventHandler());
|
||||
seriesMap = new HashMap<String, Series[]>();
|
||||
add(chart);
|
||||
}
|
||||
|
||||
private void setSeriesHideHandler(SeriesHideEventHandler seriesHideEventHandler) {
|
||||
chart.setSeriesPlotOptions(seriesPlotOptions.setSeriesHideEventHandler(seriesHideEventHandler));
|
||||
}
|
||||
|
||||
private SeriesShowEventHandler createSeriesShowEventHandler() {
|
||||
return new SeriesShowEventHandler() {
|
||||
|
||||
@Override
|
||||
public boolean onShow(SeriesShowEvent seriesShowEvent) {
|
||||
String name = seriesShowEvent.getSeriesName();
|
||||
angleOverDataSizeHistogramPanel.showSeries(name);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private SeriesHideEventHandler createSeriesHideEventHandler() {
|
||||
return new SeriesHideEventHandler() {
|
||||
|
||||
@Override
|
||||
public boolean onHide(SeriesHideEvent seriesHideEvent) {
|
||||
String name = seriesHideEvent.getSeriesName();
|
||||
angleOverDataSizeHistogramPanel.hideSeries(name);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a polar diagram chart of the Line type.
|
||||
*/
|
||||
@@ -65,6 +99,8 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
|
||||
.setPolar(true).setHeight100().setWidth100();
|
||||
polarSheetChart.setChartTitleText(stringMessages.polarSheetChart());
|
||||
polarSheetChart.getYAxis().setMin(0);
|
||||
polarSheetChart.getXAxis().setMin(-179).setMax(180).setTickInterval(45);
|
||||
polarSheetChart.setOption("/pane/startAngle", 180);
|
||||
return polarSheetChart;
|
||||
}
|
||||
|
||||
@@ -95,41 +131,42 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
|
||||
*/
|
||||
private void createSeriesForWindspeed(String name, int windSpeedLevel, String seriesName) {
|
||||
Series[] seriesPerWindSpeed = seriesMap.get(name);
|
||||
Number[] forEachDeg = initializeDataForNewSeries();
|
||||
seriesPerWindSpeed[windSpeedLevel] = chart.createSeries().setPoints(forEachDeg);
|
||||
seriesPerWindSpeed[windSpeedLevel] = chart.createSeries();
|
||||
seriesPerWindSpeed[windSpeedLevel].setName(seriesName);
|
||||
nameForSeries.put(seriesPerWindSpeed[windSpeedLevel],seriesName);
|
||||
chart.addSeries(seriesPerWindSpeed[windSpeedLevel]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An array of 360 numbers. One for each angle.
|
||||
*/
|
||||
private Number[] initializeDataForNewSeries() {
|
||||
Number[] forEachDeg = new Number[360];
|
||||
return forEachDeg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the actual data to the series for each windspeed index. Series array needs to be initialized before calling
|
||||
* this method.
|
||||
*/
|
||||
private void addValuesToSeries(String seriesId, PolarSheetsData result) {
|
||||
int stepCount = result.getStepping().getRawStepping().length;
|
||||
boolean oneSeriesSetToVisible = false;
|
||||
if (seriesMap.containsKey(seriesId)) {
|
||||
for (int i = 0; i < stepCount; i++) {
|
||||
if (hasSufficientDataForWindspeed(result.getDataCountPerAngleForWindspeed(i))) {
|
||||
String actualSeriesName = seriesId + "-" + result.getStepping().getRawStepping()[i];
|
||||
if (seriesMap.get(seriesId)[i] == null) {
|
||||
createSeriesForWindspeed(seriesId, i, actualSeriesName);
|
||||
}
|
||||
Series series = seriesMap.get(seriesId)[i];
|
||||
//series.setPoints(result.getAveragedPolarDataByWindSpeed()[i], false);
|
||||
Point[] points = createPointsWithMarkerAlphaAccordingToDataCount(result, i);
|
||||
Point[] points = createPoints(result, i);
|
||||
if (points != null) {
|
||||
series.setPoints(points);
|
||||
angleOverDataSizeHistogramPanel.addData(result.getHistogramDataMap().get(i), seriesId, actualSeriesName);
|
||||
String actualSeriesName = seriesId + "-" + result.getStepping().getRawStepping()[i];
|
||||
if (seriesMap.get(seriesId)[i] == null) {
|
||||
createSeriesForWindspeed(seriesId, i, actualSeriesName);
|
||||
}
|
||||
Series series = seriesMap.get(seriesId)[i];
|
||||
// series.setPoints(result.getAveragedPolarDataByWindSpeed()[i], false);
|
||||
|
||||
series.setPoints(points, false);
|
||||
angleOverDataSizeHistogramPanel.addData(result.getHistogramDataMap().get(i), seriesId,
|
||||
actualSeriesName, !oneSeriesSetToVisible);
|
||||
angleOverDataSizeHistogramPanel.redrawChart();
|
||||
if (!oneSeriesSetToVisible) {
|
||||
oneSeriesSetToVisible = true;
|
||||
} else {
|
||||
series.setVisible(false, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,7 +191,7 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
|
||||
* Points are created using the polar sheets generation results. Points color and alpha in the graph is calculated
|
||||
* using statistical measures.
|
||||
*/
|
||||
private Point[] createPointsWithMarkerAlphaAccordingToDataCount(PolarSheetsData result, int windspeed) {
|
||||
private Point[] createPoints(PolarSheetsData result, int windspeed) {
|
||||
Point[] points = new Point[360];
|
||||
List<Integer> dataCountList = Arrays.asList(result.getDataCountPerAngleForWindspeed(windspeed));
|
||||
Integer max = Collections.max(dataCountList);
|
||||
@@ -162,35 +199,19 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
|
||||
return null;
|
||||
}
|
||||
for (int i = 0; i < 360; i++) {
|
||||
int convertedAngle = i > 180 ? i - 360 : i;
|
||||
if (result.getHistogramDataMap().get(windspeed) == null
|
||||
|| result.getHistogramDataMap().get(windspeed).get(i) == null
|
||||
|| result.getHistogramDataMap().get(windspeed).get(i).getConfidenceMeasure() < settings
|
||||
.getMinimumConfidenceMeasure()
|
||||
|| result.getHistogramDataMap().get(windspeed).get(i).getDataCount() < settings
|
||||
.getMinimumDataCountPerAngle()) {
|
||||
points[i] = new Point(0);
|
||||
points[i] = new Point(convertedAngle, 0);
|
||||
continue;
|
||||
}
|
||||
if (points[i] == null) {
|
||||
points[i] = new Point(result.getAveragedPolarDataByWindSpeed()[windspeed][i]);
|
||||
points[i] = new Point(convertedAngle, result.getAveragedPolarDataByWindSpeed()[windspeed][i]);
|
||||
}
|
||||
double alpha = (double) dataCountList.get(i) / (double) max;
|
||||
int blue;
|
||||
int red;
|
||||
int radius;
|
||||
if (alpha > 0.2) {
|
||||
red = (int) (alpha * 255);
|
||||
blue = 0;
|
||||
radius = 2;
|
||||
} else {
|
||||
blue = (int) ((1 - alpha) * 255);
|
||||
red = 0;
|
||||
radius = 2;
|
||||
}
|
||||
|
||||
// Don't let the markers be invisible
|
||||
alpha = 0.5 + 0.5 * alpha;
|
||||
points[i].setMarker(new Marker().setFillColor(new Color(red, 0, blue, alpha)).setRadius(radius));
|
||||
}
|
||||
|
||||
return points;
|
||||
@@ -224,7 +245,7 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a the results of a polar sheet generation to the chart panel. Performs all necessary steps including adding
|
||||
* Adds the results of a polar sheet generation to the chart panel. Performs all necessary steps including adding
|
||||
* the set of series to the map and redrawing the chart.
|
||||
*
|
||||
* @param id Set of series identifier.
|
||||
@@ -248,9 +269,13 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
|
||||
* Allows setting the point select handler for the chart.
|
||||
*/
|
||||
public void setPointSelectHandler(PointSelectEventHandler pointSelectEventHandler) {
|
||||
chart.setSeriesPlotOptions(new SeriesPlotOptions().setAllowPointSelect(true).setPointSelectEventHandler(
|
||||
chart.setSeriesPlotOptions(seriesPlotOptions.setAllowPointSelect(true).setPointSelectEventHandler(
|
||||
pointSelectEventHandler));
|
||||
}
|
||||
|
||||
private void setSeriesShowHandler(SeriesShowEventHandler eventHandler) {
|
||||
chart.setSeriesPlotOptions(seriesPlotOptions.setSeriesShowEventHandler(eventHandler));
|
||||
}
|
||||
|
||||
public Series[] getSeriesPerWindspeedForName(String name) {
|
||||
return seriesMap.get(name);
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@ import com.sap.sailing.domain.common.PolarSheetsData;
|
||||
import com.sap.sailing.domain.common.PolarSheetsHistogramData;
|
||||
import com.sap.sailing.domain.common.PolarSheetsXYDiagramData;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.WindStepping;
|
||||
import com.sap.sailing.domain.common.WindSpeedStepping;
|
||||
import com.sap.sailing.domain.common.impl.PolarSheetGenerationSettingsImpl;
|
||||
import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener;
|
||||
import com.sap.sailing.gwt.ui.client.RaceSelectionModel;
|
||||
@@ -214,7 +214,7 @@ public class PolarSheetsPanel extends SplitLayoutPanel implements RaceSelectionC
|
||||
String polarSheetName = split[0];
|
||||
int windSpeed = Integer.parseInt(split[1]);
|
||||
PolarSheetsData currentPolarSheetsData = chartPanel.getPolarSheetsDataMap().get(polarSheetName);
|
||||
WindStepping stepping = currentPolarSheetsData.getStepping();
|
||||
WindSpeedStepping stepping = currentPolarSheetsData.getStepping();
|
||||
PolarSheetsHistogramData histogramData = currentPolarSheetsData.getHistogramDataMap()
|
||||
.get(stepping.getLevelIndexForValue(windSpeed)).get(angle);
|
||||
histogramPanel.setData(histogramData);
|
||||
|
||||
+25
-17
@@ -16,7 +16,9 @@ import com.google.gwt.event.dom.client.ClickHandler;
|
||||
import com.google.gwt.user.client.ui.Button;
|
||||
import com.google.gwt.user.client.ui.DialogBox;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.PolarSheetsXYDiagramData;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
|
||||
@@ -44,34 +46,40 @@ public class PolarSheetsXYDiagramPopupPanel extends DialogBox {
|
||||
});
|
||||
containerPanel.add(closeButton);
|
||||
|
||||
Point[] pointsForUpwindStarboardAverageAngle = toPointArray(result.getPointsForUpwindStarboardAverageAngle());
|
||||
Series angleSeries = chart.createSeries();
|
||||
angleSeries.setPoints(pointsForUpwindStarboardAverageAngle);
|
||||
//chart.addSeries(angleSeries);
|
||||
|
||||
Point[] pointsForUpwindStarboardAverageSpeed = toPointArray(result.getPointsForUpwindStarboardAverageSpeed());
|
||||
Series speedSeries = chart.createSeries();
|
||||
speedSeries.setName("Upwind Starboard Speed - LinearRegressions");
|
||||
speedSeries.setPoints(pointsForUpwindStarboardAverageSpeed);
|
||||
chart.addSeries(speedSeries);
|
||||
addSpeedAndConfidenceSeriesForTackAndLegType(result, Tack.STARBOARD, LegType.UPWIND, true);
|
||||
addSpeedAndConfidenceSeriesForTackAndLegType(result, Tack.PORT, LegType.UPWIND, false);
|
||||
addSpeedAndConfidenceSeriesForTackAndLegType(result, Tack.STARBOARD, LegType.DOWNWIND, false);
|
||||
addSpeedAndConfidenceSeriesForTackAndLegType(result, Tack.PORT, LegType.DOWNWIND, false);
|
||||
|
||||
Point[] pointsForUpwindStarboardAverageSpeedMovingAverage = toPointArray(result.getPointsForUpwindStarboardAverageSpeedMovingAverage());
|
||||
chart.redraw();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void addSpeedAndConfidenceSeriesForTackAndLegType(PolarSheetsXYDiagramData result, Tack tack, LegType legType, boolean showByDefault) {
|
||||
Point[] pointsForUpwindStarboardAverageSpeedMovingAverage = toPointArray(result.getPointsForAverageSpeedMovingAverage(tack, legType));
|
||||
Series speedSeriesMovingAverage = chart.createSeries();
|
||||
speedSeriesMovingAverage.setName("Upwind Starboard Speed - MovingAverage");
|
||||
speedSeriesMovingAverage.setName(tack + " " + legType + " Speed - MovingAverage");
|
||||
speedSeriesMovingAverage.setPoints(pointsForUpwindStarboardAverageSpeedMovingAverage);
|
||||
chart.addSeries(speedSeriesMovingAverage);
|
||||
chart.addSeries(speedSeriesMovingAverage, false, false);
|
||||
|
||||
Point[] pointsForUpwindStarboardAverageConfidence = toPointArray(result.getPointsForUpwindStarboardAverageConfidence());
|
||||
Series confidenceSeriesMovingAverage = chart
|
||||
Point[] pointsForUpwindStarboardAverageConfidence = toPointArray(result.getPointsForAverageConfidence(tack, legType));
|
||||
Series confidenceSeries = chart
|
||||
.createSeries()
|
||||
.setYAxis(1)
|
||||
.setType(Series.Type.SPLINE)
|
||||
.setPlotOptions(
|
||||
new SplinePlotOptions().setColor("#AA4643").setMarker(new Marker().setEnabled(false))
|
||||
.setDashStyle(PlotLine.DashStyle.SHORT_DOT));
|
||||
confidenceSeriesMovingAverage.setName("Upwind Starboard Confidence");
|
||||
confidenceSeriesMovingAverage.setPoints(pointsForUpwindStarboardAverageConfidence);
|
||||
chart.addSeries(confidenceSeriesMovingAverage);
|
||||
confidenceSeries.setName(tack + " " + legType + " Confidence");
|
||||
confidenceSeries.setPoints(pointsForUpwindStarboardAverageConfidence);
|
||||
chart.addSeries(confidenceSeries, false, false);
|
||||
|
||||
if (!showByDefault) {
|
||||
speedSeriesMovingAverage.setVisible(false, false);
|
||||
confidenceSeries.setVisible(false, false);
|
||||
}
|
||||
}
|
||||
|
||||
private Point[] toPointArray(List<Pair<Double, Double>> pointsForUpwindStarboardAverageAngle) {
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@ public class RaceFilter {
|
||||
|
||||
private boolean compliesToGpsFixFilter(RaceDTO race) {
|
||||
if (withGpsFixes!=null) {
|
||||
if (race.trackedRace.hasGPSData != withGpsFixes) {
|
||||
if (race.trackedRace == null || race.trackedRace.hasGPSData != withGpsFixes) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ public class RaceFilter {
|
||||
|
||||
private boolean compliesToWindDataFilter(RaceDTO race) {
|
||||
if (withGpsFixes!=null) {
|
||||
if (race.trackedRace.hasWindData != withWindData) {
|
||||
if (race.trackedRace == null || race.trackedRace.hasWindData != withWindData) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+13
-9
@@ -10,24 +10,24 @@ import com.google.gwt.user.client.ui.Button;
|
||||
import com.google.gwt.user.client.ui.HorizontalPanel;
|
||||
import com.google.gwt.user.client.ui.TextBox;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
import com.sap.sailing.domain.common.WindStepping;
|
||||
import com.sap.sailing.domain.common.impl.WindSteppingWithMaxDistance;
|
||||
import com.sap.sailing.domain.common.WindSpeedStepping;
|
||||
import com.sap.sailing.domain.common.impl.WindSpeedSteppingWithMaxDistance;
|
||||
|
||||
/**
|
||||
* A Panel which allows the user to configure a {@link WindStepping}.
|
||||
* A Panel which allows the user to configure a {@link WindSpeedStepping}.
|
||||
*
|
||||
* @author d054528 Frederik Petersen
|
||||
*
|
||||
*/
|
||||
public class WindSteppingConfiguratorPanel extends HorizontalPanel {
|
||||
public class WindSpeedSteppingConfiguratorPanel extends HorizontalPanel {
|
||||
|
||||
private List<TextBox> textBoxes = new ArrayList<TextBox>();
|
||||
private Button minusButton;
|
||||
private Button plusButton;
|
||||
|
||||
public WindSteppingConfiguratorPanel(WindSteppingWithMaxDistance windStepping) {
|
||||
public WindSpeedSteppingConfiguratorPanel(WindSpeedSteppingWithMaxDistance windStepping) {
|
||||
setupPlusAndMinusButtons();
|
||||
Double[] levels = windStepping.getRawStepping();
|
||||
double[] levels = windStepping.getRawStepping();
|
||||
for (int i = 0; i < levels.length; i++) {
|
||||
TextBox textBox = createSingleBox((int) Math.round(levels[i]));
|
||||
textBoxes.add(textBox);
|
||||
@@ -68,14 +68,18 @@ public class WindSteppingConfiguratorPanel extends HorizontalPanel {
|
||||
this.add(plusButton);
|
||||
}
|
||||
|
||||
public WindSteppingWithMaxDistance getStepping(double maxDistance) {
|
||||
public WindSpeedSteppingWithMaxDistance getStepping(double maxDistance) {
|
||||
List<Double> levelList = new ArrayList<Double>();
|
||||
for (TextBox box : textBoxes) {
|
||||
levelList.add(new Double(Integer.parseInt(box.getValue())));
|
||||
}
|
||||
Collections.sort(levelList);
|
||||
Double[] levels = levelList.toArray(new Double[levelList.size()]);
|
||||
return new WindSteppingWithMaxDistance(levels, maxDistance);
|
||||
double[] levels = new double[levelList.size()];
|
||||
int i=0;
|
||||
for (Double level : levelList) {
|
||||
levels[i++] = level;
|
||||
}
|
||||
return new WindSpeedSteppingWithMaxDistance(levels, maxDistance);
|
||||
}
|
||||
|
||||
private TextBox createSingleBox(int level) {
|
||||
+1
-1
@@ -96,7 +96,7 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint {
|
||||
boolean showWindChart = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_WINDCHART, false /* default*/);
|
||||
boolean showCompetitorsChart = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_COMPETITORSCHART, false /* default*/);
|
||||
boolean showViewStreamlets = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_STREAMLETS, false /* default*/);
|
||||
boolean showViewSimulation = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_SIMULATION, false /* default*/);
|
||||
boolean showViewSimulation = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_SIMULATION, true /* default*/);
|
||||
String activeCompetitorsFilterSetName = GwtHttpRequestUtils.getStringParameter(RaceBoardViewConfiguration.PARAM_VIEW_COMPETITOR_FILTER, null /* default*/);
|
||||
final boolean canReplayWhileLiveIsPossible = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, false);
|
||||
final boolean autoSelectMedia = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_AUTOSELECT_MEDIA, false);
|
||||
|
||||
+122
-75
@@ -135,7 +135,6 @@ import com.sap.sailing.domain.common.LeaderboardType;
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.ManeuverType;
|
||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||
import com.sap.sailing.domain.common.NoWindError;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.PassingInstruction;
|
||||
import com.sap.sailing.domain.common.PolarSheetGenerationResponse;
|
||||
@@ -398,7 +397,7 @@ import com.sap.sailing.simulator.PolarDiagram;
|
||||
import com.sap.sailing.simulator.SailingSimulator;
|
||||
import com.sap.sailing.simulator.SimulationParameters;
|
||||
import com.sap.sailing.simulator.TimedPositionWithSpeed;
|
||||
import com.sap.sailing.simulator.impl.PolarDiagramCSV;
|
||||
import com.sap.sailing.simulator.impl.PolarDiagramGPS;
|
||||
import com.sap.sailing.simulator.impl.SailingSimulatorImpl;
|
||||
import com.sap.sailing.simulator.impl.SimulationParametersImpl;
|
||||
import com.sap.sailing.simulator.util.SailingSimulatorConstants;
|
||||
@@ -457,7 +456,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
|
||||
private final ServiceTracker<DeviceIdentifierStringSerializationHandler, DeviceIdentifierStringSerializationHandler>
|
||||
deviceIdentifierStringSerializationHandlerTracker;
|
||||
|
||||
|
||||
private final com.sap.sailing.domain.tractracadapter.persistence.MongoObjectFactory tractracMongoObjectFactory;
|
||||
|
||||
private final DomainObjectFactory domainObjectFactory;
|
||||
@@ -1479,6 +1478,15 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getPolarResults(RegattaAndRaceIdentifier raceIdentifier) {
|
||||
TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier);
|
||||
BoatClass boatClass = trackedRace.getRace().getBoatClass();
|
||||
PolarDataService polarData = getService().getPolarDataService();
|
||||
PolarDiagram polarDiagram = new PolarDiagramGPS(boatClass, polarData);
|
||||
return new Boolean(polarDiagram != null);
|
||||
};
|
||||
|
||||
@Override
|
||||
public SimulatorResultsDTO getSimulatorResults(RegattaAndRaceIdentifier raceIdentifier, Date from, Date prevStartTime) {
|
||||
TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier);
|
||||
@@ -1492,13 +1500,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
if (trackedLeg == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Waypoint fromWaypoint = trackedLeg.getLeg().getFrom();
|
||||
Position startPosition = trackedRace.getApproximatePosition(fromWaypoint, fromTimePoint);
|
||||
Waypoint fromWaypoint = trackedLeg.getLeg().getFrom();
|
||||
|
||||
// get next mark as end-position
|
||||
Waypoint toWaypoint = trackedLeg.getLeg().getTo();
|
||||
Position endPosition = trackedRace.getApproximatePosition(toWaypoint, fromTimePoint);
|
||||
|
||||
TimePoint startTimePoint = null;
|
||||
TimePoint endTimePoint = null;
|
||||
@@ -1515,9 +1520,11 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
}
|
||||
}
|
||||
legDuration = endTimePoint.asMillis() - startTimePoint.asMillis();
|
||||
Position startPosition = trackedRace.getApproximatePosition(fromWaypoint, startTimePoint);
|
||||
Position endPosition = trackedRace.getApproximatePosition(toWaypoint, endTimePoint);
|
||||
|
||||
if (startTimePoint.asDate().equals(prevStartTime)) {
|
||||
return new SimulatorResultsDTO(startTimePoint.asDate(), 0, null, null, null, null);
|
||||
return new SimulatorResultsDTO(startTimePoint.asDate(), 0, 0, null, null, null, null);
|
||||
}
|
||||
|
||||
// determine legtype upwind/downwind/reaching
|
||||
@@ -1538,14 +1545,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
List<Position> course = new ArrayList<Position>();
|
||||
course.add(startPosition);
|
||||
course.add(endPosition);
|
||||
String csvFilePath = "PolarDiagram49STG.csv";
|
||||
PolarDiagram polarDiagram = null;
|
||||
try {
|
||||
polarDiagram = new PolarDiagramCSV(csvFilePath);
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
BoatClass boatClass = trackedRace.getRace().getBoatClass();
|
||||
PolarDataService polarData = getService().getPolarDataService();
|
||||
PolarDiagram polarDiagram = new PolarDiagramGPS(boatClass, polarData);
|
||||
SimulationParameters simulationPars = new SimulationParametersImpl(course, polarDiagram, windField, SailingSimulatorConstants.ModeEvent, true, true);
|
||||
|
||||
// for upwind/downwind, run simulation with start-time, start-position, end-position and windfield
|
||||
@@ -1579,7 +1581,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
rcDTO.coursePositions.waypointPositions.add(posDTO);
|
||||
posDTO = SimulatorServiceUtils.toPositionDTO(endPosition);
|
||||
rcDTO.coursePositions.waypointPositions.add(posDTO);
|
||||
result = new SimulatorResultsDTO(startTimePoint.asDate(), legDuration, rcDTO, pathDTOs, null, null);
|
||||
result = new SimulatorResultsDTO(startTimePoint.asDate(), timeStep.asMillis(), legDuration, rcDTO, pathDTOs, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1746,15 +1748,17 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
fix.getTimePoint());
|
||||
LegType legType;
|
||||
if (trackedLegOfCompetitor != null && trackedLegOfCompetitor.getLeg() != null) {
|
||||
TimePoint quantifiedTimePoint = quantifyTimePointWithResolution(fix.getTimePoint(), /* resolutionInMilliseconds */
|
||||
60000);
|
||||
Pair<Leg, TimePoint> cacheKey = new Pair<Leg, TimePoint>(
|
||||
trackedLegOfCompetitor.getLeg(), quantifiedTimePoint);
|
||||
TimePoint quantifiedTimePoint = quantifyTimePointWithResolution(fix.getTimePoint(), /* resolutionInMilliseconds */60000);
|
||||
Pair<Leg, TimePoint> cacheKey = new Pair<Leg, TimePoint>(trackedLegOfCompetitor.getLeg(), quantifiedTimePoint);
|
||||
legType = legTypeCache.get(cacheKey);
|
||||
if (legType == null) {
|
||||
legType = trackedRace.getTrackedLeg(trackedLegOfCompetitor.getLeg()).getLegType(
|
||||
fix.getTimePoint());
|
||||
legTypeCache.put(cacheKey, legType);
|
||||
try {
|
||||
legType = trackedRace.getTrackedLeg(trackedLegOfCompetitor.getLeg()).getLegType(fix.getTimePoint());
|
||||
legTypeCache.put(cacheKey, legType);
|
||||
} catch (NoWindException nwe) {
|
||||
// without wind, leave the leg type null, meaning "unknown"
|
||||
legType = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
legType = null;
|
||||
@@ -1781,10 +1785,13 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
trackedLegOfCompetitor.getLeg(), quantifiedTimePoint);
|
||||
legType2 = legTypeCache.get(cacheKey);
|
||||
if (legType2 == null) {
|
||||
legType2 = trackedRace.getTrackedLeg(trackedLegOfCompetitor.getLeg())
|
||||
.getLegType(
|
||||
fix.getTimePoint());
|
||||
legTypeCache.put(cacheKey, legType2);
|
||||
try {
|
||||
legType2 = trackedRace.getTrackedLeg(trackedLegOfCompetitor.getLeg()).getLegType(fix.getTimePoint());
|
||||
legTypeCache.put(cacheKey, legType2);
|
||||
} catch (NoWindException nwe) {
|
||||
// no wind information; leave leg type null, meaning "unknown"
|
||||
legType2 = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
legType2 = null;
|
||||
@@ -3009,28 +3016,25 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
// Distance for DouglasPeucker
|
||||
TimePoint timePointFrom = new MillisecondsTimePoint(from.get(competitorDTO));
|
||||
TimePoint timePointTo = new MillisecondsTimePoint(to.get(competitorDTO));
|
||||
List<GPSFixMoving> gpsFixApproximation = trackedRace.approximate(competitor, maxDistance,
|
||||
Iterable<GPSFixMoving> gpsFixApproximation = trackedRace.approximate(competitor, maxDistance,
|
||||
timePointFrom, timePointTo);
|
||||
List<GPSFixDTO> gpsFixDouglasList = new ArrayList<GPSFixDTO>();
|
||||
for (int i = 0; i < gpsFixApproximation.size(); i++) {
|
||||
GPSFix fix = gpsFixApproximation.get(i);
|
||||
SpeedWithBearing speedWithBearing;
|
||||
if (i < gpsFixApproximation.size() - 1) {
|
||||
GPSFix next = gpsFixApproximation.get(i + 1);
|
||||
GPSFix fix = null;
|
||||
for (GPSFix next : gpsFixApproximation) {
|
||||
if (fix != null) {
|
||||
Bearing bearing = fix.getPosition().getBearingGreatCircle(next.getPosition());
|
||||
Speed speed = fix.getPosition().getDistance(next.getPosition())
|
||||
.inTime(next.getTimePoint().asMillis() - fix.getTimePoint().asMillis());
|
||||
speedWithBearing = new KnotSpeedWithBearingImpl(speed.getKnots(), bearing);
|
||||
} else {
|
||||
speedWithBearing = gpsFixTrack.getEstimatedSpeed(fix.getTimePoint());
|
||||
final SpeedWithBearing speedWithBearing = new KnotSpeedWithBearingImpl(speed.getKnots(), bearing);
|
||||
GPSFixDTO fixDTO = createDouglasPeuckerGPSFixDTO(trackedRace, competitor, fix, speedWithBearing);
|
||||
gpsFixDouglasList.add(fixDTO);
|
||||
}
|
||||
Tack tack = trackedRace.getTack(competitor, fix.getTimePoint());
|
||||
TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, fix.getTimePoint());
|
||||
LegType legType = trackedLegOfCompetitor == null ? null : trackedRace.getTrackedLeg(
|
||||
trackedLegOfCompetitor.getLeg()).getLegType(fix.getTimePoint());
|
||||
Wind wind = trackedRace.getWind(fix.getPosition(), fix.getTimePoint());
|
||||
WindDTO windDTO = createWindDTOFromAlreadyAveraged(wind, fix.getTimePoint());
|
||||
GPSFixDTO fixDTO = createGPSFixDTO(fix, speedWithBearing, windDTO, tack, legType, /* extrapolated */false);
|
||||
fix = next;
|
||||
}
|
||||
if (fix != null) {
|
||||
// add one last GPSFixDTO with no successor to calculate speed/bearing to:
|
||||
final SpeedWithBearing speedWithBearing = gpsFixTrack.getEstimatedSpeed(fix.getTimePoint());
|
||||
GPSFixDTO fixDTO = createDouglasPeuckerGPSFixDTO(trackedRace, competitor, fix, speedWithBearing);
|
||||
gpsFixDouglasList.add(fixDTO);
|
||||
}
|
||||
result.put(competitorDTO, gpsFixDouglasList);
|
||||
@@ -3040,6 +3044,20 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
return result;
|
||||
}
|
||||
|
||||
private GPSFixDTO createDouglasPeuckerGPSFixDTO(TrackedRace trackedRace, Competitor competitor, GPSFix fix,
|
||||
SpeedWithBearing speedWithBearing) throws NoWindException {
|
||||
Tack tack = trackedRace.getTack(competitor, fix.getTimePoint());
|
||||
TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor,
|
||||
fix.getTimePoint());
|
||||
LegType legType = trackedLegOfCompetitor == null ? null : trackedRace.getTrackedLeg(
|
||||
trackedLegOfCompetitor.getLeg()).getLegType(fix.getTimePoint());
|
||||
Wind wind = trackedRace.getWind(fix.getPosition(), fix.getTimePoint());
|
||||
WindDTO windDTO = createWindDTOFromAlreadyAveraged(wind, fix.getTimePoint());
|
||||
GPSFixDTO fixDTO = createGPSFixDTO(fix, speedWithBearing, windDTO, tack, legType, /* extrapolated */
|
||||
false);
|
||||
return fixDTO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<CompetitorDTO, List<ManeuverDTO>> getManeuvers(RegattaAndRaceIdentifier raceIdentifier,
|
||||
Map<CompetitorDTO, Date> from, Map<CompetitorDTO, Date> to) throws NoWindException {
|
||||
@@ -3057,14 +3075,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
@Override
|
||||
public List<ManeuverDTO> call() {
|
||||
List<Maneuver> maneuversForCompetitor;
|
||||
try {
|
||||
maneuversForCompetitor = trackedRace.getManeuvers(competitor, timePointFrom,
|
||||
timePointTo, /* waitForLatest */ true);
|
||||
} catch (NoWindException e) {
|
||||
throw new NoWindError(e);
|
||||
}
|
||||
return createManeuverDTOsForCompetitor(maneuversForCompetitor, trackedRace,
|
||||
competitor);
|
||||
maneuversForCompetitor = trackedRace.getManeuvers(competitor, timePointFrom,
|
||||
timePointTo, /* waitForLatest */ true);
|
||||
return createManeuverDTOsForCompetitor(maneuversForCompetitor, trackedRace, competitor);
|
||||
}
|
||||
});
|
||||
executor.execute(future);
|
||||
@@ -3935,7 +3948,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
Set<BoatClass> boatClasses = getService().getPolarDataService().getAllBoatClassesWithPolarSheetsAvailable();
|
||||
List<String> names = new ArrayList<String>();
|
||||
for (BoatClass boatClass : boatClasses) {
|
||||
names.add(boatClass.getName());
|
||||
names.add(boatClass.getDisplayName());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
@@ -5335,39 +5348,73 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
@Override
|
||||
public PolarSheetsXYDiagramData createXYDiagramForBoatClass(String boatClassName) {
|
||||
BoatClass boatClass = getService().getBaseDomainFactory().getOrCreateBoatClass(boatClassName);
|
||||
List<Pair<Double, Double>> pointsForUpwindStarboardAverageSpeed = new ArrayList<Pair<Double, Double>>();
|
||||
List<Pair<Double, Double>> pointsForUpwindStarboardAverageAngle = new ArrayList<Pair<Double, Double>>();
|
||||
List<Pair<Double, Double>> pointsForUpwindStarboardAverageSpeedMovingAverage = new ArrayList<Pair<Double, Double>>();
|
||||
List<Pair<Double, Double>> pointsForUpwindStarboardAverageAngleMovingAverage = new ArrayList<Pair<Double, Double>>();
|
||||
List<Pair<Double, Double>> pointsForUpwindStarboardAverageConfidence = new ArrayList<Pair<Double, Double>>();
|
||||
List<Pair<Double, Double>> pointsForUpwindPortAverageSpeedMovingAverage = new ArrayList<Pair<Double, Double>>();
|
||||
List<Pair<Double, Double>> pointsForUpwindPortAverageConfidence = new ArrayList<Pair<Double, Double>>();
|
||||
List<Pair<Double, Double>> pointsForDownwindStarboardAverageSpeedMovingAverage = new ArrayList<Pair<Double, Double>>();
|
||||
List<Pair<Double, Double>> pointsForDownwindStarboardAverageConfidence = new ArrayList<Pair<Double, Double>>();
|
||||
List<Pair<Double, Double>> pointsForDownwindPortAverageSpeedMovingAverage = new ArrayList<Pair<Double, Double>>();
|
||||
List<Pair<Double, Double>> pointsForDownwindPortAverageConfidence = new ArrayList<Pair<Double, Double>>();
|
||||
for (double windInKnots = 0.1; windInKnots < 30; windInKnots = windInKnots + 0.1) {
|
||||
try {
|
||||
SpeedWithBearingWithConfidence<Void> averageUpwindStarboard = getService().getPolarDataService()
|
||||
.getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots), LegType.UPWIND,
|
||||
Tack.STARBOARD, true);
|
||||
pointsForUpwindStarboardAverageSpeed.add(new Pair<Double, Double>(windInKnots, averageUpwindStarboard
|
||||
.getObject().getKnots()));
|
||||
pointsForUpwindStarboardAverageAngle.add(new Pair<Double, Double>(windInKnots, averageUpwindStarboard
|
||||
.getObject().getBearing().getDegrees()));
|
||||
|
||||
pointsForUpwindStarboardAverageConfidence.add(new Pair<Double, Double>(windInKnots,
|
||||
averageUpwindStarboard.getConfidence()));
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> averageUpwindStarboardMovingAverage = getService().getPolarDataService()
|
||||
.getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots), LegType.UPWIND,
|
||||
Tack.STARBOARD, false);
|
||||
SpeedWithBearingWithConfidence<Void> averageUpwindStarboardMovingAverage = getService()
|
||||
.getPolarDataService().getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots),
|
||||
LegType.UPWIND, Tack.STARBOARD);
|
||||
pointsForUpwindStarboardAverageSpeedMovingAverage.add(new Pair<Double, Double>(windInKnots,
|
||||
averageUpwindStarboardMovingAverage.getObject().getKnots()));
|
||||
pointsForUpwindStarboardAverageAngleMovingAverage.add(new Pair<Double, Double>(windInKnots,
|
||||
averageUpwindStarboardMovingAverage.getObject().getBearing().getDegrees()));
|
||||
|
||||
pointsForUpwindStarboardAverageConfidence.add(new Pair<Double, Double>(windInKnots,
|
||||
averageUpwindStarboardMovingAverage.getConfidence()));
|
||||
} catch (NotEnoughDataHasBeenAddedException e) {
|
||||
// Do not add a point to the result
|
||||
}
|
||||
|
||||
try {
|
||||
SpeedWithBearingWithConfidence<Void> averageUpwindPortMovingAverage = getService()
|
||||
.getPolarDataService().getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots),
|
||||
LegType.UPWIND, Tack.PORT);
|
||||
pointsForUpwindPortAverageSpeedMovingAverage.add(new Pair<Double, Double>(windInKnots,
|
||||
averageUpwindPortMovingAverage.getObject().getKnots()));
|
||||
|
||||
pointsForUpwindPortAverageConfidence.add(new Pair<Double, Double>(windInKnots,
|
||||
averageUpwindPortMovingAverage.getConfidence()));
|
||||
} catch (NotEnoughDataHasBeenAddedException e) {
|
||||
// Do not add a point to the result
|
||||
}
|
||||
|
||||
try {
|
||||
SpeedWithBearingWithConfidence<Void> averageDownwindStarboardMovingAverage = getService()
|
||||
.getPolarDataService().getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots),
|
||||
LegType.DOWNWIND, Tack.STARBOARD);
|
||||
pointsForDownwindStarboardAverageSpeedMovingAverage.add(new Pair<Double, Double>(windInKnots,
|
||||
averageDownwindStarboardMovingAverage.getObject().getKnots()));
|
||||
|
||||
pointsForDownwindStarboardAverageConfidence.add(new Pair<Double, Double>(windInKnots,
|
||||
averageDownwindStarboardMovingAverage.getConfidence()));
|
||||
} catch (NotEnoughDataHasBeenAddedException e) {
|
||||
// Do not add a point to the result
|
||||
}
|
||||
|
||||
try {
|
||||
SpeedWithBearingWithConfidence<Void> averageDownwindPortMovingAverage = getService()
|
||||
.getPolarDataService().getAverageSpeedWithBearing(boatClass, new KnotSpeedImpl(windInKnots),
|
||||
LegType.DOWNWIND, Tack.PORT);
|
||||
pointsForDownwindPortAverageSpeedMovingAverage.add(new Pair<Double, Double>(windInKnots,
|
||||
averageDownwindPortMovingAverage.getObject().getKnots()));
|
||||
|
||||
pointsForDownwindPortAverageConfidence.add(new Pair<Double, Double>(windInKnots,
|
||||
averageDownwindPortMovingAverage.getConfidence()));
|
||||
} catch (NotEnoughDataHasBeenAddedException e) {
|
||||
// Do not add a point to the result
|
||||
}
|
||||
|
||||
}
|
||||
PolarSheetsXYDiagramData data = new PolarSheetsXYDiagramDataImpl(pointsForUpwindStarboardAverageAngle,
|
||||
pointsForUpwindStarboardAverageSpeed, pointsForUpwindStarboardAverageAngleMovingAverage,
|
||||
pointsForUpwindStarboardAverageSpeedMovingAverage, pointsForUpwindStarboardAverageConfidence);
|
||||
PolarSheetsXYDiagramData data = new PolarSheetsXYDiagramDataImpl(
|
||||
pointsForUpwindStarboardAverageSpeedMovingAverage, pointsForUpwindStarboardAverageConfidence,
|
||||
pointsForUpwindPortAverageSpeedMovingAverage, pointsForUpwindPortAverageConfidence,
|
||||
pointsForDownwindStarboardAverageSpeedMovingAverage, pointsForDownwindStarboardAverageConfidence,
|
||||
pointsForDownwindPortAverageSpeedMovingAverage, pointsForDownwindPortAverageConfidence);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
+1
-1
@@ -301,7 +301,7 @@ public class SimulatorServiceImpl extends RemoteServiceServlet implements Simula
|
||||
windFieldDTO = this.createWindFieldDTO(wf, startTime, endTime, timeStep, params); // params.isShowStreamlets2(), params.isShowLines(), params.getSeedLines());
|
||||
}
|
||||
|
||||
return new SimulatorResultsDTO(null, 0, rcDTO, pathDTOs, windFieldDTO, simulatedPaths.notificationMessage);
|
||||
return new SimulatorResultsDTO(null, timeStep.asMillis(), 0, rcDTO, pathDTOs, windFieldDTO, simulatedPaths.notificationMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+12
-1
@@ -7,6 +7,7 @@ import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
public class SimulatorResultsDTO implements IsSerializable {
|
||||
|
||||
private Date startTime;
|
||||
private long timeStep;
|
||||
private long legDuration;
|
||||
private RaceMapDataDTO raceCourse;
|
||||
private WindFieldDTO windField;
|
||||
@@ -15,6 +16,7 @@ public class SimulatorResultsDTO implements IsSerializable {
|
||||
|
||||
public SimulatorResultsDTO(){
|
||||
this.startTime = null;
|
||||
this.timeStep = 0;
|
||||
this.legDuration = 0;
|
||||
this.raceCourse = null;
|
||||
this.windField = null;
|
||||
@@ -22,8 +24,9 @@ public class SimulatorResultsDTO implements IsSerializable {
|
||||
this.notificationMessage = "";
|
||||
}
|
||||
|
||||
public SimulatorResultsDTO(final Date startTime, final long legDuration, final RaceMapDataDTO raceCourse, final PathDTO[] paths, final WindFieldDTO windField, final String notificationMessage) {
|
||||
public SimulatorResultsDTO(final Date startTime, final long timeStep, final long legDuration, final RaceMapDataDTO raceCourse, final PathDTO[] paths, final WindFieldDTO windField, final String notificationMessage) {
|
||||
this.startTime = startTime;
|
||||
this.timeStep = timeStep;
|
||||
this.legDuration = legDuration;
|
||||
this.raceCourse = raceCourse;
|
||||
this.paths = paths;
|
||||
@@ -39,6 +42,14 @@ public class SimulatorResultsDTO implements IsSerializable {
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
public long getTimeStep() {
|
||||
return this.timeStep;
|
||||
}
|
||||
|
||||
public void setTimeStep(long timeStep) {
|
||||
this.timeStep = timeStep;
|
||||
}
|
||||
|
||||
public long getLegDuration() {
|
||||
return this.legDuration;
|
||||
}
|
||||
|
||||
+1
-2
@@ -19,7 +19,7 @@ public class GoogleMapStyleHelper {
|
||||
public static MapTypeStyle createColorStyle(MapTypeStyleFeatureType featureType, RGBColor color, int saturation, int lightness) {
|
||||
return createElementStyleWithColor(featureType, MapTypeStyleElementType.ALL, color, saturation, lightness);
|
||||
}
|
||||
|
||||
|
||||
public static MapTypeStyle createElementStyleWithColor(MapTypeStyleFeatureType featureType, MapTypeStyleElementType elementType,
|
||||
RGBColor rgbColor, int saturation, int ligthness) {
|
||||
MapTypeStyle result = MapTypeStyle.newInstance();
|
||||
@@ -31,7 +31,6 @@ public class GoogleMapStyleHelper {
|
||||
typeStylers[1] = MapTypeStyler.newSaturationStyler(saturation);
|
||||
typeStylers[2] = MapTypeStyler.newLightnessStyler(ligthness);
|
||||
result.setStylers(typeStylers);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+32
-34
@@ -28,8 +28,6 @@ import com.sap.sailing.gwt.ui.simulator.util.ColorPalette;
|
||||
import com.sap.sailing.gwt.ui.simulator.util.ColorPaletteGenerator;
|
||||
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
|
||||
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
|
||||
import com.sap.sse.gwt.client.player.TimeListener;
|
||||
import com.sap.sse.gwt.client.player.Timer;
|
||||
|
||||
/**
|
||||
* A Google Maps overlay based on an HTML5 canvas for drawing simulation results on the {@link RaceMap}.
|
||||
@@ -37,7 +35,7 @@ import com.sap.sse.gwt.client.player.Timer;
|
||||
* @author Christopher Ronnewinkel (D036654)
|
||||
*
|
||||
*/
|
||||
public class RaceSimulationOverlay extends FullCanvasOverlay implements TimeListener {
|
||||
public class RaceSimulationOverlay extends FullCanvasOverlay {
|
||||
|
||||
public static final String GET_SIMULATION_CATEGORY = "getSimulation";
|
||||
private final String textColor = "Black";
|
||||
@@ -47,21 +45,18 @@ public class RaceSimulationOverlay extends FullCanvasOverlay implements TimeLis
|
||||
private double rectWidth = 20;
|
||||
private double rectHeight = 20;
|
||||
private final StringMessages stringMessages;
|
||||
private final Timer timer;
|
||||
private Date lastSimulationTime;
|
||||
private final long deltaSimulationTime = 15000;
|
||||
private final RegattaAndRaceIdentifier raceIdentifier;
|
||||
private final SailingServiceAsync sailingService;
|
||||
private final AsyncActionsExecutor asyncActionsExecutor;
|
||||
private final ColorPalette colors;
|
||||
private SimulatorResultsDTO simulationResult;
|
||||
private PathDTO racePath;
|
||||
private int raceLeg = 0;
|
||||
private Date prevStartTime;
|
||||
private Canvas simulationLegend;
|
||||
|
||||
public RaceSimulationOverlay(MapWidget map, int zIndex, final Timer timer, RegattaAndRaceIdentifier raceIdentifier, SailingServiceAsync sailingService, StringMessages stringMessages, AsyncActionsExecutor asyncActionsExecutor) {
|
||||
public RaceSimulationOverlay(MapWidget map, int zIndex, RegattaAndRaceIdentifier raceIdentifier, SailingServiceAsync sailingService, StringMessages stringMessages, AsyncActionsExecutor asyncActionsExecutor) {
|
||||
super(map, zIndex);
|
||||
this.timer = timer;
|
||||
this.raceIdentifier = raceIdentifier;
|
||||
this.sailingService = sailingService;
|
||||
this.stringMessages = stringMessages;
|
||||
@@ -69,30 +64,22 @@ public class RaceSimulationOverlay extends FullCanvasOverlay implements TimeLis
|
||||
this.colors = new ColorPaletteGenerator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void timeChanged(final Date newTime, final Date oldTime) {
|
||||
if (lastSimulationTime == null) {
|
||||
lastSimulationTime = new Date(newTime.getTime() - deltaSimulationTime);
|
||||
}
|
||||
if (Math.abs(newTime.getTime() - lastSimulationTime.getTime()) >= deltaSimulationTime) {
|
||||
lastSimulationTime = newTime;
|
||||
public void updateLeg(int newLeg, Date newTime, boolean clearCanvas) {
|
||||
if ((newLeg != raceLeg) && (this.isVisible())) {
|
||||
raceLeg = newLeg;
|
||||
if (clearCanvas) {
|
||||
this.clearCanvas();
|
||||
}
|
||||
this.simulate(newTime);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToMap() {
|
||||
if (timer != null) {
|
||||
timer.addTimeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFromMap() {
|
||||
if (timer != null) {
|
||||
timer.removeTimeListener(this);
|
||||
public void setVisible(boolean isVisible) {
|
||||
super.setVisible(isVisible);
|
||||
if (simulationLegend != null) {
|
||||
simulationLegend.setVisible(isVisible);
|
||||
}
|
||||
this.setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -153,7 +140,6 @@ public class RaceSimulationOverlay extends FullCanvasOverlay implements TimeLis
|
||||
createSimulationLegend(map);
|
||||
}
|
||||
drawLegend(simulationLegend);
|
||||
|
||||
// calibrate canvas
|
||||
super.draw();
|
||||
// draw paths
|
||||
@@ -161,14 +147,15 @@ public class RaceSimulationOverlay extends FullCanvasOverlay implements TimeLis
|
||||
PathDTO[] paths = simulationResult.getPaths();
|
||||
boolean first = true;
|
||||
int colorIdx = paths.length - 1;
|
||||
for(PathDTO path : paths) {
|
||||
for (PathDTO path : paths) {
|
||||
List<SimulatorWindDTO> points = path.getPoints();
|
||||
ctxt.setLineWidth(3.0);
|
||||
ctxt.setGlobalAlpha(0.7);
|
||||
ctxt.setStrokeStyle(this.colors.getColor(colorIdx));
|
||||
ctxt.beginPath();
|
||||
for(SimulatorWindDTO point : points) {
|
||||
Point px = mapProjection.fromLatLngToContainerPixel(LatLng.newInstance(point.position.latDeg, point.position.lngDeg));
|
||||
for (SimulatorWindDTO point : points) {
|
||||
Point px = mapProjection.fromLatLngToContainerPixel(LatLng.newInstance(point.position.latDeg,
|
||||
point.position.lngDeg));
|
||||
if (first) {
|
||||
ctxt.moveTo(px.getX(), px.getY());
|
||||
first = false;
|
||||
@@ -177,12 +164,24 @@ public class RaceSimulationOverlay extends FullCanvasOverlay implements TimeLis
|
||||
}
|
||||
}
|
||||
ctxt.stroke();
|
||||
ctxt.setGlobalAlpha(1.0);
|
||||
SimulatorWindDTO start = points.get(0);
|
||||
long timeStep = simulationResult.getTimeStep();
|
||||
for (SimulatorWindDTO point : points) {
|
||||
if ((point.timepoint - start.timepoint) % (timeStep) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Point px = mapProjection.fromLatLngToContainerPixel(LatLng.newInstance(point.position.latDeg,
|
||||
point.position.lngDeg));
|
||||
ctxt.beginPath();
|
||||
ctxt.arc(px.getX(), px.getY(), 1.5, 0, 2 * Math.PI);
|
||||
ctxt.closePath();
|
||||
ctxt.stroke();
|
||||
}
|
||||
colorIdx--;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void drawLegend(Canvas canvas) {
|
||||
if (this.simulationResult == null) {
|
||||
return;
|
||||
@@ -279,6 +278,5 @@ public class RaceSimulationOverlay extends FullCanvasOverlay implements TimeLis
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
}
|
||||
}
|
||||
+1
@@ -38,6 +38,7 @@
|
||||
|
||||
<!-- Specify the paths for translatable code -->
|
||||
<extend-property name="locale" values="de"/>
|
||||
<extend-property name="locale" values="ru"/>
|
||||
|
||||
<source path='common'/>
|
||||
<source path='client'/>
|
||||
|
||||
+2
-2
@@ -274,7 +274,7 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest
|
||||
private static final long serialVersionUID = 1234L;
|
||||
@Override
|
||||
public RegattaAndRaceIdentifier getRaceIdentifier() {
|
||||
return new RegattaNameAndRaceName("Kieler Woche (29erXX)", "Yellow Race 2");
|
||||
return new RegattaNameAndRaceName("Kieler Woche (29ERXX)", "Yellow Race 2");
|
||||
}
|
||||
@Override
|
||||
public DynamicTrackedRegatta getTrackedRegatta() {
|
||||
@@ -306,7 +306,7 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest
|
||||
|
||||
// load new RacingEventService including regatta and leaderboard
|
||||
RacingEventService resForLoading = createRacingEventServiceWithOneMockedTrackedRace(q2YellowTrackedRace);
|
||||
Regatta loadedRegatta = resForLoading.getRegattaByName("Kieler Woche (29erXX)");
|
||||
Regatta loadedRegatta = resForLoading.getRegattaByName("Kieler Woche (29ERXX)");
|
||||
assertNotNull(loadedRegatta);
|
||||
assertEquals(regatta.getName(), loadedRegatta.getName());
|
||||
assertEquals(Util.size(regatta.getSeries()), Util.size(loadedRegatta.getSeries()));
|
||||
|
||||
@@ -8,7 +8,8 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||
Fragment-Host: com.sap.sailing.polars
|
||||
Import-Package: com.sap.sailing.domain.test.mock,
|
||||
com.sap.sse.common,
|
||||
com.sap.sse.common.impl,
|
||||
org.junit;version="4.8.2"
|
||||
Require-Bundle: org.mockito.mockito-core;bundle-version="1.9.5",
|
||||
org.hamcrest;bundle-version="1.1.0"
|
||||
com.sap.sse.common.impl
|
||||
Require-Bundle: org.hamcrest;bundle-version="1.1.0",
|
||||
org.hamcrest.core;bundle-version="1.1.0",
|
||||
org.mockito.mockito-core;bundle-version="1.10.14",
|
||||
org.junit4;bundle-version="4.8.2"
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>root</artifactId>
|
||||
<groupId>com.sap.sailing</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>com.sap.sailing.polars.test</artifactId>
|
||||
<packaging>eclipse-test-plugin</packaging>
|
||||
</project>
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
package com.sap.sailing.polars.caching.test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.polars.caching.PolarFixCacheRaceInterval;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
|
||||
public class PolarFixCacheRaceIntervalTest {
|
||||
|
||||
@Test
|
||||
public void testIntervalJoin() {
|
||||
TrackedRace mockedTrackedRace1 = mock(TrackedRace.class);
|
||||
TrackedRace mockedTrackedRace2 = mock(TrackedRace.class);
|
||||
|
||||
Competitor mockedCompetitor1 = mock(Competitor.class);
|
||||
Competitor mockedCompetitor2 = mock(Competitor.class);
|
||||
|
||||
Calendar calender = Calendar.getInstance();
|
||||
calender.set(2014, 3, 17, 16, 0, 0);
|
||||
TimePoint timePoint1 = new MillisecondsTimePoint(calender.getTime());
|
||||
calender.set(2014, 3, 17, 16, 10, 0);
|
||||
TimePoint timePoint2 = new MillisecondsTimePoint(calender.getTime());
|
||||
|
||||
PolarFixCacheRaceInterval interval1 = createInterval1(
|
||||
mockedTrackedRace1, mockedTrackedRace2, mockedCompetitor1,
|
||||
mockedCompetitor2, timePoint1, timePoint2);
|
||||
|
||||
PolarFixCacheRaceInterval interval2 = createInterval2(
|
||||
mockedTrackedRace1, mockedTrackedRace2, mockedCompetitor1,
|
||||
timePoint2);
|
||||
PolarFixCacheRaceInterval joined = interval1.join(interval2);
|
||||
|
||||
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> joinedMap = joined
|
||||
.getCompetitorAndTimepointsForRace();
|
||||
|
||||
Map<Competitor, Pair<TimePoint, TimePoint>> explicitIntervalForRace1 = joinedMap.get(mockedTrackedRace1);
|
||||
assertThat(explicitIntervalForRace1, notNullValue(Map.class));
|
||||
Pair<TimePoint, TimePoint> explicitIntervalForCompetitor1 = explicitIntervalForRace1.get(mockedCompetitor1);
|
||||
assertThat(explicitIntervalForCompetitor1, notNullValue(Pair.class));
|
||||
|
||||
TimePoint start1 = explicitIntervalForCompetitor1.getA();
|
||||
TimePoint end1 = explicitIntervalForCompetitor1.getB();
|
||||
|
||||
assertThat(start1, is(timePoint1));
|
||||
assertThat(end1, is(timePoint2));
|
||||
|
||||
}
|
||||
|
||||
private PolarFixCacheRaceInterval createInterval1(
|
||||
TrackedRace mockedTrackedRace1, TrackedRace mockedTrackedRace2,
|
||||
Competitor mockedCompetitor1, Competitor mockedCompetitor2,
|
||||
TimePoint timePoint1, TimePoint timePoint2) {
|
||||
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> competitorAndTimepointsForRace = new HashMap<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>>();
|
||||
HashMap<Competitor, Pair<TimePoint, TimePoint>> mapForRace1 = new HashMap<Competitor, Pair<TimePoint, TimePoint>>();
|
||||
mapForRace1.put(mockedCompetitor1, new Pair<TimePoint, TimePoint>(timePoint1, timePoint1));
|
||||
competitorAndTimepointsForRace.put(mockedTrackedRace1, mapForRace1);
|
||||
HashMap<Competitor, Pair<TimePoint, TimePoint>> mapForRace2 = new HashMap<Competitor, Pair<TimePoint, TimePoint>>();
|
||||
mapForRace2.put(mockedCompetitor2, new Pair<TimePoint, TimePoint>(timePoint1, timePoint2));
|
||||
competitorAndTimepointsForRace.put(mockedTrackedRace2, mapForRace2);
|
||||
PolarFixCacheRaceInterval interval1 = new PolarFixCacheRaceInterval(
|
||||
competitorAndTimepointsForRace);
|
||||
return interval1;
|
||||
}
|
||||
|
||||
private PolarFixCacheRaceInterval createInterval2(
|
||||
TrackedRace mockedTrackedRace1, TrackedRace mockedTrackedRace2,
|
||||
Competitor mockedCompetitor1, TimePoint timePoint2) {
|
||||
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> competitorAndTimepointsForRace = new HashMap<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>>();
|
||||
HashMap<Competitor, Pair<TimePoint, TimePoint>> mapForRace1 = new HashMap<Competitor, Pair<TimePoint, TimePoint>>();
|
||||
mapForRace1.put(mockedCompetitor1, new Pair<TimePoint, TimePoint>(
|
||||
timePoint2, timePoint2));
|
||||
competitorAndTimepointsForRace.put(mockedTrackedRace1, mapForRace1);
|
||||
PolarFixCacheRaceInterval interval2 = new PolarFixCacheRaceInterval(
|
||||
competitorAndTimepointsForRace);
|
||||
return interval2;
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -58,6 +58,8 @@ public class PolarDataMinerTest {
|
||||
PolarDataMiner miner = new PolarDataMiner();
|
||||
|
||||
BoatClass mockedBoatClass = mock(BoatClass.class);
|
||||
|
||||
when(mockedBoatClass.getName()).thenReturn("49ER");
|
||||
|
||||
GPSFixMoving fix1_1 = createMockedFix(13, 00, 54.431952, 10.186767, 45, 10.5);
|
||||
GPSFixMoving fix1_2 = createMockedFix(13, 30, 54.485034, 10.538303, 44.8, 10.6);
|
||||
@@ -107,17 +109,17 @@ public class PolarDataMinerTest {
|
||||
}
|
||||
|
||||
SpeedWithConfidence<Void> estimatedSpeed1 = miner.estimateBoatSpeed(mockedBoatClass, new KnotSpeedImpl(15),
|
||||
new DegreeBearingImpl(44.9), true);
|
||||
new DegreeBearingImpl(44.9));
|
||||
assertThat(estimatedSpeed1, is(notNullValue()));
|
||||
assertThat(estimatedSpeed1.getObject().getKnots(), is(closeTo(10.55, EPSILON)));
|
||||
|
||||
SpeedWithConfidence<Void> estimatedSpeed2 = miner.estimateBoatSpeed(mockedBoatClass, new KnotSpeedImpl(15),
|
||||
new DegreeBearingImpl(42), true);
|
||||
new DegreeBearingImpl(42));
|
||||
assertThat(estimatedSpeed2, is(notNullValue()));
|
||||
assertThat(estimatedSpeed2.getObject().getKnots(), is(closeTo(10.05, EPSILON)));
|
||||
|
||||
SpeedWithConfidence<Void> estimatedSpeed3 = miner.estimateBoatSpeed(mockedBoatClass, new KnotSpeedImpl(15),
|
||||
new DegreeBearingImpl(42.8), true);
|
||||
new DegreeBearingImpl(42.8));
|
||||
assertThat(estimatedSpeed3, is(notNullValue()));
|
||||
assertThat(estimatedSpeed3.getObject().getKnots(), is(closeTo(10, EPSILON)));
|
||||
}
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.sap.sailing.polars.test;
|
||||
|
||||
import static org.hamcrest.Matchers.closeTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.polars.impl.CubicEquation;
|
||||
|
||||
public class CubicEquationTest {
|
||||
|
||||
|
||||
private static final double ERROR = 0.00001;
|
||||
|
||||
@Test
|
||||
public void testForOneRealRoot() {
|
||||
CubicEquation eq = new CubicEquation(3, -10, 14, 27);
|
||||
double[] result = eq.solve();
|
||||
assertThat(result.length, is(1));
|
||||
|
||||
double x1 = result[0];
|
||||
assertThat(x1, closeTo(-1, ERROR));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThreeRealAndEqualRoots() {
|
||||
CubicEquation eq = new CubicEquation(1, 6, 12, 8);
|
||||
double[] result = eq.solve();
|
||||
assertThat(result.length, is(1));
|
||||
|
||||
double x1 = result[0];
|
||||
assertThat(x1, closeTo(-2, ERROR));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThreeRealRoots() {
|
||||
CubicEquation eq = new CubicEquation(2, -4, -22, 24);
|
||||
double[] result = eq.solve();
|
||||
assertThat(result.length, is(3));
|
||||
|
||||
double x1 = result[0];
|
||||
assertTrue(Math.abs(x1 - 4) < ERROR || x1 + 3 < ERROR || x1 -1 < ERROR);
|
||||
|
||||
double x2 = result[1];
|
||||
assertTrue(Math.abs(x2 - 4) < ERROR || x2 + 3 < ERROR || x2 -1 < ERROR);
|
||||
|
||||
double x3 = result[2];
|
||||
assertTrue(Math.abs(x3 - 4) < ERROR || x3 + 3 < ERROR || x3 -1 < ERROR);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Regular → Executable
+25
-23
@@ -14,7 +14,9 @@ import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
|
||||
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.Speed;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
|
||||
import com.sap.sailing.polars.PolarDataService;
|
||||
import com.sap.sailing.polars.analysis.PolarSheetAnalyzer;
|
||||
@@ -29,26 +31,26 @@ public class PolarSheetAnalyzerTest {
|
||||
PolarSheetAnalyzer analyzer = new PolarSheetAnalyzerImpl(createMockedPolarDataService());
|
||||
BoatClass boatClass = mock(BoatClass.class);
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> result = analyzer.getAverageUpwindSpeedWithBearingOnStarboardTackFor(boatClass,
|
||||
new KnotSpeedImpl(14), true);
|
||||
SpeedWithBearingWithConfidence<Void> result = analyzer.getAverageSpeedAndCourseOverGround(boatClass,
|
||||
new KnotSpeedImpl(14), LegType.UPWIND, Tack.STARBOARD);
|
||||
assertThat(result.getObject().getKnots(), closeTo(8.5, 0.1));
|
||||
assertThat(result.getObject().getBearing().getDegrees(), closeTo(50, 0.1));
|
||||
assertThat(result.getConfidence(), closeTo(0.7, 0.1));
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> result2 = analyzer.getAverageDownwindSpeedWithBearingOnStarboardTackFor(boatClass,
|
||||
new KnotSpeedImpl(14), true);
|
||||
SpeedWithBearingWithConfidence<Void> result2 = analyzer.getAverageSpeedAndCourseOverGround(boatClass,
|
||||
new KnotSpeedImpl(14), LegType.DOWNWIND, Tack.STARBOARD);
|
||||
assertThat(result2.getObject().getKnots(), closeTo(14.2, 0.1));
|
||||
assertThat(result2.getObject().getBearing().getDegrees(), closeTo(142.8, 0.1));
|
||||
assertThat(result2.getConfidence(), closeTo(0.6, 0.1));
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> result3 = analyzer.getAverageUpwindSpeedWithBearingOnPortTackFor(boatClass,
|
||||
new KnotSpeedImpl(14), true);
|
||||
SpeedWithBearingWithConfidence<Void> result3 = analyzer.getAverageSpeedAndCourseOverGround(boatClass,
|
||||
new KnotSpeedImpl(14), LegType.UPWIND, Tack.PORT);
|
||||
assertThat(result3.getObject().getKnots(), closeTo(8.6, 0.1));
|
||||
assertThat(result3.getObject().getBearing().getDegrees(), closeTo(-49.5, 0.1));
|
||||
assertThat(result3.getConfidence(), closeTo(0.7, 0.1));
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> result4 = analyzer.getAverageDownwindSpeedWithBearingOnPortTackFor(boatClass,
|
||||
new KnotSpeedImpl(14), true);
|
||||
SpeedWithBearingWithConfidence<Void> result4 = analyzer.getAverageSpeedAndCourseOverGround(boatClass,
|
||||
new KnotSpeedImpl(14), LegType.DOWNWIND, Tack.PORT);
|
||||
assertThat(result4.getObject().getKnots(), closeTo(13.5, 0.1));
|
||||
assertThat(result4.getObject().getBearing().getDegrees(), closeTo(-143.6, 0.1));
|
||||
assertThat(result4.getConfidence(), closeTo(0.25, 0.1));
|
||||
@@ -57,52 +59,52 @@ public class PolarSheetAnalyzerTest {
|
||||
|
||||
private PolarDataService createMockedPolarDataService() throws NotEnoughDataHasBeenAddedException {
|
||||
PolarDataService mockedPolarDataService = mock(PolarDataService.class);
|
||||
Integer[] data = createCounts();
|
||||
int[] data = createCounts();
|
||||
when(mockedPolarDataService.getDataCountsForWindSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
any(Integer.class), any(Integer.class))).thenReturn(data);
|
||||
|
||||
//Starboard Upwind
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(49)), true)).thenReturn(
|
||||
argThat(new BearingMatcher(49)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(8.478048671702888), 0.5, null));
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(50)), true)).thenReturn(
|
||||
argThat(new BearingMatcher(50)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(8.466303997538812), 0.5, null));
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(51)), true)).thenReturn(
|
||||
argThat(new BearingMatcher(51)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(8.583383077026435), 0.5, null));
|
||||
|
||||
//Starboard Downwind
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(141)), true)).thenReturn(
|
||||
argThat(new BearingMatcher(141)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(13.794354053931528), 0.5, null));
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(142)), true)).thenReturn(
|
||||
argThat(new BearingMatcher(142)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(13.665782579628802), 0.5, null));
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(143)), true)).thenReturn(
|
||||
argThat(new BearingMatcher(143)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(14.169301888320263), 0.5, null));
|
||||
|
||||
//Port Upwind
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(-48)), true)).thenReturn(
|
||||
argThat(new BearingMatcher(-48)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(8.445599410456111), 0.5, null));
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(-50)), true)).thenReturn(
|
||||
argThat(new BearingMatcher(-50)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(8.553274292235153), 0.5, null));
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(-49)), true)).thenReturn(
|
||||
argThat(new BearingMatcher(-49)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(8.614582090896583), 0.5, null));
|
||||
|
||||
//Port Downwind
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(-145)), true)).thenReturn(
|
||||
argThat(new BearingMatcher(-145)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(13.78894715705271), 0.5, null));
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(-144)), true)).thenReturn(
|
||||
argThat(new BearingMatcher(-144)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(13.420656294986587), 0.5, null));
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(-143)), true)).thenReturn(
|
||||
argThat(new BearingMatcher(-143)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(13.268607457651942), 0.5, null));
|
||||
|
||||
return mockedPolarDataService;
|
||||
@@ -116,8 +118,8 @@ public class PolarSheetAnalyzerTest {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private Integer[] createCounts() {
|
||||
Integer[] dataCountsPerAngle = { 0, 0, 2, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 3, 1, 2, 1, 6, 2, 2, 2, 1, 6, 6, 17,
|
||||
private int[] createCounts() {
|
||||
int[] dataCountsPerAngle = { 0, 0, 2, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 3, 1, 2, 1, 6, 2, 2, 2, 1, 6, 6, 17,
|
||||
13, 19, 30, 43, 32, 50, 48, 57, 70, 76, 107, 129, 177, 186, 222, 237, 275, 285, 321, 322, 401, 451,
|
||||
466, 413, 468, 479, 459, 429, 360, 345, 317, 288, 278, 202, 175, 130, 113, 104, 100, 73, 45, 51, 52,
|
||||
37, 40, 36, 26, 23, 32, 33, 29, 29, 22, 24, 27, 23, 22, 23, 22, 21, 15, 18, 20, 14, 15, 21, 20, 19, 6,
|
||||
|
||||
+5
-5
@@ -42,7 +42,7 @@ import com.sap.sailing.domain.common.impl.DegreePosition;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.PolarSheetGenerationSettingsImpl;
|
||||
import com.sap.sailing.domain.common.impl.WindSourceImpl;
|
||||
import com.sap.sailing.domain.common.impl.WindSteppingWithMaxDistance;
|
||||
import com.sap.sailing.domain.common.impl.WindSpeedSteppingWithMaxDistance;
|
||||
import com.sap.sailing.domain.test.mock.MockedTrackedRace;
|
||||
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
|
||||
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
||||
@@ -78,8 +78,8 @@ public class PolarSheetGenerationTest {
|
||||
|
||||
MockTrackedRaceForPolarSheetGeneration race = new MockTrackedRaceForPolarSheetGeneration();
|
||||
|
||||
Double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. };
|
||||
WindSteppingWithMaxDistance windStepping = new WindSteppingWithMaxDistance(levels, 2.0);
|
||||
double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. };
|
||||
WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 2.0);
|
||||
PolarSheetGenerationSettings settings = new PolarSheetGenerationSettingsImpl(1, 0, 1, 20, 0, false, true, 5,
|
||||
0.05, false, windStepping, false);
|
||||
|
||||
@@ -118,8 +118,8 @@ public class PolarSheetGenerationTest {
|
||||
|
||||
@Test
|
||||
public void testHistogramBuilder() {
|
||||
Double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. };
|
||||
WindSteppingWithMaxDistance windStepping = new WindSteppingWithMaxDistance(levels, 2.0);
|
||||
double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. };
|
||||
WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 2.0);
|
||||
PolarSheetGenerationSettings settings = new PolarSheetGenerationSettingsImpl(1, 0, 1, 10, 0, false, true, 5,
|
||||
0.05, false, windStepping, false);
|
||||
PolarSheetHistogramBuilder builder = new PolarSheetHistogramBuilder(settings);
|
||||
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
package com.sap.sailing.polars.test;
|
||||
|
||||
import static org.hamcrest.Matchers.closeTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
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.KnotSpeedImpl;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.polars.regression.impl.WindSpeedAndAngleEstimator;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
|
||||
public class WindSpeedAndAngleEstimatorTest {
|
||||
|
||||
private static final double ERROR = 0.0001;
|
||||
|
||||
@Test
|
||||
public void testAverageTrueWindSpeedAndAngleEstimationForSteadyGrowth() {
|
||||
List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed = new ArrayList<>();
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(5), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(3,
|
||||
new DegreeBearingImpl(46)), 0.4, null)));
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(6), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4,
|
||||
new DegreeBearingImpl(47)), 0.6, null)));
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(7), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4.5,
|
||||
new DegreeBearingImpl(47.5)), 0.5, null)));
|
||||
WindSpeedAndAngleEstimator estimator = new WindSpeedAndAngleEstimator(averageBoatSpeedAndCourseForWindSpeed);
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(3));
|
||||
assertThat(result.getObject().getKnots(), closeTo(5, ERROR));
|
||||
assertThat(result.getObject().getBearing().getDegrees(), closeTo(46, ERROR));
|
||||
assertThat(result.getConfidence(), closeTo(0.4, ERROR));
|
||||
|
||||
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4));
|
||||
assertThat(result.getObject().getKnots(), closeTo(6, ERROR));
|
||||
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47, ERROR));
|
||||
assertThat(result.getConfidence(), closeTo(0.6, ERROR));
|
||||
|
||||
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4.5));
|
||||
assertThat(result.getObject().getKnots(), closeTo(7, ERROR));
|
||||
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47.5, ERROR));
|
||||
assertThat(result.getConfidence(), closeTo(0.5, ERROR));
|
||||
|
||||
|
||||
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4.25));
|
||||
assertThat(result.getObject().getKnots(), closeTo(6.5, ERROR));
|
||||
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47.25, ERROR));
|
||||
assertThat(result.getConfidence(), closeTo(0.55, ERROR));
|
||||
|
||||
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(3.25));
|
||||
assertThat(result.getObject().getKnots(), closeTo(5.25, ERROR));
|
||||
assertThat(result.getObject().getBearing().getDegrees(), closeTo(46.25, ERROR));
|
||||
assertThat(result.getConfidence(), closeTo(0.45, ERROR));
|
||||
|
||||
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(2.75));
|
||||
assertThat(result.getObject().getKnots(), closeTo(5, ERROR));
|
||||
assertThat(result.getObject().getBearing().getDegrees(), closeTo(46, ERROR));
|
||||
assertThat(result.getConfidence(), closeTo(0.2, ERROR));
|
||||
|
||||
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4.75));
|
||||
assertThat(result.getObject().getKnots(), closeTo(7, ERROR));
|
||||
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47.5, ERROR));
|
||||
assertThat(result.getConfidence(), closeTo(0.25, ERROR));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Not a realistic scenario in the sailing world, but testing for the sake of correctness of the algorithm
|
||||
*/
|
||||
@Test
|
||||
public void testAverageTrueWindSpeedAndAngleEstimationForSteadyDeclination() {
|
||||
List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed = new ArrayList<>();
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(7), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(3,
|
||||
new DegreeBearingImpl(46)), 0.4, null)));
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(6), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4,
|
||||
new DegreeBearingImpl(47)), 0.6, null)));
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(5), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4.5,
|
||||
new DegreeBearingImpl(47.5)), 0.5, null)));
|
||||
WindSpeedAndAngleEstimator estimator = new WindSpeedAndAngleEstimator(averageBoatSpeedAndCourseForWindSpeed);
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(3));
|
||||
assertThat(result.getObject().getKnots(), closeTo(7, ERROR));
|
||||
assertThat(result.getObject().getBearing().getDegrees(), closeTo(46, ERROR));
|
||||
assertThat(result.getConfidence(), closeTo(0.4, ERROR));
|
||||
|
||||
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4));
|
||||
assertThat(result.getObject().getKnots(), closeTo(6, ERROR));
|
||||
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47, ERROR));
|
||||
assertThat(result.getConfidence(), closeTo(0.6, ERROR));
|
||||
|
||||
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4.5));
|
||||
assertThat(result.getObject().getKnots(), closeTo(5, ERROR));
|
||||
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47.5, ERROR));
|
||||
assertThat(result.getConfidence(), closeTo(0.5, ERROR));
|
||||
|
||||
|
||||
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4.25));
|
||||
assertThat(result.getObject().getKnots(), closeTo(5.5, ERROR));
|
||||
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47.25, ERROR));
|
||||
assertThat(result.getConfidence(), closeTo(0.55, ERROR));
|
||||
|
||||
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(3.25));
|
||||
assertThat(result.getObject().getKnots(), closeTo(6.75, ERROR));
|
||||
assertThat(result.getObject().getBearing().getDegrees(), closeTo(46.25, ERROR));
|
||||
assertThat(result.getConfidence(), closeTo(0.45, ERROR));
|
||||
|
||||
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(2.75));
|
||||
assertThat(result.getObject().getKnots(), closeTo(7, ERROR));
|
||||
assertThat(result.getObject().getBearing().getDegrees(), closeTo(46, ERROR));
|
||||
assertThat(result.getConfidence(), closeTo(0.2, ERROR));
|
||||
|
||||
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4.75));
|
||||
assertThat(result.getObject().getKnots(), closeTo(5, ERROR));
|
||||
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47.5, ERROR));
|
||||
assertThat(result.getConfidence(), closeTo(0.25, ERROR));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAverageTrueWindSpeedAndAngleEstimationNegative() {
|
||||
List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed = new ArrayList<>();
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(5), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(3,
|
||||
new DegreeBearingImpl(46)), 0.4, null)));
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(6), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4,
|
||||
new DegreeBearingImpl(47)), 0.6, null)));
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(7), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4.5,
|
||||
new DegreeBearingImpl(47.5)), 0.5, null)));
|
||||
WindSpeedAndAngleEstimator estimator = new WindSpeedAndAngleEstimator(averageBoatSpeedAndCourseForWindSpeed);
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(2));
|
||||
assertThat(result, nullValue());
|
||||
|
||||
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(5.5));
|
||||
assertThat(result, nullValue());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAverageTrueWindSpeedAndAngleEstimationForNonReversibleFunction() {
|
||||
List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed = new ArrayList<>();
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(5), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(3,
|
||||
new DegreeBearingImpl(46)), 0.4, null)));
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(6), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4,
|
||||
new DegreeBearingImpl(47)), 0.6, null)));
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(7), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4.5,
|
||||
new DegreeBearingImpl(47.5)), 0.5, null)));
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(8), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4.25,
|
||||
new DegreeBearingImpl(47.5)), 0.25, null)));
|
||||
WindSpeedAndAngleEstimator estimator = new WindSpeedAndAngleEstimator(averageBoatSpeedAndCourseForWindSpeed);
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4.25));
|
||||
assertThat(result.getObject().getKnots(), closeTo(6.5, ERROR));
|
||||
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47.25, ERROR));
|
||||
assertThat(result.getConfidence(), closeTo(0.55, ERROR));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAverageTrueWindSpeedAndAngleEstimationWithAllCandidatesReturned() {
|
||||
List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed = new ArrayList<>();
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(5), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(3,
|
||||
new DegreeBearingImpl(46)), 0.4, null)));
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(6), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4,
|
||||
new DegreeBearingImpl(47)), 0.6, null)));
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(7), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4.5,
|
||||
new DegreeBearingImpl(47.5)), 0.5, null)));
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(8), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4.25,
|
||||
new DegreeBearingImpl(47.5)), 0.25, null)));
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(9), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(3,
|
||||
new DegreeBearingImpl(47.5)), 0.25, null)));
|
||||
WindSpeedAndAngleEstimator estimator = new WindSpeedAndAngleEstimator(averageBoatSpeedAndCourseForWindSpeed);
|
||||
|
||||
Set<SpeedWithBearingWithConfidence<Void>> result = estimator
|
||||
.getAverageTrueWindSpeedAndAngleCandidates(new KnotSpeedImpl(4.3));
|
||||
assertThat(result.size(), is(2));
|
||||
for (SpeedWithBearingWithConfidence<Void> candidate : result) {
|
||||
assertTrue(Math.abs(candidate.getConfidence()-0.2999999)<=ERROR ||
|
||||
Math.abs(candidate.getConfidence()-0.54)<=ERROR);
|
||||
if (Math.abs(candidate.getConfidence() - 0.2999999) < ERROR) {
|
||||
assertThat(candidate.getObject().getKnots(), closeTo(7.8, ERROR));
|
||||
assertThat(candidate.getObject().getBearing().getDegrees(), closeTo(47.5, ERROR));
|
||||
} else {
|
||||
assertThat(candidate.getObject().getKnots(), closeTo(6.6, ERROR));
|
||||
assertThat(candidate.getObject().getBearing().getDegrees(), closeTo(47.3, ERROR));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
result = estimator.getAverageTrueWindSpeedAndAngleCandidates(new KnotSpeedImpl(2.9));
|
||||
assertThat(result.size(), is(2));
|
||||
for (SpeedWithBearingWithConfidence<Void> candidate : result) {
|
||||
assertTrue(Math.abs(candidate.getConfidence()-0.2) <= ERROR ||
|
||||
Math.abs(candidate.getConfidence()-0.125) <= ERROR);
|
||||
if (Math.abs(candidate.getConfidence() - 0.2) < ERROR) {
|
||||
assertThat(candidate.getObject().getKnots(), closeTo(5, ERROR));
|
||||
assertThat(candidate.getObject().getBearing().getDegrees(), closeTo(46, ERROR));
|
||||
} else {
|
||||
assertThat(candidate.getObject().getKnots(), closeTo(9, ERROR));
|
||||
assertThat(candidate.getObject().getBearing().getDegrees(), closeTo(47.5, ERROR));
|
||||
|
||||
}
|
||||
}
|
||||
result = estimator.getAverageTrueWindSpeedAndAngleCandidates(new KnotSpeedImpl(4.9));
|
||||
assertThat(result.size(), is(1));
|
||||
SpeedWithBearingWithConfidence<Void> candidate = result.iterator().next();
|
||||
assertThat(candidate.getObject().getKnots(), closeTo(7, ERROR));
|
||||
assertThat(candidate.getObject().getBearing().getDegrees(), closeTo(47.5, ERROR));
|
||||
assertThat(candidate.getConfidence(), closeTo(0.25, ERROR));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAverageTrueWindSpeedAndAngleEstimationForTwoSamplingPointsWithSameBoatSpeed() {
|
||||
List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed = new ArrayList<>();
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(7), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4.5,
|
||||
new DegreeBearingImpl(47.5)), 0.5, null)));
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(8), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4.5,
|
||||
new DegreeBearingImpl(47.5)), 0.4, null)));
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(9), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(5.5,
|
||||
new DegreeBearingImpl(47.5)), 0.4, null)));
|
||||
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
|
||||
new KnotSpeedImpl(10), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(5.5,
|
||||
new DegreeBearingImpl(47.5)), 0.5, null)));
|
||||
WindSpeedAndAngleEstimator estimator = new WindSpeedAndAngleEstimator(averageBoatSpeedAndCourseForWindSpeed);
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4.5));
|
||||
assertThat(result.getObject().getKnots(), closeTo(7.44444, ERROR));
|
||||
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47.5, ERROR));
|
||||
assertThat(result.getConfidence(), closeTo(0.455555, ERROR));
|
||||
|
||||
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(5.5));
|
||||
assertThat(result.getObject().getKnots(), closeTo(9.555555, ERROR));
|
||||
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47.5, ERROR));
|
||||
assertThat(result.getConfidence(), closeTo(0.455555, ERROR));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Loaded 100 of 157 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user