mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-25 15:08:37 +00:00
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:
@@ -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
|
||||
|
||||
+2
@@ -30,4 +30,6 @@ public interface PolarSheetGenerationSettings extends Serializable{
|
||||
|
||||
boolean splitByWindgauges();
|
||||
|
||||
boolean areDefault();
|
||||
|
||||
}
|
||||
|
||||
+1
-3
@@ -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();
|
||||
|
||||
|
||||
+20
@@ -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();
|
||||
|
||||
}
|
||||
+12
-2
@@ -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);
|
||||
|
||||
}
|
||||
+99
-3
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+3
-9
@@ -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;
|
||||
|
||||
+57
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+68
-6
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+28
-2
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
+6
-6
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Regular → Executable
+40
-41
@@ -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
|
||||
|
||||
-24
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
-16
@@ -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);
|
||||
|
||||
}
|
||||
+3
-3
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
+1
-2
@@ -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());
|
||||
|
||||
@@ -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>
|
||||
|
||||
<!-- -->
|
||||
|
||||
Regular → Executable
+4
-1
@@ -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
Binary file not shown.
@@ -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="→" 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="←" 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="•" 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="a" 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 |
Binary file not shown.
Binary file not shown.
@@ -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
-3
@@ -5,10 +5,7 @@
|
||||
|
||||
.media_swipecontainer {
|
||||
position: relative;
|
||||
width: 830px;
|
||||
height: 622px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.media_swipewrapper {
|
||||
|
||||
+70
-59
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-6
@@ -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>
|
||||
|
||||
+13
-11
@@ -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;
|
||||
|
||||
+33
-23
@@ -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);
|
||||
|
||||
+2
-2
@@ -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" />, <span ui:field="eventStartDate" />
|
||||
|
||||
+5
-4
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+24
-63
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-4
@@ -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>
|
||||
|
||||
+53
-23
@@ -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);
|
||||
|
||||
+1
-1
@@ -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();
|
||||
}-*/;
|
||||
|
||||
}
|
||||
+1
-1
@@ -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(", ");
|
||||
// not more than 4 course areas per line
|
||||
|
||||
Executable → Regular
+78
-69
@@ -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;
|
||||
|
||||
+60
-52
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -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();
|
||||
}
|
||||
|
||||
+7
-1
@@ -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
|
||||
|
||||
+2
@@ -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
|
||||
|
||||
+2
@@ -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=Поставщик статистических данных
|
||||
|
||||
+89
@@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+130
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
+17
-9
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+89
-4
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+97
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+5
-5
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+39
-6
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
+68
-7
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+170
-18
@@ -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);
|
||||
|
||||
+64
-64
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -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="→" 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="←" 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="•" 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="a" 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 |
Binary file not shown.
Binary file not shown.
Regular → Executable
+34
-33
@@ -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>
|
||||
@@ -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/,\
|
||||
.
|
||||
+90
@@ -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;
|
||||
}
|
||||
}
|
||||
+72
@@ -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());
|
||||
}
|
||||
}
|
||||
+209
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+233
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+183
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+67
-60
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
@@ -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/,\
|
||||
.
|
||||
@@ -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>
|
||||
+28
@@ -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;
|
||||
|
||||
|
||||
}
|
||||
+48
-22
@@ -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)));
|
||||
}
|
||||
+171
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+14
@@ -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();
|
||||
|
||||
}
|
||||
+29
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+22
@@ -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;
|
||||
|
||||
}
|
||||
+162
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+27
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+97
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.sap.sailing.polars.caching;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
|
||||
public interface PolarFixCacheUpdateDoneListener {
|
||||
|
||||
void cacheUpdateDoneForBoatClass(BoatClass key);
|
||||
|
||||
}
|
||||
+56
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+21
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+83
@@ -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
-1
@@ -1,4 +1,4 @@
|
||||
package com.sap.sailing.domain.polarsheets;
|
||||
package com.sap.sailing.polars.data;
|
||||
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
|
||||
+11
@@ -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();
|
||||
|
||||
}
|
||||
+2
-1
@@ -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 {
|
||||
|
||||
+8
-3
@@ -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;
|
||||
}
|
||||
+9
-3
@@ -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
Reference in New Issue
Block a user