mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-05 11:53:53 +00:00
Merge branch 'master' into bug1562
Conflicts: java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/test/PolarSheetAnalyzerTest.java java/com.sap.sailing.polars/src/com/sap/sailing/polars/PolarDataService.java java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/PolarSheetAnalyzer.java java/com.sap.sailing.polars/src/com/sap/sailing/polars/analysis/impl/PolarSheetAnalyzerImpl.java java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/IncrementalRegressionProcessor.java java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/PolarDataMiner.java
This commit is contained in:
commit
ac48ac2417
30 files changed
+403
-735
No files matched your search
-6
@@ -6,12 +6,6 @@ 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();
|
||||
|
||||
|
||||
+1
-25
@@ -11,39 +11,15 @@ public class PolarSheetsXYDiagramDataImpl implements PolarSheetsXYDiagramData {
|
||||
|
||||
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,
|
||||
public PolarSheetsXYDiagramDataImpl(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;
|
||||
|
||||
+28
-5
@@ -10,13 +10,13 @@ import java.util.Set;
|
||||
|
||||
import org.moxieapps.gwt.highcharts.client.AxisTitle;
|
||||
import org.moxieapps.gwt.highcharts.client.Chart;
|
||||
import org.moxieapps.gwt.highcharts.client.Legend;
|
||||
import org.moxieapps.gwt.highcharts.client.Point;
|
||||
import org.moxieapps.gwt.highcharts.client.Series;
|
||||
import org.moxieapps.gwt.highcharts.client.Series.Type;
|
||||
import org.moxieapps.gwt.highcharts.client.labels.XAxisLabels;
|
||||
import org.moxieapps.gwt.highcharts.client.plotOptions.AreaPlotOptions;
|
||||
import org.moxieapps.gwt.highcharts.client.plotOptions.Marker;
|
||||
import org.moxieapps.gwt.highcharts.client.plotOptions.PlotOptions.Stacking;
|
||||
|
||||
import com.google.gwt.dom.client.Style.Unit;
|
||||
import com.google.gwt.user.client.Timer;
|
||||
@@ -37,11 +37,14 @@ public class AngleOverDataSizeHistogramPanel extends DockLayoutPanel {
|
||||
private final StringMessages stringMessages;
|
||||
|
||||
private final Map<String, Set<Series>> seriesForId;
|
||||
|
||||
private final Map<String, Series> seriesForName;
|
||||
|
||||
public AngleOverDataSizeHistogramPanel(StringMessages stringMessages) {
|
||||
super(Unit.PX);
|
||||
|
||||
this.seriesForId = new HashMap<>();
|
||||
this.seriesForName = new HashMap<>();
|
||||
this.stringMessages = stringMessages;
|
||||
setSize("100%", "100%");
|
||||
|
||||
@@ -56,25 +59,28 @@ public class AngleOverDataSizeHistogramPanel extends DockLayoutPanel {
|
||||
histogramChart.getYAxis().setMin(0).setAxisTitle(new AxisTitle().setText(stringMessages.numberOfDataPoints()));
|
||||
histogramChart.getXAxis().setLabels(new XAxisLabels().setRotation(-90f).setY(10))
|
||||
.setAxisTitle(new AxisTitle().setText(stringMessages.beatAngle()));
|
||||
histogramChart.setAreaPlotOptions(new AreaPlotOptions().setStacking(Stacking.NORMAL).setLineColor("#666666")
|
||||
histogramChart.setAreaPlotOptions(new AreaPlotOptions().setLineColor("#666666")
|
||||
.setLineWidth(1).setMarker(new Marker().setLineWidth(1).setLineColor("#666666")));
|
||||
histogramChart.setLegend(new Legend().setEnabled(false));
|
||||
return histogramChart;
|
||||
}
|
||||
|
||||
public void addData(Map<Integer, PolarSheetsHistogramData> histogramDataPerAngle, String seriesId, String actualSeriesName) {
|
||||
public void addData(Map<Integer, PolarSheetsHistogramData> histogramDataPerAngle, String seriesId, String actualSeriesName, boolean setVisible) {
|
||||
chart.setTitle("");
|
||||
Series series = chart.createSeries();
|
||||
|
||||
Point[] points = toPoints(histogramDataPerAngle);
|
||||
series.setPoints(points);
|
||||
series.setPoints(points, false);
|
||||
series.setName(actualSeriesName);
|
||||
chart.addSeries(series);
|
||||
chart.addSeries(series, false, false);
|
||||
series.setVisible(setVisible, false);
|
||||
|
||||
if (!seriesForId.containsKey(seriesId)) {
|
||||
seriesForId.put(seriesId, new HashSet<Series>());
|
||||
}
|
||||
Set<Series> seriesSet = seriesForId.get(seriesId);
|
||||
seriesSet.add(series);
|
||||
seriesForName.put(actualSeriesName, series);
|
||||
}
|
||||
|
||||
private Point[] toPoints(Map<Integer, PolarSheetsHistogramData> histogramDataPerAngle) {
|
||||
@@ -121,6 +127,7 @@ public class AngleOverDataSizeHistogramPanel extends DockLayoutPanel {
|
||||
public void removeSeries(String seriesId) {
|
||||
for (Series series : seriesForId.get(seriesId)) {
|
||||
chart.removeSeries(series);
|
||||
seriesForName.remove(series.getName());
|
||||
}
|
||||
seriesForId.remove(seriesId);
|
||||
}
|
||||
@@ -129,6 +136,22 @@ public class AngleOverDataSizeHistogramPanel extends DockLayoutPanel {
|
||||
chart.removeAllSeries();
|
||||
seriesForId.clear();
|
||||
}
|
||||
|
||||
public void redrawChart() {
|
||||
chart.redraw();
|
||||
}
|
||||
|
||||
public void showSeries(String name) {
|
||||
if (seriesForName.containsKey(name)) {
|
||||
seriesForName.get(name).show();
|
||||
}
|
||||
}
|
||||
|
||||
public void hideSeries(String name) {
|
||||
if (seriesForName.containsKey(name)) {
|
||||
seriesForName.get(name).hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+62
-31
@@ -10,12 +10,14 @@ import java.util.Map.Entry;
|
||||
|
||||
import org.moxieapps.gwt.highcharts.client.BaseChart;
|
||||
import org.moxieapps.gwt.highcharts.client.Chart;
|
||||
import org.moxieapps.gwt.highcharts.client.Color;
|
||||
import org.moxieapps.gwt.highcharts.client.Point;
|
||||
import org.moxieapps.gwt.highcharts.client.Series;
|
||||
import org.moxieapps.gwt.highcharts.client.events.PointSelectEventHandler;
|
||||
import org.moxieapps.gwt.highcharts.client.events.SeriesHideEvent;
|
||||
import org.moxieapps.gwt.highcharts.client.events.SeriesHideEventHandler;
|
||||
import org.moxieapps.gwt.highcharts.client.events.SeriesShowEvent;
|
||||
import org.moxieapps.gwt.highcharts.client.events.SeriesShowEventHandler;
|
||||
import org.moxieapps.gwt.highcharts.client.plotOptions.LinePlotOptions;
|
||||
import org.moxieapps.gwt.highcharts.client.plotOptions.Marker;
|
||||
import org.moxieapps.gwt.highcharts.client.plotOptions.SeriesPlotOptions;
|
||||
|
||||
import com.google.gwt.dom.client.Style.Unit;
|
||||
@@ -44,18 +46,50 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
|
||||
private final Map<String,PolarSheetsData> polarSheetsDataMap;
|
||||
|
||||
private final AngleOverDataSizeHistogramPanel angleOverDataSizeHistogramPanel;
|
||||
private final SeriesPlotOptions seriesPlotOptions;
|
||||
|
||||
public PolarSheetsChartPanel(StringMessages stringMessages, AngleOverDataSizeHistogramPanel angleOverDataSizeHistogramPanel) {
|
||||
super(Unit.PCT);
|
||||
seriesPlotOptions = new SeriesPlotOptions();
|
||||
this.stringMessages = stringMessages;
|
||||
this.angleOverDataSizeHistogramPanel = angleOverDataSizeHistogramPanel;
|
||||
polarSheetsDataMap = new HashMap<String, PolarSheetsData>();
|
||||
setSize("100%", "100%");
|
||||
chart = createPolarSheetChart();
|
||||
setSeriesShowHandler(createSeriesShowEventHandler());
|
||||
setSeriesHideHandler(createSeriesHideEventHandler());
|
||||
seriesMap = new HashMap<String, Series[]>();
|
||||
add(chart);
|
||||
}
|
||||
|
||||
private void setSeriesHideHandler(SeriesHideEventHandler seriesHideEventHandler) {
|
||||
chart.setSeriesPlotOptions(seriesPlotOptions.setSeriesHideEventHandler(seriesHideEventHandler));
|
||||
}
|
||||
|
||||
private SeriesShowEventHandler createSeriesShowEventHandler() {
|
||||
return new SeriesShowEventHandler() {
|
||||
|
||||
@Override
|
||||
public boolean onShow(SeriesShowEvent seriesShowEvent) {
|
||||
String name = seriesShowEvent.getSeriesName();
|
||||
angleOverDataSizeHistogramPanel.showSeries(name);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private SeriesHideEventHandler createSeriesHideEventHandler() {
|
||||
return new SeriesHideEventHandler() {
|
||||
|
||||
@Override
|
||||
public boolean onHide(SeriesHideEvent seriesHideEvent) {
|
||||
String name = seriesHideEvent.getSeriesName();
|
||||
angleOverDataSizeHistogramPanel.hideSeries(name);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a polar diagram chart of the Line type.
|
||||
*/
|
||||
@@ -116,20 +150,30 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
|
||||
*/
|
||||
private void addValuesToSeries(String seriesId, PolarSheetsData result) {
|
||||
int stepCount = result.getStepping().getRawStepping().length;
|
||||
boolean oneSeriesSetToVisible = false;
|
||||
if (seriesMap.containsKey(seriesId)) {
|
||||
for (int i = 0; i < stepCount; i++) {
|
||||
if (hasSufficientDataForWindspeed(result.getDataCountPerAngleForWindspeed(i))) {
|
||||
String actualSeriesName = seriesId + "-" + result.getStepping().getRawStepping()[i];
|
||||
if (seriesMap.get(seriesId)[i] == null) {
|
||||
createSeriesForWindspeed(seriesId, i, actualSeriesName);
|
||||
}
|
||||
Series series = seriesMap.get(seriesId)[i];
|
||||
//series.setPoints(result.getAveragedPolarDataByWindSpeed()[i], false);
|
||||
Point[] points = createPointsWithMarkerAlphaAccordingToDataCount(result, i);
|
||||
Point[] points = createPoints(result, i);
|
||||
if (points != null) {
|
||||
series.setPoints(points);
|
||||
angleOverDataSizeHistogramPanel.addData(result.getHistogramDataMap().get(i), seriesId, actualSeriesName);
|
||||
String actualSeriesName = seriesId + "-" + result.getStepping().getRawStepping()[i];
|
||||
if (seriesMap.get(seriesId)[i] == null) {
|
||||
createSeriesForWindspeed(seriesId, i, actualSeriesName);
|
||||
}
|
||||
Series series = seriesMap.get(seriesId)[i];
|
||||
// series.setPoints(result.getAveragedPolarDataByWindSpeed()[i], false);
|
||||
|
||||
series.setPoints(points, false);
|
||||
angleOverDataSizeHistogramPanel.addData(result.getHistogramDataMap().get(i), seriesId,
|
||||
actualSeriesName, !oneSeriesSetToVisible);
|
||||
angleOverDataSizeHistogramPanel.redrawChart();
|
||||
if (!oneSeriesSetToVisible) {
|
||||
oneSeriesSetToVisible = true;
|
||||
} else {
|
||||
series.setVisible(false, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,7 +198,7 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
|
||||
* Points are created using the polar sheets generation results. Points color and alpha in the graph is calculated
|
||||
* using statistical measures.
|
||||
*/
|
||||
private Point[] createPointsWithMarkerAlphaAccordingToDataCount(PolarSheetsData result, int windspeed) {
|
||||
private Point[] createPoints(PolarSheetsData result, int windspeed) {
|
||||
Point[] points = new Point[360];
|
||||
List<Integer> dataCountList = Arrays.asList(result.getDataCountPerAngleForWindspeed(windspeed));
|
||||
Integer max = Collections.max(dataCountList);
|
||||
@@ -174,23 +218,6 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
|
||||
if (points[i] == null) {
|
||||
points[i] = new Point(result.getAveragedPolarDataByWindSpeed()[windspeed][i]);
|
||||
}
|
||||
double alpha = (double) dataCountList.get(i) / (double) max;
|
||||
int blue;
|
||||
int red;
|
||||
int radius;
|
||||
if (alpha > 0.2) {
|
||||
red = (int) (alpha * 255);
|
||||
blue = 0;
|
||||
radius = 2;
|
||||
} else {
|
||||
blue = (int) ((1 - alpha) * 255);
|
||||
red = 0;
|
||||
radius = 2;
|
||||
}
|
||||
|
||||
// Don't let the markers be invisible
|
||||
alpha = 0.5 + 0.5 * alpha;
|
||||
points[i].setMarker(new Marker().setFillColor(new Color(red, 0, blue, alpha)).setRadius(radius));
|
||||
}
|
||||
|
||||
return points;
|
||||
@@ -224,7 +251,7 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a the results of a polar sheet generation to the chart panel. Performs all necessary steps including adding
|
||||
* Adds the results of a polar sheet generation to the chart panel. Performs all necessary steps including adding
|
||||
* the set of series to the map and redrawing the chart.
|
||||
*
|
||||
* @param id Set of series identifier.
|
||||
@@ -248,9 +275,13 @@ public class PolarSheetsChartPanel extends DockLayoutPanel {
|
||||
* Allows setting the point select handler for the chart.
|
||||
*/
|
||||
public void setPointSelectHandler(PointSelectEventHandler pointSelectEventHandler) {
|
||||
chart.setSeriesPlotOptions(new SeriesPlotOptions().setAllowPointSelect(true).setPointSelectEventHandler(
|
||||
chart.setSeriesPlotOptions(seriesPlotOptions.setAllowPointSelect(true).setPointSelectEventHandler(
|
||||
pointSelectEventHandler));
|
||||
}
|
||||
|
||||
private void setSeriesShowHandler(SeriesShowEventHandler eventHandler) {
|
||||
chart.setSeriesPlotOptions(seriesPlotOptions.setSeriesShowEventHandler(eventHandler));
|
||||
}
|
||||
|
||||
public Series[] getSeriesPerWindspeedForName(String name) {
|
||||
return seriesMap.get(name);
|
||||
|
||||
-10
@@ -44,16 +44,6 @@ public class PolarSheetsXYDiagramPopupPanel extends DialogBox {
|
||||
});
|
||||
containerPanel.add(closeButton);
|
||||
|
||||
Point[] pointsForUpwindStarboardAverageAngle = toPointArray(result.getPointsForUpwindStarboardAverageAngle());
|
||||
Series angleSeries = chart.createSeries();
|
||||
angleSeries.setPoints(pointsForUpwindStarboardAverageAngle);
|
||||
//chart.addSeries(angleSeries);
|
||||
|
||||
Point[] pointsForUpwindStarboardAverageSpeed = toPointArray(result.getPointsForUpwindStarboardAverageSpeed());
|
||||
Series speedSeries = chart.createSeries();
|
||||
speedSeries.setName("Upwind Starboard Speed - LinearRegressions");
|
||||
speedSeries.setPoints(pointsForUpwindStarboardAverageSpeed);
|
||||
chart.addSeries(speedSeries);
|
||||
|
||||
Point[] pointsForUpwindStarboardAverageSpeedMovingAverage = toPointArray(result.getPointsForUpwindStarboardAverageSpeedMovingAverage());
|
||||
Series speedSeriesMovingAverage = chart.createSeries();
|
||||
|
||||
+9
-22
@@ -126,6 +126,7 @@ import com.sap.sailing.domain.base.impl.RegattaImpl;
|
||||
import com.sap.sailing.domain.base.impl.SeriesImpl;
|
||||
import com.sap.sailing.domain.base.impl.TeamImpl;
|
||||
import com.sap.sailing.domain.common.Bearing;
|
||||
import com.sap.sailing.domain.common.BoatClassMasterdata;
|
||||
import com.sap.sailing.domain.common.CountryCode;
|
||||
import com.sap.sailing.domain.common.DataImportProgress;
|
||||
import com.sap.sailing.domain.common.DetailType;
|
||||
@@ -3948,10 +3949,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
|
||||
@Override
|
||||
public List<String> getBoatClassNamesWithPolarSheetsAvailable() {
|
||||
Set<BoatClass> boatClasses = getService().getPolarDataService().getAllBoatClassesWithPolarSheetsAvailable();
|
||||
Set<BoatClassMasterdata> boatClasses = getService().getPolarDataService().getAllBoatClassesWithPolarSheetsAvailable();
|
||||
List<String> names = new ArrayList<String>();
|
||||
for (BoatClass boatClass : boatClasses) {
|
||||
names.add(boatClass.getName());
|
||||
for (BoatClassMasterdata boatClass : boatClasses) {
|
||||
names.add(boatClass.getDisplayName());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
@@ -5351,39 +5352,25 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
@Override
|
||||
public PolarSheetsXYDiagramData createXYDiagramForBoatClass(String boatClassName) {
|
||||
BoatClass boatClass = getService().getBaseDomainFactory().getOrCreateBoatClass(boatClassName);
|
||||
List<Pair<Double, Double>> pointsForUpwindStarboardAverageSpeed = new ArrayList<Pair<Double, Double>>();
|
||||
List<Pair<Double, Double>> pointsForUpwindStarboardAverageAngle = new ArrayList<Pair<Double, Double>>();
|
||||
List<Pair<Double, Double>> pointsForUpwindStarboardAverageSpeedMovingAverage = new ArrayList<Pair<Double, Double>>();
|
||||
List<Pair<Double, Double>> pointsForUpwindStarboardAverageAngleMovingAverage = new ArrayList<Pair<Double, Double>>();
|
||||
List<Pair<Double, Double>> pointsForUpwindStarboardAverageConfidence = new ArrayList<Pair<Double, Double>>();
|
||||
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);
|
||||
Tack.STARBOARD);
|
||||
pointsForUpwindStarboardAverageSpeedMovingAverage.add(new Pair<Double, Double>(windInKnots,
|
||||
averageUpwindStarboardMovingAverage.getObject().getKnots()));
|
||||
pointsForUpwindStarboardAverageAngleMovingAverage.add(new Pair<Double, Double>(windInKnots,
|
||||
averageUpwindStarboardMovingAverage.getObject().getBearing().getDegrees()));
|
||||
|
||||
pointsForUpwindStarboardAverageConfidence.add(new Pair<Double, Double>(windInKnots,
|
||||
averageUpwindStarboardMovingAverage.getConfidence()));
|
||||
|
||||
} catch (NotEnoughDataHasBeenAddedException e) {
|
||||
// Do not add a point to the result
|
||||
}
|
||||
}
|
||||
PolarSheetsXYDiagramData data = new PolarSheetsXYDiagramDataImpl(pointsForUpwindStarboardAverageAngle,
|
||||
pointsForUpwindStarboardAverageSpeed, pointsForUpwindStarboardAverageAngleMovingAverage,
|
||||
pointsForUpwindStarboardAverageSpeedMovingAverage, pointsForUpwindStarboardAverageConfidence);
|
||||
PolarSheetsXYDiagramData data = new PolarSheetsXYDiagramDataImpl(pointsForUpwindStarboardAverageSpeedMovingAverage, pointsForUpwindStarboardAverageConfidence);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
package com.sap.sailing.polars.caching.test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.polars.caching.PolarFixCacheRaceInterval;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
|
||||
public class PolarFixCacheRaceIntervalTest {
|
||||
|
||||
@Test
|
||||
public void testIntervalJoin() {
|
||||
TrackedRace mockedTrackedRace1 = mock(TrackedRace.class);
|
||||
TrackedRace mockedTrackedRace2 = mock(TrackedRace.class);
|
||||
|
||||
Competitor mockedCompetitor1 = mock(Competitor.class);
|
||||
Competitor mockedCompetitor2 = mock(Competitor.class);
|
||||
|
||||
Calendar calender = Calendar.getInstance();
|
||||
calender.set(2014, 3, 17, 16, 0, 0);
|
||||
TimePoint timePoint1 = new MillisecondsTimePoint(calender.getTime());
|
||||
calender.set(2014, 3, 17, 16, 10, 0);
|
||||
TimePoint timePoint2 = new MillisecondsTimePoint(calender.getTime());
|
||||
|
||||
PolarFixCacheRaceInterval interval1 = createInterval1(
|
||||
mockedTrackedRace1, mockedTrackedRace2, mockedCompetitor1,
|
||||
mockedCompetitor2, timePoint1, timePoint2);
|
||||
|
||||
PolarFixCacheRaceInterval interval2 = createInterval2(
|
||||
mockedTrackedRace1, mockedTrackedRace2, mockedCompetitor1,
|
||||
timePoint2);
|
||||
PolarFixCacheRaceInterval joined = interval1.join(interval2);
|
||||
|
||||
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> joinedMap = joined
|
||||
.getCompetitorAndTimepointsForRace();
|
||||
|
||||
Map<Competitor, Pair<TimePoint, TimePoint>> explicitIntervalForRace1 = joinedMap.get(mockedTrackedRace1);
|
||||
assertThat(explicitIntervalForRace1, notNullValue(Map.class));
|
||||
Pair<TimePoint, TimePoint> explicitIntervalForCompetitor1 = explicitIntervalForRace1.get(mockedCompetitor1);
|
||||
assertThat(explicitIntervalForCompetitor1, notNullValue(Pair.class));
|
||||
|
||||
TimePoint start1 = explicitIntervalForCompetitor1.getA();
|
||||
TimePoint end1 = explicitIntervalForCompetitor1.getB();
|
||||
|
||||
assertThat(start1, is(timePoint1));
|
||||
assertThat(end1, is(timePoint2));
|
||||
|
||||
}
|
||||
|
||||
private PolarFixCacheRaceInterval createInterval1(
|
||||
TrackedRace mockedTrackedRace1, TrackedRace mockedTrackedRace2,
|
||||
Competitor mockedCompetitor1, Competitor mockedCompetitor2,
|
||||
TimePoint timePoint1, TimePoint timePoint2) {
|
||||
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> competitorAndTimepointsForRace = new HashMap<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>>();
|
||||
HashMap<Competitor, Pair<TimePoint, TimePoint>> mapForRace1 = new HashMap<Competitor, Pair<TimePoint, TimePoint>>();
|
||||
mapForRace1.put(mockedCompetitor1, new Pair<TimePoint, TimePoint>(timePoint1, timePoint1));
|
||||
competitorAndTimepointsForRace.put(mockedTrackedRace1, mapForRace1);
|
||||
HashMap<Competitor, Pair<TimePoint, TimePoint>> mapForRace2 = new HashMap<Competitor, Pair<TimePoint, TimePoint>>();
|
||||
mapForRace2.put(mockedCompetitor2, new Pair<TimePoint, TimePoint>(timePoint1, timePoint2));
|
||||
competitorAndTimepointsForRace.put(mockedTrackedRace2, mapForRace2);
|
||||
PolarFixCacheRaceInterval interval1 = new PolarFixCacheRaceInterval(
|
||||
competitorAndTimepointsForRace);
|
||||
return interval1;
|
||||
}
|
||||
|
||||
private PolarFixCacheRaceInterval createInterval2(
|
||||
TrackedRace mockedTrackedRace1, TrackedRace mockedTrackedRace2,
|
||||
Competitor mockedCompetitor1, TimePoint timePoint2) {
|
||||
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> competitorAndTimepointsForRace = new HashMap<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>>();
|
||||
HashMap<Competitor, Pair<TimePoint, TimePoint>> mapForRace1 = new HashMap<Competitor, Pair<TimePoint, TimePoint>>();
|
||||
mapForRace1.put(mockedCompetitor1, new Pair<TimePoint, TimePoint>(
|
||||
timePoint2, timePoint2));
|
||||
competitorAndTimepointsForRace.put(mockedTrackedRace1, mapForRace1);
|
||||
PolarFixCacheRaceInterval interval2 = new PolarFixCacheRaceInterval(
|
||||
competitorAndTimepointsForRace);
|
||||
return interval2;
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -58,6 +58,8 @@ public class PolarDataMinerTest {
|
||||
PolarDataMiner miner = new PolarDataMiner();
|
||||
|
||||
BoatClass mockedBoatClass = mock(BoatClass.class);
|
||||
|
||||
when(mockedBoatClass.getName()).thenReturn("49ER");
|
||||
|
||||
GPSFixMoving fix1_1 = createMockedFix(13, 00, 54.431952, 10.186767, 45, 10.5);
|
||||
GPSFixMoving fix1_2 = createMockedFix(13, 30, 54.485034, 10.538303, 44.8, 10.6);
|
||||
@@ -107,17 +109,17 @@ public class PolarDataMinerTest {
|
||||
}
|
||||
|
||||
SpeedWithConfidence<Void> estimatedSpeed1 = miner.estimateBoatSpeed(mockedBoatClass, new KnotSpeedImpl(15),
|
||||
new DegreeBearingImpl(44.9), true);
|
||||
new DegreeBearingImpl(44.9));
|
||||
assertThat(estimatedSpeed1, is(notNullValue()));
|
||||
assertThat(estimatedSpeed1.getObject().getKnots(), is(closeTo(10.55, EPSILON)));
|
||||
|
||||
SpeedWithConfidence<Void> estimatedSpeed2 = miner.estimateBoatSpeed(mockedBoatClass, new KnotSpeedImpl(15),
|
||||
new DegreeBearingImpl(42), true);
|
||||
new DegreeBearingImpl(42));
|
||||
assertThat(estimatedSpeed2, is(notNullValue()));
|
||||
assertThat(estimatedSpeed2.getObject().getKnots(), is(closeTo(10.05, EPSILON)));
|
||||
|
||||
SpeedWithConfidence<Void> estimatedSpeed3 = miner.estimateBoatSpeed(mockedBoatClass, new KnotSpeedImpl(15),
|
||||
new DegreeBearingImpl(42.8), true);
|
||||
new DegreeBearingImpl(42.8));
|
||||
assertThat(estimatedSpeed3, is(notNullValue()));
|
||||
assertThat(estimatedSpeed3.getObject().getKnots(), is(closeTo(10, EPSILON)));
|
||||
}
|
||||
|
||||
Regular → Executable
+16
-16
@@ -31,25 +31,25 @@ public class PolarSheetAnalyzerTest {
|
||||
BoatClass boatClass = mock(BoatClass.class);
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> result = analyzer.getAverageUpwindSpeedWithBearingOnStarboardTackFor(boatClass,
|
||||
new KnotSpeedImpl(14), true);
|
||||
new KnotSpeedImpl(14));
|
||||
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);
|
||||
new KnotSpeedImpl(14));
|
||||
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);
|
||||
new KnotSpeedImpl(14));
|
||||
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);
|
||||
new KnotSpeedImpl(14));
|
||||
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));
|
||||
@@ -64,46 +64,46 @@ public class PolarSheetAnalyzerTest {
|
||||
|
||||
//Starboard Upwind
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(49)), eq(true))).thenReturn(
|
||||
argThat(new BearingMatcher(49)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(8.478048671702888), 0.5, null));
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(50)), eq(true))).thenReturn(
|
||||
argThat(new BearingMatcher(50)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(8.466303997538812), 0.5, null));
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(51)), eq(true))).thenReturn(
|
||||
argThat(new BearingMatcher(51)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(8.583383077026435), 0.5, null));
|
||||
|
||||
//Starboard Downwind
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(141)), eq(true))).thenReturn(
|
||||
argThat(new BearingMatcher(141)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(13.794354053931528), 0.5, null));
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(142)), eq(true))).thenReturn(
|
||||
argThat(new BearingMatcher(142)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(13.665782579628802), 0.5, null));
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(143)), eq(true))).thenReturn(
|
||||
argThat(new BearingMatcher(143)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(14.169301888320263), 0.5, null));
|
||||
|
||||
//Port Upwind
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(-48)), eq(true))).thenReturn(
|
||||
argThat(new BearingMatcher(-48)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(8.445599410456111), 0.5, null));
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(-50)), eq(true))).thenReturn(
|
||||
argThat(new BearingMatcher(-50)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(8.553274292235153), 0.5, null));
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(-49)), eq(true))).thenReturn(
|
||||
argThat(new BearingMatcher(-49)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(8.614582090896583), 0.5, null));
|
||||
|
||||
//Port Downwind
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(-145)), eq(true))).thenReturn(
|
||||
argThat(new BearingMatcher(-145)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(13.78894715705271), 0.5, null));
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(-144)), eq(true))).thenReturn(
|
||||
argThat(new BearingMatcher(-144)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(13.420656294986587), 0.5, null));
|
||||
when(mockedPolarDataService.getSpeed(any(BoatClass.class), argThat(new SpeedMatcher(14)),
|
||||
argThat(new BearingMatcher(-143)), eq(true))).thenReturn(
|
||||
argThat(new BearingMatcher(-143)))).thenReturn(
|
||||
new SpeedWithConfidenceImpl<Void>(new KnotSpeedImpl(13.268607457651942), 0.5, null));
|
||||
|
||||
return mockedPolarDataService;
|
||||
|
||||
Regular → Executable
+14
-24
@@ -9,6 +9,7 @@ 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.BoatClassMasterdata;
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
|
||||
import com.sap.sailing.domain.common.PolarSheetsData;
|
||||
@@ -16,8 +17,6 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -27,7 +26,6 @@ import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
|
||||
*
|
||||
* This service uses a {@link PolarSheetAnalyzer} for more advanced analysis. Its methods are facaded in this interface for
|
||||
* central access.<p>
|
||||
*
|
||||
* The interesting methods for a client are {@link #getSpeed(BoatClass, Speed, Bearing, boolean)} if data for a specific angle is
|
||||
* needed and {@link #getAverageSpeedWithBearing(BoatClass, Speed, LegType, Tack)}
|
||||
* which also returns the average angle for the parameters provided.
|
||||
@@ -42,14 +40,11 @@ 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 <code>true</code> uses linear Regression for estimation in the wind interval, if <code>false</code>
|
||||
* simple arithmetic mean in interval
|
||||
* Boat's direction relative to the wind. either in -180 -> +180 or 0 -> 359 degrees interval. The true wind!
|
||||
* @return The speed the boat is moving at for the specified wind and bearing according to the polar diagram.
|
||||
* @throws NotEnoughDataHasBeenAddedException
|
||||
*/
|
||||
SpeedWithConfidence<Void> getSpeed(BoatClass boatClass, Speed windSpeed, Bearing bearingToTheWind, boolean useLinearRegression)
|
||||
SpeedWithConfidence<Void> getSpeed(BoatClass boatClass, Speed windSpeed, Bearing bearingToTheWind)
|
||||
throws NotEnoughDataHasBeenAddedException;
|
||||
|
||||
/**
|
||||
@@ -62,8 +57,8 @@ public interface PolarDataService {
|
||||
* @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/>
|
||||
* the true wind and a confidence which consists of the confidences of the wind speed, and boat speed sources (50%)
|
||||
* and a confidence calculated using the amount of underlying fixes (50%). 0 <= confidence < 1<br/>
|
||||
* A value with zero confidence doesn't have any significance!<br/>
|
||||
* <br/>
|
||||
*
|
||||
@@ -97,16 +92,6 @@ public interface PolarDataService {
|
||||
PolarSheetsData generatePolarSheet(Set<TrackedRace> trackedRaces, PolarSheetGenerationSettings settings,
|
||||
Executor executor) throws InterruptedException, ExecutionException;
|
||||
|
||||
void newRaceFinishedTracking(TrackedRace trackedRace);
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* The {@link BoatClass} to obtain fixes for.
|
||||
* @return All raw polar fixes for the {@link BoatClass}. The implementation is responsible for deciding wether a
|
||||
* cache is used or not.
|
||||
*/
|
||||
Set<PolarFix> getPolarFixesForBoatClass(BoatClass key);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param boatClass
|
||||
@@ -120,8 +105,16 @@ public interface PolarDataService {
|
||||
* @return The {@link BoatClass}es for which there are polar sheets available via
|
||||
* {@link PolarDataService#getPolarSheetForBoatClass(BoatClass)}
|
||||
*/
|
||||
Set<BoatClass> getAllBoatClassesWithPolarSheetsAvailable();
|
||||
Set<BoatClassMasterdata> getAllBoatClassesWithPolarSheetsAvailable();
|
||||
|
||||
/**
|
||||
* To be called in an appropriate listener.
|
||||
* Starting point for fixes entering the backend polar data mining pipeline.
|
||||
*
|
||||
* @param fix
|
||||
* @param competitor
|
||||
* @param createdTrackedRace
|
||||
*/
|
||||
void competitorPositionChanged(GPSFixMoving fix, Competitor competitor, TrackedRace createdTrackedRace);
|
||||
|
||||
/**
|
||||
@@ -133,9 +126,6 @@ public interface PolarDataService {
|
||||
* @return array with datacount for all angles in the given area, else -1
|
||||
*/
|
||||
int[] getDataCountsForWindSpeed(BoatClass boatClass, Speed windSpeed, int startAngleInclusive, int endAngleExclusive);
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> getAverageSpeedWithBearing(BoatClass boatClass, Speed windSpeed,
|
||||
LegType legType, Tack tack, boolean useLinearRegression) throws NotEnoughDataHasBeenAddedException;
|
||||
|
||||
|
||||
}
|
||||
Regular → Executable
+4
-4
@@ -8,15 +8,15 @@ import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
|
||||
public interface PolarSheetAnalyzer {
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> getAverageUpwindSpeedWithBearingOnStarboardTackFor(BoatClass boatClass,
|
||||
Speed windSpeed, boolean useLinReg) throws NotEnoughDataHasBeenAddedException;
|
||||
Speed windSpeed) throws NotEnoughDataHasBeenAddedException;
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> getAverageDownwindSpeedWithBearingOnStarboardTackFor(BoatClass boatClass,
|
||||
Speed windSpeed, boolean useLinReg) throws NotEnoughDataHasBeenAddedException;
|
||||
Speed windSpeed) throws NotEnoughDataHasBeenAddedException;
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> getAverageUpwindSpeedWithBearingOnPortTackFor(BoatClass boatClass,
|
||||
Speed windSpeed, boolean useLinReg) throws NotEnoughDataHasBeenAddedException;
|
||||
Speed windSpeed) throws NotEnoughDataHasBeenAddedException;
|
||||
|
||||
SpeedWithBearingWithConfidence<Void> getAverageDownwindSpeedWithBearingOnPortTackFor(BoatClass boatClass,
|
||||
Speed windSpeed, boolean useLinReg) throws NotEnoughDataHasBeenAddedException;
|
||||
Speed windSpeed) throws NotEnoughDataHasBeenAddedException;
|
||||
|
||||
}
|
||||
Regular → Executable
+13
-13
@@ -29,52 +29,53 @@ public class PolarSheetAnalyzerImpl implements PolarSheetAnalyzer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageUpwindSpeedWithBearingOnStarboardTackFor(BoatClass boatClass, Speed windSpeed, boolean useLinReg)
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageUpwindSpeedWithBearingOnStarboardTackFor(BoatClass boatClass, Speed windSpeed)
|
||||
throws NotEnoughDataHasBeenAddedException {
|
||||
int startAngleInclusive = 1;
|
||||
int endAngleExclusive = 90;
|
||||
SpeedWithBearingWithConfidence<Void> speedWithBearing = estimateAnglePeakAndAverageSpeed(boatClass, windSpeed, startAngleInclusive,
|
||||
endAngleExclusive, useLinReg);
|
||||
endAngleExclusive);
|
||||
return speedWithBearing;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageDownwindSpeedWithBearingOnStarboardTackFor(BoatClass boatClass, Speed windSpeed, boolean useLinReg)
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageDownwindSpeedWithBearingOnStarboardTackFor(BoatClass boatClass, Speed windSpeed)
|
||||
throws NotEnoughDataHasBeenAddedException {
|
||||
int startAngleInclusive = 91;
|
||||
int endAngleExclusive = 180;
|
||||
SpeedWithBearingWithConfidence<Void> speedWithBearing = estimateAnglePeakAndAverageSpeed(boatClass, windSpeed, startAngleInclusive,
|
||||
endAngleExclusive, useLinReg);
|
||||
endAngleExclusive);
|
||||
return speedWithBearing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageDownwindSpeedWithBearingOnPortTackFor(BoatClass boatClass, Speed windSpeed, boolean useLinReg)
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageDownwindSpeedWithBearingOnPortTackFor(BoatClass boatClass, Speed windSpeed)
|
||||
throws NotEnoughDataHasBeenAddedException {
|
||||
int startAngleInclusive = 181;
|
||||
int endAngleExclusive = 270;
|
||||
SpeedWithBearingWithConfidence<Void> speedWithBearing = estimateAnglePeakAndAverageSpeed(boatClass, windSpeed, startAngleInclusive,
|
||||
endAngleExclusive, useLinReg);
|
||||
endAngleExclusive);
|
||||
return speedWithBearing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageUpwindSpeedWithBearingOnPortTackFor(BoatClass boatClass,
|
||||
Speed windSpeed, boolean useLinReg) throws NotEnoughDataHasBeenAddedException {
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageUpwindSpeedWithBearingOnPortTackFor(BoatClass boatClass, Speed windSpeed)
|
||||
throws NotEnoughDataHasBeenAddedException {
|
||||
int startAngleInclusive = 271;
|
||||
int endAngleExclusive = 360;
|
||||
SpeedWithBearingWithConfidence<Void> speedWithBearing = estimateAnglePeakAndAverageSpeed(boatClass, windSpeed,
|
||||
startAngleInclusive, endAngleExclusive, useLinReg);
|
||||
endAngleExclusive);
|
||||
return speedWithBearing;
|
||||
}
|
||||
|
||||
private SpeedWithBearingWithConfidence<Void> estimateAnglePeakAndAverageSpeed(BoatClass boatClass, Speed windSpeed,
|
||||
int startAngleInclusive, int endAngleExclusive, boolean useLinearRegression) throws NotEnoughDataHasBeenAddedException {
|
||||
int startAngleInclusive, int endAngleExclusive) throws NotEnoughDataHasBeenAddedException {
|
||||
int[] dataCountPerAngle = getDataCountArray(boatClass, windSpeed, startAngleInclusive, endAngleExclusive);
|
||||
double estimatedPeak = estimatePeak(boatClass, windSpeed, startAngleInclusive, endAngleExclusive, dataCountPerAngle);
|
||||
double convertedAngleIfOver180 = convertAngleIfNecessary(estimatedPeak);
|
||||
SpeedWithConfidence<Void> averagedSpeedWithConfidence = estimateSpeed(boatClass, windSpeed, convertedAngleIfOver180, useLinearRegression);
|
||||
convertedAngleIfOver180);
|
||||
double originConfidence = averagedSpeedWithConfidence.getConfidence();
|
||||
double overallConfidence = (originConfidence + calcDataCountConfidence(dataCountPerAngle, estimatedPeak)) / 2;
|
||||
Bearing bearing = new DegreeBearingImpl(convertedAngleIfOver180);
|
||||
@@ -110,10 +111,9 @@ public class PolarSheetAnalyzerImpl implements PolarSheetAnalyzer {
|
||||
return convertedAngleIfOver180;
|
||||
}
|
||||
|
||||
private SpeedWithConfidence<Void> estimateSpeed(BoatClass boatClass, Speed windSpeed, double estimatedPeak,
|
||||
boolean useLinearRegression) throws NotEnoughDataHasBeenAddedException {
|
||||
private SpeedWithConfidence<Void> estimateSpeed(BoatClass boatClass, Speed windSpeed, double estimatedPeak) throws NotEnoughDataHasBeenAddedException {
|
||||
SpeedWithConfidence<Void> speed = polarDataService.getSpeed(boatClass, windSpeed, new DegreeBearingImpl(
|
||||
estimatedPeak), useLinearRegression);
|
||||
estimatedPeak));
|
||||
return speed;
|
||||
}
|
||||
|
||||
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
package com.sap.sailing.polars.caching;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.polars.data.PolarFix;
|
||||
|
||||
public class NoCacheEntryException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 5547683568438235668L;
|
||||
private final Set<TrackedRace> notCached;
|
||||
private final Set<PolarFix> resultList;
|
||||
|
||||
public NoCacheEntryException(Set<TrackedRace> notCached, Set<PolarFix> resultList) {
|
||||
this.notCached = notCached;
|
||||
this.resultList = resultList;
|
||||
}
|
||||
|
||||
public Set<TrackedRace> getNotCached() {
|
||||
return notCached;
|
||||
}
|
||||
|
||||
public Set<PolarFix> getCachedResultList() {
|
||||
return resultList;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package com.sap.sailing.polars.caching;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.polars.data.PolarFix;
|
||||
import com.sap.sailing.util.SmartFutureCache;
|
||||
|
||||
public class PolarFixCache extends
|
||||
SmartFutureCache<BoatClass, Map<RegattaAndRaceIdentifier, List<PolarFix>>, PolarFixCacheRaceInterval> {
|
||||
|
||||
private final List<PolarFixCacheUpdateDoneListener> cacheUpdateDoneListeners = new ArrayList<PolarFixCacheUpdateDoneListener>();
|
||||
|
||||
public PolarFixCache(Executor executor) {
|
||||
super(new PolarFixCacheUpdater(executor), "polarFixCache");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cache(BoatClass key, Map<RegattaAndRaceIdentifier, List<PolarFix>> value) {
|
||||
super.cache(key, value);
|
||||
synchronized (cacheUpdateDoneListeners) {
|
||||
for (PolarFixCacheUpdateDoneListener listener : cacheUpdateDoneListeners) {
|
||||
listener.cacheUpdateDoneForBoatClass(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener(PolarFixCacheUpdateDoneListener listener) {
|
||||
synchronized (cacheUpdateDoneListeners) {
|
||||
cacheUpdateDoneListeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(PolarFixCacheUpdateDoneListener listener) {
|
||||
synchronized (cacheUpdateDoneListeners) {
|
||||
if (cacheUpdateDoneListeners.contains(listener)) {
|
||||
cacheUpdateDoneListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Set<PolarFix> getFixesForTrackedRaces(Set<TrackedRace> trackedRaces) throws NoCacheEntryException {
|
||||
Set<PolarFix> resultList = new HashSet<PolarFix>();
|
||||
Set<TrackedRace> notCached = new HashSet<TrackedRace>();
|
||||
for (TrackedRace trackedRace : trackedRaces) {
|
||||
Map<RegattaAndRaceIdentifier, List<PolarFix>> result = get(trackedRace.getRace().getBoatClass(), false);
|
||||
List<PolarFix> resultForRace = result.get(trackedRace.getRaceIdentifier());
|
||||
if (resultForRace == null) {
|
||||
notCached.add(trackedRace);
|
||||
} else {
|
||||
resultList.addAll(resultForRace);
|
||||
}
|
||||
}
|
||||
if (notCached.size() > 0) {
|
||||
throw new NoCacheEntryException(notCached, resultList);
|
||||
}
|
||||
return resultList;
|
||||
}
|
||||
|
||||
}
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
package com.sap.sailing.polars.caching;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.polars.aggregation.PolarFixRaceInterval;
|
||||
import com.sap.sailing.util.SmartFutureCache.UpdateInterval;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
|
||||
public class PolarFixCacheRaceInterval implements UpdateInterval<PolarFixCacheRaceInterval>, PolarFixRaceInterval {
|
||||
|
||||
private final Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> competitorAndTimepointsForRace;
|
||||
|
||||
public PolarFixCacheRaceInterval(
|
||||
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> competitorAndTimepointsForRace) {
|
||||
this.competitorAndTimepointsForRace = competitorAndTimepointsForRace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> getCompetitorAndTimepointsForRace() {
|
||||
return competitorAndTimepointsForRace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PolarFixCacheRaceInterval join(PolarFixCacheRaceInterval otherUpdateInterval) {
|
||||
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> joinedInterval = new HashMap<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>>();
|
||||
Set<TrackedRace> racesContainedInEitherMap = new HashSet<TrackedRace>();
|
||||
for (TrackedRace race : competitorAndTimepointsForRace.keySet()) {
|
||||
racesContainedInEitherMap.add(race);
|
||||
}
|
||||
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> otherUpdateIntervalInExplicitForm = otherUpdateInterval
|
||||
.getCompetitorAndTimepointsForRace();
|
||||
for (TrackedRace race : otherUpdateIntervalInExplicitForm.keySet()) {
|
||||
racesContainedInEitherMap.add(race);
|
||||
}
|
||||
|
||||
for (TrackedRace race : racesContainedInEitherMap) {
|
||||
HashMap<Competitor, Pair<TimePoint, TimePoint>> joinedMap = new HashMap<Competitor, Pair<TimePoint, TimePoint>>();
|
||||
Map<Competitor, Pair<TimePoint, TimePoint>> mapOfTimpointCompetitorPairs = competitorAndTimepointsForRace
|
||||
.get(race);
|
||||
if (mapOfTimpointCompetitorPairs != null) {
|
||||
joinedMap.putAll(mapOfTimpointCompetitorPairs);
|
||||
}
|
||||
Map<Competitor, Pair<TimePoint, TimePoint>> mapOfOtherTimpointCompetitorPairs = otherUpdateIntervalInExplicitForm
|
||||
.get(race);
|
||||
if (mapOfOtherTimpointCompetitorPairs != null) {
|
||||
mergeMap(joinedMap, mapOfOtherTimpointCompetitorPairs);
|
||||
}
|
||||
joinedInterval.put(race, joinedMap);
|
||||
}
|
||||
return new PolarFixCacheRaceInterval(joinedInterval);
|
||||
}
|
||||
|
||||
private void mergeMap(HashMap<Competitor, Pair<TimePoint, TimePoint>> joinedMap,
|
||||
Map<Competitor, Pair<TimePoint, TimePoint>> mapOfOtherTimpointCompetitorPairs) {
|
||||
for (Entry<Competitor, Pair<TimePoint, TimePoint>> otherEntry : mapOfOtherTimpointCompetitorPairs.entrySet()) {
|
||||
|
||||
Competitor competitor = otherEntry.getKey();
|
||||
Pair<TimePoint, TimePoint> otherValue = otherEntry.getValue();
|
||||
Pair<TimePoint, TimePoint> existingValue = joinedMap.get(competitor);
|
||||
if (existingValue != null) {
|
||||
Pair<TimePoint, TimePoint> joinedTimepointInterval = joinTimepointIntervals(existingValue, otherValue);
|
||||
joinedMap.put(competitor, joinedTimepointInterval);
|
||||
} else {
|
||||
joinedMap.put(competitor, otherValue);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method ignores a possible gap between the two intervals.
|
||||
*
|
||||
* @param existingValue
|
||||
* @param otherValue
|
||||
* @return
|
||||
*/
|
||||
private Pair<TimePoint, TimePoint> joinTimepointIntervals(Pair<TimePoint, TimePoint> existingValue,
|
||||
Pair<TimePoint, TimePoint> otherValue) {
|
||||
TimePoint startExisting = existingValue.getA();
|
||||
TimePoint endExisting = existingValue.getB();
|
||||
TimePoint startOther = otherValue.getA();
|
||||
TimePoint endOther = otherValue.getB();
|
||||
|
||||
TimePoint startNew = startExisting.before(startOther) ? startExisting : startOther;
|
||||
TimePoint endNew = endExisting.after(endOther) ? endExisting : endOther;
|
||||
|
||||
return new Pair<TimePoint, TimePoint>(startNew, endNew);
|
||||
}
|
||||
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package com.sap.sailing.polars.caching;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
|
||||
public interface PolarFixCacheUpdateDoneListener {
|
||||
|
||||
void cacheUpdateDoneForBoatClass(BoatClass key);
|
||||
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
package com.sap.sailing.polars.caching;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.impl.PolarSheetGenerationSettingsImpl;
|
||||
import com.sap.sailing.polars.aggregation.PolarFixAggregator;
|
||||
import com.sap.sailing.polars.data.PolarFix;
|
||||
import com.sap.sailing.util.SmartFutureCache.CacheUpdater;
|
||||
|
||||
public class PolarFixCacheUpdater implements
|
||||
CacheUpdater<BoatClass, Map<RegattaAndRaceIdentifier, List<PolarFix>>, PolarFixCacheRaceInterval> {
|
||||
|
||||
private final Executor executor;
|
||||
|
||||
public PolarFixCacheUpdater(Executor executor) {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<RegattaAndRaceIdentifier, List<PolarFix>> computeCacheUpdate(BoatClass key,
|
||||
PolarFixCacheRaceInterval updateInterval) throws Exception {
|
||||
PolarFixAggregator aggregator = new PolarFixAggregator(updateInterval,
|
||||
PolarSheetGenerationSettingsImpl.createStandardPolarSettings(), executor);
|
||||
Thread.sleep(updateInterval.getCompetitorAndTimepointsForRace().keySet().iterator().next()
|
||||
.getMillisecondsOverWhichToAverageSpeed() / 2);
|
||||
aggregator.startPolarFixAggregation();
|
||||
|
||||
return aggregator.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<RegattaAndRaceIdentifier, List<PolarFix>> provideNewCacheValue(BoatClass key,
|
||||
Map<RegattaAndRaceIdentifier, List<PolarFix>> oldCacheValue,
|
||||
Map<RegattaAndRaceIdentifier, List<PolarFix>> computedCacheUpdate, PolarFixCacheRaceInterval updateInterval) {
|
||||
Map<RegattaAndRaceIdentifier, List<PolarFix>> newCacheValue = new HashMap<RegattaAndRaceIdentifier, List<PolarFix>>();
|
||||
if (oldCacheValue != null) {
|
||||
newCacheValue.putAll(oldCacheValue);
|
||||
}
|
||||
for (Entry<RegattaAndRaceIdentifier, List<PolarFix>> newEntry : computedCacheUpdate.entrySet()) {
|
||||
List<PolarFix> oldList = newCacheValue.get(newEntry.getKey());
|
||||
if (oldList != null) {
|
||||
oldList.addAll(newEntry.getValue());
|
||||
} else {
|
||||
newCacheValue.put(newEntry.getKey(), newEntry.getValue());
|
||||
}
|
||||
}
|
||||
return newCacheValue;
|
||||
}
|
||||
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
package com.sap.sailing.polars.caching;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.common.PolarSheetsData;
|
||||
import com.sap.sailing.polars.PolarDataService;
|
||||
import com.sap.sailing.util.SmartFutureCache;
|
||||
|
||||
public class PolarSheetPerBoatClassCache extends
|
||||
SmartFutureCache<BoatClass, PolarSheetsData, com.sap.sailing.util.SmartFutureCache.EmptyUpdateInterval>
|
||||
implements PolarFixCacheUpdateDoneListener {
|
||||
|
||||
public PolarSheetPerBoatClassCache(PolarDataService polarDataService) {
|
||||
super(new PolarSheetPerBoatClassCacheUpdater(polarDataService), "PolarSheetPerBoatClassCache");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cacheUpdateDoneForBoatClass(BoatClass key) {
|
||||
triggerUpdate(key, new EmptyUpdateInterval());
|
||||
}
|
||||
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
package com.sap.sailing.polars.caching;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
|
||||
import com.sap.sailing.domain.common.PolarSheetsData;
|
||||
import com.sap.sailing.domain.common.impl.PolarSheetGenerationSettingsImpl;
|
||||
import com.sap.sailing.polars.PolarDataService;
|
||||
import com.sap.sailing.polars.generation.PolarSheetGenerator;
|
||||
import com.sap.sailing.util.SmartFutureCache.CacheUpdater;
|
||||
import com.sap.sailing.util.SmartFutureCache.EmptyUpdateInterval;
|
||||
|
||||
public class PolarSheetPerBoatClassCacheUpdater implements
|
||||
CacheUpdater<BoatClass, PolarSheetsData, EmptyUpdateInterval> {
|
||||
|
||||
private final PolarDataService polarDataService;
|
||||
|
||||
public PolarSheetPerBoatClassCacheUpdater(PolarDataService polarDataService) {
|
||||
this.polarDataService = polarDataService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PolarSheetsData computeCacheUpdate(BoatClass key, EmptyUpdateInterval updateInterval) throws Exception {
|
||||
PolarSheetGenerationSettings settings = PolarSheetGenerationSettingsImpl.createStandardPolarSettings();
|
||||
PolarSheetGenerator generator = new PolarSheetGenerator(polarDataService.getPolarFixesForBoatClass(key),
|
||||
settings);
|
||||
PolarSheetsData generationResult = generator.generate();
|
||||
return generationResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PolarSheetsData provideNewCacheValue(BoatClass key, PolarSheetsData oldCacheValue,
|
||||
PolarSheetsData computedCacheUpdate, EmptyUpdateInterval updateInterval) {
|
||||
return computedCacheUpdate;
|
||||
}
|
||||
|
||||
}
|
||||
Regular → Executable
+9
-84
@@ -1,8 +1,5 @@
|
||||
package com.sap.sailing.polars.impl;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -12,10 +9,10 @@ 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.BoatClassMasterdata;
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
|
||||
import com.sap.sailing.domain.common.PolarSheetsData;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
||||
@@ -23,10 +20,6 @@ import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.polars.PolarDataService;
|
||||
import com.sap.sailing.polars.aggregation.PolarFixAggregator;
|
||||
import com.sap.sailing.polars.aggregation.SimplePolarFixRaceInterval;
|
||||
import com.sap.sailing.polars.analysis.PolarSheetAnalyzer;
|
||||
import com.sap.sailing.polars.analysis.impl.PolarSheetAnalyzerImpl;
|
||||
import com.sap.sailing.polars.caching.PolarFixCache;
|
||||
import com.sap.sailing.polars.caching.PolarSheetPerBoatClassCache;
|
||||
import com.sap.sailing.polars.data.PolarFix;
|
||||
import com.sap.sailing.polars.generation.PolarSheetGenerator;
|
||||
import com.sap.sailing.polars.mining.PolarDataMiner;
|
||||
@@ -43,48 +36,32 @@ import com.sap.sailing.util.SmartFutureCache;
|
||||
*/
|
||||
public class PolarDataServiceImpl implements PolarDataService {
|
||||
|
||||
private final PolarFixCache polarFixCache;
|
||||
private final PolarSheetPerBoatClassCache polarSheetPerBoatClassCache;
|
||||
|
||||
private final PolarSheetAnalyzer polarSheetAnalyzer;
|
||||
|
||||
private final PolarDataMiner polarDataMiner;
|
||||
|
||||
public PolarDataServiceImpl(Executor executor) {
|
||||
this.polarFixCache = new PolarFixCache(executor);
|
||||
this.polarSheetPerBoatClassCache = new PolarSheetPerBoatClassCache(this);
|
||||
polarFixCache.addListener(polarSheetPerBoatClassCache);
|
||||
this.polarSheetAnalyzer = new PolarSheetAnalyzerImpl(this);
|
||||
this.polarDataMiner = new PolarDataMiner();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeedWithConfidence<Void> getSpeed(BoatClass boatClass, Speed windSpeed, Bearing bearingToTheWind,
|
||||
boolean useLinearRegression) throws NotEnoughDataHasBeenAddedException {
|
||||
return polarDataMiner.estimateBoatSpeed(boatClass, windSpeed, bearingToTheWind, useLinearRegression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageSpeedWithBearing(BoatClass boatClass, Speed windSpeed,
|
||||
LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException {
|
||||
return getAverageSpeedWithBearing(boatClass, windSpeed, legType, tack, /* useLinearRegression */ true);
|
||||
public SpeedWithConfidence<Void> getSpeed(BoatClass boatClass, Speed windSpeed, Bearing bearingToTheWind) throws NotEnoughDataHasBeenAddedException {
|
||||
return polarDataMiner.estimateBoatSpeed(boatClass, windSpeed, bearingToTheWind);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageSpeedWithBearing(BoatClass boatClass,
|
||||
Speed windSpeed, LegType legType, Tack tack, boolean useLinearRegression) throws NotEnoughDataHasBeenAddedException {
|
||||
Speed windSpeed, LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException {
|
||||
SpeedWithBearingWithConfidence<Void> speedWithBearing = null;
|
||||
if (tack.equals(Tack.STARBOARD)) {
|
||||
if (legType.equals(LegType.UPWIND)) {
|
||||
speedWithBearing = polarSheetAnalyzer.getAverageUpwindSpeedWithBearingOnStarboardTackFor(boatClass, windSpeed, useLinearRegression);
|
||||
speedWithBearing = polarDataMiner.getAverageUpwindSpeedWithBearingOnStarboardTackFor(boatClass, windSpeed);
|
||||
} else if (legType.equals(LegType.DOWNWIND)) {
|
||||
speedWithBearing = polarSheetAnalyzer.getAverageDownwindSpeedWithBearingOnStarboardTackFor(boatClass, windSpeed, useLinearRegression);
|
||||
speedWithBearing = polarDataMiner.getAverageDownwindSpeedWithBearingOnStarboardTackFor(boatClass, windSpeed);
|
||||
}
|
||||
} else if (tack.equals(Tack.PORT)) {
|
||||
if (legType.equals(LegType.UPWIND)) {
|
||||
return polarSheetAnalyzer.getAverageUpwindSpeedWithBearingOnPortTackFor(boatClass, windSpeed, useLinearRegression);
|
||||
return polarDataMiner.getAverageUpwindSpeedWithBearingOnPortTackFor(boatClass, windSpeed);
|
||||
} else if (legType.equals(LegType.DOWNWIND)) {
|
||||
return polarSheetAnalyzer.getAverageDownwindSpeedWithBearingOnPortTackFor(boatClass, windSpeed, useLinearRegression);
|
||||
return polarDataMiner.getAverageDownwindSpeedWithBearingOnPortTackFor(boatClass, windSpeed);
|
||||
}
|
||||
}
|
||||
return speedWithBearing;
|
||||
@@ -95,69 +72,28 @@ public class PolarDataServiceImpl implements PolarDataService {
|
||||
public PolarSheetsData generatePolarSheet(Set<TrackedRace> trackedRaces, PolarSheetGenerationSettings settings,
|
||||
Executor executor) throws InterruptedException, ExecutionException {
|
||||
Set<PolarFix> fixes;
|
||||
// If settings are default, look into cache
|
||||
// if (settings.areDefault()) {
|
||||
// try {
|
||||
// fixes = polarFixCache.getFixesForTrackedRaces(trackedRaces);
|
||||
// } catch (NoCacheEntryException e) {
|
||||
// // If there is no cache entry for at least one of the races: Aggregate for non-cached races.
|
||||
// fixes = e.getCachedResultList();
|
||||
// PolarFixAggregator aggregator = new PolarFixAggregator(
|
||||
// new SimplePolarFixRaceInterval(e.getNotCached()),
|
||||
// settings, executor);
|
||||
// aggregator.startPolarFixAggregation();
|
||||
// fixes.addAll(aggregator.getAggregationResultAsSingleList());
|
||||
// }
|
||||
// } else {
|
||||
PolarFixAggregator aggregator = new PolarFixAggregator(new SimplePolarFixRaceInterval(trackedRaces), settings,
|
||||
executor);
|
||||
aggregator.startPolarFixAggregation();
|
||||
fixes = aggregator.getAggregationResultAsSingleList();
|
||||
// }
|
||||
PolarSheetGenerator generator = new PolarSheetGenerator(fixes, settings);
|
||||
return generator.generate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newRaceFinishedTracking(TrackedRace trackedRace) {
|
||||
// TODO add a similar listening method, that deletes all cached data on removal of race
|
||||
// HashSet<TrackedRace> set = new HashSet<TrackedRace>();
|
||||
// set.add(trackedRace);
|
||||
// polarFixCache.triggerUpdate(trackedRace.getRace().getBoatClass(), new PolarFixCacheRaceInterval(set));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PolarFix> getPolarFixesForBoatClass(BoatClass key) {
|
||||
Map<RegattaAndRaceIdentifier, List<PolarFix>> fixesInMap = polarFixCache.get(key, false);
|
||||
Set<PolarFix> resultSet = new HashSet<PolarFix>();
|
||||
for (List<PolarFix> value : fixesInMap.values()) {
|
||||
resultSet.addAll(value);
|
||||
}
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PolarSheetsData getPolarSheetForBoatClass(BoatClass boatClass) {
|
||||
return polarDataMiner.createFullSheetForBoatClass(boatClass);
|
||||
//return polarSheetPerBoatClassCache.get(boatClass, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<BoatClass> getAllBoatClassesWithPolarSheetsAvailable() {
|
||||
public Set<BoatClassMasterdata> getAllBoatClassesWithPolarSheetsAvailable() {
|
||||
return polarDataMiner.getAvailableBoatClasses();
|
||||
//return polarSheetPerBoatClassCache.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void competitorPositionChanged(final GPSFixMoving fix, final Competitor competitor,
|
||||
final TrackedRace createdTrackedRace) {
|
||||
polarDataMiner.addFix(fix, competitor, createdTrackedRace);
|
||||
// TODO build datamining pipeline for the cache
|
||||
|
||||
// PolarFixCacheRaceInterval interval = new PolarFixCacheRaceInterval(createIntervalSpecification(
|
||||
// createdTrackedRace, competitor, timePoint));
|
||||
// polarFixCache.triggerUpdate(createdTrackedRace.getRace().getBoatClass(), interval);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -165,15 +101,4 @@ public class PolarDataServiceImpl implements PolarDataService {
|
||||
return polarDataMiner.getDataCountsForWindSpeed(boatClass, windSpeed, startAngleInclusive, endAngleExclusive);
|
||||
}
|
||||
|
||||
// private Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> createIntervalSpecification(
|
||||
// TrackedRace createdTrackedRace, Competitor competitor, TimePoint timePoint) {
|
||||
// HashMap<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> result = new HashMap<TrackedRace,
|
||||
// Map<Competitor, Pair<TimePoint, TimePoint>>>();
|
||||
// HashMap<Competitor, Pair<TimePoint, TimePoint>> competitorMap = new HashMap<Competitor, Pair<TimePoint,
|
||||
// TimePoint>>();
|
||||
// competitorMap.put(competitor, new Pair<TimePoint, TimePoint>(timePoint, timePoint));
|
||||
// result.put(createdTrackedRace, competitorMap);
|
||||
// return result;
|
||||
// }
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.sap.sailing.polars.mining;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.sap.sailing.domain.common.BoatClassMasterdata;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sse.common.Util.Triple;
|
||||
import com.sap.sse.datamining.data.Cluster;
|
||||
import com.sap.sse.datamining.data.ClusterGroup;
|
||||
|
||||
public class AverageAngleContainer {
|
||||
|
||||
private ClusterGroup<Speed> speedClusterGroup;
|
||||
|
||||
private Map<BoatClassMasterdata, Map<Cluster<Speed>, Triple<Integer, Double, Double>>> dataCountAndAngleSumMap;
|
||||
|
||||
public AverageAngleContainer(ClusterGroup<Speed> speedClusterGroup) {
|
||||
this.speedClusterGroup = speedClusterGroup;
|
||||
dataCountAndAngleSumMap = new HashMap<>();
|
||||
}
|
||||
|
||||
public void addFix(BoatClassMasterdata boatClassMasterdata, Speed windSpeed,
|
||||
int roundedAngle) {
|
||||
Cluster<Speed> windCluster = speedClusterGroup.getClusterFor(windSpeed);
|
||||
if (!dataCountAndAngleSumMap.containsKey(boatClassMasterdata)) {
|
||||
dataCountAndAngleSumMap.put(boatClassMasterdata, new HashMap<>());
|
||||
}
|
||||
Map<Cluster<Speed>, Triple<Integer, Double, Double>> boatClassMap = dataCountAndAngleSumMap.get(boatClassMasterdata);
|
||||
if(!boatClassMap.containsKey(windCluster)) {
|
||||
boatClassMap.put(windCluster, createInitialTriple());
|
||||
}
|
||||
Triple<Integer, Double, Double> triple = boatClassMap.get(windCluster);
|
||||
Integer dataCount = triple.getA();
|
||||
Double sum = triple.getB();
|
||||
dataCount = dataCount + 1;
|
||||
sum = sum + roundedAngle;
|
||||
double average = sum / (double) dataCount;
|
||||
|
||||
boatClassMap.put(windCluster, new Triple<Integer, Double, Double>(dataCount, sum, average));
|
||||
}
|
||||
|
||||
private Triple<Integer, Double, Double> createInitialTriple() {
|
||||
return new Triple<Integer, Double, Double>(0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
Double getAverageAngle(BoatClassMasterdata boatClassMasterdata, Speed windSpeed) {
|
||||
Double result = null;
|
||||
if (dataCountAndAngleSumMap.containsKey(boatClassMasterdata)) {
|
||||
Map<Cluster<Speed>, Triple<Integer, Double, Double>> boatClassMap = dataCountAndAngleSumMap.get(boatClassMasterdata);
|
||||
Cluster<Speed> windCluster = speedClusterGroup.getClusterFor(windSpeed);
|
||||
if(boatClassMap.containsKey(windCluster)) {
|
||||
Triple<Integer, Double, Double> triple = boatClassMap.get(windCluster);
|
||||
result = triple.getC();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
+3
-3
@@ -3,10 +3,10 @@ package com.sap.sailing.polars.mining;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
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.common.Bearing;
|
||||
import com.sap.sailing.domain.common.BoatClassMasterdata;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
@@ -93,8 +93,8 @@ public class GPSFixMovingWithPolarContext implements PolarClusterKey {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoatClass getBoatClass() {
|
||||
return race.getRace().getBoatClass();
|
||||
public BoatClassMasterdata getBoatClassMasterData() {
|
||||
return BoatClassMasterdata.resolveBoatClass(race.getRace().getBoatClass().getName());
|
||||
}
|
||||
|
||||
private Set<WindSource> collectWindSourcesToIgnoreForBearing() {
|
||||
|
||||
Regular → Executable
+99
-22
@@ -9,13 +9,19 @@ import java.util.logging.Logger;
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
|
||||
import com.sap.sailing.domain.base.SpeedWithConfidence;
|
||||
import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl;
|
||||
import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl;
|
||||
import com.sap.sailing.domain.common.Bearing;
|
||||
import com.sap.sailing.domain.common.BoatClassMasterdata;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.common.confidence.BearingWithConfidence;
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
|
||||
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
|
||||
import com.sap.sailing.domain.tracking.WindWithConfidence;
|
||||
import com.sap.sailing.polars.analysis.PolarSheetAnalyzer;
|
||||
import com.sap.sailing.polars.regression.BoatSpeedEstimator;
|
||||
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
@@ -28,18 +34,28 @@ import com.sap.sse.datamining.factories.GroupKeyFactory;
|
||||
import com.sap.sse.datamining.impl.components.GroupedDataEntry;
|
||||
import com.sap.sse.datamining.shared.GroupKey;
|
||||
|
||||
public class IncrementalRegressionProcessor implements Processor<GroupedDataEntry<GPSFixMovingWithPolarContext>,Void> {
|
||||
|
||||
public class IncrementalRegressionProcessor implements Processor<GroupedDataEntry<GPSFixMovingWithPolarContext>, Void>,
|
||||
PolarSheetAnalyzer {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(IncrementalRegressionProcessor.class.getName());
|
||||
|
||||
private final Map<GroupKey, BoatSpeedEstimator> boatSpeedEstimators = new HashMap<GroupKey, BoatSpeedEstimator>();
|
||||
|
||||
private final ClusterGroup<Speed> speedClusterGroup;
|
||||
|
||||
private final Set<BoatClass> availableBoatClasses = new HashSet<>();
|
||||
|
||||
private final Set<BoatClassMasterdata> availableBoatClasses = new HashSet<>();
|
||||
|
||||
private final AverageAngleContainer upwindStarboardAverageAngleContainer;
|
||||
private final AverageAngleContainer downwindStarboardAverageAngleContainer;
|
||||
private final AverageAngleContainer upwindPortAverageAngleContainer;
|
||||
private final AverageAngleContainer downwindPortAverageAngleContainer;
|
||||
|
||||
public IncrementalRegressionProcessor(ClusterGroup<Speed> speedClusterGroup) {
|
||||
this.speedClusterGroup = speedClusterGroup;
|
||||
this.upwindStarboardAverageAngleContainer = new AverageAngleContainer(speedClusterGroup);
|
||||
this.downwindStarboardAverageAngleContainer = new AverageAngleContainer(speedClusterGroup);
|
||||
this.upwindPortAverageAngleContainer = new AverageAngleContainer(speedClusterGroup);
|
||||
this.downwindPortAverageAngleContainer = new AverageAngleContainer(speedClusterGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,7 +65,7 @@ public class IncrementalRegressionProcessor implements Processor<GroupedDataEntr
|
||||
synchronized (boatSpeedEstimators) {
|
||||
if (!boatSpeedEstimators.containsKey(key)) {
|
||||
boatSpeedEstimators.put(key, new BoatSpeedEstimator());
|
||||
availableBoatClasses.add(element.getDataEntry().getBoatClass());
|
||||
availableBoatClasses.add(element.getDataEntry().getBoatClassMasterData());
|
||||
}
|
||||
|
||||
boatSpeedEstimator = boatSpeedEstimators.get(key);
|
||||
@@ -57,24 +73,41 @@ public class IncrementalRegressionProcessor implements Processor<GroupedDataEntr
|
||||
GPSFixMovingWithPolarContext fix = element.getDataEntry();
|
||||
|
||||
BearingWithConfidence<Integer> angleToTheWind = fix.getAngleToTheWind();
|
||||
|
||||
|
||||
WindWithConfidence<Pair<Position, TimePoint>> windSpeed = fix.getWindSpeed();
|
||||
|
||||
|
||||
SpeedWithBearingWithConfidence<TimePoint> boatSpeedWithConfidence = fix.getBoatSpeed();
|
||||
|
||||
|
||||
//Only add GPS data if speeds and angles are not null, else do nothing!
|
||||
|
||||
// Only add GPS data if speeds and angles are not null, else do nothing!
|
||||
if (angleToTheWind != null && windSpeed != null && boatSpeedWithConfidence != null) {
|
||||
|
||||
fillAverageAngleContainer(fix, element, windSpeed);
|
||||
WindWithConfidence<Pair<Position, TimePoint>> windWithConfidenceForSpeed = windSpeed;
|
||||
|
||||
|
||||
double confidenceForWindSpeed = windWithConfidenceForSpeed.getConfidence();
|
||||
double confidenceForWindBearing = angleToTheWind.getConfidence();
|
||||
double confidenceForBoatSpeed = boatSpeedWithConfidence.getConfidence();
|
||||
double averagedConfidence = (confidenceForBoatSpeed + confidenceForWindBearing + confidenceForWindSpeed) / 3;
|
||||
boatSpeedEstimator.addData(windWithConfidenceForSpeed.getObject().getKnots(), angleToTheWind.getObject()
|
||||
.getDegrees(), boatSpeedWithConfidence.getObject().getKnots(), averagedConfidence);
|
||||
}
|
||||
boatSpeedEstimator.addData(boatSpeedWithConfidence.getObject().getKnots(), averagedConfidence);
|
||||
}
|
||||
}
|
||||
|
||||
private void fillAverageAngleContainer(GPSFixMovingWithPolarContext fix,
|
||||
GroupedDataEntry<GPSFixMovingWithPolarContext> element,
|
||||
WindWithConfidence<Pair<Position, TimePoint>> windSpeed) {
|
||||
int roundedAngle = fix.getRoundedAngleToTheWind().getAngle();
|
||||
if (roundedAngle < -90) {
|
||||
downwindPortAverageAngleContainer.addFix(element.getDataEntry().getBoatClassMasterData(),
|
||||
windSpeed.getObject(), roundedAngle);
|
||||
} else if (roundedAngle < 0) {
|
||||
upwindPortAverageAngleContainer.addFix(element.getDataEntry().getBoatClassMasterData(),
|
||||
windSpeed.getObject(), roundedAngle);
|
||||
} else if (roundedAngle > 0) {
|
||||
upwindStarboardAverageAngleContainer.addFix(element.getDataEntry().getBoatClassMasterData(),
|
||||
windSpeed.getObject(), roundedAngle);
|
||||
} else if (roundedAngle > 90) {
|
||||
downwindStarboardAverageAngleContainer.addFix(element.getDataEntry().getBoatClassMasterData(),
|
||||
windSpeed.getObject(), roundedAngle);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -109,9 +142,9 @@ public class IncrementalRegressionProcessor implements Processor<GroupedDataEntr
|
||||
}
|
||||
}
|
||||
|
||||
public SpeedWithConfidenceAndDataCount estimateBoatSpeed(final BoatClass boatClass, final Speed windSpeed,
|
||||
final Bearing angleToTheWind, boolean useLinearRegression)
|
||||
throws NotEnoughDataHasBeenAddedException {
|
||||
public Pair<SpeedWithConfidence<Void>, Integer> estimateBoatSpeed(final BoatClass boatClass, final Speed windSpeed,
|
||||
final Bearing angleToTheWind) throws NotEnoughDataHasBeenAddedException {
|
||||
int dataCount = 0;
|
||||
PolarClusterKey key = new PolarClusterKey() {
|
||||
@Override
|
||||
public RoundedAngleToTheWind getRoundedAngleToTheWind() {
|
||||
@@ -119,8 +152,8 @@ public class IncrementalRegressionProcessor implements Processor<GroupedDataEntr
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoatClass getBoatClass() {
|
||||
return boatClass;
|
||||
public BoatClassMasterdata getBoatClassMasterData() {
|
||||
return BoatClassMasterdata.resolveBoatClass(boatClass.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -141,7 +174,7 @@ public class IncrementalRegressionProcessor implements Processor<GroupedDataEntr
|
||||
throw new NotEnoughDataHasBeenAddedException();
|
||||
}
|
||||
KnotSpeedImpl speedWithoutConfidence = new KnotSpeedImpl(boatSpeedEstimator.estimateSpeed(windSpeed.getKnots(),
|
||||
angleToTheWind.getDegrees(), useLinearRegression));
|
||||
angleToTheWind.getDegrees()));
|
||||
final int dataCount = boatSpeedEstimator.getDataCount();
|
||||
double confidence = boatSpeedEstimator.getConfidence();
|
||||
return new SpeedWithConfidenceAndDataCount(new SpeedWithConfidenceImpl<Void>(
|
||||
@@ -154,7 +187,7 @@ public class IncrementalRegressionProcessor implements Processor<GroupedDataEntr
|
||||
throw new RuntimeException("Polar Data Miner failed.", failure);
|
||||
}
|
||||
|
||||
public Set<BoatClass> getAvailableBoatClasses() {
|
||||
public Set<BoatClassMasterdata> getAvailableBoatClasses() {
|
||||
return availableBoatClasses;
|
||||
}
|
||||
|
||||
@@ -170,4 +203,48 @@ public class IncrementalRegressionProcessor implements Processor<GroupedDataEntr
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageUpwindSpeedWithBearingOnStarboardTackFor(BoatClass boatClass,
|
||||
Speed windSpeed) throws NotEnoughDataHasBeenAddedException {
|
||||
Double averageAngle = upwindStarboardAverageAngleContainer.getAverageAngle(
|
||||
BoatClassMasterdata.resolveBoatClass(boatClass.getName()), windSpeed);
|
||||
return estimateSpeedForAverageAngle(boatClass, windSpeed, averageAngle);
|
||||
}
|
||||
|
||||
private SpeedWithBearingWithConfidence<Void> estimateSpeedForAverageAngle(BoatClass boatClass, Speed windSpeed,
|
||||
Double averageAngle) throws NotEnoughDataHasBeenAddedException {
|
||||
if (averageAngle == null) {
|
||||
throw new NotEnoughDataHasBeenAddedException();
|
||||
}
|
||||
DegreeBearingImpl angleToTheWind = new DegreeBearingImpl(averageAngle);
|
||||
SpeedWithConfidence<Void> estimatedSpeed = estimateBoatSpeed(boatClass, windSpeed, angleToTheWind).getA();
|
||||
SpeedWithBearing speedWithBearing = new KnotSpeedWithBearingImpl(estimatedSpeed.getObject().getKnots(),
|
||||
angleToTheWind);
|
||||
return new SpeedWithBearingWithConfidenceImpl<Void>(speedWithBearing, estimatedSpeed.getConfidence(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageDownwindSpeedWithBearingOnStarboardTackFor(
|
||||
BoatClass boatClass, Speed windSpeed) throws NotEnoughDataHasBeenAddedException {
|
||||
Double averageAngle = downwindStarboardAverageAngleContainer.getAverageAngle(
|
||||
BoatClassMasterdata.resolveBoatClass(boatClass.getName()), windSpeed);
|
||||
return estimateSpeedForAverageAngle(boatClass, windSpeed, averageAngle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageUpwindSpeedWithBearingOnPortTackFor(BoatClass boatClass,
|
||||
Speed windSpeed) throws NotEnoughDataHasBeenAddedException {
|
||||
Double averageAngle = upwindPortAverageAngleContainer.getAverageAngle(
|
||||
BoatClassMasterdata.resolveBoatClass(boatClass.getName()), windSpeed);
|
||||
return estimateSpeedForAverageAngle(boatClass, windSpeed, averageAngle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageDownwindSpeedWithBearingOnPortTackFor(BoatClass boatClass,
|
||||
Speed windSpeed) throws NotEnoughDataHasBeenAddedException {
|
||||
Double averageAngle = downwindPortAverageAngleContainer.getAverageAngle(
|
||||
BoatClassMasterdata.resolveBoatClass(boatClass.getName()), windSpeed);
|
||||
return estimateSpeedForAverageAngle(boatClass, windSpeed, averageAngle);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.sap.sailing.polars.mining;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.common.BoatClassMasterdata;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sse.datamining.data.Cluster;
|
||||
import com.sap.sse.datamining.shared.annotations.Dimension;
|
||||
@@ -22,6 +22,6 @@ public interface PolarClusterKey {
|
||||
Cluster<Speed> getWindSpeedCluster();
|
||||
|
||||
@Dimension(messageKey = "boatClass")
|
||||
BoatClass getBoatClass();
|
||||
BoatClassMasterdata getBoatClassMasterData();
|
||||
|
||||
}
|
||||
+3
-3
@@ -3,7 +3,7 @@ package com.sap.sailing.polars.mining;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.common.BoatClassMasterdata;
|
||||
import com.sap.sse.datamining.factories.FunctionFactory;
|
||||
import com.sap.sse.datamining.functions.Function;
|
||||
|
||||
@@ -17,8 +17,8 @@ public class PolarDataDimensionCollectionFactory {
|
||||
new Class<?>[0]));
|
||||
Function<WindSpeedLevel> windSpeedFunction = functionFactory.createMethodWrappingFunction(PolarClusterKey.class
|
||||
.getMethod("getWindSpeedCluster", new Class<?>[0]));
|
||||
Function<BoatClass> boatClassFunction = functionFactory.createMethodWrappingFunction(PolarClusterKey.class
|
||||
.getMethod("getBoatClass", new Class<?>[0]));
|
||||
Function<BoatClassMasterdata> boatClassFunction = functionFactory.createMethodWrappingFunction(PolarClusterKey.class
|
||||
.getMethod("getBoatClassMasterData", new Class<?>[0]));
|
||||
|
||||
dimensions.add(boatClassFunction);
|
||||
dimensions.add(angleFunction);
|
||||
|
||||
Regular → Executable
+41
-7
@@ -12,9 +12,11 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
|
||||
import com.sap.sailing.domain.base.SpeedWithConfidence;
|
||||
import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl;
|
||||
import com.sap.sailing.domain.common.Bearing;
|
||||
import com.sap.sailing.domain.common.BoatClassMasterdata;
|
||||
import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
|
||||
import com.sap.sailing.domain.common.PolarSheetsData;
|
||||
import com.sap.sailing.domain.common.PolarSheetsHistogramData;
|
||||
@@ -28,6 +30,7 @@ import com.sap.sailing.domain.common.impl.WindSteppingWithMaxDistance;
|
||||
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.polars.mining.IncrementalRegressionProcessor.SpeedWithConfidenceAndDataCount;
|
||||
import com.sap.sailing.polars.analysis.PolarSheetAnalyzer;
|
||||
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
|
||||
import com.sap.sse.datamining.components.Processor;
|
||||
import com.sap.sse.datamining.data.ClusterGroup;
|
||||
@@ -36,7 +39,7 @@ import com.sap.sse.datamining.impl.components.GroupedDataEntry;
|
||||
import com.sap.sse.datamining.impl.components.ParallelFilteringProcessor;
|
||||
import com.sap.sse.datamining.impl.components.ParallelMultiDimensionsValueNestingGroupingProcessor;
|
||||
|
||||
public class PolarDataMiner {
|
||||
public class PolarDataMiner implements PolarSheetAnalyzer {
|
||||
|
||||
private static final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3);
|
||||
private static final ThreadPoolExecutor executor = createExecutor();
|
||||
@@ -117,10 +120,9 @@ public class PolarDataMiner {
|
||||
* @return
|
||||
* @throws NotEnoughDataHasBeenAddedException
|
||||
*/
|
||||
public SpeedWithConfidence<Void> estimateBoatSpeed(BoatClass boatClass, Speed windSpeed, Bearing angleToTheWind,
|
||||
boolean useLinearRegression)
|
||||
public SpeedWithConfidence<Void> estimateBoatSpeed(BoatClass boatClass, Speed windSpeed, Bearing angleToTheWind)
|
||||
throws NotEnoughDataHasBeenAddedException {
|
||||
return incrementalRegressionProcessor.estimateBoatSpeed(boatClass, windSpeed, angleToTheWind, useLinearRegression).getSpeedWithConfidence();
|
||||
return incrementalRegressionProcessor.estimateBoatSpeed(boatClass, windSpeed, angleToTheWind).getSpeedWithConfidence();
|
||||
}
|
||||
|
||||
|
||||
@@ -144,7 +146,7 @@ public class PolarDataMiner {
|
||||
try {
|
||||
int convertedAngle = convertAngleIfNecessary(angle);
|
||||
speedWithConfidenceAndDataCount = incrementalRegressionProcessor.estimateBoatSpeed(boatClass,
|
||||
new KnotSpeedImpl(windSpeed), new DegreeBearingImpl(convertedAngle), true);
|
||||
new KnotSpeedImpl(windSpeed), new DegreeBearingImpl(convertedAngle));
|
||||
} catch (NotEnoughDataHasBeenAddedException e) {
|
||||
// No data so put in a 0 speed with 0 confidence
|
||||
speedWithConfidenceAndDataCount = new SpeedWithConfidenceAndDataCount(
|
||||
@@ -204,7 +206,7 @@ public class PolarDataMiner {
|
||||
|
||||
|
||||
|
||||
public Set<BoatClass> getAvailableBoatClasses() {
|
||||
public Set<BoatClassMasterdata> getAvailableBoatClasses() {
|
||||
return incrementalRegressionProcessor.getAvailableBoatClasses();
|
||||
}
|
||||
|
||||
@@ -216,7 +218,7 @@ public class PolarDataMiner {
|
||||
if (angle >= startAngleInclusive && angle < endAngleExclusive) {
|
||||
try {
|
||||
dataCounts[angle] = incrementalRegressionProcessor.estimateBoatSpeed(boatClass, windSpeed,
|
||||
new DegreeBearingImpl(convertAngleIfNecessary(angle)), true).getDataCount();
|
||||
new DegreeBearingImpl(convertAngleIfNecessary(angle))).getDataCount();
|
||||
} catch (NotEnoughDataHasBeenAddedException e) {
|
||||
dataCounts[angle] = 0;
|
||||
}
|
||||
@@ -227,4 +229,36 @@ public class PolarDataMiner {
|
||||
return dataCounts;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageUpwindSpeedWithBearingOnStarboardTackFor(BoatClass boatClass,
|
||||
Speed windSpeed) throws NotEnoughDataHasBeenAddedException {
|
||||
return incrementalRegressionProcessor.getAverageUpwindSpeedWithBearingOnStarboardTackFor(boatClass, windSpeed);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageDownwindSpeedWithBearingOnStarboardTackFor(
|
||||
BoatClass boatClass, Speed windSpeed) throws NotEnoughDataHasBeenAddedException {
|
||||
return incrementalRegressionProcessor.getAverageDownwindSpeedWithBearingOnStarboardTackFor(boatClass, windSpeed);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageUpwindSpeedWithBearingOnPortTackFor(BoatClass boatClass,
|
||||
Speed windSpeed) throws NotEnoughDataHasBeenAddedException {
|
||||
return incrementalRegressionProcessor.getAverageUpwindSpeedWithBearingOnPortTackFor(boatClass, windSpeed);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public SpeedWithBearingWithConfidence<Void> getAverageDownwindSpeedWithBearingOnPortTackFor(BoatClass boatClass,
|
||||
Speed windSpeed) throws NotEnoughDataHasBeenAddedException {
|
||||
return incrementalRegressionProcessor.getAverageDownwindSpeedWithBearingOnPortTackFor(boatClass, windSpeed);
|
||||
}
|
||||
|
||||
}
|
||||
+7
-3
@@ -12,14 +12,14 @@ public class RoundedAngleToTheWind {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "" + angle;
|
||||
return "" + getAngle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + angle;
|
||||
result = prime * result + getAngle();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -32,9 +32,13 @@ public class RoundedAngleToTheWind {
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
RoundedAngleToTheWind other = (RoundedAngleToTheWind) obj;
|
||||
if (angle != other.angle)
|
||||
if (getAngle() != other.getAngle())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getAngle() {
|
||||
return angle;
|
||||
}
|
||||
|
||||
}
|
||||
+26
-24
@@ -1,31 +1,31 @@
|
||||
package com.sap.sailing.polars.regression;
|
||||
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.Serializable;
|
||||
import com.sap.sailing.polars.mining.IncrementalRegressionProcessor;
|
||||
import com.sap.sailing.polars.regression.impl.IncrementalLeastSquaresProcessor;
|
||||
import com.sap.sailing.util.impl.LockUtil;
|
||||
import com.sap.sailing.util.impl.NamedReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* Combines two linear regression processors. One with the angle to the wind as the x axis and the other with the wind
|
||||
* speed on the x axis. Both y axis represent the boat speed.
|
||||
* Supplies incremental arithmetic mean for confidence and speed.
|
||||
*
|
||||
* {@link #estimateSpeed(double, double)} returns the average of the estimation of both regressions.
|
||||
* {@link #estimateSpeed(double, double)} returns the average of the speed
|
||||
*
|
||||
* Note that this class should only be used for a small interval on a polar sheet. The
|
||||
* Note that this class should only be used for a small wind interval on a polar sheet. The
|
||||
* {@link IncrementalRegressionProcessor} is one example for that. It has one instance of the {@link BoatSpeedEstimator}
|
||||
* for each wind speed level and rounded angle combination.
|
||||
* for each wind boatclass, speed level and rounded angle combination.
|
||||
*
|
||||
* @author Frederik Petersen (D054528)
|
||||
*
|
||||
*/
|
||||
public class BoatSpeedEstimator {
|
||||
public class BoatSpeedEstimator implements Serializable {
|
||||
|
||||
private IncrementalLinearRegressionProcessor angleRegression = new IncrementalLeastSquaresProcessor();
|
||||
private static final long serialVersionUID = -254184914347332658L;
|
||||
|
||||
private IncrementalLinearRegressionProcessor windSpeedRegression = new IncrementalLeastSquaresProcessor();
|
||||
|
||||
private NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock(getClass().getName(), true);
|
||||
private double speedSum = 0;
|
||||
|
||||
private transient NamedReentrantReadWriteLock lock = createLock();
|
||||
|
||||
private double confidenceSum = 0;
|
||||
|
||||
private int dataCount = 0;
|
||||
@@ -33,30 +33,24 @@ public class BoatSpeedEstimator {
|
||||
/**
|
||||
* @param useLinearRegression if false mean of wind interval is used, otherwise linear regression
|
||||
*/
|
||||
public double estimateSpeed(double windSpeed, double angleToTheWind, boolean useLinearRegression) throws NotEnoughDataHasBeenAddedException {
|
||||
public double estimateSpeed(double windSpeed, double angleToTheWind) throws NotEnoughDataHasBeenAddedException {
|
||||
LockUtil.lockForRead(lock);
|
||||
if (dataCount < 1) {
|
||||
throw new NotEnoughDataHasBeenAddedException();
|
||||
}
|
||||
double result;
|
||||
try {
|
||||
if (useLinearRegression) {
|
||||
double angleEstimated = angleRegression.getEstimatedY(angleToTheWind);
|
||||
double windSpeedEstimated = windSpeedRegression.getEstimatedY(windSpeed);
|
||||
|
||||
result = (angleEstimated + windSpeedEstimated) / 2.0;
|
||||
} else {
|
||||
//Return average for that wind interval
|
||||
result = windSpeedRegression.getMeanOfY();
|
||||
}
|
||||
result = speedSum / dataCount;
|
||||
} finally {
|
||||
LockUtil.unlockAfterRead(lock);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void addData(double windSpeed, double angleToTheWind, double boatSpeed, double confidence) {
|
||||
public void addData(double boatSpeed, double confidence) {
|
||||
LockUtil.lockForWrite(lock);
|
||||
try {
|
||||
angleRegression.addMeasuredPoint(angleToTheWind, boatSpeed);
|
||||
windSpeedRegression.addMeasuredPoint(windSpeed, boatSpeed);
|
||||
speedSum = speedSum + boatSpeed;
|
||||
dataCount++;
|
||||
confidenceSum = confidenceSum + confidence;
|
||||
} finally {
|
||||
@@ -71,5 +65,13 @@ public class BoatSpeedEstimator {
|
||||
public double getConfidence() {
|
||||
return confidenceSum / dataCount;
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream ois) {
|
||||
lock = createLock();
|
||||
}
|
||||
|
||||
private NamedReentrantReadWriteLock createLock() {
|
||||
return new NamedReentrantReadWriteLock(getClass().getName(), true);
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -42,7 +42,7 @@ public class PolarResource extends AbstractSailingServerResource {
|
||||
ResponseBuilder responseBuilder;
|
||||
try {
|
||||
speedWithConfidence = getService().getPolarDataService().getSpeed(boatClass, new KnotSpeedImpl(windSpeed),
|
||||
new DegreeBearingImpl(angle), true);
|
||||
new DegreeBearingImpl(angle));
|
||||
String resultString = "Speed: " + speedWithConfidence.getObject().getKnots() + "kn; Confidence: "
|
||||
+ speedWithConfidence.getConfidence();
|
||||
responseBuilder = Response.ok(resultString, MediaType.TEXT_PLAIN);
|
||||
|
||||
-24
@@ -88,7 +88,6 @@ import com.sap.sailing.domain.common.RegattaIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaName;
|
||||
import com.sap.sailing.domain.common.Renamable;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
|
||||
import com.sap.sailing.domain.common.WindSource;
|
||||
import com.sap.sailing.domain.common.dto.FleetDTO;
|
||||
import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO;
|
||||
@@ -1949,7 +1948,6 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
|
||||
replicate(new TrackRegatta(regatta.getRegattaIdentifier()));
|
||||
regattaTrackingCache.put(regatta, result);
|
||||
ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(result);
|
||||
addRaceTrackingFinishedListenerForPolarFixCache(result);
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
@@ -1957,28 +1955,6 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
|
||||
}
|
||||
}
|
||||
|
||||
private void addRaceTrackingFinishedListenerForPolarFixCache(DynamicTrackedRegatta result) {
|
||||
RaceListener raceListenerForPolarFixCacheUpdate = new RaceListener() {
|
||||
@Override
|
||||
public void raceRemoved(TrackedRace trackedRace) {
|
||||
// TODO remove fixes from polar fix cache
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raceAdded(final TrackedRace trackedRace) {
|
||||
trackedRace.addListener(new AbstractRaceChangeListener() {
|
||||
@Override
|
||||
public void statusChanged(TrackedRaceStatus newStatus) {
|
||||
if (newStatus.getStatus() == TrackedRaceStatusEnum.FINISHED) {
|
||||
polarDataService.newRaceFinishedTracking(trackedRace);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
result.addRaceListener(raceListenerForPolarFixCacheUpdate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DynamicTrackedRegatta getTrackedRegatta(com.sap.sailing.domain.base.Regatta regatta) {
|
||||
return regattaTrackingCache.get(regatta);
|
||||
|
||||
Reference in new issue
Block a user