Merge remote-tracking branch 'origin/master' into dataMining

This commit is contained in:
Lennart Hensler committed 2015-01-08 12:49:53 +01:00
commit 62cbabcc14
157 files changed
+6321 -1855

No files matched your search

@@ -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);
}
@@ -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() {
@@ -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();
@@ -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 &lt;= angle &lt;
* 360 and whose values are all the data points collected for this
*
*/
Map<Integer, Map<Integer, PolarSheetsHistogramData>> getHistogramDataMap();
}
@@ -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,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);
@@ -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;
}
@@ -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,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;
}
@@ -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));
}
}
@@ -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);
@@ -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;
@@ -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;
@@ -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();
@@ -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);
}
@@ -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() {
@@ -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;
}
@@ -102,7 +102,7 @@ public class SimpleDomainFactoryTest {
}
});
assertNotNull(raceDefinition.getBoatClass());
assertEquals("Unknown", raceDefinition.getBoatClass().getName());
assertEquals("UNKNOWN", raceDefinition.getBoatClass().getName());
}
@Test
File diff suppressed because it is too large. Load diff
@@ -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)));
}
}
@@ -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());
}
}
@@ -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());
@@ -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);
}
}
@@ -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));
@@ -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();
@@ -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
@@ -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
@@ -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)
@@ -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();
@@ -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;
}
}
@@ -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)) {
@@ -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;
}
@@ -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)
*
@@ -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);
+2 -1
View File
@@ -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
@@ -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);
}
}
});
}
@@ -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);
}
}
@@ -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()));
}
}
@@ -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
@@ -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,
@@ -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);
@@ -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);
}
@@ -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}
@@ -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}
@@ -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}
@@ -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);
}
}
@@ -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);
}
}
}
}
@@ -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) {
@@ -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();
}
}
@@ -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()));
@@ -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);
@@ -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);
@@ -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) {
@@ -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;
}
}
@@ -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) {
@@ -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);
@@ -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;
}
@@ -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
@@ -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;
}
@@ -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;
}
@@ -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
}
}
}));
}
}
@@ -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'/>
@@ -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"
+12
View File
@@ -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>
@@ -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;
}
}
@@ -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)));
}
@@ -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);
}
}
@@ -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,
@@ -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);
@@ -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
+48 -38
View File
@@ -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);
}
@@ -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;
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -1,9 +0,0 @@
package com.sap.sailing.polars.caching;
import com.sap.sailing.domain.base.BoatClass;
public interface PolarFixCacheUpdateDoneListener {
void cacheUpdateDoneForBoatClass(BoatClass key);
}
@@ -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;
}
}
@@ -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());
}
}
@@ -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
@@ -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;
}
}
@@ -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;
// }
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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();
/**
*
@@ -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]));
+34 -40
View File
@@ -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);
}
}
@@ -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