Changed polar sheet generation to support advanced histograms

This commit is contained in:
Frederik Petersen committed 2013-10-05 12:44:55 +02:00
1 parent 51e990bd74
commit e8f461bf75
10 files changed
+187 -113

No files matched your search

@@ -44,6 +44,7 @@ import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.common.impl.WindSteppingWithMaxDistance;
import com.sap.sailing.domain.polarsheets.DataPointWithOriginInfo;
import com.sap.sailing.domain.polarsheets.PerRaceAndCompetitorPolarSheetGenerationWorker;
import com.sap.sailing.domain.polarsheets.PolarSheetGenerationWorker;
import com.sap.sailing.domain.polarsheets.PolarSheetHistogramBuilder;
@@ -117,16 +118,16 @@ public class PolarSheetGenerationTest {
PolarSheetHistogramBuilder builder = new PolarSheetHistogramBuilder(settings);
List<Double> rawData = new ArrayList<Double>();
rawData.add(1.09);
rawData.add(1.0);
rawData.add(1.11);
rawData.add(1.46);
rawData.add(1.56);
rawData.add(2.05);
rawData.add(2.09);
rawData.add(3.0);
rawData.add(2.999);
List<DataPointWithOriginInfo> rawData = new ArrayList<DataPointWithOriginInfo>();
rawData.add(new DataPointWithOriginInfo(1.09, ""));
rawData.add(new DataPointWithOriginInfo(1.0, ""));
rawData.add(new DataPointWithOriginInfo(1.11, ""));
rawData.add(new DataPointWithOriginInfo(1.46, ""));
rawData.add(new DataPointWithOriginInfo(1.56, ""));
rawData.add(new DataPointWithOriginInfo(2.05, ""));
rawData.add(new DataPointWithOriginInfo(2.09, ""));
rawData.add(new DataPointWithOriginInfo(3.0, ""));
rawData.add(new DataPointWithOriginInfo(2.999, ""));
PolarSheetsHistogramData result = builder.build(rawData, 0, 0);
Number[] xValues = result.getxValues();
Assert.assertEquals(10, xValues.length);
@@ -142,48 +143,51 @@ public class PolarSheetGenerationTest {
@Test
public void testOutlierNeighborhoodAlgorithm() {
PolarSheetGenerationSettings settings = PolarSheetGenerationSettingsImpl.createStandardPolarSettings();
List<Double> values = new ArrayList<Double>();
values.add(0.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
values.add(20.);
List<DataPointWithOriginInfo> values = new ArrayList<DataPointWithOriginInfo>();
values.add(new DataPointWithOriginInfo(0., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
values.add(new DataPointWithOriginInfo(20., ""));
int[] count = new int[1];
count[0] = values.size();
Map<Integer, List<Double>> valuesInMap = new HashMap<Integer, List<Double>>();
Map<Integer, List<DataPointWithOriginInfo>> valuesInMap = new HashMap<Integer, List<DataPointWithOriginInfo>>();
valuesInMap.put(0, values);
double pct = PolarSheetGenerationWorker.getNeighboorhoodSizePercentage(count, valuesInMap, 0, 0, 0., settings);
double pct = PolarSheetGenerationWorker.getNeighboorhoodSizePercentage(count, valuesInMap, 0, 0,
new DataPointWithOriginInfo(0.0, ""), settings);
Assert.assertTrue(pct < 0.05);
pct = PolarSheetGenerationWorker.getNeighboorhoodSizePercentage(count, valuesInMap, 0, 2, 20., settings);
pct = PolarSheetGenerationWorker.getNeighboorhoodSizePercentage(count, valuesInMap, 0, 2,
new DataPointWithOriginInfo(20.0, ""), settings);
Assert.assertTrue(pct > 0.05);
}
@@ -1,28 +0,0 @@
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;
}
}
@@ -2,10 +2,12 @@ package com.sap.sailing.domain.polarsheets;
import com.sap.sailing.domain.common.Speed;
public interface BoatAndWindSpeed {
public interface BoatAndWindSpeedWithOriginInfo {
public Speed getBoatSpeed();
public Speed getWindSpeed();
public String getWindGaugesIdString();
}
@@ -0,0 +1,32 @@
package com.sap.sailing.domain.polarsheets;
import com.sap.sailing.domain.common.Speed;
public class BoatAndWindSpeedWithOriginInfoImpl implements BoatAndWindSpeedWithOriginInfo {
private Speed boatSpeed;
private Speed windSpeed;
private String windGaugesIdString;
public BoatAndWindSpeedWithOriginInfoImpl(Speed boatSpeed, Speed windSpeed, String windGaugesIdString) {
this.boatSpeed = boatSpeed;
this.windSpeed = windSpeed;
this.windGaugesIdString = windGaugesIdString;
}
@Override
public Speed getBoatSpeed() {
return boatSpeed;
}
@Override
public Speed getWindSpeed() {
return windSpeed;
}
@Override
public String getWindGaugesIdString() {
return windGaugesIdString;
}
}
@@ -0,0 +1,27 @@
package com.sap.sailing.domain.polarsheets;
public class DataPointWithOriginInfo implements Comparable<DataPointWithOriginInfo>{
private Double rawData;
private String windGaugeIdString;
public DataPointWithOriginInfo(Double rawData, String windGaugeIdString) {
this.rawData = rawData;
this.windGaugeIdString = windGaugeIdString;
}
public Double getRawData() {
return rawData;
}
public String getWindGaugeIdString() {
return windGaugeIdString;
}
@Override
public int compareTo(DataPointWithOriginInfo o) {
return getRawData().compareTo(o.getRawData());
}
}
@@ -152,8 +152,7 @@ public class PerRaceAndCompetitorPolarSheetGenerationWorker implements Runnable{
//continue;
}
polarSheetGenerationWorker.addPolarData(Math.round(polarFix.getAngleToWind()),
polarFix.getBoatSpeed(), polarFix.getWindSpeed());
polarSheetGenerationWorker.addPolarData(polarFix);
}
}
@@ -21,6 +21,7 @@ public class PolarFix {
private SpeedWithBearing boatSpeed;
private Speed windSpeed;
private double angleToWind;
private String gaugeIdString;
public PolarFix(GPSFixMoving fix, TrackedRace race, GPSFixTrack<Competitor, GPSFixMoving> track, Wind windSpeed,
PolarSheetGenerationSettings settings) {
@@ -28,6 +29,7 @@ public class PolarFix {
Bearing bearing = boatSpeed.getBearing();
Position position = fix.getPosition();
gaugeIdString = createWindGaugesString(race);
this.windSpeed = windSpeed;
Set<WindSource> windSourcesToExclude;
if (settings.useOnlyEstimatedForWindDirection()) {
@@ -45,6 +47,15 @@ public class PolarFix {
angleToWind = bearing.getDifferenceTo(windBearing).getDegrees();
}
private String createWindGaugesString(TrackedRace race) {
Iterable<WindSource> gaugeWindSources = race.getWindSources(WindSourceType.EXPEDITION);
String gaugeIdString = "";
for (WindSource source : gaugeWindSources) {
gaugeIdString = gaugeIdString + "+" + source.getId();
}
return gaugeIdString;
}
public Set<WindSource> collectWindSourcesToIgnoreForBearing(TrackedRace race, boolean excludeCourseBased) {
Set<WindSource> windSourcesToExclude = new HashSet<WindSource>();
Iterable<WindSource> combinedSources = race.getWindSources(WindSourceType.COMBINED);
@@ -83,7 +94,9 @@ public class PolarFix {
public double getAngleToWind() {
return angleToWind;
}
public String getGaugeIdString() {
return gaugeIdString;
}
}
@@ -36,7 +36,7 @@ public class PolarSheetGenerationWorker implements Future<PolarSheetsData>{
private final Set<PerRaceAndCompetitorPolarSheetGenerationWorker> workers;
private final List<List<BoatAndWindSpeed>> polarData;
private final List<List<BoatAndWindSpeedWithOriginInfo>> polarData;
private final Executor executor;
@@ -80,10 +80,10 @@ public class PolarSheetGenerationWorker implements Future<PolarSheetsData>{
}
}
private List<List<BoatAndWindSpeed>> initializePolarDataContainer() {
List<List<BoatAndWindSpeed>> container = new ArrayList<List<BoatAndWindSpeed>>();
private List<List<BoatAndWindSpeedWithOriginInfo>> initializePolarDataContainer() {
List<List<BoatAndWindSpeedWithOriginInfo>> container = new ArrayList<List<BoatAndWindSpeedWithOriginInfo>>();
for (int i = 0; i < 360; i++) {
container.add(new ArrayList<BoatAndWindSpeed>());
container.add(new ArrayList<BoatAndWindSpeedWithOriginInfo>());
}
return container;
}
@@ -108,12 +108,16 @@ public class PolarSheetGenerationWorker implements Future<PolarSheetsData>{
* @param windSpeed
* wind's speed in knots
*/
protected void addPolarData(long roundedAngle, Speed boatSpeed, Speed windSpeed) {
protected void addPolarData(PolarFix polarFix) {
long roundedAngle = Math.round(polarFix.getAngleToWind());
Speed boatSpeed = polarFix.getBoatSpeed();
Speed windSpeed = polarFix.getWindSpeed();
int angle = (int) roundedAngle;
if (angle < 0) {
angle = (360 + angle);
}
BoatAndWindSpeed speeds = new BoatAndWindSpeedImpl(boatSpeed, windSpeed);
BoatAndWindSpeedWithOriginInfo speeds = new BoatAndWindSpeedWithOriginInfoImpl(boatSpeed, windSpeed,
polarFix.getGaugeIdString());
polarData.get(angle).add(speeds);
}
@@ -145,14 +149,14 @@ public class PolarSheetGenerationWorker implements Future<PolarSheetsData>{
}
for (int angleIndex = 0; angleIndex < 360; angleIndex++) {
// Avoid Concurrent modification of lists, so get current state as an array; lock would slow things down
BoatAndWindSpeed[] values = polarData.get(angleIndex).toArray(
new BoatAndWindSpeed[polarData.get(angleIndex).size()]);
BoatAndWindSpeedWithOriginInfo[] values = polarData.get(angleIndex).toArray(
new BoatAndWindSpeedWithOriginInfo[polarData.get(angleIndex).size()]);
dataCount = dataCount + values.length;
dataCountPerAngle[angleIndex] = values.length;
double[] sumsPerWindSpeed = new double[levelCount];
int[] dataCountPerWindSpeed = new int[levelCount];
Map<Integer, List<Double>> dataSetForWindLevel = new HashMap<Integer, List<Double>>();
for (BoatAndWindSpeed singleDataPoint : values) {
Map<Integer, List<DataPointWithOriginInfo>> dataSetForWindLevel = new HashMap<Integer, List<DataPointWithOriginInfo>>();
for (BoatAndWindSpeedWithOriginInfo singleDataPoint : values) {
if (singleDataPoint != null) {
double windSpeed = singleDataPoint.getWindSpeed().getKnots();
int level = stepping.getLevelIndexForValue(windSpeed);
@@ -160,10 +164,11 @@ public class PolarSheetGenerationWorker implements Future<PolarSheetsData>{
continue;
}
double speed = singleDataPoint.getBoatSpeed().getKnots();
String windGaugesIdString = singleDataPoint.getWindGaugesIdString();
if (!dataSetForWindLevel.containsKey(level)) {
dataSetForWindLevel.put(level, new ArrayList<Double>());
dataSetForWindLevel.put(level, new ArrayList<DataPointWithOriginInfo>());
}
dataSetForWindLevel.get(level).add(speed);
dataSetForWindLevel.get(level).add(new DataPointWithOriginInfo(speed, windGaugesIdString));
sumsPerWindSpeed[level] = sumsPerWindSpeed[level] + speed;
dataCountPerWindSpeed[level]++;
}
@@ -171,17 +176,17 @@ public class PolarSheetGenerationWorker implements Future<PolarSheetsData>{
for (int levelIndex = 0; levelIndex < levelCount; levelIndex++) {
if (settings.shouldRemoveOutliers() && dataSetForWindLevel.get(levelIndex) != null) {
List<Double> withoutOutliers = new ArrayList<Double>();
List<DataPointWithOriginInfo> withoutOutliers = new ArrayList<DataPointWithOriginInfo>();
Collections.sort(dataSetForWindLevel.get(levelIndex));
if (dataCountPerWindSpeed[levelIndex] > /* Minimum data count for outlier detection */10) {
for (int dataIndex = 0; dataIndex < dataSetForWindLevel.get(levelIndex).size(); dataIndex++) {
Double dataPoint = dataSetForWindLevel.get(levelIndex).get(dataIndex);
DataPointWithOriginInfo dataPoint = dataSetForWindLevel.get(levelIndex).get(dataIndex);
double pct = getNeighboorhoodSizePercentage(dataCountPerWindSpeed, dataSetForWindLevel,
levelIndex, dataIndex, dataPoint, settings);
if (pct >= settings.getOutlierMinimumNeighborhoodPct()) {
withoutOutliers.add(dataPoint);
} else {
sumsPerWindSpeed[levelIndex] = sumsPerWindSpeed[levelIndex] - dataPoint;
sumsPerWindSpeed[levelIndex] = sumsPerWindSpeed[levelIndex] - dataPoint.getRawData();
dataCountPerWindSpeed[levelIndex]--;
}
}
@@ -191,7 +196,7 @@ public class PolarSheetGenerationWorker implements Future<PolarSheetsData>{
}
if (dataCountPerWindSpeed[levelIndex] < settings.getMinimumDataCountPerAngle()) {
dataSetForWindLevel.put(levelIndex, new ArrayList<Double>());
dataSetForWindLevel.put(levelIndex, new ArrayList<DataPointWithOriginInfo>());
sumsPerWindSpeed[levelIndex] = 0;
dataCountPerWindSpeed[levelIndex] = 0;
}
@@ -209,7 +214,7 @@ public class PolarSheetGenerationWorker implements Future<PolarSheetsData>{
Double standardDeviation = Math.sqrt(variance);
coefficiantOfVariation = standardDeviation / average;
}
List<Double> rawData = dataSetForWindLevel.get(levelIndex);
List<DataPointWithOriginInfo> rawData = dataSetForWindLevel.get(levelIndex);
if (rawData == null || rawData.size() <= 0) {
continue;
}
@@ -239,8 +244,8 @@ public class PolarSheetGenerationWorker implements Future<PolarSheetsData>{
* to the complete dataset.
*/
public static double getNeighboorhoodSizePercentage(int[] dataCountPerWindSpeed,
Map<Integer, List<Double>> dataSetForWindLevel, int levelIndex, int dataIndex, Double dataPoint,
PolarSheetGenerationSettings settings) {
Map<Integer, List<DataPointWithOriginInfo>> dataSetForWindLevel, int levelIndex, int dataIndex,
DataPointWithOriginInfo dataPoint, PolarSheetGenerationSettings settings) {
int neighborCount = 0;
double pct = .0;
@@ -253,8 +258,8 @@ public class PolarSheetGenerationWorker implements Future<PolarSheetsData>{
done = true;
} else {
Double dataPointToCheckForNeighborStatus = dataSetForWindLevel.get(levelIndex).get(
index);
if (dataPoint - dataPointToCheckForNeighborStatus <= settings
index).getRawData();
if (dataPoint.getRawData() - dataPointToCheckForNeighborStatus <= settings
.getOutlierDetectionNeighborhoodRadius()) {
neighborCount++;
} else {
@@ -275,8 +280,8 @@ public class PolarSheetGenerationWorker implements Future<PolarSheetsData>{
done = true;
} else {
Double dataPointToCheckForNeighborStatus = dataSetForWindLevel.get(levelIndex).get(
index);
if (dataPointToCheckForNeighborStatus - dataPoint <= settings
index).getRawData();
if (dataPointToCheckForNeighborStatus - dataPoint.getRawData() <= settings
.getOutlierDetectionNeighborhoodRadius()) {
neighborCount++;
} else {
@@ -309,7 +314,7 @@ public class PolarSheetGenerationWorker implements Future<PolarSheetsData>{
* @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<BoatAndWindSpeed>> getCompleteData() {
public List<List<BoatAndWindSpeedWithOriginInfo>> getCompleteData() {
return polarData;
}
@@ -1,7 +1,9 @@
package com.sap.sailing.domain.polarsheets;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.sap.sailing.domain.common.PolarSheetGenerationSettings;
import com.sap.sailing.domain.common.PolarSheetsHistogramData;
@@ -15,9 +17,13 @@ public class PolarSheetHistogramBuilder {
this.settings = settings;
}
public PolarSheetsHistogramData build(List<Double> rawData, int angleIndex, double coefficiantOfVariation) {
Double min = Collections.min(rawData);
Double max = Collections.max(rawData);
public PolarSheetsHistogramData build(List<DataPointWithOriginInfo> dataPointsWithOriginInfo, int angleIndex,
double coefficiantOfVariation) {
DataPointWithOriginInfo minPoint = Collections.min(dataPointsWithOriginInfo);
DataPointWithOriginInfo maxPoint = Collections.max(dataPointsWithOriginInfo);
Double min = minPoint.getRawData();
Double max = maxPoint.getRawData();
int numberOfColumns = settings.getNumberOfHistogramColumns();
double range = (max - min) / numberOfColumns;
@@ -26,9 +32,10 @@ public class PolarSheetHistogramBuilder {
xValues[u] = min + u * range + (0.5 * range);
}
Map<String, Integer[]> yValuesByGaugeIds = new HashMap<String, Integer[]>();
Integer[] yValues = new Integer[numberOfColumns];
for (Double dataPoint : rawData) {
double notRoundedYet = (dataPoint - min) / range;
for (DataPointWithOriginInfo dataPoint : dataPointsWithOriginInfo) {
double notRoundedYet = (dataPoint.getRawData() - min) / range;
int u = (int) notRoundedYet;
if (u == numberOfColumns) {
// For max value
@@ -38,10 +45,22 @@ public class PolarSheetHistogramBuilder {
yValues[u] = 0;
}
yValues[u]++;
String gaugeIdsString = dataPoint.getWindGaugeIdString();
if (!yValuesByGaugeIds.containsKey(gaugeIdsString)) {
yValuesByGaugeIds.put(gaugeIdsString, new Integer[numberOfColumns]);
}
Integer[] yValuesForThatGaugeIdsString = yValuesByGaugeIds.get(gaugeIdsString);
if (yValuesForThatGaugeIdsString[u] == null) {
yValuesForThatGaugeIdsString[u] = 0;
}
yValuesForThatGaugeIdsString[u]++;
}
PolarSheetsHistogramData histogramData = new PolarSheetsHistogramDataImpl(angleIndex, xValues, yValues,
rawData.size(), coefficiantOfVariation);
dataPointsWithOriginInfo.size(), coefficiantOfVariation);
return histogramData;
}
@@ -42,8 +42,9 @@ public class PolarSheetsHistogramPanel extends DockLayoutPanel {
public void setData(PolarSheetsHistogramData data) {
chart.removeAllSeries();
chart.setTitle(new ChartTitle().setText(stringMessages.histogram()),
new ChartSubtitle().setText(stringMessages.angleAndTotalNumberOfDataPointsAndCovAndCm(data.getAngle(), data.getDataCount(), data.getCoefficiantOfVariation(), data.getConfidenceMeasure())));
chart.setTitle(new ChartTitle().setText(stringMessages.histogram()), new ChartSubtitle().setText(stringMessages
.angleAndTotalNumberOfDataPointsAndCovAndCm(data.getAngle(), data.getDataCount(),
data.getCoefficiantOfVariation(), data.getConfidenceMeasure())));
Point[] points = toPoints(data);
Series series = chart.createSeries();
series.setPoints(points);