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:
Axel Uhl committed 2015-01-02 15:54:51 +01:00
commit ac48ac2417
30 files changed
+403 -735

No files matched your search

@@ -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();
@@ -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;
@@ -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();
}
}
@@ -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);
@@ -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();
@@ -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;
}
@@ -1,90 +0,0 @@
package com.sap.sailing.polars.caching.test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.polars.caching.PolarFixCacheRaceInterval;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class PolarFixCacheRaceIntervalTest {
@Test
public void testIntervalJoin() {
TrackedRace mockedTrackedRace1 = mock(TrackedRace.class);
TrackedRace mockedTrackedRace2 = mock(TrackedRace.class);
Competitor mockedCompetitor1 = mock(Competitor.class);
Competitor mockedCompetitor2 = mock(Competitor.class);
Calendar calender = Calendar.getInstance();
calender.set(2014, 3, 17, 16, 0, 0);
TimePoint timePoint1 = new MillisecondsTimePoint(calender.getTime());
calender.set(2014, 3, 17, 16, 10, 0);
TimePoint timePoint2 = new MillisecondsTimePoint(calender.getTime());
PolarFixCacheRaceInterval interval1 = createInterval1(
mockedTrackedRace1, mockedTrackedRace2, mockedCompetitor1,
mockedCompetitor2, timePoint1, timePoint2);
PolarFixCacheRaceInterval interval2 = createInterval2(
mockedTrackedRace1, mockedTrackedRace2, mockedCompetitor1,
timePoint2);
PolarFixCacheRaceInterval joined = interval1.join(interval2);
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> joinedMap = joined
.getCompetitorAndTimepointsForRace();
Map<Competitor, Pair<TimePoint, TimePoint>> explicitIntervalForRace1 = joinedMap.get(mockedTrackedRace1);
assertThat(explicitIntervalForRace1, notNullValue(Map.class));
Pair<TimePoint, TimePoint> explicitIntervalForCompetitor1 = explicitIntervalForRace1.get(mockedCompetitor1);
assertThat(explicitIntervalForCompetitor1, notNullValue(Pair.class));
TimePoint start1 = explicitIntervalForCompetitor1.getA();
TimePoint end1 = explicitIntervalForCompetitor1.getB();
assertThat(start1, is(timePoint1));
assertThat(end1, is(timePoint2));
}
private PolarFixCacheRaceInterval createInterval1(
TrackedRace mockedTrackedRace1, TrackedRace mockedTrackedRace2,
Competitor mockedCompetitor1, Competitor mockedCompetitor2,
TimePoint timePoint1, TimePoint timePoint2) {
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> competitorAndTimepointsForRace = new HashMap<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>>();
HashMap<Competitor, Pair<TimePoint, TimePoint>> mapForRace1 = new HashMap<Competitor, Pair<TimePoint, TimePoint>>();
mapForRace1.put(mockedCompetitor1, new Pair<TimePoint, TimePoint>(timePoint1, timePoint1));
competitorAndTimepointsForRace.put(mockedTrackedRace1, mapForRace1);
HashMap<Competitor, Pair<TimePoint, TimePoint>> mapForRace2 = new HashMap<Competitor, Pair<TimePoint, TimePoint>>();
mapForRace2.put(mockedCompetitor2, new Pair<TimePoint, TimePoint>(timePoint1, timePoint2));
competitorAndTimepointsForRace.put(mockedTrackedRace2, mapForRace2);
PolarFixCacheRaceInterval interval1 = new PolarFixCacheRaceInterval(
competitorAndTimepointsForRace);
return interval1;
}
private PolarFixCacheRaceInterval createInterval2(
TrackedRace mockedTrackedRace1, TrackedRace mockedTrackedRace2,
Competitor mockedCompetitor1, TimePoint timePoint2) {
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> competitorAndTimepointsForRace = new HashMap<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>>();
HashMap<Competitor, Pair<TimePoint, TimePoint>> mapForRace1 = new HashMap<Competitor, Pair<TimePoint, TimePoint>>();
mapForRace1.put(mockedCompetitor1, new Pair<TimePoint, TimePoint>(
timePoint2, timePoint2));
competitorAndTimepointsForRace.put(mockedTrackedRace1, mapForRace1);
PolarFixCacheRaceInterval interval2 = new PolarFixCacheRaceInterval(
competitorAndTimepointsForRace);
return interval2;
}
}
@@ -58,6 +58,8 @@ public class PolarDataMinerTest {
PolarDataMiner miner = new PolarDataMiner();
BoatClass mockedBoatClass = mock(BoatClass.class);
when(mockedBoatClass.getName()).thenReturn("49ER");
GPSFixMoving fix1_1 = createMockedFix(13, 00, 54.431952, 10.186767, 45, 10.5);
GPSFixMoving fix1_2 = createMockedFix(13, 30, 54.485034, 10.538303, 44.8, 10.6);
@@ -107,17 +109,17 @@ public class PolarDataMinerTest {
}
SpeedWithConfidence<Void> estimatedSpeed1 = miner.estimateBoatSpeed(mockedBoatClass, new KnotSpeedImpl(15),
new DegreeBearingImpl(44.9), true);
new DegreeBearingImpl(44.9));
assertThat(estimatedSpeed1, is(notNullValue()));
assertThat(estimatedSpeed1.getObject().getKnots(), is(closeTo(10.55, EPSILON)));
SpeedWithConfidence<Void> estimatedSpeed2 = miner.estimateBoatSpeed(mockedBoatClass, new KnotSpeedImpl(15),
new DegreeBearingImpl(42), true);
new DegreeBearingImpl(42));
assertThat(estimatedSpeed2, is(notNullValue()));
assertThat(estimatedSpeed2.getObject().getKnots(), is(closeTo(10.05, EPSILON)));
SpeedWithConfidence<Void> estimatedSpeed3 = miner.estimateBoatSpeed(mockedBoatClass, new KnotSpeedImpl(15),
new DegreeBearingImpl(42.8), true);
new DegreeBearingImpl(42.8));
assertThat(estimatedSpeed3, is(notNullValue()));
assertThat(estimatedSpeed3.getObject().getKnots(), is(closeTo(10, EPSILON)));
}
@@ -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;
+14 -24
View File
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -1,27 +0,0 @@
package com.sap.sailing.polars.caching;
import java.util.Set;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.polars.data.PolarFix;
public class NoCacheEntryException extends Exception {
private static final long serialVersionUID = 5547683568438235668L;
private final Set<TrackedRace> notCached;
private final Set<PolarFix> resultList;
public NoCacheEntryException(Set<TrackedRace> notCached, Set<PolarFix> resultList) {
this.notCached = notCached;
this.resultList = resultList;
}
public Set<TrackedRace> getNotCached() {
return notCached;
}
public Set<PolarFix> getCachedResultList() {
return resultList;
}
}
@@ -1,67 +0,0 @@
package com.sap.sailing.polars.caching;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.polars.data.PolarFix;
import com.sap.sailing.util.SmartFutureCache;
public class PolarFixCache extends
SmartFutureCache<BoatClass, Map<RegattaAndRaceIdentifier, List<PolarFix>>, PolarFixCacheRaceInterval> {
private final List<PolarFixCacheUpdateDoneListener> cacheUpdateDoneListeners = new ArrayList<PolarFixCacheUpdateDoneListener>();
public PolarFixCache(Executor executor) {
super(new PolarFixCacheUpdater(executor), "polarFixCache");
}
@Override
protected void cache(BoatClass key, Map<RegattaAndRaceIdentifier, List<PolarFix>> value) {
super.cache(key, value);
synchronized (cacheUpdateDoneListeners) {
for (PolarFixCacheUpdateDoneListener listener : cacheUpdateDoneListeners) {
listener.cacheUpdateDoneForBoatClass(key);
}
}
}
public void addListener(PolarFixCacheUpdateDoneListener listener) {
synchronized (cacheUpdateDoneListeners) {
cacheUpdateDoneListeners.add(listener);
}
}
public void removeListener(PolarFixCacheUpdateDoneListener listener) {
synchronized (cacheUpdateDoneListeners) {
if (cacheUpdateDoneListeners.contains(listener)) {
cacheUpdateDoneListeners.remove(listener);
}
}
}
public Set<PolarFix> getFixesForTrackedRaces(Set<TrackedRace> trackedRaces) throws NoCacheEntryException {
Set<PolarFix> resultList = new HashSet<PolarFix>();
Set<TrackedRace> notCached = new HashSet<TrackedRace>();
for (TrackedRace trackedRace : trackedRaces) {
Map<RegattaAndRaceIdentifier, List<PolarFix>> result = get(trackedRace.getRace().getBoatClass(), false);
List<PolarFix> resultForRace = result.get(trackedRace.getRaceIdentifier());
if (resultForRace == null) {
notCached.add(trackedRace);
} else {
resultList.addAll(resultForRace);
}
}
if (notCached.size() > 0) {
throw new NoCacheEntryException(notCached, resultList);
}
return resultList;
}
}
@@ -1,97 +0,0 @@
package com.sap.sailing.polars.caching;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.polars.aggregation.PolarFixRaceInterval;
import com.sap.sailing.util.SmartFutureCache.UpdateInterval;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
public class PolarFixCacheRaceInterval implements UpdateInterval<PolarFixCacheRaceInterval>, PolarFixRaceInterval {
private final Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> competitorAndTimepointsForRace;
public PolarFixCacheRaceInterval(
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> competitorAndTimepointsForRace) {
this.competitorAndTimepointsForRace = competitorAndTimepointsForRace;
}
@Override
public Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> getCompetitorAndTimepointsForRace() {
return competitorAndTimepointsForRace;
}
@Override
public PolarFixCacheRaceInterval join(PolarFixCacheRaceInterval otherUpdateInterval) {
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> joinedInterval = new HashMap<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>>();
Set<TrackedRace> racesContainedInEitherMap = new HashSet<TrackedRace>();
for (TrackedRace race : competitorAndTimepointsForRace.keySet()) {
racesContainedInEitherMap.add(race);
}
Map<TrackedRace, Map<Competitor, Pair<TimePoint, TimePoint>>> otherUpdateIntervalInExplicitForm = otherUpdateInterval
.getCompetitorAndTimepointsForRace();
for (TrackedRace race : otherUpdateIntervalInExplicitForm.keySet()) {
racesContainedInEitherMap.add(race);
}
for (TrackedRace race : racesContainedInEitherMap) {
HashMap<Competitor, Pair<TimePoint, TimePoint>> joinedMap = new HashMap<Competitor, Pair<TimePoint, TimePoint>>();
Map<Competitor, Pair<TimePoint, TimePoint>> mapOfTimpointCompetitorPairs = competitorAndTimepointsForRace
.get(race);
if (mapOfTimpointCompetitorPairs != null) {
joinedMap.putAll(mapOfTimpointCompetitorPairs);
}
Map<Competitor, Pair<TimePoint, TimePoint>> mapOfOtherTimpointCompetitorPairs = otherUpdateIntervalInExplicitForm
.get(race);
if (mapOfOtherTimpointCompetitorPairs != null) {
mergeMap(joinedMap, mapOfOtherTimpointCompetitorPairs);
}
joinedInterval.put(race, joinedMap);
}
return new PolarFixCacheRaceInterval(joinedInterval);
}
private void mergeMap(HashMap<Competitor, Pair<TimePoint, TimePoint>> joinedMap,
Map<Competitor, Pair<TimePoint, TimePoint>> mapOfOtherTimpointCompetitorPairs) {
for (Entry<Competitor, Pair<TimePoint, TimePoint>> otherEntry : mapOfOtherTimpointCompetitorPairs.entrySet()) {
Competitor competitor = otherEntry.getKey();
Pair<TimePoint, TimePoint> otherValue = otherEntry.getValue();
Pair<TimePoint, TimePoint> existingValue = joinedMap.get(competitor);
if (existingValue != null) {
Pair<TimePoint, TimePoint> joinedTimepointInterval = joinTimepointIntervals(existingValue, otherValue);
joinedMap.put(competitor, joinedTimepointInterval);
} else {
joinedMap.put(competitor, otherValue);
}
}
}
/**
* This method ignores a possible gap between the two intervals.
*
* @param existingValue
* @param otherValue
* @return
*/
private Pair<TimePoint, TimePoint> joinTimepointIntervals(Pair<TimePoint, TimePoint> existingValue,
Pair<TimePoint, TimePoint> otherValue) {
TimePoint startExisting = existingValue.getA();
TimePoint endExisting = existingValue.getB();
TimePoint startOther = otherValue.getA();
TimePoint endOther = otherValue.getB();
TimePoint startNew = startExisting.before(startOther) ? startExisting : startOther;
TimePoint endNew = endExisting.after(endOther) ? endExisting : endOther;
return new Pair<TimePoint, TimePoint>(startNew, endNew);
}
}
@@ -1,9 +0,0 @@
package com.sap.sailing.polars.caching;
import com.sap.sailing.domain.base.BoatClass;
public interface PolarFixCacheUpdateDoneListener {
void cacheUpdateDoneForBoatClass(BoatClass key);
}
@@ -1,56 +0,0 @@
package com.sap.sailing.polars.caching;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Executor;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.impl.PolarSheetGenerationSettingsImpl;
import com.sap.sailing.polars.aggregation.PolarFixAggregator;
import com.sap.sailing.polars.data.PolarFix;
import com.sap.sailing.util.SmartFutureCache.CacheUpdater;
public class PolarFixCacheUpdater implements
CacheUpdater<BoatClass, Map<RegattaAndRaceIdentifier, List<PolarFix>>, PolarFixCacheRaceInterval> {
private final Executor executor;
public PolarFixCacheUpdater(Executor executor) {
this.executor = executor;
}
@Override
public Map<RegattaAndRaceIdentifier, List<PolarFix>> computeCacheUpdate(BoatClass key,
PolarFixCacheRaceInterval updateInterval) throws Exception {
PolarFixAggregator aggregator = new PolarFixAggregator(updateInterval,
PolarSheetGenerationSettingsImpl.createStandardPolarSettings(), executor);
Thread.sleep(updateInterval.getCompetitorAndTimepointsForRace().keySet().iterator().next()
.getMillisecondsOverWhichToAverageSpeed() / 2);
aggregator.startPolarFixAggregation();
return aggregator.get();
}
@Override
public Map<RegattaAndRaceIdentifier, List<PolarFix>> provideNewCacheValue(BoatClass key,
Map<RegattaAndRaceIdentifier, List<PolarFix>> oldCacheValue,
Map<RegattaAndRaceIdentifier, List<PolarFix>> computedCacheUpdate, PolarFixCacheRaceInterval updateInterval) {
Map<RegattaAndRaceIdentifier, List<PolarFix>> newCacheValue = new HashMap<RegattaAndRaceIdentifier, List<PolarFix>>();
if (oldCacheValue != null) {
newCacheValue.putAll(oldCacheValue);
}
for (Entry<RegattaAndRaceIdentifier, List<PolarFix>> newEntry : computedCacheUpdate.entrySet()) {
List<PolarFix> oldList = newCacheValue.get(newEntry.getKey());
if (oldList != null) {
oldList.addAll(newEntry.getValue());
} else {
newCacheValue.put(newEntry.getKey(), newEntry.getValue());
}
}
return newCacheValue;
}
}
@@ -1,21 +0,0 @@
package com.sap.sailing.polars.caching;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.common.PolarSheetsData;
import com.sap.sailing.polars.PolarDataService;
import com.sap.sailing.util.SmartFutureCache;
public class PolarSheetPerBoatClassCache extends
SmartFutureCache<BoatClass, PolarSheetsData, com.sap.sailing.util.SmartFutureCache.EmptyUpdateInterval>
implements PolarFixCacheUpdateDoneListener {
public PolarSheetPerBoatClassCache(PolarDataService polarDataService) {
super(new PolarSheetPerBoatClassCacheUpdater(polarDataService), "PolarSheetPerBoatClassCache");
}
@Override
public void cacheUpdateDoneForBoatClass(BoatClass key) {
triggerUpdate(key, new EmptyUpdateInterval());
}
}
@@ -1,36 +0,0 @@
package com.sap.sailing.polars.caching;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
import com.sap.sailing.domain.common.PolarSheetsData;
import com.sap.sailing.domain.common.impl.PolarSheetGenerationSettingsImpl;
import com.sap.sailing.polars.PolarDataService;
import com.sap.sailing.polars.generation.PolarSheetGenerator;
import com.sap.sailing.util.SmartFutureCache.CacheUpdater;
import com.sap.sailing.util.SmartFutureCache.EmptyUpdateInterval;
public class PolarSheetPerBoatClassCacheUpdater implements
CacheUpdater<BoatClass, PolarSheetsData, EmptyUpdateInterval> {
private final PolarDataService polarDataService;
public PolarSheetPerBoatClassCacheUpdater(PolarDataService polarDataService) {
this.polarDataService = polarDataService;
}
@Override
public PolarSheetsData computeCacheUpdate(BoatClass key, EmptyUpdateInterval updateInterval) throws Exception {
PolarSheetGenerationSettings settings = PolarSheetGenerationSettingsImpl.createStandardPolarSettings();
PolarSheetGenerator generator = new PolarSheetGenerator(polarDataService.getPolarFixesForBoatClass(key),
settings);
PolarSheetsData generationResult = generator.generate();
return generationResult;
}
@Override
public PolarSheetsData provideNewCacheValue(BoatClass key, PolarSheetsData oldCacheValue,
PolarSheetsData computedCacheUpdate, EmptyUpdateInterval updateInterval) {
return computedCacheUpdate;
}
}
@@ -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;
// }
}
@@ -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,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() {
@@ -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,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);
@@ -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);
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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);
@@ -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);