Merge remote-tracking branch 'origin/master' into regatta-log

Conflicts:
	java/com.sap.sailing.domain/META-INF/MANIFEST.MF
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
This commit is contained in:
Fredrik Teschke
2014-12-11 11:19:39 +01:00
156 changed files with 6757 additions and 1741 deletions
+4 -3
View File
@@ -528,9 +528,10 @@ if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then
if [ $gwtcompile -eq 1 ]; then
echo "INFO: Compiling GWT (rm -rf com.sap.$PROJECT_TYPE.gwt.ui/com.sap.$PROJECT_TYPE.*)"
rm -rf com.sap.$PROJECT_TYPE.gwt.ui/com.sap.$PROJECT_TYPE.*
GWT_XML_FILES=`find com.sap.$PROJECT_TYPE.gwt.ui/src/main/resources -name '*.gwt.xml'`
if [ $onegwtpermutationonly -eq 1 ]; then
echo "INFO: Patching .gwt.xml files such that only one GWT permutation needs to be compiled"
for i in com.sap.$PROJECT_TYPE.gwt.ui/src/main/resources/com/sap/$PROJECT_TYPE/gwt/ui/*.gwt.xml; do
for i in $GWT_XML_FILES; do
echo "INFO: Patching $i files such that only one GWT permutation needs to be compiled"
cp $i $i.bak
cat $i | sed -e 's/^[ ]*<extend-property *name="locale" *values="de" *\/>/<!-- <extend-property name="locale" values="de"\/> --> <set-property name="user.agent" value="gecko1_8" \/>/' >$i.sed
@@ -538,7 +539,7 @@ if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then
done
else
echo "INFO: Patching .gwt.xml files such that all GWT permutations are compiled"
for i in com.sap.$PROJECT_TYPE.gwt.ui/src/main/resources/com/sap/$PROJECT_TYPE/gwt/ui/*.gwt.xml; do
for i in $GWT_XML_FILES; do
echo "INFO: Patching $i files such that all GWT permutations are compiled"
cp $i $i.bak
cat $i | sed -e 's/<!-- <extend-property *name="locale" *values="de" *\/> --> <set-property name="user.agent" value="gecko1_8" \/>/<extend-property name="locale" values="de"\/>/' >$i.sed
@@ -632,7 +633,7 @@ if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then
if [ $gwtcompile -eq 1 ]; then
# Now move back the backup .gwt.xml files before they were (maybe) patched
echo "INFO: restoring backup copies of .gwt.xml files after they has been patched before"
for i in com.sap.$PROJECT_TYPE.gwt.ui/src/main/resources/com/sap/$PROJECT_TYPE/gwt/ui/*.gwt.xml; do
for i in $GWT_XML_FILES; do
mv -v $i.bak $i
done
fi
@@ -30,4 +30,6 @@ public interface PolarSheetGenerationSettings extends Serializable{
boolean splitByWindgauges();
boolean areDefault();
}
@@ -8,10 +8,8 @@ public interface PolarSheetsData extends Serializable {
Number[][] getAveragedPolarDataByWindSpeed();
int getDataCount();
boolean isComplete();
Integer[] getDataCountPerAngleForWindspeed(int beaufort);
Integer[] getDataCountPerAngleForWindspeed(int windIndex);
WindStepping getStepping();
@@ -0,0 +1,20 @@
package com.sap.sailing.domain.common;
import java.io.Serializable;
import java.util.List;
import com.sap.sse.common.Util.Pair;
public interface PolarSheetsXYDiagramData extends Serializable {
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();
}
@@ -6,8 +6,18 @@ public interface WindStepping extends Serializable{
public abstract int getLevelIndexForValue(double speed);
public abstract int getSteppedValueForValue(double speed);
public abstract Double getSteppedValueForValue(double speed);
Integer[] getRawStepping();
Double[] getRawStepping();
public abstract int getLevelIndexFloorForValue(double speed);
public abstract int getLevelIndexCeilingForValue(double speed);
double getDistanceToLevelFloor(double speed);
int hashCode();
boolean equals(Object obj);
}
@@ -1,13 +1,28 @@
package com.sap.sailing.domain.common.impl;
import java.util.ArrayList;
import java.util.List;
import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSettings {
public static PolarSheetGenerationSettings createStandardPolarSettings() {
Integer[] levels = { 4, 6, 8, 10, 12, 14, 16, 20, 25, 30 };
WindSteppingWithMaxDistance windStepping = new WindSteppingWithMaxDistance(levels, 2.0);
return new PolarSheetGenerationSettingsImpl(200, 0.1, 10, 20, 0.1, true, true, 2, 0.05, true, windStepping,
Double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. };
WindSteppingWithMaxDistance windStepping = new WindSteppingWithMaxDistance(levels, 2.5);
return new PolarSheetGenerationSettingsImpl(50, 0.1, 20, 20, 0.1, true, true, 2, 0.05, true, windStepping,
false);
}
public static PolarSheetGenerationSettings createBackendPolarSettings() {
List<Double> levelList = new ArrayList<Double>();
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);
return new PolarSheetGenerationSettingsImpl(50, 0.1, 20, 20, 0.1, true, true, 2, 0.05, true, windStepping,
false);
}
@@ -108,4 +123,85 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet
return splitByWindGauges;
}
@Override
public boolean areDefault() {
return createStandardPolarSettings().equals(this);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(minimumConfidenceMeasure);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + ((minimumDataCountPerAngle == null) ? 0 : minimumDataCountPerAngle.hashCode());
result = prime * result + ((minimumDataCountPerGraph == null) ? 0 : minimumDataCountPerGraph.hashCode());
temp = Double.doubleToLongBits(minimumWindConfidence);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + ((numberOfHistogramColumns == null) ? 0 : numberOfHistogramColumns.hashCode());
temp = Double.doubleToLongBits(outlierDetectionNeighboorhoodRadius);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(outlierMinimumNeighboorhoodPct);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + (shouldRemoveOutliers ? 1231 : 1237);
result = prime * result + (splitByWindGauges ? 1231 : 1237);
result = prime * result + (useOnlyEstimationForWindDirection ? 1231 : 1237);
result = prime * result + (useOnlyWindGaugesForWindSpeed ? 1231 : 1237);
result = prime * result + ((windStepping == null) ? 0 : windStepping.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PolarSheetGenerationSettingsImpl other = (PolarSheetGenerationSettingsImpl) obj;
if (Double.doubleToLongBits(minimumConfidenceMeasure) != Double
.doubleToLongBits(other.minimumConfidenceMeasure))
return false;
if (minimumDataCountPerAngle == null) {
if (other.minimumDataCountPerAngle != null)
return false;
} else if (!minimumDataCountPerAngle.equals(other.minimumDataCountPerAngle))
return false;
if (minimumDataCountPerGraph == null) {
if (other.minimumDataCountPerGraph != null)
return false;
} else if (!minimumDataCountPerGraph.equals(other.minimumDataCountPerGraph))
return false;
if (Double.doubleToLongBits(minimumWindConfidence) != Double.doubleToLongBits(other.minimumWindConfidence))
return false;
if (numberOfHistogramColumns == null) {
if (other.numberOfHistogramColumns != null)
return false;
} else if (!numberOfHistogramColumns.equals(other.numberOfHistogramColumns))
return false;
if (Double.doubleToLongBits(outlierDetectionNeighboorhoodRadius) != Double
.doubleToLongBits(other.outlierDetectionNeighboorhoodRadius))
return false;
if (Double.doubleToLongBits(outlierMinimumNeighboorhoodPct) != Double
.doubleToLongBits(other.outlierMinimumNeighboorhoodPct))
return false;
if (shouldRemoveOutliers != other.shouldRemoveOutliers)
return false;
if (splitByWindGauges != other.splitByWindGauges)
return false;
if (useOnlyEstimationForWindDirection != other.useOnlyEstimationForWindDirection)
return false;
if (useOnlyWindGaugesForWindSpeed != other.useOnlyWindGaugesForWindSpeed)
return false;
if (windStepping == null) {
if (other.windStepping != null)
return false;
} else if (!windStepping.equals(other.windStepping))
return false;
return true;
}
}
@@ -12,8 +12,6 @@ public class PolarSheetsDataImpl implements PolarSheetsData {
private Number[][] averagedPolarDataByWindSpeed;
private boolean complete;
private int dataCount;
private WindStepping stepping;
@@ -25,9 +23,10 @@ public class PolarSheetsDataImpl implements PolarSheetsData {
//For GWT Serialization
PolarSheetsDataImpl() {};
public PolarSheetsDataImpl(Number[][] averagedPolarDataByWindSpeed, boolean complete, int dataCount, Map<Integer,Integer[]> dataCountPerAngleForWindspeed, WindStepping stepping, Map<Integer, Map<Integer, PolarSheetsHistogramData>> histogramDataMap) {
public PolarSheetsDataImpl(Number[][] averagedPolarDataByWindSpeed, int dataCount,
Map<Integer, Integer[]> dataCountPerAngleForWindspeed, WindStepping stepping,
Map<Integer, Map<Integer, PolarSheetsHistogramData>> histogramDataMap) {
this.averagedPolarDataByWindSpeed = averagedPolarDataByWindSpeed;
this.complete = complete;
this.dataCount = dataCount;
this.dataCountPerAngleForWindspeed = dataCountPerAngleForWindspeed;
this.stepping = stepping;
@@ -40,11 +39,6 @@ public class PolarSheetsDataImpl implements PolarSheetsData {
return averagedPolarDataByWindSpeed;
}
@Override
public boolean isComplete() {
return complete;
}
@Override
public int getDataCount() {
return dataCount;
@@ -0,0 +1,57 @@
package com.sap.sailing.domain.common.impl;
import java.util.List;
import com.sap.sailing.domain.common.PolarSheetsXYDiagramData;
import com.sap.sse.common.Util.Pair;
public class PolarSheetsXYDiagramDataImpl implements PolarSheetsXYDiagramData {
private static final long serialVersionUID = 778667444303004468L;
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;
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;
}
@Override
public List<Pair<Double, Double>> getPointsForUpwindStarboardAverageAngle() {
return pointsForUpwindStarboardAverageAngle;
}
@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;
}
}
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.common.impl;
import java.util.Arrays;
import com.sap.sailing.domain.common.WindStepping;
public class WindSteppingImpl implements WindStepping {
@@ -9,9 +11,9 @@ public class WindSteppingImpl implements WindStepping {
protected WindSteppingImpl() {};
private static final long serialVersionUID = 2215693490331489508L;
protected Integer[] levels;
protected Double[] levels;
public WindSteppingImpl(Integer[] levels) {
public WindSteppingImpl(Double[] levels) {
this.levels = levels;
}
@@ -22,7 +24,7 @@ public class WindSteppingImpl implements WindStepping {
@Override
public int getLevelIndexForValue(double speed) {
for (int i = 0; i < levels.length - 1; i++) {
if (speed < levels[i] + ((levels[i+1] - levels[i]) / 2)) {
if (speed < levels[i] + ((levels[i+1] - levels[i]) / 2.)) {
return i;
}
}
@@ -30,18 +32,78 @@ public class WindSteppingImpl implements WindStepping {
}
@Override
public Integer[] getRawStepping() {
public Double[] getRawStepping() {
return levels;
}
@Override
public int getSteppedValueForValue(double speed) {
public Double getSteppedValueForValue(double speed) {
int levelIndex = getLevelIndexForValue(speed);
int result = - 1;
Double result = -1.0;
if (levelIndex >= 0) {
result = levels[levelIndex];
}
return result;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(levels);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
WindSteppingImpl other = (WindSteppingImpl) obj;
if (!Arrays.equals(levels, other.levels))
return false;
return true;
}
@Override
public int getLevelIndexFloorForValue(double speed) {
int result = -1;
for (int i = 0; i < levels.length - 1; i++) {
if (speed >= levels[i]) {
result = i;
}
}
return result;
}
@Override
public double getDistanceToLevelFloor(double speed) {
double result = 0;
int floor = getLevelIndexFloorForValue(speed);
if (floor == -1) {
result = 0;
} else {
result = speed - levels[floor];
}
return result;
}
@Override
public int getLevelIndexCeilingForValue(double speed) {
int floor = getLevelIndexFloorForValue(speed);
int result;
if (floor == -1) {
result = -1;
}
if (levels[floor] == speed) {
result = floor;
} else {
result = floor + 1;
}
return result;
}
}
@@ -11,7 +11,7 @@ public class WindSteppingWithMaxDistance extends WindSteppingImpl {
super();
};
public WindSteppingWithMaxDistance(Integer[] levels, double maxDistance) {
public WindSteppingWithMaxDistance(Double[] levels, double maxDistance) {
super(levels);
this.maxDistance = maxDistance;
}
@@ -20,7 +20,7 @@ public class WindSteppingWithMaxDistance extends WindSteppingImpl {
public int getLevelIndexForValue(double speed) {
int result = -1;
for (int i = 0; i < levels.length - 1; i++) {
double threshold = levels[i] + ((levels[i+1] - levels[i]) / 2);
double threshold = levels[i] + ((levels[i+1] - levels[i]) / 2.);
if (speed < threshold) {
if (threshold - speed <= maxDistance * 2) {
result = i;
@@ -39,4 +39,30 @@ public class WindSteppingWithMaxDistance extends WindSteppingImpl {
public double getMaxDistance() {
return maxDistance;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
long temp;
temp = Double.doubleToLongBits(maxDistance);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
WindSteppingWithMaxDistance other = (WindSteppingWithMaxDistance) obj;
if (Double.doubleToLongBits(maxDistance) != Double.doubleToLongBits(other.maxDistance))
return false;
return true;
}
}
@@ -102,7 +102,7 @@ extends TrackImpl<EventT> implements AbstractLog<EventT, VisitorT> {
logger.finer(String.format("%s (%s) was added to log %s.", event, event.getClass().getName(), getId()));
onSuccessfulAdd(event, notifyListeners);
} else {
logger.fine(String.format("%s (%s) was not added to race log %s because it already existed there.", event, event.getClass().getName(), getId()));
logger.finer(String.format("%s (%s) was not added to race log %s because it already existed there.", event, event.getClass().getName(), getId()));
}
return isAdded;
}
@@ -12,26 +12,26 @@ public class WindSteppingTest {
@Test
public void testStepping() {
Integer[] levels = {2,4};
Double[] levels = {2.,4.};
WindStepping stepping = new WindSteppingImpl(levels);
Assert.assertEquals(0, stepping.getLevelIndexForValue(1));
Assert.assertEquals(1, stepping.getLevelIndexForValue(5));
Assert.assertEquals(1, stepping.getLevelIndexForValue(3));
Assert.assertEquals(2, stepping.getSteppedValueForValue(1.064));
Assert.assertEquals(4, stepping.getSteppedValueForValue(7.8365));
Assert.assertEquals(2., stepping.getSteppedValueForValue(1.064), 0.005);
Assert.assertEquals(4., stepping.getSteppedValueForValue(7.8365), 0.005);
}
@Test
public void testSteppingWithMaxDistance() {
Integer[] levels = {2,4};
Double[] levels = {2.,4.};
WindStepping stepping = new WindSteppingWithMaxDistance(levels, 1.0);
Assert.assertEquals(0, stepping.getLevelIndexForValue(1));
Assert.assertEquals(1, stepping.getLevelIndexForValue(5));
Assert.assertEquals(-1, stepping.getLevelIndexForValue(5.01));
Assert.assertEquals(-1, stepping.getLevelIndexForValue(0.5));
Assert.assertEquals(-1, stepping.getLevelIndexForValue(8));
Assert.assertEquals(2, stepping.getSteppedValueForValue(1.064));
Assert.assertEquals(-1, stepping.getSteppedValueForValue(7.8365));
Assert.assertEquals(2.0, stepping.getSteppedValueForValue(1.064), 0.005);
Assert.assertEquals(-1.0, stepping.getSteppedValueForValue(7.8365), 0.005);
}
}
+40 -41
View File
@@ -1,41 +1,40 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Domain
Bundle-SymbolicName: com.sap.sailing.domain
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ClassPath: .
Export-Package: com.sap.sailing.domain.base,
com.sap.sailing.domain.base.impl,
com.sap.sailing.domain.confidence,
com.sap.sailing.domain.leaderboard,
com.sap.sailing.domain.leaderboard.caching,
com.sap.sailing.domain.leaderboard.impl,
com.sap.sailing.domain.leaderboard.meta,
com.sap.sailing.domain.markpassingcalculation,
com.sap.sailing.domain.markpassingcalculation.splining,
com.sap.sailing.domain.masterdataimport,
com.sap.sailing.domain.polarsheets,
com.sap.sailing.domain.racelog,
com.sap.sailing.domain.racelog.impl,
com.sap.sailing.domain.racelog.tracking,
com.sap.sailing.domain.regattalike,
com.sap.sailing.domain.regattalog,
com.sap.sailing.domain.regattalog.impl,
com.sap.sailing.domain.trackfiles,
com.sap.sailing.domain.trackimport,
com.sap.sailing.domain.tracking,
com.sap.sailing.domain.tracking.impl,
com.sap.sailing.util,
com.sap.sailing.util.impl;x-friends:="com.sap.sailing.server.replication,com.sap.sailing.domain.test"
Bundle-ActivationPolicy: lazy
Require-Bundle: com.googlecode.java-diff-utils;bundle-version="1.3.0",
com.sap.sailing.domain.shared.android;bundle-version="1.0.0",
com.sap.sailing.domain.common,
org.json.simple;bundle-version="1.1.0",
com.sap.sailing.geocoding,
com.sap.sse.datamining.shared;bundle-version="1.0.0",
com.sap.sse.common,
com.sap.sse,
com.sap.sse.replication
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Domain
Bundle-SymbolicName: com.sap.sailing.domain
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ClassPath: .
Export-Package: com.sap.sailing.domain.base,
com.sap.sailing.domain.base.impl,
com.sap.sailing.domain.confidence,
com.sap.sailing.domain.leaderboard,
com.sap.sailing.domain.leaderboard.caching,
com.sap.sailing.domain.leaderboard.impl,
com.sap.sailing.domain.leaderboard.meta,
com.sap.sailing.domain.markpassingcalculation,
com.sap.sailing.domain.markpassingcalculation.splining,
com.sap.sailing.domain.masterdataimport,
com.sap.sailing.domain.racelog,
com.sap.sailing.domain.racelog.impl,
com.sap.sailing.domain.racelog.tracking,
com.sap.sailing.domain.regattalike,
com.sap.sailing.domain.regattalog,
com.sap.sailing.domain.regattalog.impl,
com.sap.sailing.domain.trackfiles,
com.sap.sailing.domain.trackimport,
com.sap.sailing.domain.tracking,
com.sap.sailing.domain.tracking.impl,
com.sap.sailing.util,
com.sap.sailing.util.impl;x-friends:="com.sap.sailing.server.replication,com.sap.sailing.domain.test"
Bundle-ActivationPolicy: lazy
Require-Bundle: com.googlecode.java-diff-utils;bundle-version="1.3.0",
com.sap.sailing.domain.shared.android;bundle-version="1.0.0",
com.sap.sailing.domain.common,
org.json.simple;bundle-version="1.1.0",
com.sap.sailing.geocoding,
com.sap.sse.datamining.shared;bundle-version="1.0.0",
com.sap.sse.common,
com.sap.sse,
com.sap.sse.replication
@@ -1,24 +0,0 @@
package com.sap.sailing.domain.polarsheets;
import java.util.logging.Level;
import java.util.logging.Logger;
public class AngleSpeedOddClassifier implements OddFixClassifier {
private static final Logger logger = Logger.getLogger(AngleSpeedOddClassifier.class.getName());
@Override
public boolean classifiesAsOdd(PolarFix fix) {
double angleToWind = fix.getAngleToWind();
double speedInKnots = fix.getBoatSpeed().getKnots();
if (angleToWind < 20 && angleToWind > -20) {
if (speedInKnots > 2) {
logger.log(Level.INFO, "Boat goes fast against the wind. Data-Point should have been excluded during maneuver check.");
return true;
}
}
return false;
}
}
@@ -1,16 +0,0 @@
package com.sap.sailing.domain.polarsheets;
public interface OddFixClassifier {
/**
* Used for classifying fixes from a logical perspective.
*
* Example: An OddFixClassifier could check if a boat has high speed when going against the wind.
*
* @param polarFix to be classified
* @return True if odd from a sailing perspective, false if it makes sense. Classification depends on the type of OddFixClassifier used.
*/
public boolean classifiesAsOdd(PolarFix polarFix);
}
@@ -22,8 +22,8 @@ public interface DynamicTrackedRegatta extends TrackedRegatta {
* may be <code>null</code> which means that no update will be fired to any
* {@link DynamicRaceDefinitionSet}.
*/
DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable<Sideline> sidelines, WindStore windStore,
GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed,
DynamicRaceDefinitionSet raceDefinitionSetToUpdate);
DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable<Sideline> sidelines,
WindStore windStore, GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
long millisecondsOverWhichToAverageSpeed, DynamicRaceDefinitionSet raceDefinitionSetToUpdate);
}
@@ -66,7 +66,7 @@ public class PolarSheetGenerationServiceTest {
SailingService service = new MockSailingServiceForPolarSheetGeneration();
Integer[] levels = { 4, 6, 8, 10, 12, 14, 16, 20, 25, 30 };
Double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. };
WindSteppingWithMaxDistance windStepping = new WindSteppingWithMaxDistance(levels, 2.0);
PolarSheetGenerationSettings settings = new PolarSheetGenerationSettingsImpl(1, 0, 1, 20, 0, false, true, 5,
0.05, false, windStepping, false);
@@ -81,7 +81,6 @@ public class PolarSheetGenerationServiceTest {
PolarSheetsData results = triggerData.getData();
Assert.assertTrue(results.isComplete());
Assert.assertNotNull(results);
Assert.assertEquals(4, results.getDataCount());
+6 -2
View File
@@ -27,14 +27,18 @@
<!-- be added before this line. -->
<!-- -->
<script type="text/javascript" src="com.sap.sailing.gwt.Home/com.sap.sailing.gwt.Home.nocache.js"></script>
<script type="text/javascript" src="js/newhome/idangerous.swiper.js"></script>
<script type="text/javascript" src="js/jquery-1.5.2.min.js"></script>
<script src="js/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="highcharts/js/highcharts.js"></script>
<!-- Optionally, add a highcharts theme file -->
<script type="text/javascript" src="highcharts/js/themes/grid.js"></script>
<link rel="stylesheet" type="text/css" href="js/jquery.slick/slick-1.3.15.css"/>
<script type="text/javascript" src="js/jquery.slick/slick-1.3.15.min.js"></script>
</head>
<!-- -->
+4 -1
View File
@@ -45,7 +45,10 @@ Require-Bundle: com.sap.sailing.domain,
com.sap.sse.security.common
Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator
Bundle-ActivationPolicy: lazy
Import-Package: javax.jms;version="1.1.0",
Import-Package: com.sap.sailing.polars,
com.sap.sailing.polars.factory,
com.sap.sailing.polars.regression,
javax.jms;version="1.1.0",
javax.servlet;version="3.1.0",
javax.servlet.descriptor;version="3.1.0",
javax.servlet.http;version="3.1.0",
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by Fontastic.me</metadata>
<defs>
<font id="slick" horiz-adv-x="512">
<font-face font-family="slick" units-per-em="512" ascent="480" descent="-32"/>
<missing-glyph horiz-adv-x="512" />
<glyph unicode="&#8594;" d="M241 113l130 130c4 4 6 8 6 13 0 5-2 9-6 13l-130 130c-3 3-7 5-12 5-5 0-10-2-13-5l-29-30c-4-3-6-7-6-12 0-5 2-10 6-13l87-88-87-88c-4-3-6-8-6-13 0-5 2-9 6-12l29-30c3-3 8-5 13-5 5 0 9 2 12 5z m234 143c0-40-9-77-29-110-20-34-46-60-80-80-33-20-70-29-110-29-40 0-77 9-110 29-34 20-60 46-80 80-20 33-29 70-29 110 0 40 9 77 29 110 20 34 46 60 80 80 33 20 70 29 110 29 40 0 77-9 110-29 34-20 60-46 80-80 20-33 29-70 29-110z"/>
<glyph unicode="&#8592;" d="M296 113l29 30c4 3 6 7 6 12 0 5-2 10-6 13l-87 88 87 88c4 3 6 8 6 13 0 5-2 9-6 12l-29 30c-3 3-8 5-13 5-5 0-9-2-12-5l-130-130c-4-4-6-8-6-13 0-5 2-9 6-13l130-130c3-3 7-5 12-5 5 0 10 2 13 5z m179 143c0-40-9-77-29-110-20-34-46-60-80-80-33-20-70-29-110-29-40 0-77 9-110 29-34 20-60 46-80 80-20 33-29 70-29 110 0 40 9 77 29 110 20 34 46 60 80 80 33 20 70 29 110 29 40 0 77-9 110-29 34-20 60-46 80-80 20-33 29-70 29-110z"/>
<glyph unicode="&#8226;" d="M475 256c0-40-9-77-29-110-20-34-46-60-80-80-33-20-70-29-110-29-40 0-77 9-110 29-34 20-60 46-80 80-20 33-29 70-29 110 0 40 9 77 29 110 20 34 46 60 80 80 33 20 70 29 110 29 40 0 77-9 110-29 34-20 60-46 80-80 20-33 29-70 29-110z"/>
<glyph unicode="&#97;" d="M475 439l0-128c0-5-1-9-5-13-4-4-8-5-13-5l-128 0c-8 0-13 3-17 11-3 7-2 14 4 20l40 39c-28 26-62 39-100 39-20 0-39-4-57-11-18-8-33-18-46-32-14-13-24-28-32-46-7-18-11-37-11-57 0-20 4-39 11-57 8-18 18-33 32-46 13-14 28-24 46-32 18-7 37-11 57-11 23 0 44 5 64 15 20 9 38 23 51 42 2 1 4 3 7 3 3 0 5-1 7-3l39-39c2-2 3-3 3-6 0-2-1-4-2-6-21-25-46-45-76-59-29-14-60-20-93-20-30 0-58 5-85 17-27 12-51 27-70 47-20 19-35 43-47 70-12 27-17 55-17 85 0 30 5 58 17 85 12 27 27 51 47 70 19 20 43 35 70 47 27 12 55 17 85 17 28 0 55-5 81-15 26-11 50-26 70-45l37 37c6 6 12 7 20 4 8-4 11-9 11-17z"/>
</font></defs></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,57 @@
@charset "UTF-8";
/* Slider */
.slick-slider { position: relative; display: block; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -ms-touch-action: pan-y; touch-action: pan-y; -webkit-tap-highlight-color: transparent; }
.slick-list { position: relative; overflow: hidden; display: block; margin: 0; padding: 0; }
.slick-list:focus { outline: none; }
.slick-loading .slick-list { background: #fff url("./ajax-loader.gif") center center no-repeat; }
.slick-list.dragging { cursor: pointer; cursor: hand; }
.slick-slider .slick-track { -webkit-transform: translate3d(0, 0, 0); -moz-transform: translate3d(0, 0, 0); -ms-transform: translate3d(0, 0, 0); -o-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); }
.slick-track { position: relative; left: 0; top: 0; display: block; }
.slick-track:before, .slick-track:after { content: ""; display: table; }
.slick-track:after { clear: both; }
.slick-loading .slick-track { visibility: hidden; }
.slick-slide { float: left; height: 100%; min-height: 1px; display: none; }
[dir="rtl"] .slick-slide { float: right; }
.slick-slide img { display: block; }
.slick-slide.slick-loading img { display: none; }
.slick-slide.dragging img { pointer-events: none; }
.slick-initialized .slick-slide { display: block; }
.slick-loading .slick-slide { visibility: hidden; }
.slick-vertical .slick-slide { display: block; height: auto; border: 1px solid transparent; }
/* Icons */
@font-face { font-family: "slick"; src: url("./fonts/slick.eot"); src: url("./fonts/slick.eot?#iefix") format("embedded-opentype"), url("./fonts/slick.woff") format("woff"), url("./fonts/slick.ttf") format("truetype"), url("./fonts/slick.svg#slick") format("svg"); font-weight: normal; font-style: normal; }
/* Arrows */
.slick-prev, .slick-next { position: absolute; display: block; height: 20px; width: 20px; line-height: 0; font-size: 0; cursor: pointer; background: transparent; color: transparent; top: 50%; margin-top: -10px; padding: 0; border: none; outline: none; }
.slick-prev:hover, .slick-prev:focus, .slick-next:hover, .slick-next:focus { outline: none; background: transparent; color: transparent; }
.slick-prev:hover:before, .slick-prev:focus:before, .slick-next:hover:before, .slick-next:focus:before { opacity: 1; }
.slick-prev.slick-disabled:before, .slick-next.slick-disabled:before { opacity: 0.25; }
.slick-prev:before, .slick-next:before { font-family: "slick"; font-size: 20px; line-height: 1; color: white; opacity: 0.75; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
.slick-prev { left: -25px; }
[dir="rtl"] .slick-prev { left: auto; right: -25px; }
.slick-prev:before { content: "←"; }
[dir="rtl"] .slick-prev:before { content: "→"; }
.slick-next { right: -25px; }
[dir="rtl"] .slick-next { left: -25px; right: auto; }
.slick-next:before { content: "→"; }
[dir="rtl"] .slick-next:before { content: "←"; }
/* Dots */
.slick-slider { margin-bottom: 30px; }
.slick-dots { position: absolute; bottom: -45px; list-style: none; display: block; text-align: center; padding: 0; width: 100%; }
.slick-dots li { position: relative; display: inline-block; height: 20px; width: 20px; margin: 0 5px; padding: 0; cursor: pointer; }
.slick-dots li button { border: 0; background: transparent; display: block; height: 20px; width: 20px; outline: none; line-height: 0; font-size: 0; color: transparent; padding: 5px; cursor: pointer; }
.slick-dots li button:hover, .slick-dots li button:focus { outline: none; }
.slick-dots li button:hover:before, .slick-dots li button:focus:before { opacity: 1; }
.slick-dots li button:before { position: absolute; top: 0; left: 0; content: "•"; width: 20px; height: 20px; font-family: "slick"; font-size: 6px; line-height: 20px; text-align: center; color: black; opacity: 0.25; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
.slick-dots li.slick-active button:before { color: black; opacity: 0.75; }
/*# sourceMappingURL=slick.css.map */
@@ -0,0 +1,7 @@
{
"version": 3,
"mappings": ";;AAuCA,aAAc,GACV,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,KAAK,EACd,UAAU,EAAE,UAAU,EACtB,eAAe,EAAE,UAAU,EAC3B,qBAAqB,EAAE,IAAI,EAC3B,mBAAmB,EAAE,IAAI,EACzB,kBAAkB,EAAE,IAAI,EACxB,gBAAgB,EAAE,IAAI,EACtB,eAAe,EAAE,IAAI,EACrB,WAAW,EAAE,IAAI,EACjB,gBAAgB,EAAE,KAAK,EACvB,YAAY,EAAE,KAAK,EACnB,2BAA2B,EAAE,WAAW;;AAE5C,WAAY,GACR,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,KAAK,EACd,MAAM,EAAE,CAAC,EACT,OAAO,EAAE,CAAC;AAEV,iBAAQ,GACJ,OAAO,EAAE,IAAI;AAGjB,0BAAiB,GACb,UAAU,EAAE,qDAA+D;AAG/E,oBAAW,GACP,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,IAAI;;AAGpB,0BAA2B,GACvB,iBAAiB,EAAE,oBAAoB,EACvC,cAAc,EAAE,oBAAoB,EACpC,aAAa,EAAE,oBAAoB,EACnC,YAAY,EAAE,oBAAoB,EAClC,SAAS,EAAE,oBAAoB;;AAGnC,YAAa,GACT,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,CAAC,EACP,GAAG,EAAE,CAAC,EACN,OAAO,EAAE,KAAK;AAEd,uCACQ,GACJ,OAAO,EAAE,EAAE,EACX,OAAO,EAAE,KAAK;AAGlB,kBAAQ,GACJ,KAAK,EAAE,IAAI;AAGf,2BAAiB,GACb,UAAU,EAAE,MAAM;;AAG1B,YAAa,GACT,KAAK,EAAE,IAAI,EACX,MAAM,EAAE,IAAI,EACZ,UAAU,EAAE,GAAG,EAWf,OAAO,EAAE,IAAI;AAVb,wBAAc,GACV,KAAK,EAAE,KAAK;AAEhB,gBAAI,GACA,OAAO,EAAE,KAAK;AAElB,8BAAoB,GAChB,OAAO,EAAE,IAAI;AAKjB,yBAAe,GACX,cAAc,EAAE,IAAI;AAGxB,+BAAqB,GACjB,OAAO,EAAE,KAAK;AAGlB,2BAAiB,GACb,UAAU,EAAE,MAAM;AAGtB,4BAAkB,GACd,OAAO,EAAE,KAAK,EACd,MAAM,EAAE,IAAI,EACZ,MAAM,EAAE,qBAAqB;;;AAMnC,UASC,GARG,WAAW,EAAC,OAAO,EACnB,GAAG,EAAK,wBAA2B,EACnC,GAAG,EAAK,gMAA8D,EAItE,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM;;AAMxB,wBACY,GACR,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,KAAK,EACd,MAAM,EAAE,IAAI,EACZ,KAAK,EAAE,IAAI,EACX,WAAW,EAAE,CAAC,EACd,SAAS,EAAE,CAAC,EACZ,MAAM,EAAE,OAAO,EACf,UAAU,EAAE,WAAW,EACvB,KAAK,EAAE,WAAW,EAClB,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,KAAK,EACjB,OAAO,EAAE,CAAC,EACV,MAAM,EAAE,IAAI,EACZ,OAAO,EAAE,IAAI;AACb,0EAAiB,GACf,OAAO,EAAE,IAAI,EACb,UAAU,EAAE,WAAW,EACvB,KAAK,EAAE,WAAW;AAClB,sGAAS,GACP,OAAO,EA/JI,CAAC;AAkKhB,oEAAwB,GACpB,OAAO,EAlKM,IAAG;;AAqKxB,sCAAuC,GACnC,WAAW,EAjLK,OAAO,EAkLvB,SAAS,EAAE,IAAI,EACf,WAAW,EAAE,CAAC,EACd,KAAK,EAlLW,KAAK,EAmLrB,OAAO,EA5KO,IAAG,EA6KjB,sBAAsB,EAAE,WAAW,EACnC,uBAAuB,EAAE,SAAS;;AAEtC,WAAY,GACR,IAAI,EAAE,KAAK;AACX,uBAAc,GACV,IAAI,EAAG,IAAI,EACX,KAAK,EAAE,KAAK;AAEhB,kBAAS,GACL,OAAO,EA3LQ,GAAO;AA4LtB,8BAAc,GACV,OAAO,EA5LI,GAAO;;AAgM9B,WAAY,GACR,KAAK,EAAE,KAAK;AACZ,uBAAc,GACV,IAAI,EAAG,KAAK,EACZ,KAAK,EAAE,IAAI;AAEf,kBAAS,GACL,OAAO,EAvMQ,GAAO;AAwMtB,8BAAc,GACV,OAAO,EA1MI,GAAO;;;AAiN9B,aAAc,GACV,aAAa,EAAE,IAAI;;AAEvB,WAAY,GACR,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,KAAK,EACb,UAAU,EAAE,IAAI,EAChB,OAAO,EAAE,KAAK,EACd,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,CAAC,EACV,KAAK,EAAE,IAAI;AAEX,cAAG,GACC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,IAAI,EACZ,KAAK,EAAE,IAAI,EACX,MAAM,EAAE,KAAK,EACb,OAAO,EAAE,CAAC,EACV,MAAM,EAAE,OAAO;AAEf,qBAAO,GACH,MAAM,EAAE,CAAC,EACT,UAAU,EAAE,WAAW,EACvB,OAAO,EAAE,KAAK,EACd,MAAM,EAAE,IAAI,EACZ,KAAK,EAAE,IAAI,EACX,OAAO,EAAE,IAAI,EACb,WAAW,EAAE,CAAC,EACd,SAAS,EAAE,CAAC,EACZ,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,GAAG,EACZ,MAAM,EAAE,OAAO;AACf,wDAAiB,GACb,OAAO,EAAE,IAAI;AACb,sEAAS,GACP,OAAO,EAhPN,CAAC;AAoPR,4BAAS,GACL,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,CAAC,EACN,IAAI,EAAE,CAAC,EACP,OAAO,EA3PD,GAAO,EA4Pb,KAAK,EAAE,IAAI,EACX,MAAM,EAAE,IAAI,EACZ,WAAW,EArQP,OAAO,EAsQX,SAAS,EA9PR,GAAG,EA+PJ,WAAW,EAAE,IAAI,EACjB,UAAU,EAAE,MAAM,EAClB,KAAK,EArQI,KAAgB,EAsQzB,OAAO,EA/PF,IAAG,EAgQR,sBAAsB,EAAE,WAAW,EACnC,uBAAuB,EAAE,SAAS;AAK1C,yCAA6B,GACzB,KAAK,EA9QQ,KAAgB,EA+Q7B,OAAO,EA1QD,IAAG",
"sources": ["slick.scss"],
"names": [],
"file": "slick.css"
}
File diff suppressed because one or more lines are too long
@@ -5,10 +5,7 @@
.media_swipecontainer {
position: relative;
width: 830px;
height: 622px;
margin: 0 auto;
overflow: hidden;
}
.media_swipewrapper {
@@ -1,107 +1,129 @@
package com.sap.sailing.gwt.home.client.shared.mainmedia;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.ImageSize;
import com.sap.sailing.gwt.home.client.app.PlaceNavigator;
import com.sap.sailing.gwt.home.client.shared.stage.StageEventType;
import com.sap.sailing.gwt.idangerous.Swiper;
import com.sap.sailing.gwt.ui.common.client.YoutubeApi;
import com.sap.sailing.gwt.ui.shared.EventBaseDTO;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.gwt.client.controls.carousel.ImageCarousel;
public class MainMedia extends Composite {
private static final int MAX_VIDEO_COUNT = 3;
private static final MainMediaResources.LocalCss STYLES = MainMediaResources.INSTANCE.css();
@UiField
HTMLPanel videosPanel;
@UiField HTMLPanel videosPanel;
@UiField DivElement videoLightBoxData;
@UiField HTMLPanel mediaSlides;
@UiField
DivElement videoLightBoxData;
@UiField
ImageCarousel imageCarousel;
private final HashSet<String> addedVideoUrls = new HashSet<String>(MAX_VIDEO_COUNT);
private Swiper swiper;
private int videoCounter;
interface MainMediaUiBinder extends UiBinder<Widget, MainMedia> {
}
private static MainMediaUiBinder uiBinder = GWT.create(MainMediaUiBinder.class);
public MainMedia(PlaceNavigator navigator) {
videoCounter = 0;
MainMediaResources.INSTANCE.css().ensureInjected();
initWidget(uiBinder.createAndBindUi(this));
}
public void setFeaturedEvents(List<Pair<StageEventType, EventBaseDTO>> featuredEvents) {
for(Pair<StageEventType, EventBaseDTO> featuredEventTypeAndEvent: featuredEvents) {
if(featuredEventTypeAndEvent.getB().getVideoURLs().size() > 0 && videoCounter < MAX_VIDEO_COUNT) {
addVideoToVideoPanel(featuredEventTypeAndEvent.getB());
for (Pair<StageEventType, EventBaseDTO> featuredEventTypeAndEvent : featuredEvents) {
if (featuredEventTypeAndEvent.getB().getVideoURLs().size() > 0 && addedVideoUrls.size() < MAX_VIDEO_COUNT) {
String youTubeRandomUrl = getRandomVideoURL(featuredEventTypeAndEvent.getB());
addVideoToVideoPanel(youTubeRandomUrl, featuredEventTypeAndEvent.getB());
}
}
}
public void setRecentEvents(List<EventBaseDTO> recentEvents) {
List<String> photoGalleryUrls = new ArrayList<String>();
class Holder {
public Holder(String url, int height, int width) {
this.url = url;
this.height = height;
this.width = width;
}
String url;
int height;
int width;
}
final List<Holder> photoGalleryUrls = new LinkedList<>();
final List<Pair<String, EventBaseDTO>> videoCandidates = new LinkedList<>();
for (EventBaseDTO event : recentEvents) {
photoGalleryUrls.addAll(event.getPhotoGalleryImageURLs());
if (!event.getVideoURLs().isEmpty() && videoCounter < MAX_VIDEO_COUNT) {
addVideoToVideoPanel(event);
for (String url : event.getSailingLovesPhotographyImages()) {
ImageSize size = event.getImageSize(url);
photoGalleryUrls.add(new Holder(url, size.getHeight(), size.getWidth()));
}
for (String videoUrl : event.getVideoURLs()) {
videoCandidates.add(new Pair<String, EventBaseDTO>(videoUrl, event));
}
}
final int numberOfCandidatesAvailable = videoCandidates.size();
if (numberOfCandidatesAvailable <= (MAX_VIDEO_COUNT - addedVideoUrls.size())) {
// add all we have, no randomize
for (Pair<String, EventBaseDTO> videoCandidateInfo : videoCandidates) {
addVideoToVideoPanel(videoCandidateInfo.getA(), videoCandidateInfo.getB());
}
} else {
// fill up the list randomly from videoCandidates
final Random videosRandomizer = new Random(numberOfCandidatesAvailable);
randomlyPick: for (int i = 0; i < numberOfCandidatesAvailable; i++) {
int nextVideoindex = videosRandomizer.nextInt(numberOfCandidatesAvailable);
final Pair<String, EventBaseDTO> videoCandidateInfo = videoCandidates.get(nextVideoindex);
final String youtubeUrl = videoCandidateInfo.getA();
addVideoToVideoPanel(youtubeUrl, videoCandidateInfo.getB());
if (addedVideoUrls.size() == MAX_VIDEO_COUNT) {
break randomlyPick;
}
}
}
// shuffle the image url list (Remark: Collections.shuffle() is not implemented in GWT)
int gallerySize = photoGalleryUrls.size();
final int gallerySize = photoGalleryUrls.size();
Random random = new Random(gallerySize);
for (int i = 0; i < gallerySize; i++) {
Collections.swap(photoGalleryUrls, i, random.nextInt(gallerySize));
}
for (String url : photoGalleryUrls) {
SimplePanel imageContainer = new SimplePanel();
imageContainer.addStyleName(STYLES.media_swiperslide());
String image = "url(" + url + ")";
imageContainer.getElement().getStyle().setBackgroundImage(image);
mediaSlides.add(imageContainer);
for (Holder holder : photoGalleryUrls) {
imageCarousel.addImage(holder.url, holder.height, holder.width);
}
this.swiper = Swiper.createWithLoopOption(STYLES.media_swipecontainer(), STYLES.media_swipewrapper(), STYLES.media_swiperslide());
// See bug 2232: the stage image sizes are scaled incorrectly. https://github.com/ubilabs/sap-sailing-analytics/issues/421 and
// http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=2232 have the details. A quick fix may be to send a resize event
// after everything has been rendered.
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
swiper.reInit();
}
});
}
private void addVideoToVideoPanel(EventBaseDTO event) {
String youtubeUrl = getRandomVideoURL(event);
private void addVideoToVideoPanel(String youtubeUrl, EventBaseDTO event) {
if (addedVideoUrls.contains(youtubeUrl)) {
return;
}
String eventName = event.getName();
String youtubeId = YoutubeApi.getIdByUrl(youtubeUrl);
if (youtubeId != null && !youtubeId.trim().isEmpty()) {
MainMediaVideo video = new MainMediaVideo(eventName, youtubeId);
videosPanel.add(video);
videoCounter++;
addedVideoUrls.add(youtubeUrl);
}
}
private String getRandomVideoURL(EventBaseDTO event) {
final String result;
List<String> videoURLs = event.getVideoURLs();
@@ -113,17 +135,6 @@ public class MainMedia extends Composite {
return result;
}
@UiHandler("nextPictureLink")
public void nextStageTeaserLinkClicked(ClickEvent e) {
if (this.swiper != null) {
this.swiper.swipeNext();
}
}
@UiHandler("prevPictureLink")
public void prevStageTeaserLinkClicked(ClickEvent e) {
if (this.swiper != null) {
this.swiper.swipePrev();
}
}
}
@@ -1,5 +1,9 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui" xmlns:s="urn:import:com.sap.sailing.gwt.home.client.shared">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui"
xmlns:s="urn:import:com.sap.sailing.gwt.home.client.shared"
xmlns:sse="urn:import:com.sap.sse.gwt.client.controls"
>
<ui:with field="i18n" type="com.sap.sailing.gwt.common.client.i18n.TextMessages" />
<ui:with field="res" type="com.sap.sailing.gwt.common.client.SharedResources" />
<ui:with field="local_res" type="com.sap.sailing.gwt.home.client.shared.mainmedia.MainMediaResources" />
@@ -20,11 +24,7 @@
</div>
<div class="{res.mainCss.grid}">
<div class="{res.mediaCss.small12} {res.mediaCss.columns}">
<div class="{local_res.css.media_swipecontainer}">
<g:HTMLPanel ui:field="mediaSlides" styleName="{local_res.css.media_swipewrapper}" />
<g:Anchor ui:field="prevPictureLink" styleName="{local_res.css.media_slideshow_controls} {local_res.css.media_slideshow_controlsprev}" />
<g:Anchor ui:field="nextPictureLink" styleName="{local_res.css.media_slideshow_controls} {local_res.css.media_slideshow_controlsnext}" />
</div>
<sse:carousel.ImageCarousel ui:field="imageCarousel" />
</div>
</div>
</div>
@@ -1,17 +1,16 @@
.eventteasercontainer {
font-size: 15px;
font-size: 1rem;
text-align: center;
width: 100%;
height: 24em;
}
.eventteaser {
font-size: 15px;
font-size: 1rem;
display: inline-block;
margin-bottom: 3em;
text-align: left;
max-width: 100%;
width: 100%;
}
.eventteaser:hover {
text-decoration: none;
@@ -21,13 +20,16 @@
}
.eventteaser:hover .eventteaser_image {
opacity: 0.9;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=90)";
filter: literal("alpha(opacity=90)");
}
.eventteaser_image {
position: relative;
display: inline-block;
max-width: 100%;
position: relative;
display: inline-block;
max-width: 100%;
width: 100%;
height: 240px;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
}
.eventteaser_series {
position: absolute;
@@ -35,10 +37,10 @@
right: 0;
}
.eventteaser_name {
font-size: 1.466666666666667em;
font-weight: bold;
color: #008fcc;
margin-top: 0.333333333333333em;
font-size: 1.466666666666667em;
font-weight: bold;
color: #008fcc;
margin-top: 0.333333333333333em;
}
.eventteaser_location {
color: #222;
@@ -1,14 +1,12 @@
package com.sap.sailing.gwt.home.client.shared.recentevent;
import java.util.List;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.ImageElement;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.UriUtils;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
@@ -23,37 +21,44 @@ import com.sap.sailing.gwt.home.client.shared.LongNamesUtil;
import com.sap.sailing.gwt.ui.shared.EventBaseDTO;
public class RecentEvent extends Composite {
@UiField SpanElement eventName;
@UiField SpanElement venueName;
@UiField SpanElement eventStartDate;
@UiField Anchor eventOverviewLink;
@UiField ImageElement eventImage;
@UiField DivElement isLiveDiv;
@UiField
SpanElement eventName;
@UiField
SpanElement venueName;
@UiField
SpanElement eventStartDate;
@UiField
Anchor eventOverviewLink;
@UiField
DivElement eventImageContainerDiv;
@UiField
DivElement isLiveDiv;
private final EventBaseDTO event;
private final HomePlacesNavigator navigator;
private final PlaceNavigation<EventPlace> eventNavigation;
private final PlaceNavigation<EventPlace> eventNavigation;
interface RecentEventUiBinder extends UiBinder<Widget, RecentEvent> {
}
private static RecentEventUiBinder uiBinder = GWT.create(RecentEventUiBinder.class);
public RecentEvent(final HomePlacesNavigator navigator, final EventBaseDTO event) {
this.navigator = navigator;
this.event = event;
RecentEventResources.INSTANCE.css().ensureInjected();
initWidget(uiBinder.createAndBindUi(this));
eventNavigation = navigator.getEventNavigation(event.id.toString(), event.getBaseURL(), event.isOnRemoteServer());
eventNavigation = navigator.getEventNavigation(event.id.toString(), event.getBaseURL(),
event.isOnRemoteServer());
eventOverviewLink.setHref(eventNavigation.getTargetUrl());
updateUI();
}
private void updateUI() {
SafeHtml safeHtmlEventName = LongNamesUtil.breakLongName(event.getName());
eventName.setInnerSafeHtml(safeHtmlEventName);
@@ -62,14 +67,19 @@ public class RecentEvent extends Composite {
}
venueName.setInnerText(event.venue.getName());
eventStartDate.setInnerText(EventDatesFormatterUtil.formatDateRangeWithoutYear(event.startDate, event.endDate));
List<String> photoGalleryImageURLs = event.getPhotoGalleryImageURLs();
if (photoGalleryImageURLs.isEmpty()) {
eventImage.setSrc(RecentEventResources.INSTANCE.defaultEventPhotoImage().getSafeUri().asString());
final StringBuilder thumbnailUrlBuilder = new StringBuilder("url('");
final String thumbnailImageUrl = event.getEventThumbnailImageUrl();
if (thumbnailImageUrl == null || thumbnailImageUrl.isEmpty()) {
thumbnailUrlBuilder.append(RecentEventResources.INSTANCE.defaultEventPhotoImage().getSafeUri().asString());
} else {
eventImage.setSrc(photoGalleryImageURLs.get(0));
thumbnailUrlBuilder.append(UriUtils.fromString(thumbnailImageUrl).asString());
}
thumbnailUrlBuilder.append("')");
eventImageContainerDiv.getStyle().setBackgroundImage(thumbnailUrlBuilder.toString());
}
@UiHandler("eventOverviewLink")
public void goToEventOverview(ClickEvent e) {
navigator.goToPlace(eventNavigation);
@@ -8,9 +8,9 @@
<g:HTMLPanel styleName="{res.mediaCss.small12} {res.mediaCss.medium6} {res.mediaCss.large4} {res.mediaCss.columns}">
<div class="{local_res.css.eventteasercontainer}">
<g:Anchor ui:field="eventOverviewLink" styleName="{local_res.css.eventteaser}">
<figure class="{local_res.css.eventteaser_image}"><img ui:field="eventImage" />
<div class="{local_res.css.eventteaser_image}" ui:field="eventImageContainerDiv" >
<div ui:field="isLiveDiv" data-labeltype="live" class="{local_res.css.eventteaser_series} {res.mainCss.label}"><ui:text from='{i18n.live}'/></div>
</figure>
</div>
<div class="{local_res.css.eventteaser_name}"><span ui:field="eventName" /></div>
<div class="{local_res.css.eventteaser_location}">
<span ui:field="venueName" />,&nbsp;<span ui:field="eventStartDate" />
@@ -11,11 +11,12 @@ public class PopularEventStageTeaser extends StageTeaser {
title.setInnerText(event.getName());
subtitle.setInnerText(event.venue.getName());
countdown.getStyle().setDisplay(Display.NONE);
bandCount.setAttribute("data-bandcount", "1");
stageTeaserBandsPanel.getElement().appendChild(new PopularEventStageTeaserBand(event, placeNavigator).getElement());
stageTeaserBandsPanel.getElement().appendChild(
new PopularEventStageTeaserBand(event, placeNavigator).getElement());
}
}
@@ -4,105 +4,66 @@ import java.util.ArrayList;
import java.util.List;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.client.app.HomePlacesNavigator;
import com.sap.sailing.gwt.idangerous.Swiper;
import com.sap.sailing.gwt.ui.shared.EventBaseDTO;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.gwt.client.controls.carousel.WidgetCarousel;
public class Stage extends Composite {
@SuppressWarnings("unused")
private List<Pair<StageEventType, EventBaseDTO>> featuredEvents;
private List<StageTeaser> stageTeaserComposites;
@UiField HTMLPanel stageElementsPanel;
@UiField Anchor nextStageTeaserLink;
@UiField Anchor prevStageTeaserLink;
@UiField
WidgetCarousel widgetCarousel;
private StageTeaser stageTeaser;
private Swiper swiper;
private final HomePlacesNavigator placeNavigator;
interface StageUiBinder extends UiBinder<Widget, Stage> {
}
private static StageUiBinder uiBinder = GWT.create(StageUiBinder.class);
public Stage(HomePlacesNavigator placeNavigator) {
this.placeNavigator = placeNavigator;
StageResources.INSTANCE.css().ensureInjected();
initWidget(uiBinder.createAndBindUi(this));
stageTeaserComposites = new ArrayList<StageTeaser>();
}
public void setFeaturedEvents(List<Pair<StageEventType, EventBaseDTO>> featuredEvents) {
this.featuredEvents = featuredEvents;
for(Pair<StageEventType, EventBaseDTO> typeAndEvent: featuredEvents) {
for (Pair<StageEventType, EventBaseDTO> typeAndEvent : featuredEvents) {
switch (typeAndEvent.getA()) {
case POPULAR:
stageTeaser = new PopularEventStageTeaser(typeAndEvent.getB(), placeNavigator);
break;
case RUNNING:
stageTeaser = new LiveEventStageTeaser(typeAndEvent.getB(), placeNavigator);
break;
case UPCOMING_SOON:
stageTeaser = new UpcomingEventStageTeaser(typeAndEvent.getB(), placeNavigator);
break;
case POPULAR:
stageTeaser = new PopularEventStageTeaser(typeAndEvent.getB(), placeNavigator);
break;
case RUNNING:
stageTeaser = new LiveEventStageTeaser(typeAndEvent.getB(), placeNavigator);
break;
case UPCOMING_SOON:
stageTeaser = new UpcomingEventStageTeaser(typeAndEvent.getB(), placeNavigator);
break;
}
stageElementsPanel.add(stageTeaser);
widgetCarousel.addWidget(stageTeaser);
stageTeaserComposites.add(stageTeaser);
}
if (featuredEvents.size() <= 1) {
prevStageTeaserLink.setVisible(false);
nextStageTeaserLink.setVisible(false);
} else {
swiper = Swiper.createWithoutLoopOption(StageResources.INSTANCE.css().stage_teasers(),
StageResources.INSTANCE.css().swiperwrapper(),
StageResources.INSTANCE.css().swiperslide(), new Swiper.PageChangeListener() {
@Override
public void pageChanged(int newPageIndex, int pageCount) {
boolean isFirstSlide = newPageIndex == 0;
boolean isLastSlide = newPageIndex == pageCount - 1;
nextStageTeaserLink.setVisible(!isLastSlide);
prevStageTeaserLink.setVisible(!isFirstSlide);
}
});
prevStageTeaserLink.setVisible(false);
}
}
public void adjustSize() {
swiper.reInit();
}
@UiHandler("nextStageTeaserLink")
public void nextStageTeaserLinkClicked(ClickEvent e) {
if (this.swiper != null) {
this.swiper.swipeNext();
}
}
@UiHandler("prevStageTeaserLink")
public void prevStageTeaserLinkClicked(ClickEvent e) {
if (this.swiper != null) {
this.swiper.swipePrev();
}
}
}
@@ -1,6 +1,10 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui" xmlns:s="urn:import:com.sap.sailing.gwt.home.client.shared">
xmlns:g="urn:import:com.google.gwt.user.client.ui"
xmlns:s="urn:import:com.sap.sailing.gwt.home.client.shared"
xmlns:sse="urn:import:com.sap.sse.gwt.client.controls"
>
<ui:with field="i18n" type="com.sap.sailing.gwt.common.client.i18n.TextMessages" />
<ui:with field="res" type="com.sap.sailing.gwt.common.client.SharedResources" />
<ui:with field="media_res" type="com.sap.sailing.gwt.home.client.shared.mainmedia.MainMediaResources" />
@@ -9,13 +13,11 @@
<!-- The main stage -->
<div class="{local_res.css.stage}">
<div class="{local_res.css.stage_teasers} {local_res.css.swipercontainer}">
<g:HTMLPanel ui:field="stageElementsPanel" styleName="{local_res.css.swiperwrapper}" />
<sse:carousel.WidgetCarousel ui:field="widgetCarousel" />
</div>
<div class="{local_res.css.stage_progress}">
<div class="{local_res.css.stage_progress_bar}"></div>
</div>
<g:Anchor ui:field="prevStageTeaserLink" styleName="{local_res.css.stage_controls} {media_res.css.media_slideshow_controls} {media_res.css.media_slideshow_controlsprev}"></g:Anchor>
<g:Anchor ui:field="nextStageTeaserLink" styleName="{local_res.css.stage_controls} {media_res.css.media_slideshow_controls} {media_res.css.media_slideshow_controlsnext}"></g:Anchor>
</div>
</g:HTMLPanel>
</ui:UiBinder>
@@ -1,5 +1,6 @@
package com.sap.sailing.gwt.home.client.shared.stage;
import com.google.gwt.animation.client.Animation;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.SpanElement;
@@ -16,39 +17,68 @@ import com.sap.sailing.gwt.home.client.shared.Countdown.RemainingTime;
import com.sap.sailing.gwt.ui.shared.EventBaseDTO;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.gwt.client.controls.carousel.LazyLoadable;
public abstract class StageTeaser extends Composite {
@UiField DivElement bandCount;
@UiField SpanElement subtitle;
@UiField SpanElement title;
@UiField DivElement countdown;
@UiField DivElement countdownMajor;
@UiField DivElement countdownMajorValue;
@UiField DivElement countdownMajorUnit;
@UiField DivElement countdownMinor;
@UiField DivElement countdownMinorValue;
@UiField DivElement countdownMinorUnit;
@UiField HTMLPanel stageTeaserBandsPanel;
@UiField DivElement teaserImage;
public abstract class StageTeaser extends Composite implements LazyLoadable {
@UiField
DivElement bandCount;
@UiField
SpanElement subtitle;
@UiField
SpanElement title;
@UiField
DivElement countdown;
@UiField
DivElement countdownMajor;
@UiField
DivElement countdownMajorValue;
@UiField
DivElement countdownMajorUnit;
@UiField
DivElement countdownMinor;
@UiField
DivElement countdownMinorValue;
@UiField
DivElement countdownMinorUnit;
@UiField
HTMLPanel stageTeaserBandsPanel;
@UiField
DivElement teaserImage;
interface StageTeaserUiBinder extends UiBinder<Widget, StageTeaser> {
}
private static StageTeaserUiBinder uiBinder = GWT.create(StageTeaserUiBinder.class);
public StageTeaser(EventBaseDTO event) {
StageResources.INSTANCE.css().ensureInjected();
initWidget(uiBinder.createAndBindUi(this));
String stageImageUrl = event.getStageImageURL() != null ? event.getStageImageURL() : StageResources.INSTANCE.defaultStageEventTeaserImage().getSafeUri().asString();;
private static StageTeaserUiBinder uiBinder = GWT.create(StageTeaserUiBinder.class);
private final EventBaseDTO event;
@Override
public void doInitializeLazyComponents() {
String stageImageUrl = event.getStageImageURL() != null ? event.getStageImageURL() : StageResources.INSTANCE
.defaultStageEventTeaserImage().getSafeUri().asString();
String backgroundImage = "url(" + stageImageUrl + ")";
teaserImage.getStyle().setBackgroundImage(backgroundImage);
teaserImage.getStyle().setOpacity(0);
new Animation() {
@Override
protected void onUpdate(double progress) {
teaserImage.getStyle().setOpacity(progress);
}
}.run(1000);
}
public StageTeaser(EventBaseDTO event) {
this.event = event;
StageResources.INSTANCE.css().ensureInjected();
initWidget(uiBinder.createAndBindUi(this));
if (event.startDate != null) {
TimePoint eventStart = new MillisecondsTimePoint(event.startDate);
CountdownListener countdownListener = new CountdownListener() {
@Override
public void changed(RemainingTime major, RemainingTime minor) {
updateCountdown(major, minor);
@@ -6,6 +6,7 @@
<ui:with field="res" type="com.sap.sailing.gwt.common.client.SharedResources" />
<ui:with field="local_res" type="com.sap.sailing.gwt.home.client.shared.stage.StageResources" />
<g:HTMLPanel styleName="{local_res.css.stageteaser_content_band_item} {res.mediaCss.columns}">
<g:Anchor ui:field="actionLink" styleName="{res.mainCss.button} {local_res.css.stageteaser_content_band_action}" />
<div class="{local_res.css.stageteaser_content_band_text}">
<div class="{local_res.css.stageteaser_content_band_text_headline}"><span ui:field="bandTitle" /></div>
<div class="{local_res.css.stageteaser_content_band_text_name}">
@@ -13,6 +14,5 @@
<div ui:field="isLiveDiv" data-labeltype="live" class="{res.mainCss.label}"><ui:text from='{i18n.live}'/></div>
</div>
</div>
<g:Anchor ui:field="actionLink" styleName="{res.mainCss.button} {local_res.css.stageteaser_content_band_action}" />
</g:HTMLPanel>
</ui:UiBinder>
@@ -1,58 +0,0 @@
package com.sap.sailing.gwt.idangerous;
import com.google.gwt.core.client.JavaScriptObject;
/**
* Wrapping http://www.idangero.us/sliders/swiper/
*
*/
public final class Swiper extends JavaScriptObject {
/**
* Event notification about slide change.
* Concrete use case this listener is tailored for: hide/show prev/next buttons when there a no slides left in a given direction.
*
*/
public static interface PageChangeListener {
void pageChanged(int newPageIndex, int pageCount);
}
protected Swiper() {};
public static Swiper createWithLoopOption(String containerClass, String wrapperClass, String slideClass) {
return Swiper.createWithDefaultOptions(containerClass, wrapperClass, slideClass, null, true);
}
public static Swiper createWithoutLoopOption(String containerClass, String wrapperClass, String slideClass, PageChangeListener pageChangeListener) {
return Swiper.createWithDefaultOptions(containerClass, wrapperClass, slideClass, pageChangeListener, false);
}
private static native Swiper createWithDefaultOptions(String containerClass, String wrapperClass, String slideClass, PageChangeListener pageChangeListener, boolean loop) /*-{
var options = {
loop: loop,
wrapperClass: wrapperClass,
slideClass: slideClass,
};
if (pageChangeListener) {
options.onSlideChangeEnd = function(swiper) {
//var slideCount = swiper.slides.length - swiper.loopedSlides*2; //see https://github.com/nolimits4web/Swiper/issues/797
var slideCount = swiper.slides.length;
pageChangeListener.@com.sap.sailing.gwt.idangerous.Swiper.PageChangeListener::pageChanged(II)(swiper.activeIndex, slideCount);
};
}
return new $wnd.Swiper('.'+containerClass, options);
}-*/;
public native void reInit() /*-{
this.reInit(true);
}-*/;
public native void swipeNext() /*-{
this.swipeNext();
}-*/;
public native void swipePrev() /*-{
this.swipePrev();
}-*/;
}
@@ -250,7 +250,7 @@ public class EventListComposite extends Composite implements EventsRefresher, Le
int courseAreasCount = event.venue.getCourseAreas().size();
int i = 1;
for (CourseAreaDTO courseArea : event.venue.getCourseAreas()) {
builder.appendEscaped(courseArea.getName());
builder.appendEscaped(courseArea.getName() == null ? "null" : courseArea.getName());
if (i < courseAreasCount) {
builder.appendHtmlConstant(",&nbsp;");
// not more than 4 course areas per line
@@ -21,6 +21,7 @@ import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sailing.domain.common.PolarSheetGenerationResponse;
import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
import com.sap.sailing.domain.common.PolarSheetsXYDiagramData;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaIdentifier;
@@ -356,10 +357,16 @@ public interface SailingService extends RemoteService {
void createCourseArea(UUID eventId, String courseAreaName);
List<String> getBoatClassNamesWithPolarSheetsAvailable();
void removeCourseArea(UUID eventId, UUID courseAreaId);
List<Util.Pair<String, String>> getLeaderboardsNamesOfMetaLeaderboard(String metaLeaderboardName);
PolarSheetGenerationResponse showCachedPolarSheetForBoatClass(String boatClassName);
Util.Pair<String, LeaderboardType> checkLeaderboardName(String leaderboardName);
/** for backward compatibility with the regatta overview */
@@ -396,16 +403,16 @@ public interface SailingService extends RemoteService {
Iterable<CompetitorDTO> getCompetitors();
/**
*
* @param leaderboardName
* @param lookInRaceLogs If set to {@code true}, the {@link RaceLog}s are checked for the competitor registrations.
* If set to {@code false}, the {@link RaceDefinition}s are checked instead.
* @return
*/
Iterable<CompetitorDTO> getCompetitorsOfLeaderboard(String leaderboardName, boolean lookInRaceLogs);
/**
*
* @param leaderboardName
* @param lookInRaceLogs If set to {@code true}, the {@link RaceLog}s are checked for the competitor registrations.
* If set to {@code false}, the {@link RaceDefinition}s are checked instead.
* @return
*/
Iterable<CompetitorDTO> getCompetitorsOfLeaderboard(String leaderboardName, boolean lookInRaceLogs);
CompetitorDTO addOrUpdateCompetitor(CompetitorDTO competitor);
CompetitorDTO addOrUpdateCompetitor(CompetitorDTO competitor);
void allowCompetitorResetToDefaults(Iterable<CompetitorDTO> competitors);
@@ -430,29 +437,29 @@ public interface SailingService extends RemoteService {
void removeIgtimiAccount(String eMailOfAccountToRemove);
Map<RegattaAndRaceIdentifier, Integer> importWindFromIgtimi(List<RaceDTO> selectedRaces, boolean correctByDeclination) throws Exception;
void denoteForRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName) throws Exception;
/**
* Revoke the {@link RaceLogDenoteForTrackingEvent}. This does not affect an existing {@code RaceLogRaceTracker}
* or {@link TrackedRace} for this {@code RaceLog}.
*
* @see RaceLogTrackingAdapter#removeDenotationForRaceLogTracking
*/
void removeDenotationForRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName);
void denoteForRaceLogTracking(String leaderboardName) throws Exception;
/**
* Performs all the necessary steps to start tracking the race.
* The {@code RaceLog} needs to be denoted for racelog-tracking beforehand.
*
* @see RaceLogTrackingAdapter#startTracking
*/
void startRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName)
throws NotDenotedForRaceLogTrackingException, Exception;
void setCompetitorRegistrations(String leaderboardName, String raceColumnName, String fleetName, Set<CompetitorDTO> competitors);
void denoteForRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName) throws Exception;
/**
* Revoke the {@link RaceLogDenoteForTrackingEvent}. This does not affect an existing {@code RaceLogRaceTracker}
* or {@link TrackedRace} for this {@code RaceLog}.
*
* @see RaceLogTrackingAdapter#removeDenotationForRaceLogTracking
*/
void removeDenotationForRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName);
void denoteForRaceLogTracking(String leaderboardName) throws Exception;
/**
* Performs all the necessary steps to start tracking the race.
* The {@code RaceLog} needs to be denoted for racelog-tracking beforehand.
*
* @see RaceLogTrackingAdapter#startTracking
*/
void startRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName)
throws NotDenotedForRaceLogTrackingException, Exception;
void setCompetitorRegistrations(String leaderboardName, String raceColumnName, String fleetName, Set<CompetitorDTO> competitors);
/**
* Get the competitors registered in this racelog. Does not automatically include the competitors
@@ -465,43 +472,43 @@ public interface SailingService extends RemoteService {
* {@link #getCompetitorRegistrations(String, String, String) registered for the racelog}.
*/
Collection<CompetitorDTO> getCompetitorRegistrations(String leaderboardName) throws DoesNotHaveRegattaLogException;
void addMarkToRaceLog(String leaderboardName, String raceColumnName, String fleetName, MarkDTO markDTO);
Collection<MarkDTO> getMarksInRaceLog(String leaderboardName, String raceColumnName, String fleetName);
/**
* Adds the course definition to the racelog, while trying to reuse existing marks, controlpoints and waypoints
* from the previous course definition in the racelog.
*/
void addCourseDefinitionToRaceLog(String leaderboardName, String raceColumnName, String fleetName, List<Util.Pair<ControlPointDTO, PassingInstruction>> course);
RaceCourseDTO getLastCourseDefinitionInRaceLog(String leaderboardName, String raceColumnName, String fleetName);
/**
* Adds a fix to the {@link GPSFixStore}, and creates a mapping with a virtual device for exactly the current timepoint.
*/
void pingMarkViaRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName, MarkDTO mark, PositionDTO position);
void copyCourseAndCompetitorsToOtherRaceLogs(Util.Triple<String, String, String> raceLogFrom,
Set<Util.Triple<String, String, String>> raceLogsTo);
void addDeviceMappingToRaceLog(String leaderboardName, String raceColumnName, String fleetName, DeviceMappingDTO mapping)
throws TransformationException;
List<DeviceMappingDTO> getDeviceMappingsFromRaceLog(String leaderboardName, String raceColumnName, String fleetName)
throws TransformationException;
List<String> getDeserializableDeviceIdentifierTypes();
void closeOpenEndedDeviceMapping(String leaderboardName, String raceColumnName, String fleetName, DeviceMappingDTO mapping,
Date closingTimePoint) throws NoCorrespondingServiceRegisteredException, TransformationException;
/**
* Revoke the events in the {@code RaceLog} that are identified by the {@code eventIds}.
* This only affects such events that implement {@link Revokable}.
*/
void revokeRaceLogEvents(String leaderboardName, String raceColumnName, String fleetName, List<UUID> eventIds)
void addMarkToRaceLog(String leaderboardName, String raceColumnName, String fleetName, MarkDTO markDTO);
Collection<MarkDTO> getMarksInRaceLog(String leaderboardName, String raceColumnName, String fleetName);
/**
* Adds the course definition to the racelog, while trying to reuse existing marks, controlpoints and waypoints
* from the previous course definition in the racelog.
*/
void addCourseDefinitionToRaceLog(String leaderboardName, String raceColumnName, String fleetName, List<Util.Pair<ControlPointDTO, PassingInstruction>> course);
RaceCourseDTO getLastCourseDefinitionInRaceLog(String leaderboardName, String raceColumnName, String fleetName);
/**
* Adds a fix to the {@link GPSFixStore}, and creates a mapping with a virtual device for exactly the current timepoint.
*/
void pingMarkViaRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName, MarkDTO mark, PositionDTO position);
void copyCourseAndCompetitorsToOtherRaceLogs(Util.Triple<String, String, String> raceLogFrom,
Set<Util.Triple<String, String, String>> raceLogsTo);
void addDeviceMappingToRaceLog(String leaderboardName, String raceColumnName, String fleetName, DeviceMappingDTO mapping)
throws TransformationException;
List<DeviceMappingDTO> getDeviceMappingsFromRaceLog(String leaderboardName, String raceColumnName, String fleetName)
throws TransformationException;
List<String> getDeserializableDeviceIdentifierTypes();
void closeOpenEndedDeviceMapping(String leaderboardName, String raceColumnName, String fleetName, DeviceMappingDTO mapping,
Date closingTimePoint) throws NoCorrespondingServiceRegisteredException, TransformationException;
/**
* Revoke the events in the {@code RaceLog} that are identified by the {@code eventIds}.
* This only affects such events that implement {@link Revokable}.
*/
void revokeRaceLogEvents(String leaderboardName, String raceColumnName, String fleetName, List<UUID> eventIds)
throws NotRevokableException;
Collection<String> getGPSFixImporterTypes();
@@ -539,6 +546,8 @@ public interface SailingService extends RemoteService {
* @return The RaceDTO of the modified race or <code>null</code>, if the given newStartTimeReceived was null.
*/
RaceDTO setStartTimeReceivedForRace(RaceIdentifier raceIdentifier, Date newStartTimeReceived);
PolarSheetsXYDiagramData createXYDiagramForBoatClass(String itemText);
void setCompetitorRegistrations(String leaderboardName, Set<CompetitorDTO> competitors)
throws DoesNotHaveRegattaLogException;
@@ -16,6 +16,7 @@ import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sailing.domain.common.PolarSheetGenerationResponse;
import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
import com.sap.sailing.domain.common.PolarSheetsXYDiagramData;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaIdentifier;
@@ -514,10 +515,10 @@ public interface SailingServiceAsync extends BuildVersionRetriever {
void getCompetitors(AsyncCallback<Iterable<CompetitorDTO>> asyncCallback);
void getCompetitorsOfLeaderboard(String leaderboardName, boolean lookInRaceLogs,
AsyncCallback<Iterable<CompetitorDTO>> asyncCallback);
void getCompetitorsOfLeaderboard(String leaderboardName, boolean lookInRaceLogs,
AsyncCallback<Iterable<CompetitorDTO>> asyncCallback);
void addOrUpdateCompetitor(CompetitorDTO competitor, AsyncCallback<CompetitorDTO> asyncCallback);
void addOrUpdateCompetitor(CompetitorDTO competitor, AsyncCallback<CompetitorDTO> asyncCallback);
void allowCompetitorResetToDefaults(Iterable<CompetitorDTO> competitors, AsyncCallback<Void> asyncCallback);
@@ -552,24 +553,29 @@ public interface SailingServiceAsync extends BuildVersionRetriever {
void importWindFromIgtimi(List<RaceDTO> selectedRaces, boolean correctByDeclination, AsyncCallback<Map<RegattaAndRaceIdentifier, Integer>> asyncCallback);
void getBoatClassNamesWithPolarSheetsAvailable(AsyncCallback<List<String>> asyncCallback);
void getEventById(UUID id, boolean withStatisticalData, AsyncCallback<EventDTO> callback);
void showCachedPolarSheetForBoatClass(String boatClassName,
AsyncCallback<PolarSheetGenerationResponse> asyncCallback);
void getLeaderboardsByEvent(EventDTO event, AsyncCallback<List<StrippedLeaderboardDTO>> callback);
void denoteForRaceLogTracking(String leaderboardName,
String raceColumnName, String fleetName,
AsyncCallback<Void> callback);
void denoteForRaceLogTracking(String leaderboardName, AsyncCallback<Void> callback);
void startRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback<Void> callback);
void denoteForRaceLogTracking(String leaderboardName,
String raceColumnName, String fleetName,
AsyncCallback<Void> callback);
void denoteForRaceLogTracking(String leaderboardName, AsyncCallback<Void> callback);
void startRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback<Void> callback);
/**
* Set the competitor registrations in the racelog. Unregisters formerly registered competitors
* that are not listed in {@code competitors}.
*/
void setCompetitorRegistrations(String leaderboardName, String raceColumnName, String fleetName,
*/
void setCompetitorRegistrations(String leaderboardName, String raceColumnName, String fleetName,
Set<CompetitorDTO> competitors, AsyncCallback<Void> callback);
/**
@@ -577,52 +583,52 @@ public interface SailingServiceAsync extends BuildVersionRetriever {
* that are not listed in {@code competitors}.
*/
void setCompetitorRegistrations(String leaderboardName,Set<CompetitorDTO> competitors,
AsyncCallback<Void> callback);
void getCompetitorRegistrations(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback<Collection<CompetitorDTO>> callback);
void addMarkToRaceLog(String leaderboardName, String raceColumnName, String fleetName, MarkDTO markDTO,
AsyncCallback<Void> callback);
void getMarksInRaceLog(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback<Collection<MarkDTO>> callback);
void addCourseDefinitionToRaceLog(String leaderboardName, String raceColumnName, String fleetName,
List<Util.Pair<ControlPointDTO, PassingInstruction>> course, AsyncCallback<Void> callback);
void getLastCourseDefinitionInRaceLog(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback<RaceCourseDTO> callback);
void pingMarkViaRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName, MarkDTO mark,
PositionDTO position, AsyncCallback<Void> callback);
AsyncCallback<Void> callback);
void getCompetitorRegistrations(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback<Collection<CompetitorDTO>> callback);
void addMarkToRaceLog(String leaderboardName, String raceColumnName, String fleetName, MarkDTO markDTO,
AsyncCallback<Void> callback);
void getMarksInRaceLog(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback<Collection<MarkDTO>> callback);
void addCourseDefinitionToRaceLog(String leaderboardName, String raceColumnName, String fleetName,
List<Util.Pair<ControlPointDTO, PassingInstruction>> course, AsyncCallback<Void> callback);
void getLastCourseDefinitionInRaceLog(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback<RaceCourseDTO> callback);
void pingMarkViaRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName, MarkDTO mark,
PositionDTO position, AsyncCallback<Void> callback);
void getDeserializableDeviceIdentifierTypes(AsyncCallback<List<String>> callback);
/**
* Do not only use the racelog, but also other logs (e.g. RegattaLogs) in the hierarchy of the race.
*/
void getDeviceMappingsFromLogHierarchy(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback<List<DeviceMappingDTO>> callback);
void getDeviceMappingsFromRaceLog(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback<List<DeviceMappingDTO>> callback);
void addDeviceMappingToRaceLog(String leaderboardName, String raceColumnName, String fleetName,
DeviceMappingDTO mapping, AsyncCallback<Void> callback);
void closeOpenEndedDeviceMapping(String leaderboardName, String raceColumnName, String fleetName,
DeviceMappingDTO mapping, Date closingTimePoint, AsyncCallback<Void> callback);
void revokeRaceLogEvents(String leaderboardName, String raceColumnName, String fleetName, List<UUID> eventIds,
AsyncCallback<Void> callback);
AsyncCallback<List<DeviceMappingDTO>> callback);
void getDeviceMappingsFromRaceLog(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback<List<DeviceMappingDTO>> callback);
void addDeviceMappingToRaceLog(String leaderboardName, String raceColumnName, String fleetName,
DeviceMappingDTO mapping, AsyncCallback<Void> callback);
void closeOpenEndedDeviceMapping(String leaderboardName, String raceColumnName, String fleetName,
DeviceMappingDTO mapping, Date closingTimePoint, AsyncCallback<Void> callback);
void revokeRaceLogEvents(String leaderboardName, String raceColumnName, String fleetName, List<UUID> eventIds,
AsyncCallback<Void> callback);
void removeSeries(RegattaIdentifier regattaIdentifier, String seriesName, AsyncCallback<Void> callback);
void removeDenotationForRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback<Void> callback);
void copyCourseAndCompetitorsToOtherRaceLogs(Util.Triple<String, String, String> raceLogFrom,
void removeDenotationForRaceLogTracking(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback<Void> callback);
void copyCourseAndCompetitorsToOtherRaceLogs(Util.Triple<String, String, String> raceLogFrom,
Set<Util.Triple<String, String, String>> raceLogsTo, AsyncCallback<Void> callback);
void getGPSFixImporterTypes(AsyncCallback<Collection<String>> callback);
@@ -661,5 +667,7 @@ public interface SailingServiceAsync extends BuildVersionRetriever {
void getCompetitorRegistrations(String leaderboardName, AsyncCallback<Collection<CompetitorDTO>> callback);
void createXYDiagramForBoatClass(String itemText, AsyncCallback<PolarSheetsXYDiagramData> asyncCallback);
void getEventsForLeaderboard(String leaderboardName, AsyncCallback<Collection<EventDTO>> callback);
}
}
@@ -967,6 +967,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages {
String distanceToLine();
String beatAngleTooltip();
String beatAngle();
String showBoatClassChartsLabel();
String showDiagram();
String runAutomaticallyTooltip();
String rerunQueryAfterRefreshTooltip();
String queryDefinitionProvider();
@@ -1111,4 +1113,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages {
String serverReplies();
String errorCommunicatingWithServer();
String userManagement();
String showXYDiagram();
String xyDiagram();
String boatSpeed();
String confidence();
}
@@ -951,6 +951,8 @@ timeSinceStart={0} since start
distanceToLine=Distance to start line
beatAngleTooltip=The angle between the competitors direction and the wind
beatAngle=Beat Angle
showBoatClassChartsLabel=You can also view the overall diagram for the available boat classes.
showDiagram=Show Diagram
runAutomaticallyTooltip=Run the query automatically, after you changed e.g. the statistic or the grouping.
rerunQueryAfterRefreshTooltip=Reruns the query after the tables have been refreshed.
queryDefinitionProvider=Query Definition Provider
@@ -1092,4 +1094,8 @@ serverError=An error occurred while attempting to contact the server. Please che
remoteProcedureCall=Remote Procedure Call
serverReplies=Server replies
errorCommunicatingWithServer=Error communicating with server
userManagement=User Management
userManagement=User Management
showXYDiagram=Show x-y-diagram
xyDiagram=x-y-Diagram
boatSpeed=Boat Speed
confidence=Confidence
@@ -947,6 +947,8 @@ timeSinceStart={0} seit dem Start
distanceToLine=Abstand zur Startlinie
beatAngleTooltip=Der Winkel zwischen Windrichtung und Fahrtrichtung des Teilnehmers
beatAngle=Winkel zum Wind
showBoatClassChartsLabel=Im folgenden können außerdem auch die existierenden Diagramme für die vorhandenen Bootsklassen angezeigt werden.
showDiagram=Diagramm anzeigen
runAutomaticallyTooltip=Führe die Anfrage automatisch durch, wenn zum Beispiel die Statistik oder die Gruppierung geändert wurde.
rerunQueryAfterRefreshTooltip=Wiederholt die Anfrage nachdem die Tabellen aktualisiert wurden.
queryDefinitionProvider=Anfragen-Definitions-Versorger
@@ -943,6 +943,8 @@ distanceToLine=Растояние до линии старта
beatAngleTooltip=Угол между направлением движения участника и ветром
beatAngle=Угол лавирования
runAutomaticallyTooltip=Запустить запрос автоматически, например, после изменения статистических данных или группировки.
showBoatClassChartsLabel=You can also view the overall diagram for the available boat classes.
showDiagram=Show Diagram
rerunQueryAfterRefreshTooltip=Повторно запускает запрос после обновления таблиц.
queryDefinitionProvider=Поставщик определения запроса
statisticProvider=Поставщик статистических данных
@@ -0,0 +1,89 @@
package com.sap.sailing.gwt.ui.datamining.client.presentation;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.moxieapps.gwt.highcharts.client.Chart;
import org.moxieapps.gwt.highcharts.client.Series;
import org.moxieapps.gwt.highcharts.client.plotOptions.LinePlotOptions;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.ui.datamining.ResultsPresenter;
import com.sap.sse.datamining.shared.GroupKey;
import com.sap.sse.datamining.shared.QueryResult;
/**
* Expects the inner key in the group key to be the angle for the polar chart. (x-value) Maybe the angle should be
* transfered as part of the result value in the future. But in the backend the angle is treated as a key (for polar
* sheets) for grouping all polar fixes with the same rounded angle
*
* @author Frederik Petersen D054528
*
*/
public class PolarChart implements ResultsPresenter<Number> {
private final Chart chart = createPolarChartWidget();
private final Map<String, Series> dataByKeyStringAndAngle = new HashMap<String, Series>();
@Override
public Widget getWidget() {
return chart;
}
/**
* Creates a polar diagram chart of the Line type.
*/
private Chart createPolarChartWidget() {
Chart polarSheetChart = new Chart().setType(Series.Type.LINE)
.setLinePlotOptions(new LinePlotOptions().setLineWidth(1))
.setPolar(true).setHeight100().setWidth100();
polarSheetChart.getYAxis().setMin(0);
return polarSheetChart;
}
@Override
public void showResult(QueryResult<Number> result) {
resetChartData();
Map<GroupKey, Number> results = result.getResults();
if (!results.isEmpty()) {
for (Entry<GroupKey, Number> resultEntry : results.entrySet()) {
GroupKey key = resultEntry.getKey();
StringBuilder keyStringBuilder = new StringBuilder();
GroupKey currentKey = key;
while (currentKey.hasSubKey()) {
keyStringBuilder.append(currentKey.getMainKey());
currentKey = currentKey.getSubKey();
}
int angle = Integer.parseInt(currentKey.toString());
String keyString = keyStringBuilder.toString();
Series series = dataByKeyStringAndAngle.get(keyString);
if (series == null) {
series = chart.createSeries();
dataByKeyStringAndAngle.put(keyString, series);
}
series.addPoint(angle, resultEntry.getValue());
series.setName(keyString);
}
}
}
private void resetChartData() {
dataByKeyStringAndAngle.clear();
chart.removeAllSeries();
}
@Override
public void showError(String error) {
// TODO Auto-generated method stub
}
@Override
public void showError(String mainError, Iterable<String> detailedErrors) {
// TODO Auto-generated method stub
}
}
@@ -0,0 +1,130 @@
package com.sap.sailing.gwt.ui.polarsheets;
import java.util.HashMap;
import java.util.HashSet;
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.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;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.sap.sailing.domain.common.PolarSheetsHistogramData;
import com.sap.sailing.gwt.ui.client.StringMessages;
/**
* Shows the underlying distribution for any point in the polar diagrams
* of the {@link PolarSheetsChartPanel}.
*
* @author d054528 Frederik Petersen
*
*/
public class AngleOverDataSizeHistogramPanel extends DockLayoutPanel {
private final Chart chart;
private final StringMessages stringMessages;
private final Map<String, Set<Series>> seriesForId;
public AngleOverDataSizeHistogramPanel(StringMessages stringMessages) {
super(Unit.PX);
this.seriesForId = new HashMap<>();
this.stringMessages = stringMessages;
setSize("100%", "100%");
chart = createHistogramChart();
chart.getElement().setAttribute("align", "top");
add(chart);
}
private Chart createHistogramChart() {
Chart histogramChart = new Chart().setType(Type.AREA).setHeight100().setWidth100();
histogramChart.setChartTitleText(stringMessages.histogram());
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")
.setLineWidth(1).setMarker(new Marker().setLineWidth(1).setLineColor("#666666")));
return histogramChart;
}
public void addData(Map<Integer, PolarSheetsHistogramData> histogramDataPerAngle, String seriesId, String actualSeriesName) {
chart.setTitle("");
Series series = chart.createSeries();
Point[] points = toPoints(histogramDataPerAngle);
series.setPoints(points);
series.setName(actualSeriesName);
chart.addSeries(series);
if (!seriesForId.containsKey(seriesId)) {
seriesForId.put(seriesId, new HashSet<Series>());
}
Set<Series> seriesSet = seriesForId.get(seriesId);
seriesSet.add(series);
}
private Point[] toPoints(Map<Integer, PolarSheetsHistogramData> histogramDataPerAngle) {
Number[] xValues = new Number[360];
Number[] yValues = new Number[360];
for (int i = 0; i < 360; i++) {
xValues[i] = i;
yValues[i] = 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]);
}
return points;
}
@Override
protected void onLoad() {
Timer timer = new Timer() {
@Override
public void run() {
chart.setSizeToMatchContainer();
}
};
timer.schedule(200);
super.onLoad();
}
@Override
public void onResize() {
chart.setSizeToMatchContainer();
super.onResize();
}
public void removeSeries(String seriesId) {
for (Series series : seriesForId.get(seriesId)) {
chart.removeSeries(series);
}
seriesForId.remove(seriesId);
}
public void removeAllSeries() {
chart.removeAllSeries();
seriesForId.clear();
}
}
@@ -42,10 +42,13 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
private PolarSheetGenerationSettings settings;
private final Map<String,PolarSheetsData> polarSheetsDataMap;
private final AngleOverDataSizeHistogramPanel angleOverDataSizeHistogramPanel;
public PolarSheetsChartPanel(StringMessages stringMessages) {
public PolarSheetsChartPanel(StringMessages stringMessages, AngleOverDataSizeHistogramPanel angleOverDataSizeHistogramPanel) {
super(Unit.PCT);
this.stringMessages = stringMessages;
this.angleOverDataSizeHistogramPanel = angleOverDataSizeHistogramPanel;
polarSheetsDataMap = new HashMap<String, PolarSheetsData>();
setSize("100%", "100%");
chart = createPolarSheetChart();
@@ -90,13 +93,12 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
* @param windSpeedLevel The id of the windspeed level in the windspeed steppings
* @param windSpeed The actual windspeed
*/
private void createSeriesForWindspeed(String name, int windSpeedLevel, int windSpeed) {
private void createSeriesForWindspeed(String name, int windSpeedLevel, String seriesName) {
Series[] seriesPerWindSpeed = seriesMap.get(name);
Number[] forEachDeg = initializeDataForNewSeries();
seriesPerWindSpeed[windSpeedLevel] = chart.createSeries().setPoints(forEachDeg);
String actualSeriesName = name + "-" + windSpeed;
seriesPerWindSpeed[windSpeedLevel].setName(actualSeriesName);
nameForSeries.put(seriesPerWindSpeed[windSpeedLevel],actualSeriesName);
seriesPerWindSpeed[windSpeedLevel].setName(seriesName);
nameForSeries.put(seriesPerWindSpeed[windSpeedLevel],seriesName);
chart.addSeries(seriesPerWindSpeed[windSpeedLevel]);
}
@@ -117,14 +119,16 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
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, result.getStepping().getRawStepping()[i]);
createSeriesForWindspeed(seriesId, i, actualSeriesName);
}
Series series = seriesMap.get(seriesId)[i];
series.setPoints(result.getAveragedPolarDataByWindSpeed()[i], false);
//series.setPoints(result.getAveragedPolarDataByWindSpeed()[i], false);
Point[] points = createPointsWithMarkerAlphaAccordingToDataCount(result, i);
if (points != null) {
series.setPoints(points);
angleOverDataSizeHistogramPanel.addData(result.getHistogramDataMap().get(i), seriesId, actualSeriesName);
}
}
}
@@ -154,14 +158,16 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
Point[] points = new Point[360];
List<Integer> dataCountList = Arrays.asList(result.getDataCountPerAngleForWindspeed(windspeed));
Integer max = Collections.max(dataCountList);
if (max <= 0) {
if (max < settings.getMinimumDataCountPerAngle()) {
return null;
}
for (int i = 0; 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()) {
.getMinimumConfidenceMeasure()
|| result.getHistogramDataMap().get(windspeed).get(i).getDataCount() < settings
.getMinimumDataCountPerAngle()) {
points[i] = new Point(0);
continue;
}
@@ -203,6 +209,7 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
}
seriesMap.remove(seriesId);
polarSheetsDataMap.remove(seriesId);
angleOverDataSizeHistogramPanel.removeSeries(seriesId);
}
}
@@ -213,6 +220,7 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
chart.removeAllSeries();
seriesMap.clear();
polarSheetsDataMap.clear();
angleOverDataSizeHistogramPanel.clear();
}
/**
@@ -12,9 +12,11 @@ import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.SplitLayoutPanel;
import com.google.gwt.user.client.ui.TextBox;
@@ -24,6 +26,7 @@ import com.sap.sailing.domain.common.PolarSheetGenerationResponse;
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.PolarSheetsXYDiagramData;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.WindStepping;
import com.sap.sailing.domain.common.impl.PolarSheetGenerationSettingsImpl;
@@ -58,6 +61,7 @@ public class PolarSheetsPanel extends SplitLayoutPanel implements RaceSelectionC
private List<RegattaAndRaceIdentifier> selectedRaces;
private PolarSheetsChartPanel chartPanel;
private PolarSheetsHistogramPanel histogramPanel;
private AngleOverDataSizeHistogramPanel angleOverDataSizeHistogramPanel;
private Label polarSheetsGenerationLabel;
private Label dataCountLabel;
@@ -93,8 +97,10 @@ public class PolarSheetsPanel extends SplitLayoutPanel implements RaceSelectionC
namingBox = new TextBox();
namingContainer.add(namingBox);
leftPanel.add(namingContainer);
addBoatClassDiagramArea(leftPanel);
DockLayoutPanel rightPanel = new DockLayoutPanel(Unit.PCT);
PolarSheetsChartPanel polarSheetsChartPanel = createPolarSheetsChartPanel();
angleOverDataSizeHistogramPanel = new AngleOverDataSizeHistogramPanel(stringMessages);
PolarSheetsChartPanel polarSheetsChartPanel = createPolarSheetsChartPanel(angleOverDataSizeHistogramPanel);
DockLayoutPanel polarChartAndControlPanel = new DockLayoutPanel(Unit.PCT);
PolarChartControlPanel polarChartControlPanel = new PolarChartControlPanel(stringMessages,
polarSheetsChartPanel);
@@ -108,7 +114,11 @@ public class PolarSheetsPanel extends SplitLayoutPanel implements RaceSelectionC
rightPanel.addNorth(polarChartAndControlPanel, 70);
histogramPanel = new PolarSheetsHistogramPanel(stringMessages);
histogramPanel.getElement().setAttribute("align", "top");
rightPanel.addSouth(histogramPanel, 30);
//rightPanel.addSouth(histogramPanel, 30);
angleOverDataSizeHistogramPanel.getElement().setAttribute("align", "top");
rightPanel.addSouth(angleOverDataSizeHistogramPanel, 30);
add(rightPanel);
@@ -119,6 +129,81 @@ public class PolarSheetsPanel extends SplitLayoutPanel implements RaceSelectionC
chartPanel.setSettings(initialSettings);
}
private void addBoatClassDiagramArea(VerticalPanel leftPanel) {
leftPanel.add(new Label(stringMessages.showBoatClassChartsLabel()));
final ListBox boatClassListBox = createBoatClassListBox();
leftPanel.add(boatClassListBox);
Button showBoatClassDiagramButton = new Button(stringMessages.showDiagram());
showBoatClassDiagramButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
chartPanel.showLoadingInfo();
sailingService.showCachedPolarSheetForBoatClass(
boatClassListBox.getItemText(boatClassListBox.getSelectedIndex()),
new AsyncCallback<PolarSheetGenerationResponse>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(caught.getLocalizedMessage());
}
@Override
public void onSuccess(PolarSheetGenerationResponse result) {
String name = createNameForPolarSheet(result);
chartPanel.getPolarSheetsDataMap().put(result.getId(), result.getData());
chartPanel.setData(name, result.getData());
chartPanel.hideLoadingInfo();
}
});
}
});
leftPanel.add(showBoatClassDiagramButton);
Button showXYDiagramsButton = new Button(stringMessages.showXYDiagram());
showXYDiagramsButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
sailingService.createXYDiagramForBoatClass(
boatClassListBox.getItemText(boatClassListBox.getSelectedIndex()), new AsyncCallback<PolarSheetsXYDiagramData>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(caught.getLocalizedMessage());
}
@Override
public void onSuccess(PolarSheetsXYDiagramData result) {
PolarSheetsXYDiagramPopupPanel popupPanel = new PolarSheetsXYDiagramPopupPanel(stringMessages, result);
popupPanel.show();
popupPanel.center();
}
});
}
});
leftPanel.add(showXYDiagramsButton);
}
private ListBox createBoatClassListBox() {
final ListBox boatClassListBox = new ListBox();
sailingService.getBoatClassNamesWithPolarSheetsAvailable(new AsyncCallback<List<String>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(caught.getLocalizedMessage());
}
@Override
public void onSuccess(List<String> result) {
for (String item : result) {
boatClassListBox.addItem(item);
}
}
});
return boatClassListBox;
}
private void setEventListenersForPolarSheetChart() {
PointSelectEventHandler pointSelectEventHandler = new PointSelectEventHandler() {
@Override
@@ -144,8 +229,8 @@ public class PolarSheetsPanel extends SplitLayoutPanel implements RaceSelectionC
return polarSheetsGenerationStatusLabel;
}
private PolarSheetsChartPanel createPolarSheetsChartPanel() {
chartPanel = new PolarSheetsChartPanel(stringMessages);
private PolarSheetsChartPanel createPolarSheetsChartPanel(AngleOverDataSizeHistogramPanel angleOverDataSizeHistogramPanel) {
chartPanel = new PolarSheetsChartPanel(stringMessages, angleOverDataSizeHistogramPanel);
return chartPanel;
}
@@ -0,0 +1,97 @@
package com.sap.sailing.gwt.ui.polarsheets;
import java.util.List;
import org.moxieapps.gwt.highcharts.client.AxisTitle;
import org.moxieapps.gwt.highcharts.client.Chart;
import org.moxieapps.gwt.highcharts.client.PlotLine;
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.plotOptions.Marker;
import org.moxieapps.gwt.highcharts.client.plotOptions.SplinePlotOptions;
import com.google.gwt.event.dom.client.ClickEvent;
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.PolarSheetsXYDiagramData;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.common.Util.Pair;
public class PolarSheetsXYDiagramPopupPanel extends DialogBox {
private final Chart chart;
private final StringMessages stringMessages;
public PolarSheetsXYDiagramPopupPanel(StringMessages stringMessages, PolarSheetsXYDiagramData result) {
this.stringMessages = stringMessages;
chart = createChart();
VerticalPanel containerPanel = new VerticalPanel();
this.add(containerPanel);
containerPanel.add(chart);
Button closeButton = new Button(stringMessages.close());
closeButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
PolarSheetsXYDiagramPopupPanel.this.hide(false);
}
});
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);
Point[] pointsForUpwindStarboardAverageSpeedMovingAverage = toPointArray(result.getPointsForUpwindStarboardAverageSpeedMovingAverage());
Series speedSeriesMovingAverage = chart.createSeries();
speedSeriesMovingAverage.setName("Upwind Starboard Speed - MovingAverage");
speedSeriesMovingAverage.setPoints(pointsForUpwindStarboardAverageSpeedMovingAverage);
chart.addSeries(speedSeriesMovingAverage);
Point[] pointsForUpwindStarboardAverageConfidence = toPointArray(result.getPointsForUpwindStarboardAverageConfidence());
Series confidenceSeriesMovingAverage = 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);
}
private Point[] toPointArray(List<Pair<Double, Double>> pointsForUpwindStarboardAverageAngle) {
Point[] points = new Point[pointsForUpwindStarboardAverageAngle.size()];
int i = 0;
for (Pair<Double, Double> point : pointsForUpwindStarboardAverageAngle) {
points[i] = new Point(point.getA(), point.getB());
i++;
}
return points;
}
private Chart createChart() {
Chart chart = new Chart().setType(Type.LINE);
chart.setWidth(1000);
chart.setTitle(stringMessages.xyDiagram());
chart.getXAxis().setAxisTitle(new AxisTitle().setText(stringMessages.windSpeed()));
chart.getYAxis(0).setAxisTitle(new AxisTitle().setText(stringMessages.boatSpeed()));
chart.getYAxis(1).setAxisTitle(new AxisTitle().setText(stringMessages.confidence()));
return chart;
}
}
@@ -27,9 +27,9 @@ public class WindSteppingConfiguratorPanel extends HorizontalPanel {
public WindSteppingConfiguratorPanel(WindSteppingWithMaxDistance windStepping) {
setupPlusAndMinusButtons();
Integer[] levels = windStepping.getRawStepping();
Double[] levels = windStepping.getRawStepping();
for (int i = 0; i < levels.length; i++) {
TextBox textBox = createSingleBox(levels[i]);
TextBox textBox = createSingleBox((int) Math.round(levels[i]));
textBoxes.add(textBox);
}
updateTextBoxes();
@@ -69,12 +69,12 @@ public class WindSteppingConfiguratorPanel extends HorizontalPanel {
}
public WindSteppingWithMaxDistance getStepping(double maxDistance) {
List<Integer> levelList = new ArrayList<Integer>();
List<Double> levelList = new ArrayList<Double>();
for (TextBox box : textBoxes) {
levelList.add(Integer.parseInt(box.getValue()));
levelList.add(new Double(Integer.parseInt(box.getValue())));
}
Collections.sort(levelList);
Integer[] levels = levelList.toArray(new Integer[levelList.size()]);
Double[] levels = levelList.toArray(new Double[levelList.size()]);
return new WindSteppingWithMaxDistance(levels, maxDistance);
}
@@ -10,6 +10,7 @@ import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
@@ -25,6 +26,7 @@ import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.domain.common.dto.RaceDTO;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel;
import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory;
import com.sap.sailing.gwt.ui.client.GlobalNavigationPanel;
import com.sap.sailing.gwt.ui.client.LeaderboardUpdateListener;
import com.sap.sailing.gwt.ui.client.MediaServiceAsync;
@@ -55,6 +57,7 @@ import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sse.common.filter.FilterSet;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.URLEncoder;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
import com.sap.sse.gwt.client.player.TimeRangeWithZoomModel;
import com.sap.sse.gwt.client.player.Timer;
@@ -358,20 +361,51 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra
} else {
fleetForRaceName = " - "+fleetForRaceName;
}
Label raceNameLabel = new Label(stringMessages.race() + " " + raceColumn.getRaceColumnName());
final Label raceNameLabel = new Label(stringMessages.race() + " " + raceColumn.getRaceColumnName());
raceNameLabel.setStyleName("RaceName-Label");
Label raceAdditionalInformationLabel = new Label(seriesName + fleetForRaceName);
final Label raceAdditionalInformationLabel = new Label(seriesName + fleetForRaceName);
raceAdditionalInformationLabel.setStyleName("RaceSeriesAndFleet-Label");
raceInformationHeader.clear();
raceInformationHeader.add(raceNameLabel);
raceInformationHeader.add(raceAdditionalInformationLabel);
Anchor regattaNameAnchor = new Anchor(raceIdentifier.getRegattaName());
final Anchor regattaNameAnchor = new Anchor(raceIdentifier.getRegattaName());
if (event != null) {
regattaNameAnchor.setHref("/gwt/Home.html#EventPlace:eventId="+event.id.toString()+"&navigationTab=Regattas&leaderboardName="+leaderboardName);
// we don't use the EntryPointLinkFactory here, because of lacking support for Places
String debugParam = Window.Location.getParameter("gwt.codesvr");
String link = "/gwt/Home.html";
if (debugParam != null && !debugParam.isEmpty()) {
link += "?gwt.codesvr=" + debugParam;
}
link += "#EventPlace:eventId="+event.id.toString();
link += "&navigationTab=Regatta&leaderboardName=" + URLEncoder.encode(leaderboardName);
regattaNameAnchor.setHref(link);
} else {
regattaNameAnchor.setHref("javascript:window.history.back();");
String leaderboardGroupNameParam = Window.Location.getParameter("leaderboardGroupName");
if(leaderboardGroupNameParam != null) {
Map<String, String> leaderboardGroupLinkParameters = new HashMap<String, String>();
leaderboardGroupLinkParameters.put("showRaceDetails", "true");
leaderboardGroupLinkParameters.put("leaderboardGroupName", leaderboardGroupNameParam);
String leaderBoardGroupLink = EntryPointLinkFactory.createLeaderboardGroupLink(leaderboardGroupLinkParameters);
regattaNameAnchor.setHref(leaderBoardGroupLink);
} else {
// fallback
regattaNameAnchor.setHref("javascript:window.history.back();");
}
}
regattaNameAnchor.setStyleName("RegattaName-Anchor");
// TODO: Strange behavior... check
// Window.addResizeHandler(new ResizeHandler() {
// @Override
// public void onResize(ResizeEvent event) {
// int headerPanelWidth = raceMap.getRightHeaderPanel().getOffsetWidth() - 150; // 150px is the width of the sapLogo
// int raceNameAndFleetLabelWidth = raceInformationHeader.getOffsetWidth();
// int regattaAnchorWidth = regattaNameAnchor.getOffsetWidth();
// boolean overlap = raceNameAndFleetLabelWidth + regattaAnchorWidth > headerPanelWidth;
// raceInformationHeader.setVisible(!overlap);
// }
// });
Label raceTimeLabel = computeRaceInformation(raceColumn, fleet);
raceTimeLabel.setStyleName("RaceTime-Label");
regattaAndRaceTimeInformationHeader.clear();
@@ -398,6 +432,5 @@ public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, Ra
raceInformationLabel.setText(formatter.format(raceColumn.getStartDate(fleet)));
return raceInformationLabel;
}
}
@@ -101,6 +101,7 @@ import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.RemoteSailingServerReference;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.base.Sideline;
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher;
@@ -140,6 +141,7 @@ import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sailing.domain.common.PolarSheetGenerationResponse;
import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
import com.sap.sailing.domain.common.PolarSheetsData;
import com.sap.sailing.domain.common.PolarSheetsXYDiagramData;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.RaceFetcher;
import com.sap.sailing.domain.common.RaceIdentifier;
@@ -183,6 +185,7 @@ import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.common.impl.PolarSheetGenerationResponseImpl;
import com.sap.sailing.domain.common.impl.PolarSheetsXYDiagramDataImpl;
import com.sap.sailing.domain.common.impl.TimeRangeImpl;
import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.common.media.MediaTrack;
@@ -212,7 +215,6 @@ import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater;
import com.sap.sailing.domain.persistence.DomainObjectFactory;
import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory;
import com.sap.sailing.domain.polarsheets.PolarSheetGenerationWorker;
import com.sap.sailing.domain.racelog.RaceLogStore;
import com.sap.sailing.domain.racelog.RaceStateOfSameDayHelper;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
@@ -337,6 +339,8 @@ import com.sap.sailing.manage2sail.EventResultDescriptor;
import com.sap.sailing.manage2sail.Manage2SailEventResultsParserImpl;
import com.sap.sailing.manage2sail.RaceResultDescriptor;
import com.sap.sailing.manage2sail.RegattaResultDescriptor;
import com.sap.sailing.polars.PolarDataService;
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.resultimport.ResultUrlProvider;
import com.sap.sailing.resultimport.ResultUrlRegistry;
import com.sap.sailing.server.RacingEventService;
@@ -481,7 +485,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
private final SwissTimingReplayService swissTimingReplayService;
private final QuickRanksLiveCache quickRanksLiveCache;
public SailingServiceImpl() {
BundleContext context = Activator.getDefault();
Activator activator = Activator.getInstance();
@@ -3913,15 +3916,33 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
for (RegattaAndRaceIdentifier race : selectedRaces) {
trackedRaces.add(service.getTrackedRace(race));
}
PolarSheetGenerationWorker genWorker = new PolarSheetGenerationWorker(trackedRaces, settings, executor);
genWorker.startPolarSheetGeneration();
if (name == null || name.isEmpty()) {
name = getCommonBoatClass(trackedRaces);
}
PolarSheetsData result = genWorker.get();
PolarDataService polarDataService = service.getPolarDataService();
PolarSheetsData result = polarDataService.generatePolarSheet(trackedRaces, settings, executor);
return new PolarSheetGenerationResponseImpl(id, name, result);
}
@Override
public List<String> getBoatClassNamesWithPolarSheetsAvailable() {
Set<BoatClass> boatClasses = getService().getPolarDataService().getAllBoatClassesWithPolarSheetsAvailable();
List<String> names = new ArrayList<String>();
for (BoatClass boatClass : boatClasses) {
names.add(boatClass.getName());
}
return names;
}
@Override
public PolarSheetGenerationResponse showCachedPolarSheetForBoatClass(String boatClassName) {
BoatClass boatClass = getService().getBaseDomainFactory().getOrCreateBoatClass(boatClassName);
PolarSheetsData data = getService().getPolarDataService().getPolarSheetForBoatClass(boatClass);
String name = boatClassName + "_OVERALL";
String id = name;
return new PolarSheetGenerationResponseImpl(id, name, data);
}
private String getCommonBoatClass(Set<TrackedRace> trackedRaces) {
BoatClass boatClass = null;
for (TrackedRace race : trackedRaces) {
@@ -5175,7 +5196,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
}
return null;
}
@Override
public ArrayList<EventDTO> getEventsForLeaderboard(String leaderboardName) {
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
@@ -5191,4 +5212,44 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
}
return new ArrayList<>(events.values());
}
}
@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>>();
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);
pointsForUpwindStarboardAverageSpeedMovingAverage.add(new Pair<Double, Double>(windInKnots,
averageUpwindStarboardMovingAverage.getObject().getKnots()));
pointsForUpwindStarboardAverageAngleMovingAverage.add(new Pair<Double, Double>(windInKnots,
averageUpwindStarboardMovingAverage.getObject().getBearing().getDegrees()));
} catch (NotEnoughDataHasBeenAddedException e) {
// Do not add a point to the result
}
}
PolarSheetsXYDiagramData data = new PolarSheetsXYDiagramDataImpl(pointsForUpwindStarboardAverageAngle,
pointsForUpwindStarboardAverageSpeed, pointsForUpwindStarboardAverageAngleMovingAverage,
pointsForUpwindStarboardAverageSpeedMovingAverage, pointsForUpwindStarboardAverageConfidence);
return data;
}
}
@@ -5,6 +5,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -15,8 +16,11 @@ import com.sap.sailing.domain.common.dto.NamedDTO;
import com.sap.sse.common.Util;
public class EventBaseDTO extends NamedDTO implements IsSerializable {
private static final int MINIMUM_IMAGE_HEIGHT_FOR_SAILING_PHOTOGRAPHY_IN_PIXELS = 500;
private static final long serialVersionUID = 818666323178097939L;
private static final String STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE = "stage";
private static final String THUMBNAIL_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE = "eventteaser";
public VenueDTO venue;
public Date startDate;
public Date endDate;
@@ -33,33 +37,37 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable {
/** placeholder for social media URL's -> attributes will be implemented later on */
private String facebookURL;
private String twitterURL;
private String lastThumbnail;
/**
* For the image URL keys holds the sizes of these images if known. An image size is "known" by this object if it was
* provided to the {@link #setImageSize} method.
* For the image URL keys holds the sizes of these images if known. An image size is "known" by this object if it
* was provided to the {@link #setImageSize} method.
*/
private Map<String, ImageSize> imageSizes;
/**
* The base URL for the server instance on which the data for this event can be reached. Could be something like
* <code>http://sapsailing.com</code> for archived events that will forever remain in the archive, or
* <code>http://danishleague2014.sapsailing.com</code> for other events that may not yet be archived or may change
* servers at any time in the future, therefore requiring a dedicated stable URL that the Apache server can
* resolve to the correct host IP and Java server instance.
* servers at any time in the future, therefore requiring a dedicated stable URL that the Apache server can resolve
* to the correct host IP and Java server instance.
*/
private String baseURL;
/**
* Indicates whether the event is hosted on a remote server or not
* Indicates whether the event is hosted on a remote server or not
*/
private boolean isOnRemoteServer;
EventBaseDTO() {} // for serialization only
EventBaseDTO() {
} // for serialization only
public EventBaseDTO(List<? extends LeaderboardGroupBaseDTO> leaderboardGroups) {
this.leaderboardGroups = leaderboardGroups;
this.imageSizes = new HashMap<String, ImageSize>();
}
public EventBaseDTO(String name, List<? extends LeaderboardGroupBaseDTO> leaderboardGroups) {
super(name);
this.leaderboardGroups = leaderboardGroups;
@@ -68,12 +76,12 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable {
public boolean isRunning() {
Date now = new Date();
if(startDate != null && endDate != null && (now.after(startDate) && now.before(endDate))) {
if (startDate != null && endDate != null && (now.after(startDate) && now.before(endDate))) {
return true;
}
return false;
}
public String getDescription() {
return description;
}
@@ -114,7 +122,7 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable {
public void addImageURL(String imageURL) {
imageURLs.add(imageURL);
}
public void addVideoURL(String videoURL) {
videoURLs.add(videoURL);
}
@@ -128,11 +136,14 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable {
}
/**
* The stage image is determined from the {@link #imageURLs} collection by a series of heuristics and fall-back rules:
* The stage image is determined from the {@link #imageURLs} collection by a series of heuristics and fall-back
* rules:
* <ol>
* <li>If one or more image URLs has "stage" (ignoring case) in its name, only they are considered candidates.</li>
* <li>If no image URL has "stage" (ignoring case) in its name, all images from {@link #imageURLs} are considered candidates.</li>
* <li>From all candidates, the one with the biggest known size (determined by the product of width and height) is chosen.</li>
* <li>If no image URL has "stage" (ignoring case) in its name, all images from {@link #imageURLs} are considered
* candidates.</li>
* <li>From all candidates, the one with the biggest known size (determined by the product of width and height) is
* chosen.</li>
* <li>If the size isn't known for any candidate, the first candidate in {@link #imageURLs} is picked.</li>
* </ol>
*/
@@ -190,10 +201,151 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable {
return result;
}
public LinkedList<String> getSailingLovesPhotographyImages() {
final LinkedList<String> acceptedImages = new LinkedList<String>();
for (String candidateImageUrl : imageURLs) {
ImageSize imageSize = getImageSize(candidateImageUrl);
if (imageSize != null && imageSize.getHeight() > MINIMUM_IMAGE_HEIGHT_FOR_SAILING_PHOTOGRAPHY_IN_PIXELS
&& !candidateImageUrl.toLowerCase().contains(STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE)) {
acceptedImages.add(candidateImageUrl);
}
}
return acceptedImages;
}
public String getEventThumbnailImageUrl() {
if (lastThumbnail == null) {
lastThumbnail = findEventThumbnailImageUrl();
}
return lastThumbnail;
}
private String findEventThumbnailImageUrl() {
final class ImageHolder {
final int PERFECT_HEIGHT = 240;
final int PERFECT_WIDTH = 370;
final double PERFECT_RATIO = PERFECT_HEIGHT / (double) PERFECT_WIDTH;
final String url;
final int height;
final int width;
final int size;
final double ratio;
ImageHolder() {
this.url = "";
this.height = -1;
this.width = -1;
this.size = -1;
this.ratio = -1;
}
ImageHolder(String url, ImageSize imageSize) {
if (url == null || imageSize == null) {
this.url = "";
this.height = -1;
this.width = -1;
this.size = -1;
this.ratio = -1;
} else {
this.url = url;
this.height = imageSize.getHeight();
this.width = imageSize.getWidth();
this.size = height * width;
this.ratio = height / (double) width;
}
}
boolean isBetterWorstcaseThan(ImageHolder otherImageHolder) {
if (isNull()) {
return false;
}
if (url.contains(STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE)) {
return false;
}
if (otherImageHolder.isNull()) {
return true;
}
if (!isBigEnough() && otherImageHolder.isBigEnough()) {
return true;
}
if (this.fitsRatio() && otherImageHolder.fitsRatio() && otherImageHolder.isBigEnough()
&& otherImageHolder.isSmallerThan(this)) {
return true;
}
return false;
}
boolean isSmallerThan(ImageHolder otherImageHolder) {
return size < otherImageHolder.size;
}
boolean isBigEnough() {
return height >= PERFECT_HEIGHT && width >= PERFECT_WIDTH;
}
boolean fitsRatio() {
return ratio == PERFECT_RATIO;
}
boolean isPerfectFit() {
return (height == PERFECT_HEIGHT && width == PERFECT_WIDTH);
}
boolean isNull() {
return size == -1;
}
}
// search for name pattern
for (String imageUrl : imageURLs) {
if (imageUrl != null
&& imageUrl.toLowerCase().contains(THUMBNAIL_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE)) {
return imageUrl;
}
}
ImageHolder actualWorstcase = new ImageHolder();
ImageHolder bestFit = new ImageHolder();
for (String candidateImageUrl : getPhotoGalleryImageURLs()) {
final ImageHolder candidate = new ImageHolder(candidateImageUrl, getImageSize(candidateImageUrl));
if (candidate.isPerfectFit()) {
return candidate.url;
}
if (candidate.fitsRatio() && candidate.isBigEnough()) {
if (candidate.isSmallerThan(bestFit)) {
bestFit = candidate;
}
}
if (candidate.isBetterWorstcaseThan(actualWorstcase)) {
actualWorstcase = candidate;
}
}
if (!bestFit.isNull()) {
return bestFit.url;
}
if (!actualWorstcase.isNull()) {
return actualWorstcase.url;
}
return null;
}
public List<String> getVideoURLs() {
return videoURLs;
}
public List<String> getSponsorImageURLs() {
return sponsorImageURLs;
}
@@ -225,7 +377,7 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable {
public void setTwitterURL(String twitterURL) {
this.twitterURL = twitterURL;
}
public void setImageSize(String imageURL, ImageSize imageSize) {
if (imageSize == null) {
imageSizes.remove(imageURL);
@@ -1,64 +1,64 @@
package com.sap.sailing.gwt.ui.shared;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class EventDTO extends EventBaseDTO {
private static final long serialVersionUID = -7100030301376959817L;
public List<RegattaDTO> regattas;
private Date currentServerTime;
private List<LeaderboardGroupDTO> leaderboardGroups; // keeps the more specific type accessible in a type-safe way
public EventDTO() {
this(new ArrayList<LeaderboardGroupDTO>());
}
private EventDTO(List<LeaderboardGroupDTO> leaderboardGroups) {
super(leaderboardGroups);
this.leaderboardGroups = leaderboardGroups;
initCurrentServerTime();
}
public EventDTO(String name) {
this(name, new ArrayList<LeaderboardGroupDTO>());
}
private EventDTO(String name, List<LeaderboardGroupDTO> leaderboardGroups) {
super(name, leaderboardGroups);
this.leaderboardGroups = leaderboardGroups;
initCurrentServerTime();
regattas = new ArrayList<RegattaDTO>();
}
public boolean isFakeSeries() {
return leaderboardGroups.size() == 1 && leaderboardGroups.get(0).hasOverallLeaderboard();
}
public boolean isRunning() {
return getCurrentServerTime().after(startDate) && getCurrentServerTime().before(endDate);
}
public boolean isFinished() {
return getCurrentServerTime().after(endDate);
}
private void initCurrentServerTime() {
currentServerTime = new Date();
}
public Date getCurrentServerTime() {
return currentServerTime;
}
public void addLeaderboardGroup(LeaderboardGroupDTO leaderboardGroup) {
leaderboardGroups.add(leaderboardGroup);
}
public List<LeaderboardGroupDTO> getLeaderboardGroups() {
return leaderboardGroups;
}
}
package com.sap.sailing.gwt.ui.shared;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class EventDTO extends EventBaseDTO {
private static final long serialVersionUID = -7100030301376959817L;
public List<RegattaDTO> regattas;
private Date currentServerTime;
private List<LeaderboardGroupDTO> leaderboardGroups; // keeps the more specific type accessible in a type-safe way
public EventDTO() {
this(new ArrayList<LeaderboardGroupDTO>());
}
private EventDTO(List<LeaderboardGroupDTO> leaderboardGroups) {
super(leaderboardGroups);
this.leaderboardGroups = leaderboardGroups;
initCurrentServerTime();
}
public EventDTO(String name) {
this(name, new ArrayList<LeaderboardGroupDTO>());
}
private EventDTO(String name, List<LeaderboardGroupDTO> leaderboardGroups) {
super(name, leaderboardGroups);
this.leaderboardGroups = leaderboardGroups;
initCurrentServerTime();
regattas = new ArrayList<RegattaDTO>();
}
public boolean isFakeSeries() {
return leaderboardGroups.size() == 1 && leaderboardGroups.get(0).hasOverallLeaderboard();
}
public boolean isRunning() {
return getCurrentServerTime().after(startDate) && getCurrentServerTime().before(endDate);
}
public boolean isFinished() {
return getCurrentServerTime().after(endDate);
}
private void initCurrentServerTime() {
currentServerTime = new Date();
}
public Date getCurrentServerTime() {
return currentServerTime;
}
public void addLeaderboardGroup(LeaderboardGroupDTO leaderboardGroup) {
leaderboardGroups.add(leaderboardGroup);
}
public List<LeaderboardGroupDTO> getLeaderboardGroups() {
return leaderboardGroups;
}
}
@@ -0,0 +1,14 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by Fontastic.me</metadata>
<defs>
<font id="slick" horiz-adv-x="512">
<font-face font-family="slick" units-per-em="512" ascent="480" descent="-32"/>
<missing-glyph horiz-adv-x="512" />
<glyph unicode="&#8594;" d="M241 113l130 130c4 4 6 8 6 13 0 5-2 9-6 13l-130 130c-3 3-7 5-12 5-5 0-10-2-13-5l-29-30c-4-3-6-7-6-12 0-5 2-10 6-13l87-88-87-88c-4-3-6-8-6-13 0-5 2-9 6-12l29-30c3-3 8-5 13-5 5 0 9 2 12 5z m234 143c0-40-9-77-29-110-20-34-46-60-80-80-33-20-70-29-110-29-40 0-77 9-110 29-34 20-60 46-80 80-20 33-29 70-29 110 0 40 9 77 29 110 20 34 46 60 80 80 33 20 70 29 110 29 40 0 77-9 110-29 34-20 60-46 80-80 20-33 29-70 29-110z"/>
<glyph unicode="&#8592;" d="M296 113l29 30c4 3 6 7 6 12 0 5-2 10-6 13l-87 88 87 88c4 3 6 8 6 13 0 5-2 9-6 12l-29 30c-3 3-8 5-13 5-5 0-9-2-12-5l-130-130c-4-4-6-8-6-13 0-5 2-9 6-13l130-130c3-3 7-5 12-5 5 0 10 2 13 5z m179 143c0-40-9-77-29-110-20-34-46-60-80-80-33-20-70-29-110-29-40 0-77 9-110 29-34 20-60 46-80 80-20 33-29 70-29 110 0 40 9 77 29 110 20 34 46 60 80 80 33 20 70 29 110 29 40 0 77-9 110-29 34-20 60-46 80-80 20-33 29-70 29-110z"/>
<glyph unicode="&#8226;" d="M475 256c0-40-9-77-29-110-20-34-46-60-80-80-33-20-70-29-110-29-40 0-77 9-110 29-34 20-60 46-80 80-20 33-29 70-29 110 0 40 9 77 29 110 20 34 46 60 80 80 33 20 70 29 110 29 40 0 77-9 110-29 34-20 60-46 80-80 20-33 29-70 29-110z"/>
<glyph unicode="&#97;" d="M475 439l0-128c0-5-1-9-5-13-4-4-8-5-13-5l-128 0c-8 0-13 3-17 11-3 7-2 14 4 20l40 39c-28 26-62 39-100 39-20 0-39-4-57-11-18-8-33-18-46-32-14-13-24-28-32-46-7-18-11-37-11-57 0-20 4-39 11-57 8-18 18-33 32-46 13-14 28-24 46-32 18-7 37-11 57-11 23 0 44 5 64 15 20 9 38 23 51 42 2 1 4 3 7 3 3 0 5-1 7-3l39-39c2-2 3-3 3-6 0-2-1-4-2-6-21-25-46-45-76-59-29-14-60-20-93-20-30 0-58 5-85 17-27 12-51 27-70 47-20 19-35 43-47 70-12 27-17 55-17 85 0 30 5 58 17 85 12 27 27 51 47 70 19 20 43 35 70 47 27 12 55 17 85 17 28 0 55-5 81-15 26-11 50-26 70-45l37 37c6 6 12 7 20 4 8-4 11-9 11-17z"/>
</font></defs></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+34 -33
View File
@@ -1,33 +1,34 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Test
Bundle-SymbolicName: com.sap.sailing.mongodb.test
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Fragment-Host: com.sap.sse.mongodb
Require-Bundle: com.sap.sailing.domain,
com.sap.sailing.domain.shared.android,
com.sap.sailing.domain.test,
com.tractrac.clientmodule;bundle-version="2.0.0",
com.sap.sailing.domain.tractracadapter,
com.sap.sailing.domain.persistence,
com.sap.sailing.server,
com.sap.sailing.expeditionconnector,
org.json.simple;bundle-version="1.1.0",
com.sap.sailing.domain.swisstimingadapter,
org.objenesis;bundle-version="1.3.0",
org.junit4;bundle-version="4.8.2",
org.hamcrest;bundle-version="1.1.0",
org.mockito.mockito-core;bundle-version="1.9.5",
com.sap.sailing.domain.racelogtrackingadapter.testsupport,
com.sap.sailing.server.gateway;bundle-version="1.0.0",
com.sap.sse.common,
com.sap.sse.operationaltransformation,
com.sap.sse.replication,
com.sap.sse
Import-Package: com.sap.sailing.domain.common,
com.sap.sailing.domain.common.impl,
com.sap.sailing.domain.common.racelog,
com.sap.sailing.domain.common.racelog.tracking
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Test
Bundle-SymbolicName: com.sap.sailing.mongodb.test
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Fragment-Host: com.sap.sse.mongodb
Require-Bundle: com.sap.sailing.domain,
com.sap.sailing.domain.shared.android,
com.sap.sailing.domain.test,
com.tractrac.clientmodule;bundle-version="2.0.0",
com.sap.sailing.domain.tractracadapter,
com.sap.sailing.domain.persistence,
com.sap.sailing.server,
com.sap.sailing.expeditionconnector,
org.json.simple;bundle-version="1.1.0",
com.sap.sailing.domain.swisstimingadapter,
org.objenesis;bundle-version="1.3.0",
org.junit4;bundle-version="4.8.2",
org.hamcrest;bundle-version="1.1.0",
org.mockito.mockito-core;bundle-version="1.9.5",
com.sap.sailing.polars,
com.sap.sailing.domain.racelogtrackingadapter.testsupport,
com.sap.sailing.server.gateway,
com.sap.sse.common,
com.sap.sse.operationaltransformation,
com.sap.sse.replication,
com.sap.sse
Import-Package: com.sap.sailing.domain.common,
com.sap.sailing.domain.common.impl,
com.sap.sailing.domain.common.racelog,
com.sap.sailing.domain.common.racelog.tracking
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>com.sap.sailing.polars.test</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -0,0 +1,3 @@
eclipse.preferences.version=1
pluginProject.extensions=false
resolve.requirebundle=false
@@ -0,0 +1,14 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: PolarsTest
Bundle-SymbolicName: com.sap.sailing.polars.test
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: SAP
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"
@@ -0,0 +1,4 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.
@@ -0,0 +1,90 @@
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;
}
}
@@ -0,0 +1,72 @@
package com.sap.sailing.polars.clusters.test;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.polars.clusters.SpeedClusterGroup;
import com.sap.sse.datamining.data.Cluster;
public class SpeedClusterGroupTest {
@Test
public void testWithZeroLevels() {
double[] levelMidsInKnots = {};
SpeedClusterGroup group = new SpeedClusterGroup("test", levelMidsInKnots, 4);
assertThat(group.getClusterFor(new KnotSpeedImpl(0)), nullValue());
assertThat(group.getClusterFor(new KnotSpeedImpl(6)), nullValue());
}
@Test
public void testWithOneLevelReachingZero() {
double[] levelMidsInKnots = { 4 };
SpeedClusterGroup group = new SpeedClusterGroup("test", levelMidsInKnots, 4);
Cluster<Speed> clusterForZero = group.getClusterFor(new KnotSpeedImpl(0));
assertThat(clusterForZero, notNullValue());
Cluster<Speed> clusterForSix = group.getClusterFor(new KnotSpeedImpl(6));
assertThat(clusterForSix, notNullValue());
assertThat(clusterForSix == clusterForZero, equalTo(true));
assertThat(group.getClusterFor(new KnotSpeedImpl(9)), nullValue());
}
@Test
public void testWithOneLevelNotReachingZero() {
double[] levelMidsInKnots = { 7 };
SpeedClusterGroup group = new SpeedClusterGroup("test", levelMidsInKnots, 4);
Cluster<Speed> clusterForNine = group.getClusterFor(new KnotSpeedImpl(9));
assertThat(clusterForNine, notNullValue());
Cluster<Speed> clusterForSix = group.getClusterFor(new KnotSpeedImpl(6));
assertThat(clusterForSix, notNullValue());
assertThat(clusterForSix == clusterForNine, equalTo(true));
assertThat(group.getClusterFor(new KnotSpeedImpl(0)), nullValue());
}
@Test
public void testWithMultipleLevelsWithNoEmptyRoomInbetween() {
double[] levelMidsInKnots = { 4, 6, 8, 10, 12, 15 };
SpeedClusterGroup group = new SpeedClusterGroup("test", levelMidsInKnots, 4);
Cluster<Speed> clusterForFourteen = group.getClusterFor(new KnotSpeedImpl(14));
assertThat(clusterForFourteen, notNullValue());
Cluster<Speed> clusterForThirteen = group.getClusterFor(new KnotSpeedImpl(13));
assertThat(clusterForThirteen, notNullValue());
assertThat(clusterForThirteen == clusterForFourteen, equalTo(false));
assertThat(group.getClusterFor(new KnotSpeedImpl(20)), nullValue());
}
@Test
public void testWithMultipleLevelsWithRoomInbetween() {
double[] levelMidsInKnots = { 4, 6, 15 };
SpeedClusterGroup group = new SpeedClusterGroup("test", levelMidsInKnots, 4);
Cluster<Speed> clusterForFourteen = group.getClusterFor(new KnotSpeedImpl(14));
assertThat(clusterForFourteen, notNullValue());
Cluster<Speed> clusterForSeven = group.getClusterFor(new KnotSpeedImpl(7));
assertThat(clusterForSeven, notNullValue());
assertThat(clusterForSeven == clusterForFourteen, equalTo(false));
assertThat(group.getClusterFor(new KnotSpeedImpl(10.5)), nullValue());
}
}
@@ -0,0 +1,209 @@
package com.sap.sailing.polars.mining.test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.closeTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeoutException;
import org.junit.Test;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.SpeedWithConfidence;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
import com.sap.sailing.domain.tracking.impl.WindImpl;
import com.sap.sailing.domain.tracking.impl.WindWithConfidenceImpl;
import com.sap.sailing.polars.mining.PolarDataMiner;
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class PolarDataMinerTest {
private static final int MILLISECONDS_OVER_WHICH_TO_AVERAGE_SPEED = 30;
private static final double EPSILON = 1E-4;
@Test
public void testGrouping() throws InterruptedException, TimeoutException, NoSuchMethodException,
NotEnoughDataHasBeenAddedException {
PolarDataMiner miner = new PolarDataMiner();
BoatClass mockedBoatClass = mock(BoatClass.class);
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);
Competitor competitor1 = mock(Competitor.class);
GPSFixMoving fix2_1 = createMockedFix(13, 00, 54.443942, 10.172739, 42.2, 10);
GPSFixMoving fix2_2 = createMockedFix(13, 30, 54.425394, 10.177404, 41.8, 10.1);
Competitor competitor2 = mock(Competitor.class);
GPSFixMoving fix3_1 = createMockedFix(13, 45, 54.873740, 10.193648, 43.2, 10);
Competitor competitor3 = mock(Competitor.class);
Map<Competitor, Set<GPSFixMoving>> fixesPerCompetitor = new HashMap<Competitor, Set<GPSFixMoving>>();
Set<GPSFixMoving> setForCompetitor1 = new HashSet<GPSFixMoving>();
setForCompetitor1.add(fix1_1);
setForCompetitor1.add(fix1_2);
fixesPerCompetitor.put(competitor1, setForCompetitor1);
Set<GPSFixMoving> setForCompetitor2 = new HashSet<GPSFixMoving>();
setForCompetitor2.add(fix2_1);
setForCompetitor2.add(fix2_2);
fixesPerCompetitor.put(competitor2, setForCompetitor2);
Set<GPSFixMoving> setForCompetitor3 = new HashSet<GPSFixMoving>();
setForCompetitor2.add(fix3_1);
fixesPerCompetitor.put(competitor3, setForCompetitor3);
TrackedRace trackedRace = createMockedTrackedRace(fixesPerCompetitor, mockedBoatClass);
for (Entry<Competitor, Set<GPSFixMoving>> entry : fixesPerCompetitor.entrySet()) {
Competitor competitor = entry.getKey();
Set<GPSFixMoving> fixSet = entry.getValue();
for (GPSFixMoving fix : fixSet) {
miner.addFix(fix, competitor, trackedRace);
}
}
int millisLeft = 500000;
while (miner.isCurrentlyActiveAndOrHasQueue() && millisLeft > 0) {
Thread.sleep(100);
millisLeft = millisLeft - 100;
if (miner.isCurrentlyActiveAndOrHasQueue() && millisLeft <= 0) {
throw new TimeoutException();
}
}
SpeedWithConfidence<Void> estimatedSpeed1 = miner.estimateBoatSpeed(mockedBoatClass, new KnotSpeedImpl(15),
new DegreeBearingImpl(44.9), true);
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);
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);
assertThat(estimatedSpeed3, is(notNullValue()));
assertThat(estimatedSpeed3.getObject().getKnots(), is(closeTo(10, EPSILON)));
}
private GPSFixMoving createMockedFix(int hour, int minute, double lat, double lng, double bearingRaw, double speed) {
GPSFixMoving fix = mock(GPSFixMoving.class);
when(fix.getPosition()).thenReturn(new DegreePosition(lat, lng));
Calendar cal = Calendar.getInstance();
cal.set(2014, 4, 3, hour, minute);
TimePoint fixTimePoint = new MillisecondsTimePoint(cal.getTime());
when(fix.getTimePoint()).thenReturn(fixTimePoint);
Bearing bearing = new DegreeBearingImpl(bearingRaw);
SpeedWithBearing speedWithBearing = new KnotSpeedWithBearingImpl(speed, bearing);
when(fix.getSpeed()).thenReturn(speedWithBearing);
return fix;
}
private TrackedRace createMockedTrackedRace(Map<Competitor, Set<GPSFixMoving>> fixesPerCompetitor,
BoatClass mockedBoatClass) {
TrackedRace trackedRace = mock(TrackedRace.class);
for (Entry<Competitor, Set<GPSFixMoving>> competitorEntry : fixesPerCompetitor.entrySet()) {
Competitor competitor = competitorEntry.getKey();
DynamicGPSFixTrack<Competitor, GPSFixMoving> track = new DynamicGPSFixMovingTrackImpl<Competitor>(
competitor, MILLISECONDS_OVER_WHICH_TO_AVERAGE_SPEED);
when(trackedRace.getTrack(competitor)).thenReturn(track);
for (GPSFixMoving fix : competitorEntry.getValue()) {
track.add(fix);
}
}
RaceDefinition mockedRaceDefinition = createMockedRaceDefinition();
when(mockedRaceDefinition.getBoatClass()).thenReturn(mockedBoatClass);
when(trackedRace.getRace()).thenReturn(mockedRaceDefinition);
for (Competitor competitor : fixesPerCompetitor.keySet()) {
MarkPassing markpassing = createMockedStartMarkPassing();
when(trackedRace.getMarkPassing(eq(competitor), any(Waypoint.class))).thenReturn(markpassing);
}
Calendar cal = Calendar.getInstance();
cal.set(2014, 4, 3, 12, 00);
TimePoint startOfRace = new MillisecondsTimePoint(cal.getTime());
cal.set(2014, 4, 3, 15, 00);
TimePoint endOfRace = new MillisecondsTimePoint(cal.getTime());
when(trackedRace.getStartOfRace()).thenReturn(startOfRace);
when(trackedRace.getEndOfRace()).thenReturn(endOfRace);
Bearing windBearing = new DegreeBearingImpl(180);
SpeedWithBearing windSpeed = new KnotSpeedWithBearingImpl(15, windBearing);
Wind wind = new WindImpl(new DegreePosition(54.431952, 10.186767), startOfRace, windSpeed);
WindWithConfidence<Pair<Position, TimePoint>> windWithConfidence = new WindWithConfidenceImpl<>(wind, 0.5, null, false);
// Always return same wind
when(trackedRace.getWind(any(Position.class), any(TimePoint.class))).thenReturn(wind);
when(trackedRace.getWind(any(Position.class), any(TimePoint.class), any())).thenReturn(wind);
when(trackedRace.getWindWithConfidence(any(Position.class), any(TimePoint.class), any())).thenReturn(windWithConfidence);
return trackedRace;
}
private MarkPassing createMockedStartMarkPassing() {
Calendar cal = Calendar.getInstance();
cal.set(2014, 4, 3, 12, 15);
TimePoint startOfRaceForCompetitor = new MillisecondsTimePoint(cal.getTime());
MarkPassing passing = mock(MarkPassing.class);
when(passing.getTimePoint()).thenReturn(startOfRaceForCompetitor);
return passing;
}
private RaceDefinition createMockedRaceDefinition() {
RaceDefinition raceDefinition = mock(RaceDefinition.class);
Course mockedCourse = createMockedCourse();
when(raceDefinition.getCourse()).thenReturn(mockedCourse);
BoatClass mockedBoatClass = mock(BoatClass.class);
when(mockedBoatClass.getManeuverDegreeAngleThreshold()).thenReturn(20.0);
when(raceDefinition.getBoatClass()).thenReturn(mockedBoatClass);
return raceDefinition;
}
private Course createMockedCourse() {
Course course = mock(Course.class);
return course;
}
}
@@ -0,0 +1,233 @@
package com.sap.sailing.polars.regression.test;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import org.hamcrest.number.IsCloseTo;
import org.junit.Test;
import com.sap.sailing.polars.regression.IncrementalLinearRegressionProcessor;
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.polars.regression.impl.IncrementalLeastSquaresProcessor;
public class IncrementalLinearRegressionTest {
private static double EPSILON = 1E-12;
private static double VAGUE_EPSILON = 0.1;
private IncrementalLinearRegressionProcessor createRegressionProcessor() {
IncrementalLinearRegressionProcessor processor = new IncrementalLeastSquaresProcessor();
return processor;
}
@Test
public void testRegressionWithTwoDataPoints() throws NotEnoughDataHasBeenAddedException {
IncrementalLinearRegressionProcessor processor = createRegressionProcessor();
double x1 = 3;
double y1 = 7;
processor.addMeasuredPoint(x1, y1);
double x2 = 6;
double y2 = 14;
processor.addMeasuredPoint(x2, y2);
double y = processor.getEstimatedY(x1);
assertThat(y, new IsCloseTo(y1, EPSILON));
y = processor.getEstimatedY(x2);
assertThat(y, new IsCloseTo(y2, EPSILON));
y = processor.getEstimatedY(4.5);
assertThat(y, new IsCloseTo(10.5, EPSILON));
}
@Test
public void testRegressionWithMultipleDataPoints() throws NotEnoughDataHasBeenAddedException {
IncrementalLinearRegressionProcessor processor = createRegressionProcessor();
for (int i = -50; i < 51; i++) {
processor.addMeasuredPoint(i, i);
}
double y = processor.getEstimatedY(-2);
assertThat(y, new IsCloseTo(-2, EPSILON));
// 2 Points off the line
processor.addMeasuredPoint(-8, -3);
processor.addMeasuredPoint(8, -3);
y = processor.getEstimatedY(-2);
assertThat(y, new IsCloseTo(-2, VAGUE_EPSILON));
y = processor.getEstimatedY(20);
assertThat(y, new IsCloseTo(20, VAGUE_EPSILON));
}
@Test
public void testRegressionWithABigAmountOfDataPoints() throws NotEnoughDataHasBeenAddedException {
IncrementalLinearRegressionProcessor processor = createRegressionProcessor();
for (int i = -1000; i < 1000; i++) {
// For every even number + 1, else - 1
double y = i % 2 == 0 ? i + 1 : i - 1;
processor.addMeasuredPoint(i, y);
}
double y = processor.getEstimatedY(-2);
assertThat(y, new IsCloseTo(-2, VAGUE_EPSILON));
y = processor.getEstimatedY(20);
assertThat(y, new IsCloseTo(20, VAGUE_EPSILON));
}
@Test
public void testRegressionWithZeroValueData() throws NotEnoughDataHasBeenAddedException {
IncrementalLinearRegressionProcessor processor = createRegressionProcessor();
double x1 = 0;
double y1 = 0;
processor.addMeasuredPoint(x1, y1);
double x2 = 5;
double y2 = 0;
processor.addMeasuredPoint(x2, y2);
double y = processor.getEstimatedY(x1);
assertThat(y, new IsCloseTo(y1, EPSILON));
y = processor.getEstimatedY(x2);
assertThat(y, new IsCloseTo(y2, EPSILON));
}
@Test
public void testRegressionWithNegativeData() throws NotEnoughDataHasBeenAddedException {
IncrementalLinearRegressionProcessor processor = createRegressionProcessor();
double x1 = -5;
double y1 = -5;
processor.addMeasuredPoint(x1, y1);
double x2 = 5;
double y2 = 5;
processor.addMeasuredPoint(x2, y2);
double y = processor.getEstimatedY(x1);
assertThat(y, new IsCloseTo(y1, EPSILON));
y = processor.getEstimatedY(x2);
assertThat(y, new IsCloseTo(y2, EPSILON));
y = processor.getEstimatedY(0);
assertThat(y, new IsCloseTo(0, EPSILON));
}
@Test
public void testRegressionWithSmallValuedData() throws NotEnoughDataHasBeenAddedException {
IncrementalLinearRegressionProcessor processor = createRegressionProcessor();
double x1 = 5E-9;
double y1 = 5E-9;
for (int i = 0; i < 1000; i++) {
double yTimesI = y1 * i;
double xTimesI = x1 * i;
processor.addMeasuredPoint(xTimesI, yTimesI);
if (i > 0) {
double y = processor.getEstimatedY(xTimesI);
assertThat(y, new IsCloseTo(yTimesI, EPSILON));
}
}
// Add data that is off the line but offsetting each other
processor.addMeasuredPoint(-x1, y1);
processor.addMeasuredPoint(x1, -y1);
double y = processor.getEstimatedY(0);
assertThat(y, new IsCloseTo(0, EPSILON));
}
@Test(expected = NotEnoughDataHasBeenAddedException.class)
public void testNoDataExceptionThrowing() throws NotEnoughDataHasBeenAddedException {
IncrementalLinearRegressionProcessor processor = createRegressionProcessor();
processor.getEstimatedY(15);
}
/**
* Assert that y is constant for only one added data point
*
* @throws NotEnoughDataHasBeenAddedException
*/
@Test
public void testRegressionWithOneDataPoint() throws NotEnoughDataHasBeenAddedException {
IncrementalLinearRegressionProcessor processor = createRegressionProcessor();
double x1 = 3;
double y1 = 5;
processor.addMeasuredPoint(x1, y1);
double y = processor.getEstimatedY(20);
assertThat(y, is(y1));
}
/*
* The following tests and the data are taken from: org.apache.commons.math3.stat.regression.SimpleRegressionTest
*
* http://svn.apache.org/viewvc/commons/proper/math/trunk/src/test/java/org/apache/commons/math3/stat/regression/
* SimpleRegressionTest.java?view=markup
*
* They are adapted to use the IncrementalLinearRegressionProcessor, but the data and the asserts were kept as is
*/
/*
* NIST "Norris" refernce data set from
* http://www.itl.nist.gov/div898/strd/lls/data/LINKS/DATA/Norris.dat
* Strangely, order is {y,x}
*/
private double[][] data = { { 0.1, 0.2 }, {338.8, 337.4 }, {118.1, 118.2 },
{888.0, 884.6 }, {9.2, 10.1 }, {228.1, 226.5 }, {668.5, 666.3 }, {998.5, 996.3 },
{449.1, 448.6 }, {778.9, 777.0 }, {559.2, 558.2 }, {0.3, 0.4 }, {0.1, 0.6 }, {778.1, 775.5 },
{668.8, 666.9 }, {339.3, 338.0 }, {448.9, 447.5 }, {10.8, 11.6 }, {557.7, 556.0 },
{228.3, 228.1 }, {998.0, 995.8 }, {888.8, 887.6 }, {119.6, 120.2 }, {0.3, 0.3 },
{0.6, 0.3 }, {557.6, 556.8 }, {339.3, 339.1 }, {888.0, 887.2 }, {998.5, 999.0 },
{778.9, 779.0 }, {10.2, 11.1 }, {117.6, 118.3 }, {228.9, 229.2 }, {668.4, 669.1 },
{449.2, 448.9 }, {0.2, 0.5 }
};
@Test
public void testNorris() throws NotEnoughDataHasBeenAddedException {
IncrementalLinearRegressionProcessor regression = createRegressionProcessor();
for (int i = 0; i < data.length; i++) {
regression.addMeasuredPoint(data[i][1], data[i][0]);
}
// Tests against certified values from
// http://www.itl.nist.gov/div898/strd/lls/data/LINKS/DATA/Norris.dat
assertThat(regression.getSlope(), new IsCloseTo(1.00211681802045, EPSILON));
assertThat(regression.getIntercept(), new IsCloseTo(-0.262323073774029, EPSILON));
// ------------ End certified data tests
assertThat(regression.getEstimatedY(0), new IsCloseTo(-0.262323073774029, EPSILON));
assertThat(regression.getEstimatedY(1), new IsCloseTo(1.00211681802045 - 0.26232307377402, EPSILON));
}
@Test
public void testPerfect() {
IncrementalLinearRegressionProcessor regression = createRegressionProcessor();
int n = 100;
for (int i = 0; i < n; i++) {
regression.addMeasuredPoint(((double) i) / (n - 1), i);
}
assertTrue(regression.getSlope() > 0);
}
@Test
public void testPerfectNegative() {
IncrementalLinearRegressionProcessor regression = createRegressionProcessor();
int n = 100;
for (int i = 0; i < n; i++) {
regression.addMeasuredPoint(-((double) i) / (n - 1), i);
}
assertTrue(regression.getSlope() < 0);
}
}
@@ -0,0 +1,183 @@
package com.sap.sailing.polars.test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.closeTo;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
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.Speed;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.polars.PolarDataService;
import com.sap.sailing.polars.analysis.PolarSheetAnalyzer;
import com.sap.sailing.polars.analysis.impl.PolarSheetAnalyzerImpl;
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
public class PolarSheetAnalyzerTest {
@Test
public void testSpeedAndBearingCalculation() throws NotEnoughDataHasBeenAddedException {
PolarSheetAnalyzer analyzer = new PolarSheetAnalyzerImpl(createMockedPolarDataService());
BoatClass boatClass = mock(BoatClass.class);
SpeedWithBearingWithConfidence<Void> result = analyzer.getAverageUpwindSpeedWithBearingOnStarboardTackFor(boatClass,
new KnotSpeedImpl(14), true);
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);
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);
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);
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));
}
private PolarDataService createMockedPolarDataService() throws NotEnoughDataHasBeenAddedException {
PolarDataService mockedPolarDataService = mock(PolarDataService.class);
Integer[] 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(
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(
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(
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(
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(
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(
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(
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(
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(
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(
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(
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(
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(13.268607457651942), 0.5, null));
return mockedPolarDataService;
}
/**
* The following data is taken from a real race. (49ER yellow R2, KW 2014)
* Only the data for windspeed 14kn is used. The first block contains the
* boatspeeds for every angle to the wind. The second block contains the
* datacount for each angle (number of underlying fixes).
*
* @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,
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,
12, 10, 9, 10, 8, 1, 9, 6, 6, 14, 16, 7, 7, 6, 2, 5, 3, 6, 5, 8, 7, 14, 14, 12, 9, 20, 14, 22, 28, 14,
20, 22, 29, 30, 38, 45, 50, 72, 87, 117, 114, 110, 145, 148, 156, 165, 150, 194, 224, 211, 196, 199,
198, 182, 155, 125, 85, 69, 65, 49, 46, 61, 47, 48, 51, 45, 21, 14, 8, 9, 8, 8, 3, 7, 5, 2, 10, 8, 7,
7, 10, 8, 13, 13, 11, 5, 9, 9, 8, 15, 6, 3, 5, 11, 9, 12, 5, 13, 12, 18, 15, 31, 25, 35, 36, 39, 59,
71, 55, 52, 64, 72, 66, 70, 89, 104, 81, 120, 130, 117, 107, 113, 137, 136, 126, 108, 103, 92, 85, 76,
65, 75, 75, 70, 57, 49, 49, 35, 44, 35, 18, 22, 17, 18, 10, 6, 4, 10, 9, 10, 8, 4, 4, 4, 10, 4, 4, 4,
9, 8, 8, 7, 7, 6, 4, 7, 4, 1, 6, 6, 4, 6, 5, 5, 5, 6, 5, 9, 7, 12, 16, 10, 25, 25, 30, 29, 47, 44, 31,
69, 68, 79, 62, 66, 86, 72, 84, 89, 75, 73, 62, 104, 110, 147, 137, 170, 204, 310, 347, 354, 377, 433,
457, 419, 436, 467, 502, 513, 525, 463, 422, 405, 363, 309, 282, 262, 186, 162, 123, 97, 81, 45, 35,
30, 24, 20, 12, 9, 8, 5, 6, 4, 6, 7, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1 };
return dataCountsPerAngle;
}
private class SpeedMatcher extends ArgumentMatcher<Speed> {
private final double speedToMatchInKnots;
public SpeedMatcher(double speedToMatchInKnots) {
this.speedToMatchInKnots = speedToMatchInKnots;
}
@Override
public boolean matches(Object argument) {
boolean result = false;
if (argument != null) {
Speed speed = (Speed) argument;
if (speed.getKnots() > speedToMatchInKnots - 0.05 && speed.getKnots() < speedToMatchInKnots + 0.05) {
result = true;
}
}
return result;
}
}
private class BearingMatcher extends ArgumentMatcher<Bearing> {
private final double bearingToMatchInDegrees;
public BearingMatcher(double bearingToMatchInDegrees) {
this.bearingToMatchInDegrees = bearingToMatchInDegrees;
}
@Override
public boolean matches(Object argument) {
boolean result = false;
if (argument != null) {
Bearing bearing = (Bearing) argument;
if (bearing.getDegrees() > bearingToMatchInDegrees - 0.4999999
&& bearing.getDegrees() < bearingToMatchInDegrees + 0.49999999) {
result = true;
}
}
return result;
}
}
}
@@ -1,4 +1,4 @@
package com.sap.sailing.domain.test;
package com.sap.sailing.polars.test;
import java.util.ArrayList;
import java.util.HashMap;
@@ -9,6 +9,7 @@ import java.util.NavigableSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
@@ -42,10 +43,6 @@ 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.polarsheets.DataPointWithOriginInfo;
import com.sap.sailing.domain.polarsheets.PerRaceAndCompetitorPolarSheetGenerationWorker;
import com.sap.sailing.domain.polarsheets.PolarSheetGenerationWorker;
import com.sap.sailing.domain.polarsheets.PolarSheetHistogramBuilder;
import com.sap.sailing.domain.test.mock.MockedTrackedRace;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sailing.domain.tracking.GPSFixMoving;
@@ -59,13 +56,21 @@ import com.sap.sailing.domain.tracking.impl.MarkPassingByTimeComparator;
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
import com.sap.sailing.domain.tracking.impl.WindImpl;
import com.sap.sailing.domain.tracking.impl.WindWithConfidenceImpl;
import com.sap.sailing.polars.aggregation.PolarFixAggregationWorker;
import com.sap.sailing.polars.aggregation.PolarFixAggregator;
import com.sap.sailing.polars.aggregation.SimplePolarFixRaceInterval;
import com.sap.sailing.polars.data.DataPointWithOriginInfo;
import com.sap.sailing.polars.data.PolarFix;
import com.sap.sailing.polars.data.impl.DataPointWithOriginInfoImpl;
import com.sap.sailing.polars.generation.PolarSheetGenerator;
import com.sap.sailing.polars.generation.PolarSheetHistogramBuilder;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class PolarSheetGenerationTest {
@Test
public void testPolarSheetRawDataGeneration() throws InterruptedException {
public void testPolarSheetRawDataGeneration() throws InterruptedException, ExecutionException {
Executor executor = new ThreadPoolExecutor(/* corePoolSize */Runtime.getRuntime().availableProcessors(),
/* maximumPoolSize */Runtime.getRuntime().availableProcessors(),
/* keepAliveTime */60, TimeUnit.SECONDS,
@@ -73,7 +78,7 @@ public class PolarSheetGenerationTest {
MockTrackedRaceForPolarSheetGeneration race = new MockTrackedRaceForPolarSheetGeneration();
Integer[] levels = { 4, 6, 8, 10, 12, 14, 16, 20, 25, 30 };
Double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. };
WindSteppingWithMaxDistance windStepping = new WindSteppingWithMaxDistance(levels, 2.0);
PolarSheetGenerationSettings settings = new PolarSheetGenerationSettingsImpl(1, 0, 1, 20, 0, false, true, 5,
0.05, false, windStepping, false);
@@ -81,27 +86,29 @@ public class PolarSheetGenerationTest {
TimePoint startTime = new MillisecondsTimePoint(9);
TimePoint endTime = new MillisecondsTimePoint(80);
// Only used for storing and exporting results in this test case:
PolarSheetGenerationWorker resultContainer = new PolarSheetGenerationWorker(new HashSet<TrackedRace>(),
PolarFixAggregator resultContainer = new PolarFixAggregator(new SimplePolarFixRaceInterval(
new HashSet<TrackedRace>()),
settings, executor);
BoatClass forelle = new BoatClassImpl("Forelle", true);
Competitor competitor = new CompetitorImpl(UUID.randomUUID(), "Hans Frantz", Color.RED, new TeamImpl("SAP", null, null),
new BoatImpl("Schnelle Forelle", forelle, "GER000"));
PerRaceAndCompetitorPolarSheetGenerationWorker task = new PerRaceAndCompetitorPolarSheetGenerationWorker(race,
resultContainer, startTime, endTime, competitor, settings);
PolarFixAggregationWorker task = new PolarFixAggregationWorker(race,
resultContainer, startTime, endTime,
competitor, settings, null);
executor.execute(task);
double timeUntilTimeout = 1000;
while (!task.isDone() && timeUntilTimeout > 0) {
Thread.sleep(100);
timeUntilTimeout = timeUntilTimeout - 0.1;
}
Set<PolarFix> fixes = resultContainer.getAggregationResultAsSingleList();
Assert.assertTrue(task.isDone());
PolarSheetsData data = resultContainer.getPolarData();
PolarSheetGenerator generator = new PolarSheetGenerator(fixes, settings);
PolarSheetsData data = generator.generate();
Assert.assertEquals(4, data.getDataCount());
Assert.assertEquals(4.0, data.getAveragedPolarDataByWindSpeed()[0][45]);
Assert.assertEquals(2.0, data.getAveragedPolarDataByWindSpeed()[0][55]);
@@ -111,7 +118,7 @@ public class PolarSheetGenerationTest {
@Test
public void testHistogramBuilder() {
Integer[] levels = { 4, 6, 8, 10, 12, 14, 16, 20, 25, 30 };
Double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. };
WindSteppingWithMaxDistance windStepping = new WindSteppingWithMaxDistance(levels, 2.0);
PolarSheetGenerationSettings settings = new PolarSheetGenerationSettingsImpl(1, 0, 1, 10, 0, false, true, 5,
0.05, false, windStepping, false);
@@ -119,15 +126,15 @@ public class PolarSheetGenerationTest {
List<DataPointWithOriginInfo> rawData = new ArrayList<DataPointWithOriginInfo>();
rawData.add(new DataPointWithOriginInfo(1.09, "", ""));
rawData.add(new DataPointWithOriginInfo(1.0, "", ""));
rawData.add(new DataPointWithOriginInfo(1.11, "", ""));
rawData.add(new DataPointWithOriginInfo(1.46, "", ""));
rawData.add(new DataPointWithOriginInfo(1.56, "", ""));
rawData.add(new DataPointWithOriginInfo(2.05, "", ""));
rawData.add(new DataPointWithOriginInfo(2.09, "", ""));
rawData.add(new DataPointWithOriginInfo(3.0, "", ""));
rawData.add(new DataPointWithOriginInfo(2.999, "", ""));
rawData.add(new DataPointWithOriginInfoImpl(1.09, "", ""));
rawData.add(new DataPointWithOriginInfoImpl(1.0, "", ""));
rawData.add(new DataPointWithOriginInfoImpl(1.11, "", ""));
rawData.add(new DataPointWithOriginInfoImpl(1.46, "", ""));
rawData.add(new DataPointWithOriginInfoImpl(1.56, "", ""));
rawData.add(new DataPointWithOriginInfoImpl(2.05, "", ""));
rawData.add(new DataPointWithOriginInfoImpl(2.09, "", ""));
rawData.add(new DataPointWithOriginInfoImpl(3.0, "", ""));
rawData.add(new DataPointWithOriginInfoImpl(2.999, "", ""));
PolarSheetsHistogramData result = builder.build(rawData, 0, 0);
Number[] xValues = result.getxValues();
Assert.assertEquals(10, xValues.length);
@@ -144,49 +151,49 @@ public class PolarSheetGenerationTest {
public void testOutlierNeighborhoodAlgorithm() {
PolarSheetGenerationSettings settings = PolarSheetGenerationSettingsImpl.createStandardPolarSettings();
List<DataPointWithOriginInfo> values = new ArrayList<DataPointWithOriginInfo>();
values.add(new DataPointWithOriginInfo(0., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfo(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(0., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
values.add(new DataPointWithOriginInfoImpl(20., "", ""));
int[] count = new int[1];
count[0] = values.size();
Map<Integer, List<DataPointWithOriginInfo>> valuesInMap = new HashMap<Integer, List<DataPointWithOriginInfo>>();
valuesInMap.put(0, values);
double pct = PolarSheetGenerationWorker.getNeighboorhoodSizePercentage(count, valuesInMap, 0, 0,
new DataPointWithOriginInfo(0.0, "", ""), settings);
double pct = PolarSheetGenerator.getNeighboorhoodSizePercentage(count, valuesInMap, 0, 0,
new DataPointWithOriginInfoImpl(0.0, "", ""), settings);
Assert.assertTrue(pct < 0.05);
pct = PolarSheetGenerationWorker.getNeighboorhoodSizePercentage(count, valuesInMap, 0, 2,
new DataPointWithOriginInfo(20.0, "", ""), settings);
pct = PolarSheetGenerator.getNeighboorhoodSizePercentage(count, valuesInMap, 0, 2,
new DataPointWithOriginInfoImpl(20.0, "", ""), settings);
Assert.assertTrue(pct > 0.05);
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>com.sap.sailing.polars</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.pde.PluginNature</nature>
</natures>
</projectDescription>
@@ -0,0 +1,23 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Domain
Bundle-SymbolicName: com.sap.sailing.polars
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ClassPath: .
Export-Package:
com.sap.sailing.polars,
com.sap.sailing.polars.analysis,
com.sap.sailing.polars.data,
com.sap.sailing.polars.factory,
com.sap.sailing.polars.regression
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"
Import-Package:
com.sap.sailing.util,
com.sap.sse.common
@@ -0,0 +1,4 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.
+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</artifactId>
<packaging>eclipse-plugin</packaging>
</project>
@@ -0,0 +1,28 @@
package com.sap.sailing.polars;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Speed;
public class NoPolarDataAvailableException extends Exception {
private static final long serialVersionUID = -6939576877174310618L;
private final BoatClass boatClass;
private final Speed windSpeed;
private final Bearing bearingToTheWind;
public NoPolarDataAvailableException(BoatClass boatClass, Speed windSpeed, Bearing bearingToTheWind) {
this.boatClass = boatClass;
this.windSpeed = windSpeed;
this.bearingToTheWind = bearingToTheWind;
}
@Override
public String getMessage() {
return String
.format("There was no polar data available for boat class '%s' for an angle to the wind of %.1f degrees and the wind speed of %.1f knots.",
boatClass.getName(), bearingToTheWind.getDegrees(), windSpeed.getKnots());
}
}
@@ -0,0 +1,134 @@
package com.sap.sailing.polars;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
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.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.Speed;
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.
*
* 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
* needed and {@link #getAverageSpeedWithBearing(BoatClass, Speed, LegType, Tack)}
* which also returns the average angle for the provided parameters.
*
* @author Frederik Petersen (D054528)
*
*/
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
* @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)
throws NotEnoughDataHasBeenAddedException;
/**
*
* @param boatClass
* @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.
* @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%)
* 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/>
*
* 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.
*/
SpeedWithBearingWithConfidence<Void> getAverageSpeedWithBearing(BoatClass boatClass, Speed windSpeed,
LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException;
/**
* Generates a polar sheet for geven races and settings using the provided executor for the worker threads. This
* method does not access a cache for now.
*
* @param trackedRaces
* The set of races to generate the diagram for.
* @param settings
* Settings as supplied by the user.
* @param executor
* The executor to run the worker threads with.
* @return The generated polar sheet with meta data.
*/
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
* The {@link BoatClass} to obtain the polar sheet for.
* @return The polar sheet for all existing races of the {@link BoatClass}.
*/
PolarSheetsData getPolarSheetForBoatClass(BoatClass boatClass);
/**
*
* @return The {@link BoatClass}es for which there are polar sheets available via
* {@link PolarDataService#getPolarSheetForBoatClass(BoatClass)}
*/
Set<BoatClass> getAllBoatClassesWithPolarSheetsAvailable();
void competitorPositionChanged(GPSFixMoving fix, Competitor competitor, TrackedRace createdTrackedRace);
/**
* Returns underlying datacount for a given boat class and windspeed.
* @param boatClass
* @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
*/
Integer[] getDataCountsForWindSpeed(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive);
SpeedWithBearingWithConfidence<Void> getAverageSpeedWithBearing(BoatClass boatClass, Speed windSpeed,
LegType legType, Tack tack, boolean useLinReg) throws NotEnoughDataHasBeenAddedException;
}
@@ -1,4 +1,4 @@
package com.sap.sailing.domain.polarsheets;
package com.sap.sailing.polars.aggregation;
import java.util.ArrayList;
import java.util.Collection;
@@ -18,23 +18,23 @@ import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sailing.polars.data.PolarFix;
import com.sap.sailing.polars.data.impl.PolarFixImpl;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
/**
* Iterates through the fixes of one competitor in one tracked race and fills the {@link PolarSheetGenerationWorker}
* Iterates through the fixes of one competitor in one tracked race and fills the {@link PolarFixAggregator}
* with datapoints that are found for the speed of the boat and its angle to the wind
*
* @author D054528 Frederik Petersen
*
*/
public class PerRaceAndCompetitorPolarSheetGenerationWorker implements Runnable {
// private static final Logger logger = Logger.getLogger(PerRaceAndCompetitorPolarSheetGenerationWorker.class.getName());
public class PolarFixAggregationWorker implements Runnable {
private final TrackedRace race;
private final PolarSheetGenerationWorker polarSheetGenerationWorker;
private final PolarFixAggregator polarSheetGenerationWorker;
private TimePoint startTime;
@@ -50,9 +50,11 @@ public class PerRaceAndCompetitorPolarSheetGenerationWorker implements Runnable
private PolarSheetGenerationSettings settings;
public PerRaceAndCompetitorPolarSheetGenerationWorker(TrackedRace race,
PolarSheetGenerationWorker polarSheetGenerationWorker, TimePoint startTime, TimePoint endTime,
Competitor competitor, PolarSheetGenerationSettings settings) {
private Pair<TimePoint, TimePoint> intervalBeginningAndEnd;
public PolarFixAggregationWorker(TrackedRace race, PolarFixAggregator polarSheetGenerationWorker,
TimePoint startTime, TimePoint endTime, Competitor competitor, PolarSheetGenerationSettings settings,
Pair<TimePoint, TimePoint> intervalBeginningAndEnd) {
super();
this.race = race;
this.polarSheetGenerationWorker = polarSheetGenerationWorker;
@@ -60,9 +62,24 @@ public class PerRaceAndCompetitorPolarSheetGenerationWorker implements Runnable
this.endTime = endTime;
this.competitor = competitor;
this.settings = settings;
this.intervalBeginningAndEnd = intervalBeginningAndEnd;
optimizeStartTime();
optimizeEndTime();
checkIfRaceAborted();
checkIfIntervalBeforeOrAfterRace();
}
private void checkIfIntervalBeforeOrAfterRace() {
if (intervalBeginningAndEnd != null) {
TimePoint intervalStart = intervalBeginningAndEnd.getA();
if (intervalStart.after(endTime)) {
noConfidence = true;
}
TimePoint intervalEnd = intervalBeginningAndEnd.getB();
if (intervalEnd.before(startTime)) {
noConfidence = true;
}
}
}
private void checkIfRaceAborted() {
@@ -108,7 +125,17 @@ public class PerRaceAndCompetitorPolarSheetGenerationWorker implements Runnable
GPSFixTrack<Competitor, GPSFixMoving> track = race.getTrack(competitor);
track.lockForRead();
try {
Iterator<GPSFixMoving> fixesIterator = track.getFixesIterator(startTime, true);
Iterator<GPSFixMoving> fixesIterator;
if (intervalBeginningAndEnd != null) {
TimePoint startOfIntervalToCheck = intervalBeginningAndEnd.getA().before(startTime) ? startTime
: intervalBeginningAndEnd.getA();
TimePoint endOfIntervalToCheck = intervalBeginningAndEnd.getB().after(endTime) ? endTime
: intervalBeginningAndEnd.getB();
fixesIterator = track.getFixes(startOfIntervalToCheck, true, endOfIntervalToCheck, true).iterator();
} else {
// No timepoint interval given
fixesIterator = track.getFixesIterator(startTime, true);
}
TimePoint lastConsideredTimePoint = null;
if (finishedEarlyAtWaypoint != -1) {
NavigableSet<MarkPassing> markPassings = race.getMarkPassings(competitor);
@@ -136,40 +163,39 @@ public class PerRaceAndCompetitorPolarSheetGenerationWorker implements Runnable
private void addFixIfValid(GPSFixTrack<Competitor, GPSFixMoving> track, GPSFixMoving fix) {
if (!track.hasDirectionChange(fix.getTimePoint(), race.getRace().getBoatClass()
.getManeuverDegreeAngleThreshold())) {
List<Util.Pair<String, WindWithConfidence<Util.Pair<Position, TimePoint>>>> windWithConfidenceList = new ArrayList<Util.Pair<String, WindWithConfidence<Util.Pair<Position, TimePoint>>>>();
List<Pair<String, WindWithConfidence<Pair<Position, TimePoint>>>> windWithConfidenceList = new ArrayList<Pair<String, WindWithConfidence<Pair<Position, TimePoint>>>>();
if (settings.useOnlyWindGaugesForWindSpeed()) {
if (settings.splitByWindgauges()) {
windWithConfidenceList.addAll(addAllWindsOfWindGaugesSplitOneByOne(fix));
} else {
windWithConfidenceList
.add(new Util.Pair<String, WindWithConfidence<Util.Pair<Position, TimePoint>>>(
.add(new Pair<String, WindWithConfidence<Pair<Position, TimePoint>>>(
createWindGaugesString(race), race.getWindWithConfidence(
fix.getPosition(), fix.getTimePoint(),
collectWindSourcesToIgnoreForSpeed())));
}
} else {
windWithConfidenceList.add(new Util.Pair<String, WindWithConfidence<Util.Pair<Position, TimePoint>>>(
windWithConfidenceList.add(new Pair<String, WindWithConfidence<Pair<Position, TimePoint>>>(
"Combined", race.getWindWithConfidence(fix.getPosition(), fix.getTimePoint())));
}
for (Util.Pair<String, WindWithConfidence<Util.Pair<Position, TimePoint>>> windWithSourceIdStringPair : windWithConfidenceList) {
WindWithConfidence<Util.Pair<Position, TimePoint>> windWithConfidence = windWithSourceIdStringPair
for (Pair<String, WindWithConfidence<Pair<Position, TimePoint>>> windWithSourceIdStringPair : windWithConfidenceList) {
WindWithConfidence<Pair<Position, TimePoint>> windWithConfidence = windWithSourceIdStringPair
.getB();
if (windWithConfidence != null && windWithConfidence.useSpeed()
&& windWithConfidence.getConfidence() >= settings.getMinimumWindConfidence()) {
PolarFix polarFix = new PolarFix(fix, race, track, windWithConfidence.getObject(),
PolarFix polarFix = new PolarFixImpl(fix, race, track, windWithConfidence.getObject(),
settings, windWithSourceIdStringPair.getA());
polarSheetGenerationWorker.addPolarData(polarFix);
polarSheetGenerationWorker.addPolarFix(race.getRaceIdentifier(), polarFix);
}
}
}
}
private Collection<? extends Util.Pair<String, WindWithConfidence<Util.Pair<Position, TimePoint>>>> addAllWindsOfWindGaugesSplitOneByOne(
private Collection<? extends Pair<String, WindWithConfidence<Pair<Position, TimePoint>>>> addAllWindsOfWindGaugesSplitOneByOne(
GPSFixMoving fix) {
Iterable<WindSource> windGaugeSources = race.getWindSources(WindSourceType.EXPEDITION);
List<Util.Pair<String, WindWithConfidence<Util.Pair<Position, TimePoint>>>> windWithConfidenceList
= new ArrayList<Util.Pair<String, WindWithConfidence<Util.Pair<Position, TimePoint>>>>();
List<Pair<String, WindWithConfidence<Pair<Position, TimePoint>>>> windWithConfidenceList = new ArrayList<Pair<String, WindWithConfidence<Pair<Position, TimePoint>>>>();
for (WindSource windGaugeSource : windGaugeSources) {
Iterable<WindSource> allSources = race.getWindSources();
Set<WindSource> allSourcesButTheSingleWindGaugeSource = new HashSet<WindSource>();
@@ -178,7 +204,7 @@ public class PerRaceAndCompetitorPolarSheetGenerationWorker implements Runnable
allSourcesButTheSingleWindGaugeSource.add(windSource);
}
}
windWithConfidenceList.add(new Util.Pair<String, WindWithConfidence<Util.Pair<Position, TimePoint>>>(
windWithConfidenceList.add(new Pair<String, WindWithConfidence<Pair<Position, TimePoint>>>(
windGaugeSource.getId().toString(), race.getWindWithConfidence(fix.getPosition(), fix.getTimePoint(),
allSourcesButTheSingleWindGaugeSource)));
}
@@ -0,0 +1,171 @@
package com.sap.sailing.polars.aggregation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.polars.data.PolarFix;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
/**
* Allows extracting data about average speed for each angle creating the foundation of polar-sheet generation. Tasks
* are assigned to the executor per race & per competitor and the results are filled on the go to allow progress
* indication in the polar-sheet itself.
*
* @author D054528 Frederik Petersen
*
*/
public class PolarFixAggregator implements Future<Map<RegattaAndRaceIdentifier, List<PolarFix>>> {
private final Set<PolarFixAggregationWorker> workers;
private final Executor executor;
private final Map<RegattaAndRaceIdentifier, List<PolarFix>> fixes = new HashMap<RegattaAndRaceIdentifier, List<PolarFix>>();
/**
* Will prepare the {@link PolarFixAggregationWorker}s per race & per competitor. This includes determining start
* and end time.
*
* @param updateInterval
* from which the data is to be collected
* @param settings
* @param executor
* executes the tasks upon {@link #startPolarFixAggregation()}
*/
public PolarFixAggregator(PolarFixRaceInterval updateInterval, PolarSheetGenerationSettings settings,
Executor executor) {
this.executor = executor;
workers = new HashSet<PolarFixAggregationWorker>();
for (Entry<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> entry : updateInterval
.getCompetitorAndTimepointsForRace().entrySet()) {
TrackedRace race = entry.getKey();
TimePoint startTime = race.getStartOfRace();
TimePoint endTime = race.getEndOfRace();
if (endTime == null) {
endTime = race.getTimePointOfNewestEvent();
}
RaceDefinition raceDefinition = race.getRace();
Map<Competitor, Pair<TimePoint, TimePoint>> detailedInterval = entry.getValue();
if (detailedInterval == null) {
Iterable<Competitor> competitors = raceDefinition.getCompetitors();
for (Competitor competitor : competitors) {
PolarFixAggregationWorker task = new PolarFixAggregationWorker(race, this, startTime, endTime,
competitor, settings, null);
workers.add(task);
}
} else {
for (Entry<Competitor, Pair<TimePoint, TimePoint>> competitorEntry : detailedInterval.entrySet()) {
Competitor competitor = competitorEntry.getKey();
Pair<TimePoint, TimePoint> intervalBeginningAndEnd = competitorEntry.getValue();
PolarFixAggregationWorker task = new PolarFixAggregationWorker(race, this, startTime, endTime,
competitor, settings, intervalBeginningAndEnd);
workers.add(task);
}
}
}
}
/**
* Starts the {@link PolarFixAggregationWorker}s
*/
public void startPolarFixAggregation() {
for (Runnable task : workers) {
executor.execute(task);
}
}
/**
* To be called from {@link PolarFixAggregationWorker} for adding a datapoint to the list of
* results
* wind's speed in knots
*/
protected void addPolarFix(RegattaAndRaceIdentifier raceId, PolarFix polarFix) {
synchronized (fixes) {
List<PolarFix> existingList = fixes.get(raceId);
if (existingList == null) {
existingList = new ArrayList<PolarFix>();
fixes.put(raceId, existingList);
}
existingList.add(polarFix);
}
}
private boolean allWorkersDone() {
boolean complete = true;
for (PolarFixAggregationWorker task : workers) {
if (!task.isDone()) {
complete = false;
break;
}
}
return complete;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return allWorkersDone();
}
@Override
public Map<RegattaAndRaceIdentifier, List<PolarFix>> get() throws InterruptedException, ExecutionException {
while (!isDone()) {
Thread.sleep(100);
}
return fixes;
}
@Override
public Map<RegattaAndRaceIdentifier, List<PolarFix>> get(long timeout, TimeUnit unit) throws InterruptedException,
ExecutionException,
TimeoutException {
long timeRun = 0;
long timeoutInMillis = unit.toMillis(timeout);
while (!isDone() && timeRun < timeoutInMillis) {
Thread.sleep(100);
timeRun = timeRun + 100;
}
if (timeRun >= timeoutInMillis) {
throw new TimeoutException();
}
return fixes;
}
public Set<PolarFix> getAggregationResultAsSingleList() throws InterruptedException,
ExecutionException {
Set<PolarFix> fixes = new HashSet<PolarFix>();
Map<RegattaAndRaceIdentifier, List<PolarFix>> aggregationResult = get();
for (List<PolarFix> list : aggregationResult.values()) {
fixes.addAll(list);
}
return fixes;
}
}
@@ -0,0 +1,14 @@
package com.sap.sailing.polars.aggregation;
import java.util.Map;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
public interface PolarFixRaceInterval {
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> getCompetitorAndTimepointsForRace();
}
@@ -0,0 +1,29 @@
package com.sap.sailing.polars.aggregation;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
public class SimplePolarFixRaceInterval implements PolarFixRaceInterval {
private final Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> intervalMap;
public SimplePolarFixRaceInterval(Set<TrackedRace> races) {
intervalMap = new HashMap<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>>();
for (TrackedRace race : races) {
intervalMap.put(race, null);
}
}
@Override
public Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> getCompetitorAndTimepointsForRace() {
return intervalMap;
}
}
@@ -0,0 +1,22 @@
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.Speed;
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;
}
@@ -0,0 +1,162 @@
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.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.polars.PolarDataService;
import com.sap.sailing.polars.analysis.PolarSheetAnalyzer;
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
/**
* Extracts typical measures from polar sheets.
*
* @author Frederik Petersen (D054528)
*
*/
public class PolarSheetAnalyzerImpl implements PolarSheetAnalyzer {
private static final int MINIMUM_DATA_COUNT_FOR_ONE_ANGLE = 5;
private PolarDataService polarDataService;
public PolarSheetAnalyzerImpl(PolarDataService polarDataService) {
this.polarDataService = polarDataService;
}
@Override
public SpeedWithBearingWithConfidence<Void> getAverageUpwindSpeedWithBearingOnStarboardTackFor(BoatClass boatClass, Speed windSpeed, boolean useLinReg)
throws NotEnoughDataHasBeenAddedException {
int startAngleInclusive = 1;
int endAngleExclusive = 90;
SpeedWithBearingWithConfidence<Void> speedWithBearing = estimateAnglePeakAndAverageSpeed(boatClass, windSpeed, startAngleInclusive,
endAngleExclusive, useLinReg);
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);
double estimatedPeak = estimatePeak(boatClass, windSpeed, startAngleInclusive, endAngleExclusive, dataCountPerAngle);
double convertedAngleIfOver180 = convertAngleIfNecessary(estimatedPeak);
SpeedWithConfidence<Void> averagedSpeedWithConfidence = estimateSpeed(boatClass, windSpeed,
convertedAngleIfOver180, useLinearRegression);
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);
}
/**
* Uses the formula 1-​e^​((‑x)/​150) to create a confidence for the estimated peak.
*
* If the peak has 100 data points underlying, the confidence will be approx. 0.5
*
* @param dataCountPerAngle
* @param estimatedPeak
* @return
*/
private double calcDataCountConfidence(Integer[] 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);
return dataCountPerAngle;
}
private double convertAngleIfNecessary(double estimatedPeak) {
double convertedAngleIfOver180 = estimatedPeak;
if (estimatedPeak > 180) {
convertedAngleIfOver180 = estimatedPeak - 360;
}
return convertedAngleIfOver180;
}
private SpeedWithConfidence<Void> estimateSpeed(BoatClass boatClass, Speed windSpeed, double estimatedPeak,
boolean useLinearRegression) throws NotEnoughDataHasBeenAddedException {
SpeedWithConfidence<Void> speed = polarDataService.getSpeed(boatClass, windSpeed, new DegreeBearingImpl(
estimatedPeak), useLinearRegression);
return speed;
}
private double estimatePeak(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive, Integer[] 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);
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.
throw new NotEnoughDataHasBeenAddedException("Only " + maxDataCount
+ " points have been added for the angle with the most points. No significance.");
}
int sumOfAllUpperHalfAngles = 0;
int numberOfAnglesAdded = 0;
for (int i = startAngleInclusive; i < endAngleExclusive; i++) {
Integer dataCount = dataCountPerAngle[i];
if (dataCount > maxDataCount / 2) {
sumOfAllUpperHalfAngles = sumOfAllUpperHalfAngles + i * dataCount;
numberOfAnglesAdded = numberOfAnglesAdded + dataCount;
}
}
double estimatedPeak = (double) sumOfAllUpperHalfAngles / (double) numberOfAnglesAdded;
return estimatedPeak;
}
}
@@ -0,0 +1,27 @@
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;
}
}
@@ -0,0 +1,67 @@
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;
}
}
@@ -0,0 +1,97 @@
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);
}
}
@@ -0,0 +1,9 @@
package com.sap.sailing.polars.caching;
import com.sap.sailing.domain.base.BoatClass;
public interface PolarFixCacheUpdateDoneListener {
void cacheUpdateDoneForBoatClass(BoatClass key);
}
@@ -0,0 +1,56 @@
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;
}
}
@@ -0,0 +1,21 @@
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());
}
}
@@ -0,0 +1,36 @@
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;
}
}
@@ -0,0 +1,83 @@
package com.sap.sailing.polars.clusters;
import java.util.ArrayList;
import java.util.Collection;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sse.datamining.data.Cluster;
import com.sap.sse.datamining.data.ClusterBoundary;
import com.sap.sse.datamining.impl.data.ClusterWithLowerAndUpperBoundaries;
import com.sap.sse.datamining.impl.data.ComparableClusterBoundary;
import com.sap.sse.datamining.impl.data.ComparisonStrategy;
import com.sap.sse.datamining.impl.data.FixClusterGroup;
public class SpeedClusterGroup extends FixClusterGroup<Speed> {
/**
* A {@link SpeedClusterGroup} lets the user set the level mids for all its clusters. The Boundaries will
* automatically be determined. They are in the middle between each level mid but only if the distance between level
* mid and boundary is smaller or equal {@code maxDistance}
*
*
* @param name
* @param levelMidsInKnots
* sorted low -> high. E.g. [2,4,6,10,15,20,30]
* @param maxDistanceInKnots
* the clusters will max span <-maxDistanceInKnots-|mid|-maxDistanceinKnots->
*/
public SpeedClusterGroup(String name, double[] levelMidsInKnots, double maxDistanceInKnots) {
super(name, createClustersForLevelMids(levelMidsInKnots, maxDistanceInKnots));
}
private static Collection<Cluster<Speed>> createClustersForLevelMids(double[] levelMidsInKnots,
double maxDistanceInKnots) {
ArrayList<Cluster<Speed>> clusterList = new ArrayList<Cluster<Speed>>();
for (int index = 0; index < levelMidsInKnots.length; index++) {
ClusterBoundary<Speed> lowerBoundary = createLowerBoundary(levelMidsInKnots, maxDistanceInKnots, index);
ClusterBoundary<Speed> upperBoundary = createUpperBoundary(levelMidsInKnots, maxDistanceInKnots, index);
Cluster<Speed> cluster = new ClusterWithLowerAndUpperBoundaries<Speed>(levelMidsInKnots[index] + "kn", lowerBoundary,
upperBoundary);
clusterList.add(cluster);
}
return clusterList;
}
private static ClusterBoundary<Speed> createUpperBoundary(double[] levelMidsInKnots, double maxDistanceInKnots,
int index) {
ClusterBoundary<Speed> upperBoundary;
double upperBoundaryValue;
if (index == levelMidsInKnots.length - 1) {
upperBoundaryValue = levelMidsInKnots[index] + maxDistanceInKnots;
} else {
double biggestPossibleUpperBoundary = levelMidsInKnots[index] + maxDistanceInKnots;
double midBetweenCurrentLevelMidAndUpperLevelMid = levelMidsInKnots[index]
+ (0.5 * (levelMidsInKnots[index + 1] - levelMidsInKnots[index]));
upperBoundaryValue = biggestPossibleUpperBoundary < midBetweenCurrentLevelMidAndUpperLevelMid ? biggestPossibleUpperBoundary
: midBetweenCurrentLevelMidAndUpperLevelMid;
}
upperBoundary = new ComparableClusterBoundary<Speed>(new KnotSpeedImpl(upperBoundaryValue),
ComparisonStrategy.LOWER_THAN);
return upperBoundary;
}
private static ClusterBoundary<Speed> createLowerBoundary(double[] levelMidsInKnots, double maxDistanceInKnots,
int index) {
ClusterBoundary<Speed> lowerBoundary;
double lowerBoundaryValue;
if (index > 0) {
double lowestPossibleLowerBoundary = levelMidsInKnots[index] - maxDistanceInKnots;
double midBetweenLowerLevelMidAndCurrentLevelMid = levelMidsInKnots[index - 1]
+ (0.5 * (levelMidsInKnots[index] - levelMidsInKnots[index - 1]));
lowerBoundaryValue = lowestPossibleLowerBoundary > midBetweenLowerLevelMidAndCurrentLevelMid ? lowestPossibleLowerBoundary
: midBetweenLowerLevelMidAndCurrentLevelMid;
} else {
double lowestPossibleLowerBoundary = levelMidsInKnots[index] - maxDistanceInKnots;
lowerBoundaryValue = lowestPossibleLowerBoundary <= 0 ? 0 : lowestPossibleLowerBoundary;
}
lowerBoundary = new ComparableClusterBoundary<Speed>(new KnotSpeedImpl(lowerBoundaryValue),
ComparisonStrategy.GREATER_EQUALS_THAN);
return lowerBoundary;
}
}
@@ -1,4 +1,4 @@
package com.sap.sailing.domain.polarsheets;
package com.sap.sailing.polars.data;
import com.sap.sailing.domain.common.Speed;
@@ -0,0 +1,11 @@
package com.sap.sailing.polars.data;
public interface DataPointWithOriginInfo extends Comparable<DataPointWithOriginInfo> {
public abstract Double getRawData();
public abstract String getWindGaugeIdString();
public abstract String getDayString();
}
@@ -0,0 +1,18 @@
package com.sap.sailing.polars.data;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
public interface PolarFix {
public abstract SpeedWithBearing getBoatSpeed();
public abstract Speed getWindSpeed();
public abstract double getAngleToWind();
public abstract String getGaugeIdString();
public abstract String getDayString();
}
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.polarsheets;
package com.sap.sailing.polars.data.impl;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.polars.data.BoatAndWindSpeedWithOriginInfo;
public class BoatAndWindSpeedWithOriginInfoImpl implements BoatAndWindSpeedWithOriginInfo {
@@ -1,6 +1,8 @@
package com.sap.sailing.domain.polarsheets;
package com.sap.sailing.polars.data.impl;
public class DataPointWithOriginInfo implements Comparable<DataPointWithOriginInfo>{
import com.sap.sailing.polars.data.DataPointWithOriginInfo;
public class DataPointWithOriginInfoImpl implements DataPointWithOriginInfo {
private Double rawData;
@@ -8,20 +10,23 @@ public class DataPointWithOriginInfo implements Comparable<DataPointWithOriginIn
private String dayString;
public DataPointWithOriginInfo(Double rawData, String windGaugeIdString, String dayString) {
public DataPointWithOriginInfoImpl(Double rawData, String windGaugeIdString, String dayString) {
this.rawData = rawData;
this.windGaugeIdString = windGaugeIdString;
this.dayString = dayString;
}
@Override
public Double getRawData() {
return rawData;
}
@Override
public String getWindGaugeIdString() {
return windGaugeIdString;
}
@Override
public String getDayString() {
return dayString;
}
@@ -1,4 +1,4 @@
package com.sap.sailing.domain.polarsheets;
package com.sap.sailing.polars.data.impl;
import java.text.SimpleDateFormat;
import java.util.HashSet;
@@ -16,9 +16,10 @@ import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.polars.data.PolarFix;
import com.sap.sse.common.TimePoint;
public class PolarFix {
public class PolarFixImpl implements PolarFix {
private SpeedWithBearing boatSpeed;
private Speed windSpeed;
@@ -26,7 +27,7 @@ public class PolarFix {
private String gaugeIdString;
private String dayString;
public PolarFix(GPSFixMoving fix, TrackedRace race, GPSFixTrack<Competitor, GPSFixMoving> track, Wind windSpeed,
public PolarFixImpl(GPSFixMoving fix, TrackedRace race, GPSFixTrack<Competitor, GPSFixMoving> track, Wind windSpeed,
PolarSheetGenerationSettings settings, String gaugeIdString) {
boatSpeed = track.getEstimatedSpeed(fix.getTimePoint());
Bearing bearing = boatSpeed.getBearing();
@@ -93,22 +94,27 @@ public class PolarFix {
return windSourcesToExclude;
}
@Override
public SpeedWithBearing getBoatSpeed() {
return boatSpeed;
}
@Override
public Speed getWindSpeed() {
return windSpeed;
}
@Override
public double getAngleToWind() {
return angleToWind;
}
@Override
public String getGaugeIdString() {
return gaugeIdString;
}
@Override
public String getDayString() {
return dayString;
}

Some files were not shown because too many files have changed in this diff Show More