PolarSheetGeneration is now happening on a per windspeed basis

This commit is contained in:
Frederik Petersen
2013-02-08 11:28:58 +01:00
parent ce80f0e211
commit d30aea90ec
15 changed files with 170 additions and 79 deletions
@@ -4,12 +4,12 @@ import java.io.Serializable;
public interface PolarSheetsData extends Serializable {
Number[] getValues();
Number[][] getAveragedPolarDataByWindSpeed();
int getDataCount();
boolean isComplete();
Integer[] getDataCountPerAngle();
Integer[] getDataCountPerAngleForWindspeed(int beaufort);
}
@@ -1,32 +1,34 @@
package com.sap.sailing.domain.common.impl;
import java.util.Map;
import com.sap.sailing.domain.common.PolarSheetsData;
public class PolarSheetsDataImpl implements PolarSheetsData {
private static final long serialVersionUID = -4649254807341866894L;
private Number[] values;
private Number[][] averagedPolarDataByWindSpeed;
private boolean complete;
private int dataCount;
private Integer[] dataCountPerAngle;
private Map<Integer,Integer[]> dataCountPerAngleForWindspeed;
//For GWT Serialization
PolarSheetsDataImpl() {};
public PolarSheetsDataImpl(Number[] values, boolean complete, int dataCount, Integer[] dataCountPerAngle) {
this.values = values;
public PolarSheetsDataImpl(Number[][] averagedPolarDataByWindSpeed, boolean complete, int dataCount, Map<Integer,Integer[]> dataCountPerAngleForWindspeed) {
this.averagedPolarDataByWindSpeed = averagedPolarDataByWindSpeed;
this.complete = complete;
this.dataCount = dataCount;
this.dataCountPerAngle = dataCountPerAngle;
this.dataCountPerAngleForWindspeed = dataCountPerAngleForWindspeed;
}
@Override
public Number[] getValues() {
return values;
public Number[][] getAveragedPolarDataByWindSpeed() {
return averagedPolarDataByWindSpeed;
}
@Override
@@ -39,9 +41,10 @@ public class PolarSheetsDataImpl implements PolarSheetsData {
return dataCount;
}
@Override
public Integer[] getDataCountPerAngle() {
return dataCountPerAngle;
public Integer[] getDataCountPerAngleForWindspeed(int beaufort) {
return dataCountPerAngleForWindspeed.get(beaufort);
}
}
@@ -74,9 +74,9 @@ public class PolarSheetGenerationTest {
PolarSheetsData data = resultContainer.getPolarData();
Assert.assertEquals(4, data.getDataCount());
Assert.assertEquals(4.0, data.getValues()[45]);
Assert.assertEquals(2.0, data.getValues()[55]);
Assert.assertEquals(6.0, data.getValues()[30]);
Assert.assertEquals(4.0, data.getAveragedPolarDataByWindSpeed()[1][45]);
Assert.assertEquals(2.0, data.getAveragedPolarDataByWindSpeed()[1][55]);
Assert.assertEquals(6.0, data.getAveragedPolarDataByWindSpeed()[1][30]);
}
@@ -0,0 +1,11 @@
package com.sap.sailing.domain.polarsheets;
import com.sap.sailing.domain.common.Speed;
public interface BoatAndWindSpeed {
public Speed getBoatSpeed();
public Speed getWindSpeed();
}
@@ -0,0 +1,28 @@
package com.sap.sailing.domain.polarsheets;
import com.sap.sailing.domain.common.Speed;
public class BoatAndWindSpeedImpl implements BoatAndWindSpeed{
private Speed boatSpeed;
private Speed windSpeed;
public BoatAndWindSpeedImpl(Speed boatSpeed, Speed windSpeed) {
this.boatSpeed = boatSpeed;
this.windSpeed = windSpeed;
}
@Override
public Speed getBoatSpeed() {
return boatSpeed;
}
@Override
public Speed getWindSpeed() {
return windSpeed;
}
}
@@ -49,7 +49,7 @@ public class PerRaceAndCompetitorPolarSheetGenerationWorker implements Callable<
private void optimizeEndTime() {
NavigableSet<MarkPassing> markPassings = race.getMarkPassings(competitor);
if (markPassings.size() < 1) {
if (markPassings == null || markPassings.size() < 1) {
return;
}
MarkPassing passedFinish = markPassings.last();
@@ -61,7 +61,7 @@ public class PerRaceAndCompetitorPolarSheetGenerationWorker implements Callable<
private void optimizeStartTime() {
NavigableSet<MarkPassing> markPassings = race.getMarkPassings(competitor);
if (markPassings.size() < 1) {
if (markPassings == null || markPassings.size() < 1) {
return;
}
MarkPassing passedStart = markPassings.first();
@@ -89,17 +89,13 @@ public class PerRaceAndCompetitorPolarSheetGenerationWorker implements Callable<
}
SpeedWithBearing speedWithBearing = fix.getSpeed();
double speed = speedWithBearing.getKnots();
Bearing bearing = speedWithBearing.getBearing();
Position position = fix.getPosition();
Wind wind = race.getWind(position, fix.getTimePoint());
Bearing windBearing = wind.getFrom();
// TODO windspeed stuff
// double windSpeed = wind.getKnots();
double normalizedSpeed = speed;
double angleToWind = bearing.getDifferenceTo(windBearing).getDegrees();
polarSheetGenerationWorker.addPolarData(Math.round(angleToWind), normalizedSpeed);
polarSheetGenerationWorker.addPolarData(Math.round(angleToWind), speedWithBearing, wind);
}
track.unlockAfterRead();
@@ -1,8 +1,10 @@
package com.sap.sailing.domain.polarsheets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
@@ -11,6 +13,7 @@ import java.util.concurrent.RunnableFuture;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.common.PolarSheetsData;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.impl.PolarSheetsDataImpl;
import com.sap.sailing.domain.tracking.TrackedRace;
@@ -27,7 +30,7 @@ public class PolarSheetGenerationWorker {
private final Set<RunnableFuture<Void>> workers;
private final List<List<Double>> polarData;
private final List<List<BoatAndWindSpeed>> polarData;
private final Executor executor;
@@ -63,10 +66,10 @@ public class PolarSheetGenerationWorker {
}
}
private List<List<Double>> initializePolarDataContainer() {
List<List<Double>> container = new ArrayList<List<Double>>();
private List<List<BoatAndWindSpeed>> initializePolarDataContainer() {
List<List<BoatAndWindSpeed>> container = new ArrayList<List<BoatAndWindSpeed>>();
for (int i = 0; i < 360; i++) {
container.add(new ArrayList<Double>());
container.add(new ArrayList<BoatAndWindSpeed>());
}
return container;
}
@@ -86,15 +89,18 @@ public class PolarSheetGenerationWorker {
*
* @param roundedAngle
* boat's angle to the wind
* @param speed
* boat's speed
* @param boatSpeed
* boat's speed in knots
* @param windSpeed
* wind's speed in knots
*/
protected void addPolarData(long roundedAngle, double speed) {
protected void addPolarData(long roundedAngle, Speed boatSpeed, Speed windSpeed) {
int angle = (int) roundedAngle;
if (angle < 0) {
angle = (360 + angle);
}
polarData.get(angle).add(speed);
BoatAndWindSpeed speeds = new BoatAndWindSpeedImpl(boatSpeed, windSpeed);
polarData.get(angle).add(speeds);
}
/**
@@ -104,26 +110,41 @@ public class PolarSheetGenerationWorker {
* product.
*/
public PolarSheetsData getPolarData() {
Number[] averagedPolarData = new Number[360];
Number[][] averagedPolarDataByWindSpeed = new Number[13][360];
Integer[] dataCountPerAngle = new Integer[360];
int dataCount = 0;
Map<Integer, Integer[]> dataCountPerAngleForWindspeed = new HashMap<Integer, Integer[]>();
for (int i = 0; i < 13; i++) {
dataCountPerAngleForWindspeed.put(i, new Integer[360]);
}
for (int i = 0; i < 360; i++) {
// Avoid Concurrent modification, lock would slow things down
Double[] values = polarData.get(i).toArray(new Double[polarData.get(i).size()]);
BoatAndWindSpeed[] values = polarData.get(i).toArray(new BoatAndWindSpeed[polarData.get(i).size()]);
dataCount = dataCount + values.length;
dataCountPerAngle[i] = values.length;
if (values.length < 1) {
averagedPolarData[i] = 0;
} else {
double sum = 0;
for (Double singleData : values) {
if (singleData != null) {
sum = sum + singleData;
}
double[] sumsPerWindSpeed = new double[13];
int[] dataCountPerWindSpeed = new int[13];
for (BoatAndWindSpeed singleDataPoint : values) {
if (singleDataPoint != null) {
int windSpeed = (int) singleDataPoint.getWindSpeed().getBeaufort();
//TODO enable different kinds of metrics for boats speed
sumsPerWindSpeed[windSpeed] = sumsPerWindSpeed[windSpeed] + singleDataPoint.getBoatSpeed().getKnots();
dataCountPerWindSpeed[windSpeed]++;
}
double average = sum / values.length;
averagedPolarData[i] = average;
}
for (int j = 0; j < 13; j++) {
Double average = sumsPerWindSpeed[j] / dataCountPerWindSpeed[j];
if (average.isNaN()) {
average = new Double(0);
}
averagedPolarDataByWindSpeed[j][i] = average;
dataCountPerAngleForWindspeed.get(j)[i] = dataCountPerWindSpeed[j];
}
}
boolean complete = true;
for (RunnableFuture<Void> future : workers) {
@@ -133,7 +154,8 @@ public class PolarSheetGenerationWorker {
}
}
PolarSheetsData data = new PolarSheetsDataImpl(averagedPolarData, complete, dataCount, dataCountPerAngle);
PolarSheetsData data = new PolarSheetsDataImpl(averagedPolarDataByWindSpeed, complete, dataCount,
dataCountPerAngleForWindspeed);
return data;
}
@@ -144,7 +166,7 @@ public class PolarSheetGenerationWorker {
* @return The complete set of datapoints that have been added. Can be quite big, depending on the amount of races
* and competitors.
*/
public List<List<Double>> getCompleteData() {
public List<List<BoatAndWindSpeed>> getCompleteData() {
return polarData;
}
@@ -77,9 +77,9 @@ public class PolarSheetGenerationServiceTest {
Assert.assertNotNull(results);
Assert.assertEquals(4, results.getDataCount());
Assert.assertEquals(4.0, results.getValues()[45]);
Assert.assertEquals(2.0, results.getValues()[55]);
Assert.assertEquals(6.0, results.getValues()[30]);
Assert.assertEquals(4.0, results.getAveragedPolarDataByWindSpeed()[1][45]);
Assert.assertEquals(2.0, results.getAveragedPolarDataByWindSpeed()[1][55]);
Assert.assertEquals(6.0, results.getAveragedPolarDataByWindSpeed()[1][30]);
@@ -9,22 +9,24 @@ public class GetPolarSheetDataByAngleAction extends DefaultAsyncAction<PolarShee
private final SailingServiceAsync sailingService;
private final String polarSheetId;
private final int angle;
private final int windSpeed;
public GetPolarSheetDataByAngleAction(SailingServiceAsync sailingService,
String polarSheetId, int angle, AsyncCallback<PolarSheetsHistogramData> callback) {
String polarSheetId, int angle, int windSpeed, AsyncCallback<PolarSheetsHistogramData> callback) {
super(callback);
this.sailingService = sailingService;
this.polarSheetId = polarSheetId;
this.angle = angle;
this.windSpeed = windSpeed;
}
@Override
public void execute(AsyncActionsExecutor asyncActionsExecutor) {
sailingService.getPolarSheetData(polarSheetId, angle, (AsyncCallback<PolarSheetsHistogramData>) getWrapperCallback(asyncActionsExecutor));
sailingService.getPolarSheetData(polarSheetId, angle, windSpeed, (AsyncCallback<PolarSheetsHistogramData>) getWrapperCallback(asyncActionsExecutor));
}
}
@@ -291,5 +291,5 @@ public interface SailingService extends RemoteService {
PolarSheetsData getPolarSheetsGenerationResults(String id);
PolarSheetsHistogramData getPolarSheetData(String polarSheetId, int angle);
PolarSheetsHistogramData getPolarSheetData(String polarSheetId, int angle, int windSpeed);
}
@@ -443,5 +443,5 @@ public interface SailingServiceAsync {
void getPolarSheetsGenerationResults(String id, AsyncCallback<PolarSheetsData> asyncCallback);
void getPolarSheetData(String polarSheetId, int angle, AsyncCallback<PolarSheetsHistogramData> wrapperCallback);
void getPolarSheetData(String polarSheetId, int angle, int windSpeed, AsyncCallback<PolarSheetsHistogramData> wrapperCallback);
}
@@ -24,13 +24,13 @@ public class PolarSheetsChartPanel extends SimplePanel implements RequiresResize
private StringMessages stringMessages;
private Chart chart;
private Map<String, Series> seriesMap;
private Map<String, Series[]> seriesMap;
public PolarSheetsChartPanel(StringMessages stringMessages) {
this.stringMessages = stringMessages;
setSize("100%", "100%");
setPixelSize(800, 800);
chart = createPolarSheetChart();
seriesMap = new HashMap<String, Series>();
seriesMap = new HashMap<String, Series[]>();
setWidget(chart);
}
@@ -45,11 +45,14 @@ public class PolarSheetsChartPanel extends SimplePanel implements RequiresResize
private void newSeries(String name) {
if (!seriesMap.containsKey(name)) {
Number[] forEachDeg = initializeDataForNewSeries();
Series newSeries = chart.createSeries().setPoints(forEachDeg);
newSeries.setName(name);
chart.addSeries(newSeries);
seriesMap.put(name, newSeries);
Series[] seriesPerWindSpeed = new Series[13];
for (int i = 0; i < 13; i++) {
Number[] forEachDeg = initializeDataForNewSeries();
seriesPerWindSpeed[i] = chart.createSeries().setPoints(forEachDeg);
seriesPerWindSpeed[i].setName(name+"-"+(i));
chart.addSeries(seriesPerWindSpeed[i]);
}
seriesMap.put(name, seriesPerWindSpeed);
} else {
// TODO exception handling
}
@@ -62,24 +65,26 @@ public class PolarSheetsChartPanel extends SimplePanel implements RequiresResize
private void addValuesToSeries(String seriesId, PolarSheetsData result) {
if (seriesMap.containsKey(seriesId)) {
Series series = seriesMap.get(seriesId);
series.setPoints(result.getValues(), false);
Point[] points = createPointsWithMarkerAlphaAccordingToDataCount(result);
if (points != null) {
series.setPoints(points);
for (int i = 0; i < 13; i++) {
Series series = seriesMap.get(seriesId)[i];
series.setPoints(result.getAveragedPolarDataByWindSpeed()[i], false);
Point[] points = createPointsWithMarkerAlphaAccordingToDataCount(result, i);
if (points != null) {
series.setPoints(points);
}
}
}
}
private Point[] createPointsWithMarkerAlphaAccordingToDataCount(PolarSheetsData result) {
private Point[] createPointsWithMarkerAlphaAccordingToDataCount(PolarSheetsData result, int beaufort) {
Point[] points = new Point[360];
List<Integer> dataCountList = Arrays.asList(result.getDataCountPerAngle());
List<Integer> dataCountList = Arrays.asList(result.getDataCountPerAngleForWindspeed(beaufort));
Integer max = Collections.max(dataCountList);
if (max <= 0) {
return null;
}
for (int i = 0; i < 360; i++) {
points[i] = new Point(result.getValues()[i]);
points[i] = new Point(result.getAveragedPolarDataByWindSpeed()[beaufort][i]);
if (points[i] == null) {
points[i] = new Point(0);
}
@@ -109,8 +114,10 @@ public class PolarSheetsChartPanel extends SimplePanel implements RequiresResize
public void removeSeries(String seriesId) {
if (seriesMap.containsKey(seriesId)) {
Series series = seriesMap.get(seriesId);
chart.removeSeries(series);
Series[] seriesPerWindSpeed = seriesMap.get(seriesId);
for (Series series : seriesPerWindSpeed) {
chart.removeSeries(series);
}
seriesMap.remove(seriesId);
}
}
@@ -24,6 +24,7 @@ public class PolarSheetsHistogramPanel extends SimplePanel implements RequiresRe
this.stringMessages = stringMessages;
setSize("100%", "100%");
chart = createHistogramChart();
chart.getElement().setAttribute("align", "top");
setWidget(chart);
}
@@ -79,6 +79,7 @@ public class PolarSheetsPanel extends FormPanel implements RaceSelectionChangeLi
leftPanel.add(dataCountLabel);
VerticalPanel rightPanel = addPolarSheetsChartPanel(splitPanel);
histogramPanel = new PolarSheetsHistogramPanel(stringMessages);
histogramPanel.getElement().setAttribute("align", "top");
rightPanel.add(histogramPanel);
mainPanel.add(splitPanel);
@@ -91,11 +92,15 @@ public class PolarSheetsPanel extends FormPanel implements RaceSelectionChangeLi
@Override
public boolean onMouseOver(PointMouseOverEvent pointMouseOverEvent) {
int x = (int) pointMouseOverEvent.getXAsLong();
String polarSheetName = pointMouseOverEvent.getSeriesName();
int angle = (int) pointMouseOverEvent.getXAsLong();
String polarSheetNameWithWind = pointMouseOverEvent.getSeriesName();
String[] split = polarSheetNameWithWind.split("-");
String polarSheetName = split[0] + "-" + split[1];
String polarSheetId = nameIdMapping.get(polarSheetName);
int windSpeed = Integer.parseInt(split[2]);
GetPolarSheetDataByAngleAction action = new GetPolarSheetDataByAngleAction(sailingService,
polarSheetId, x, new AsyncCallback<PolarSheetsHistogramData>() {
polarSheetId, angle, windSpeed, new AsyncCallback<PolarSheetsHistogramData>() {
@Override
public void onSuccess(PolarSheetsHistogramData result) {
@@ -127,6 +132,7 @@ public class PolarSheetsPanel extends FormPanel implements RaceSelectionChangeLi
verticalPanel.setSize("100%", "100%");
chartPanel = new PolarSheetsChartPanel(stringMessages);
verticalPanel.add(chartPanel);
verticalPanel.setCellHeight(chartPanel, "800px");
splitPanel.add(verticalPanel);
splitPanel.setCellWidth(verticalPanel, "50%");
return verticalPanel;
@@ -116,6 +116,7 @@ import com.sap.sailing.domain.persistence.DomainObjectFactory;
import com.sap.sailing.domain.persistence.MongoFactory;
import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.persistence.MongoWindStoreFactory;
import com.sap.sailing.domain.polarsheets.BoatAndWindSpeed;
import com.sap.sailing.domain.polarsheets.PolarSheetGenerationWorker;
import com.sap.sailing.domain.swisstimingadapter.SwissTimingArchiveConfiguration;
import com.sap.sailing.domain.swisstimingadapter.SwissTimingConfiguration;
@@ -3156,22 +3157,36 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
}
@Override
public PolarSheetsHistogramData getPolarSheetData(String polarSheetId, int angle) {
public PolarSheetsHistogramData getPolarSheetData(String polarSheetId, int angle, int windSpeed) {
HttpServletRequest httpServletRequest = this.getThreadLocalRequest();
HttpSession session = httpServletRequest.getSession();
@SuppressWarnings("unchecked")
List<List<Double>> data = (List<List<Double>>) session.getAttribute(polarSheetId);
List<List<BoatAndWindSpeed>> data = (List<List<BoatAndWindSpeed>>) session.getAttribute(polarSheetId);
if (data == null) {
//TODO exception handling
return null;
}
List<Double> dataForAngle = data.get(angle);
List<BoatAndWindSpeed> dataForAngle = data.get(angle);
if (dataForAngle.size() < 1) {
//TODO exception handling
return null;
}
Double min = Collections.min(dataForAngle);
Double max = Collections.max(dataForAngle);
List<Double> dataForAngleAndWindSpeed = new ArrayList<Double>();
for (BoatAndWindSpeed dataPoint: dataForAngle) {
if (((int) dataPoint.getWindSpeed().getBeaufort()) == windSpeed) {
dataForAngleAndWindSpeed.add(dataPoint.getBoatSpeed().getKnots());
}
}
if (dataForAngleAndWindSpeed.size() < 1) {
//TODO exception handling
return null;
}
Double min = Collections.min(dataForAngleAndWindSpeed);
Double max = Collections.max(dataForAngleAndWindSpeed);
//TODO make number of columns dynamic to chart size
int numberOfColumns = 20;
double range = (max - min) / numberOfColumns;
@@ -3181,7 +3196,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
}
Integer[] yValues = new Integer[numberOfColumns];
for (Double dataPoint : dataForAngle) {
for (Double dataPoint : dataForAngleAndWindSpeed) {
int i = (int) (((dataPoint - min) / range));
if (i == numberOfColumns) {
//For max value
@@ -3193,7 +3208,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
yValues[i]++;
}
PolarSheetsHistogramData histogramData = new PolarSheetsHistogramDataImpl(angle, xValues, yValues, dataForAngle.size());
PolarSheetsHistogramData histogramData = new PolarSheetsHistogramDataImpl(angle, xValues, yValues, dataForAngleAndWindSpeed.size());
return histogramData;