Clean up and documentation

removed unused classes in com.sap.sailing.polars[.test] and added
documentation to used classes.
This commit is contained in:
Frederik Petersen committed 2015-09-24 10:24:41 +02:00
1 parent 6628befff5
commit 705326bdee
23 files changed
+191 -536

No files matched your search

@@ -1,260 +0,0 @@
package com.sap.sailing.polars.test;
import static org.hamcrest.Matchers.closeTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl;
import com.sap.sailing.domain.common.Speed;
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.polars.regression.impl.WindSpeedAndAngleEstimator;
import com.sap.sse.common.Util.Pair;
public class WindSpeedAndAngleEstimatorTest {
private static final double ERROR = 0.0001;
@Test
public void testAverageTrueWindSpeedAndAngleEstimationForSteadyGrowth() {
List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed = new ArrayList<>();
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(5), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(3,
new DegreeBearingImpl(46)), 0.4, null)));
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(6), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4,
new DegreeBearingImpl(47)), 0.6, null)));
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(7), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4.5,
new DegreeBearingImpl(47.5)), 0.5, null)));
WindSpeedAndAngleEstimator estimator = new WindSpeedAndAngleEstimator(averageBoatSpeedAndCourseForWindSpeed);
SpeedWithBearingWithConfidence<Void> result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(3));
assertThat(result.getObject().getKnots(), closeTo(5, ERROR));
assertThat(result.getObject().getBearing().getDegrees(), closeTo(46, ERROR));
assertThat(result.getConfidence(), closeTo(0.4, ERROR));
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4));
assertThat(result.getObject().getKnots(), closeTo(6, ERROR));
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47, ERROR));
assertThat(result.getConfidence(), closeTo(0.6, ERROR));
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4.5));
assertThat(result.getObject().getKnots(), closeTo(7, ERROR));
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47.5, ERROR));
assertThat(result.getConfidence(), closeTo(0.5, ERROR));
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4.25));
assertThat(result.getObject().getKnots(), closeTo(6.5, ERROR));
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47.25, ERROR));
assertThat(result.getConfidence(), closeTo(0.55, ERROR));
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(3.25));
assertThat(result.getObject().getKnots(), closeTo(5.25, ERROR));
assertThat(result.getObject().getBearing().getDegrees(), closeTo(46.25, ERROR));
assertThat(result.getConfidence(), closeTo(0.45, ERROR));
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(2.75));
assertThat(result.getObject().getKnots(), closeTo(5, ERROR));
assertThat(result.getObject().getBearing().getDegrees(), closeTo(46, ERROR));
assertThat(result.getConfidence(), closeTo(0.2, ERROR));
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4.75));
assertThat(result.getObject().getKnots(), closeTo(7, ERROR));
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47.5, ERROR));
assertThat(result.getConfidence(), closeTo(0.25, ERROR));
}
/**
* Not a realistic scenario in the sailing world, but testing for the sake of correctness of the algorithm
*/
@Test
public void testAverageTrueWindSpeedAndAngleEstimationForSteadyDeclination() {
List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed = new ArrayList<>();
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(7), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(3,
new DegreeBearingImpl(46)), 0.4, null)));
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(6), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4,
new DegreeBearingImpl(47)), 0.6, null)));
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(5), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4.5,
new DegreeBearingImpl(47.5)), 0.5, null)));
WindSpeedAndAngleEstimator estimator = new WindSpeedAndAngleEstimator(averageBoatSpeedAndCourseForWindSpeed);
SpeedWithBearingWithConfidence<Void> result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(3));
assertThat(result.getObject().getKnots(), closeTo(7, ERROR));
assertThat(result.getObject().getBearing().getDegrees(), closeTo(46, ERROR));
assertThat(result.getConfidence(), closeTo(0.4, ERROR));
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4));
assertThat(result.getObject().getKnots(), closeTo(6, ERROR));
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47, ERROR));
assertThat(result.getConfidence(), closeTo(0.6, ERROR));
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4.5));
assertThat(result.getObject().getKnots(), closeTo(5, ERROR));
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47.5, ERROR));
assertThat(result.getConfidence(), closeTo(0.5, ERROR));
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4.25));
assertThat(result.getObject().getKnots(), closeTo(5.5, ERROR));
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47.25, ERROR));
assertThat(result.getConfidence(), closeTo(0.55, ERROR));
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(3.25));
assertThat(result.getObject().getKnots(), closeTo(6.75, ERROR));
assertThat(result.getObject().getBearing().getDegrees(), closeTo(46.25, ERROR));
assertThat(result.getConfidence(), closeTo(0.45, ERROR));
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(2.75));
assertThat(result.getObject().getKnots(), closeTo(7, ERROR));
assertThat(result.getObject().getBearing().getDegrees(), closeTo(46, ERROR));
assertThat(result.getConfidence(), closeTo(0.2, ERROR));
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4.75));
assertThat(result.getObject().getKnots(), closeTo(5, ERROR));
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47.5, ERROR));
assertThat(result.getConfidence(), closeTo(0.25, ERROR));
}
@Test
public void testAverageTrueWindSpeedAndAngleEstimationNegative() {
List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed = new ArrayList<>();
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(5), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(3,
new DegreeBearingImpl(46)), 0.4, null)));
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(6), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4,
new DegreeBearingImpl(47)), 0.6, null)));
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(7), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4.5,
new DegreeBearingImpl(47.5)), 0.5, null)));
WindSpeedAndAngleEstimator estimator = new WindSpeedAndAngleEstimator(averageBoatSpeedAndCourseForWindSpeed);
SpeedWithBearingWithConfidence<Void> result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(2));
assertThat(result, nullValue());
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(5.5));
assertThat(result, nullValue());
}
@Test
public void testAverageTrueWindSpeedAndAngleEstimationForNonReversibleFunction() {
List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed = new ArrayList<>();
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(5), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(3,
new DegreeBearingImpl(46)), 0.4, null)));
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(6), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4,
new DegreeBearingImpl(47)), 0.6, null)));
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(7), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4.5,
new DegreeBearingImpl(47.5)), 0.5, null)));
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(8), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4.25,
new DegreeBearingImpl(47.5)), 0.25, null)));
WindSpeedAndAngleEstimator estimator = new WindSpeedAndAngleEstimator(averageBoatSpeedAndCourseForWindSpeed);
SpeedWithBearingWithConfidence<Void> result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4.25));
assertThat(result.getObject().getKnots(), closeTo(6.5, ERROR));
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47.25, ERROR));
assertThat(result.getConfidence(), closeTo(0.55, ERROR));
}
@Test
public void testAverageTrueWindSpeedAndAngleEstimationWithAllCandidatesReturned() {
List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed = new ArrayList<>();
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(5), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(3,
new DegreeBearingImpl(46)), 0.4, null)));
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(6), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4,
new DegreeBearingImpl(47)), 0.6, null)));
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(7), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4.5,
new DegreeBearingImpl(47.5)), 0.5, null)));
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(8), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4.25,
new DegreeBearingImpl(47.5)), 0.25, null)));
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(9), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(3,
new DegreeBearingImpl(47.5)), 0.25, null)));
WindSpeedAndAngleEstimator estimator = new WindSpeedAndAngleEstimator(averageBoatSpeedAndCourseForWindSpeed);
Set<SpeedWithBearingWithConfidence<Void>> result = estimator
.getAverageTrueWindSpeedAndAngleCandidates(new KnotSpeedImpl(4.3));
assertThat(result.size(), is(2));
for (SpeedWithBearingWithConfidence<Void> candidate : result) {
assertTrue(Math.abs(candidate.getConfidence()-0.2999999)<=ERROR ||
Math.abs(candidate.getConfidence()-0.54)<=ERROR);
if (Math.abs(candidate.getConfidence() - 0.2999999) < ERROR) {
assertThat(candidate.getObject().getKnots(), closeTo(7.8, ERROR));
assertThat(candidate.getObject().getBearing().getDegrees(), closeTo(47.5, ERROR));
} else {
assertThat(candidate.getObject().getKnots(), closeTo(6.6, ERROR));
assertThat(candidate.getObject().getBearing().getDegrees(), closeTo(47.3, ERROR));
}
}
result = estimator.getAverageTrueWindSpeedAndAngleCandidates(new KnotSpeedImpl(2.9));
assertThat(result.size(), is(2));
for (SpeedWithBearingWithConfidence<Void> candidate : result) {
assertTrue(Math.abs(candidate.getConfidence()-0.2) <= ERROR ||
Math.abs(candidate.getConfidence()-0.125) <= ERROR);
if (Math.abs(candidate.getConfidence() - 0.2) < ERROR) {
assertThat(candidate.getObject().getKnots(), closeTo(5, ERROR));
assertThat(candidate.getObject().getBearing().getDegrees(), closeTo(46, ERROR));
} else {
assertThat(candidate.getObject().getKnots(), closeTo(9, ERROR));
assertThat(candidate.getObject().getBearing().getDegrees(), closeTo(47.5, ERROR));
}
}
result = estimator.getAverageTrueWindSpeedAndAngleCandidates(new KnotSpeedImpl(4.9));
assertThat(result.size(), is(1));
SpeedWithBearingWithConfidence<Void> candidate = result.iterator().next();
assertThat(candidate.getObject().getKnots(), closeTo(7, ERROR));
assertThat(candidate.getObject().getBearing().getDegrees(), closeTo(47.5, ERROR));
assertThat(candidate.getConfidence(), closeTo(0.25, ERROR));
}
@Test
public void testAverageTrueWindSpeedAndAngleEstimationForTwoSamplingPointsWithSameBoatSpeed() {
List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed = new ArrayList<>();
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(7), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4.5,
new DegreeBearingImpl(47.5)), 0.5, null)));
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(8), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(4.5,
new DegreeBearingImpl(47.5)), 0.4, null)));
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(9), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(5.5,
new DegreeBearingImpl(47.5)), 0.4, null)));
averageBoatSpeedAndCourseForWindSpeed.add(new Pair<Speed, SpeedWithBearingWithConfidence<Void>>(
new KnotSpeedImpl(10), new SpeedWithBearingWithConfidenceImpl<Void>(new KnotSpeedWithBearingImpl(5.5,
new DegreeBearingImpl(47.5)), 0.5, null)));
WindSpeedAndAngleEstimator estimator = new WindSpeedAndAngleEstimator(averageBoatSpeedAndCourseForWindSpeed);
SpeedWithBearingWithConfidence<Void> result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(4.5));
assertThat(result.getObject().getKnots(), closeTo(7.44444, ERROR));
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47.5, ERROR));
assertThat(result.getConfidence(), closeTo(0.455555, ERROR));
result = estimator.getAverageTrueWindSpeedAndAngle(new KnotSpeedImpl(5.5));
assertThat(result.getObject().getKnots(), closeTo(9.555555, ERROR));
assertThat(result.getObject().getBearing().getDegrees(), closeTo(47.5, ERROR));
assertThat(result.getConfidence(), closeTo(0.455555, ERROR));
}
}
@@ -3,6 +3,14 @@ package com.sap.sailing.polars;
import com.sap.sailing.polars.impl.PolarDataServiceImpl;
import com.sap.sse.replication.OperationWithResult;
/**
*
* Needed in the context of the initial replication of the polar data service.
*
* @author D054528 Frederik Petersen
*
* @param <T>
*/
public interface PolarDataOperation<T> extends OperationWithResult<PolarDataServiceImpl, T> {
}
@@ -13,6 +13,12 @@ import org.osgi.framework.ServiceRegistration;
import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sse.replication.Replicable;
/**
* Handles OSGi (de-)registration of the polar data service.
*
* @author D054528 (Frederik Petersen)
*
*/
public class Activator implements BundleActivator {
private static final Logger logger = Logger.getLogger(Activator.class.getName());
@@ -1,5 +1,12 @@
package com.sap.sailing.polars.impl;
/**
* Represents cubic equations in the form of a*x^3 + b*x^2 + c*x + d = 0 and can solve roots for x with {@link #solve()}.
* !Note that complex roots are not returned.!
*
* @author D054528 (Frederik Petersen)
*
*/
public class CubicEquation {
private double a;
@@ -7,6 +14,9 @@ public class CubicEquation {
private double c;
private double d;
/**
* Create a cubic equation in the form of a*x^3 + b*x^2 + c*x + d = 0.
*/
public CubicEquation(double a, double b, double c, double d) {
this.a = a;
this.b = b;
@@ -16,6 +26,7 @@ public class CubicEquation {
/**
* Following: http://www.1728.org/cubic2.htm !The complex roots are not returned!
* Returned array can be of length one or three. Depending on how many real roots there are.
*/
public double[] solve() {
@@ -47,14 +47,14 @@ import com.sap.sse.replication.OperationWithResult;
import com.sap.sse.replication.ReplicationMasterDescriptor;
import com.sap.sse.replication.impl.OperationWithResultWithIdWrapper;
import com.sap.sse.replication.impl.ReplicableWithObjectInputStream;
import com.sap.sse.util.SmartFutureCache;
/**
* Uses two chained {@link SmartFutureCache}s. One to store {@link PolarFix}es extracted from {@link TrackedRace}s and
* the other one for storing one polar sheet per boat class. This enables quick access to desired measures like optimal
* beat angles.
* Uses a custom datamining pipeline to aggregate incoming fixes in two regression based polar containers.
*
* For more information on polars in SAP Sailing Analytics, please see: http://wiki.sapsailing.com/wiki/Polars
*
* @author Frederik Petersen (D054528)
* @author Axel Uhl
*
*/
public class PolarDataServiceImpl implements PolarDataService,
@@ -79,6 +79,9 @@ public class PolarDataServiceImpl implements PolarDataService,
private DomainFactory domainFactory;
/**
* Constructs the polar data service with default generation settings.
*/
public PolarDataServiceImpl() {
PolarSheetGenerationSettings settings = PolarSheetGenerationSettingsImpl.createBackendPolarSettings();
ClusterGroup<Bearing> angleClusterGroup = createAngleClusterGroup();
@@ -9,6 +9,11 @@ import com.sap.sse.datamining.components.ProcessorInstruction;
import com.sap.sse.datamining.impl.components.AbstractParallelProcessor;
import com.sap.sse.datamining.impl.components.AbstractProcessorInstruction;
/**
* This simple abstract processor can be used to enrich data.
*
* @author D054528 (Frederik Petersen)
*/
public abstract class AbstractEnrichingProcessor<InputType, ResultType> extends
AbstractParallelProcessor<InputType, ResultType> {
@@ -28,6 +33,9 @@ public abstract class AbstractEnrichingProcessor<InputType, ResultType> extends
};
}
/**
* Takes an input element, enriches it with context data and returns that result.
*/
protected abstract ResultType enrich(InputType element);
@Override
@@ -25,6 +25,17 @@ import com.sap.sailing.polars.regression.impl.IncrementalAnyOrderLeastSquaresImp
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
/**
* This container has two regressions. One for boatSpeed over windSpeed and one for beatAngle over windSpeed
* estimations.
*
* It can return speed and angle for a given windSpeed and should only be used for restricted sets of input data. There
* should be one instance per BoatClass+LegType combination. This seperation needs to be taken care of in a higher level
* of the application.
*
* @author D054528 (Frederik Petersen)
*
*/
public class AngleAndSpeedRegression implements Serializable {
private static final long serialVersionUID = 6343595388753945979L;
@@ -43,6 +54,10 @@ public class AngleAndSpeedRegression implements Serializable {
angleRegression.addData(windSpeedInKnots, angleToTheWind.getObject().getDegrees());
}
/**
* Estimate the speed and angle for a given windSpeed by using the regressions contained in this class.
* Seperation by boatclass and legtype needs to be done on a higher level.
*/
public SpeedWithBearingWithConfidence<Void> estimateSpeedAndAngle(Speed windSpeed)
throws NotEnoughDataHasBeenAddedException {
double windSpeedInKnots = windSpeed.getKnots();
@@ -57,6 +72,9 @@ public class AngleAndSpeedRegression implements Serializable {
speedRegression.getNumberOfAddedPoints() / 100.0), null);
}
/**
* Estimates wind speed candidates for the given input scenario based on the speed and angle regressions.
*/
public Set<SpeedWithBearingWithConfidence<Void>> estimateTrueWindSpeedAndAngleCandidates(Speed speedOverGround,
LegType legType, Tack tack) throws NotEnoughDataHasBeenAddedException {
double[] coefficiants = speedRegression.getOrCreatePolynomialFunction().getCoefficients();
@@ -4,6 +4,12 @@ import com.sap.sailing.domain.common.Bearing;
import com.sap.sse.datamining.annotations.Dimension;
import com.sap.sse.datamining.data.Cluster;
/**
* Allows to group the incoming fixes by beat angle range.
*
* @author D054528 (Frederik Petersen)
*
*/
public interface AngleClusterPolarClusterKey extends BasePolarClusterKey {
@Dimension(messageKey = "angleCluster")
@@ -1,21 +0,0 @@
package com.sap.sailing.polars.mining;
import java.io.Serializable;
public class AverageAngleContainer implements Serializable {
private static final long serialVersionUID = -6370647955050312847L;
private int dataCount = 0;
private double angleSumDeg = 0.0;
private double averageAngleDeg = 0.0;
public void addFix(double roundedAngleDeg) {
dataCount++;
angleSumDeg += roundedAngleDeg;
averageAngleDeg = angleSumDeg / (double) dataCount;
}
Double getAverageAngleDeg() {
return averageAngleDeg;
}
}
@@ -3,6 +3,13 @@ package com.sap.sailing.polars.mining;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sse.datamining.annotations.Dimension;
/**
*
* Allows grouping the incoming fixes by boat class.
*
* @author D054528 (Frederik Petersen)
*
*/
public interface BasePolarClusterKey {
@Dimension(messageKey = "boatClass")
@@ -15,8 +15,27 @@ import com.sap.sse.datamining.impl.data.ComparatorClusterBoundary;
import com.sap.sse.datamining.impl.data.ComparisonStrategy;
import com.sap.sse.datamining.impl.data.FixClusterGroup;
/**
* Allows grouping angles into angle ranges.
*
* @author D054528 (Frederik Petersen)
*
*/
public class BearingClusterGroup extends FixClusterGroup<Bearing> {
/**
* Creates an angle range grouping.
*
* E.g. call {@link #BearingClusterGroup(0, 10, 5)} if you wanr to group angle data that ranges from 0 to 10° into
* two containers. 0->5 and 5->10
*
* @param startAngle
* smallest angle
* @param endAngle
* biggest angle
* @param clusterSize
* how big should each range be.
*/
public BearingClusterGroup(int startAngle, int endAngle, int clusterSize) {
super(createClusters(startAngle, endAngle, clusterSize));
}
@@ -23,6 +23,13 @@ import com.sap.sse.datamining.factories.GroupKeyFactory;
import com.sap.sse.datamining.impl.components.GroupedDataEntry;
import com.sap.sse.datamining.shared.GroupKey;
/**
* Groups incoming fixes by boatclass and legtype into {@link AngleAndSpeedRegression} instances and
* provides access methods to regression data.
*
* @author D054528 (Frederik Petersen)
*
*/
public class CubicRegressionPerCourseProcessor implements
Processor<GroupedDataEntry<GPSFixMovingWithPolarContext>, Void>, Serializable {
@@ -66,6 +73,9 @@ public class CubicRegressionPerCourseProcessor implements
}
}
/**
* Returns speed and twa for a given scenario, if data is available in the regression.
*/
public SpeedWithBearingWithConfidence<Void> getAverageSpeedAndCourseOverGround(BoatClass boatClass,
Speed windSpeed, LegType legType) throws NotEnoughDataHasBeenAddedException {
GroupKey key = createGroupKey(boatClass, legType);
@@ -78,6 +88,9 @@ public class CubicRegressionPerCourseProcessor implements
return estimatedSpeedAndAngle;
}
/**
* Returns windspeed and windangle candidates for a given scenario, if enough data is available.
*/
public Set<SpeedWithBearingWithConfidence<Void>> estimateTrueWindSpeedAndAngleCandidates(BoatClass boatClass,
Speed speedOverGround, LegType legType, Tack tack) {
GroupKey key = createGroupKey(boatClass, legType);
@@ -118,6 +131,14 @@ public class CubicRegressionPerCourseProcessor implements
return compoundKey;
}
/**
* Allows direct access to speed regression functions for debugging purposes.
*
* For actual usage of the data please use
* {@link #getAverageSpeedAndCourseOverGround(BoatClass, Speed, LegType)}
* and
* {@link #estimateTrueWindSpeedAndAngleCandidates(BoatClass, Speed, LegType, Tack)}.
*/
public PolynomialFunction getSpeedRegressionFunction(BoatClass boatClass, LegType legType)
throws NotEnoughDataHasBeenAddedException {
GroupKey key = createGroupKey(boatClass, legType);
@@ -130,6 +151,14 @@ public class CubicRegressionPerCourseProcessor implements
return polynomialFunction;
}
/**
* Allows direct access to angle regression functions for debugging purposes.
*
* For actual usage of the data please use
* {@link #getAverageSpeedAndCourseOverGround(BoatClass, Speed, LegType)}
* and
* {@link #estimateTrueWindSpeedAndAngleCandidates(BoatClass, Speed, LegType, Tack)}.
*/
public PolynomialFunction getAngleRegressionFunction(BoatClass boatClass, LegType legType)
throws NotEnoughDataHasBeenAddedException {
GroupKey key = createGroupKey(boatClass, legType);
@@ -4,6 +4,11 @@ import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
* Encapsulates a {@link GPSFixMoving} with more data needed for polar aggregation.
* @author D054528 (Frederik Petersen)
*
*/
public class GPSFixMovingWithOriginInfo {
private final GPSFixMoving fix;
@@ -27,6 +27,13 @@ import com.sap.sse.common.Util.Pair;
import com.sap.sse.datamining.data.Cluster;
import com.sap.sse.datamining.data.ClusterGroup;
/**
* Encapsulates a {@link GPSFixMoving} with some polar context. Some of the important and computeintensive data is
* calculated once and then cached for further usage in this class.
*
* @author D054528 (Frederik Petersen)
*
*/
public class GPSFixMovingWithPolarContext implements LegTypePolarClusterKey, AngleClusterPolarClusterKey {
private final GPSFixMoving fix;
@@ -3,6 +3,11 @@ package com.sap.sailing.polars.mining;
import com.sap.sailing.domain.common.LegType;
import com.sap.sse.datamining.annotations.Dimension;
/**
* Allows grouping incoming fixes by legtype.
* @author D054528 (Frederik Petersen)
*
*/
public interface LegTypePolarClusterKey extends BasePolarClusterKey {
@Dimension(messageKey = "legType")
@@ -10,6 +10,13 @@ import com.sap.sse.datamining.data.Cluster;
import com.sap.sse.datamining.factories.FunctionFactory;
import com.sap.sse.datamining.functions.Function;
/**
* Needed in the context of the datamining pipeline for the polar backend.
* It provides the information about grouping classes.
*
* @author D054528 (Frederik Petersen)
*
*/
public class PolarDataDimensionCollectionFactory {
private static void addTackAndLegTypeDimensions(Collection<Function<?>> dimensions, FunctionFactory functionFactory)
@@ -55,6 +55,17 @@ import com.sap.sse.datamining.impl.components.ParallelMultiDimensionsValueNestin
import com.sap.sse.datamining.impl.functions.SimpleParameterizedFunction;
import com.sap.sse.util.impl.ThreadFactoryWithPriority;
/**
* Entry point for the aggregation of backend polar data and backend to that data.
*
* Creates a polar data pipeline upon creation and puts incoming GPS fixes into that pipeline.
* Also holds references to the actual data containers in which the aggregation results lay.
*
* For more information on polars in SAP Sailing Analytics, please see: http://wiki.sapsailing.com/wiki/Polars
*
* @author D054528 (Frederik Petersen)
*
*/
public class PolarDataMiner {
private static final int EXECUTOR_QUEUE_SIZE = 100;
@@ -22,6 +22,14 @@ import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.datamining.components.FilterCriterion;
/**
* Contains several filter criteria for incoming fixes concerning their validity for the polar aggregation.
*
* It is used in the backend polar mining pipeline.
*
* @author D054528 (Frederik Petersen)
*
*/
public class PolarFixFilterCriteria implements FilterCriterion<GPSFixMovingWithPolarContext> {
private static final Logger logger = Logger.getLogger(PolarFixFilterCriteria.class.getName());
@@ -2,6 +2,14 @@ package com.sap.sailing.polars.mining;
import com.sap.sailing.domain.common.Bearing;
/**
* Rounds a {@link Bearing} and provides {@link #hashCode()} and {@link #equals(Object)} methods.
*
* (Basically an Integer class, but handles rounding in constructor).
*
* @author D054528 (Frederik Petersen)
*
*/
public class RoundedTrueWindAngle {
private final int angleDeg;
@@ -29,6 +29,14 @@ import com.sap.sse.datamining.factories.GroupKeyFactory;
import com.sap.sse.datamining.impl.components.GroupedDataEntry;
import com.sap.sse.datamining.shared.GroupKey;
/**
* Holds one speed regression per BoatClass, WindSpeed, Beat Angle Range combination and provides means for adding and
* accessing data. Data is added incrementally in and not all historic fixes need to be maintained. This allows for a
* very space efficient structure.
*
* @author D054528 (Frederik Petersen)
*
*/
public class SpeedRegressionPerAngleClusterProcessor implements
Processor<GroupedDataEntry<GPSFixMovingWithPolarContext>, Void>, Serializable {
@@ -92,6 +100,8 @@ public class SpeedRegressionPerAngleClusterProcessor implements
* There are angle clusters (size defined in the data mining pipeline construction), which each have their own
* regression for boatspeed over windspeed. We don't know the thresholds or centers of the angle clusters here, so
* we roughly interpolate by taking 10 values from angle-5 deg to angle+5 deg and average the speeds.
*
* At the time of writing the size of each angle range is 5° so this method provides a pretty smooth interpolation.
*/
public SpeedWithConfidence<Void> estimateBoatSpeed(BoatClass boatClass, Speed windSpeed, Bearing trueWindAngle)
throws NotEnoughDataHasBeenAddedException {
@@ -153,6 +163,13 @@ public class SpeedRegressionPerAngleClusterProcessor implements
throw new RuntimeException("Polar Data Miner failed.", failure);
}
/**
* Allows direct access to angle regression functions for debugging purposes.
*
* For actual usage of the data please use
* {@link #estimateBoatSpeed(BoatClass, Speed, Bearing)}.
*/
public PolynomialFunction getSpeedRegressionFunction(BoatClass boatClass, double trueWindAngle)
throws NotEnoughDataHasBeenAddedException {
GroupKey key = createGroupKey(boatClass, new DegreeBearingImpl(trueWindAngle));
@@ -1,54 +0,0 @@
package com.sap.sailing.polars.mining;
import java.io.Serializable;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.WindSpeedStepping;
public class WindSpeedLevel implements Serializable {
private static final long serialVersionUID = -4498750034316806342L;
private final int level;
private final WindSpeedStepping stepping;
public WindSpeedLevel(Speed windSpeed, WindSpeedStepping stepping) {
this.level = stepping.getLevelIndexForValue(windSpeed.getKnots());
this.stepping = stepping;
}
@Override
public String toString() {
// FIXME right now the toString equals for different steppings. This will resolve in an equal groupkey in the
// datamining framework. The current approach is to use only the default WindStepping.
return "" + level;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + level;
result = prime * result + ((stepping == null) ? 0 : stepping.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
WindSpeedLevel other = (WindSpeedLevel) obj;
if (level != other.level)
return false;
if (stepping == null) {
if (other.stepping != null)
return false;
} else if (!stepping.equals(other.stepping))
return false;
return true;
}
}
@@ -26,6 +26,8 @@ import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
* > http://erikerlandson.github.io/blog/2012/07/05/deriving-an-incremental-form-of-the-polynomial-regression-equations/
* </a>
*
* It can be used in parallel, because it uses locking for adding and reading data.
*
*
* @author Frederik Petersen D054528
*
@@ -225,6 +227,8 @@ public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSqua
CUBIC, CUBIC_NO_INTERCEPT;
/**
* Use to provide a fast alternative to actually computing the inverseMatrix from scratch.
* Right now this uses precalculated equations for cubic functions.
*
* @param realMatrix
* matrix to inverse
@@ -1,197 +0,0 @@
package com.sap.sailing.polars.regression.impl;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sse.common.Util.Pair;
public class WindSpeedAndAngleEstimator {
private static final double DEFAULT_MAX_DISTANCE_TO_SAMPLING_POINT = 0.5;
private final List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed;
private final double maxDistanceToSamplingPointFunctionInKnots;
/**
*
* @param averageBoatSpeedAndCourseForWindSpeed
* List of sampling points. Needs to be sorted by windspeed (A of the Pair object) low to high
*/
public WindSpeedAndAngleEstimator(
List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed) {
this(averageBoatSpeedAndCourseForWindSpeed, DEFAULT_MAX_DISTANCE_TO_SAMPLING_POINT);
}
/**
*
* @param averageBoatSpeedAndCourseForWindSpeed
* List of sampling points. Needs to be sorted by windspeed (A of the Pair object) low to high
* @param maxDistanceToSamplingPointFunctionInKnots
* If there is only a sampling point on one side, what's the maximal distance to consider valid when
* comparing?
*/
public WindSpeedAndAngleEstimator(
List<Pair<Speed, SpeedWithBearingWithConfidence<Void>>> averageBoatSpeedAndCourseForWindSpeed,
double maxDistanceToSamplingPointFunctionInKnots) {
this.averageBoatSpeedAndCourseForWindSpeed = averageBoatSpeedAndCourseForWindSpeed;
this.maxDistanceToSamplingPointFunctionInKnots = maxDistanceToSamplingPointFunctionInKnots;
}
/**
* Looks at all entries in {@link #dataCountAndAngleSumMap} and determines the wind speed and true wind angle at
* which the boat has most likely been sailing to achieve the <code>boatSpeed</code> provided.
*
* BE ADVISED: The result candidate with the highest confidence, which is returned here, is not necessarily the
* candidate which is the most likely to be correct. It really depends on the quality and quantity of gathered polar
* data.
*
* Use {@link #getAverageTrueWindSpeedAndAngleCandidates(Speed)} to get all candidates.
*
* @return null if none was found, otherwise candidate with highest confidence, found in the list returned by
* {@link #getAverageTrueWindSpeedAndAngleCandidates(Speed)}
*/
public SpeedWithBearingWithConfidence<Void> getAverageTrueWindSpeedAndAngle(Speed boatSpeed) {
Set<SpeedWithBearingWithConfidence<Void>> resultCandidates = getAverageTrueWindSpeedAndAngleCandidates(boatSpeed);
SpeedWithBearingWithConfidence<Void> resultCandidateWithHighestConfidence = null;
for (SpeedWithBearingWithConfidence<Void> resultCandidate : resultCandidates) {
if (resultCandidateWithHighestConfidence == null
|| resultCandidate.getConfidence() > resultCandidateWithHighestConfidence.getConfidence()) {
resultCandidateWithHighestConfidence = resultCandidate;
}
}
return resultCandidateWithHighestConfidence;
}
/**
* Uses the constructor-supplied sampling points to find wind and course candidates for the supplied boatSpeed.
*
* @return empty set if the underlying data was not sufficient to estimate wind. Otherwise a set of candidates with
* windspeed course of the boat relative to the wind and a confidence which was derived from the confidences
* of underlying fixes, the amount of underlying fixes and the distance between the input boatSpeed and the
* sampling points supplied by the gathered polar data.
*/
public Set<SpeedWithBearingWithConfidence<Void>> getAverageTrueWindSpeedAndAngleCandidates(Speed boatSpeed) {
double requestedBoatSpeedInKnots = boatSpeed.getKnots();
Pair<Speed, SpeedWithBearingWithConfidence<Void>> last = null;
Set<SpeedWithBearingWithConfidence<Void>> resultCandidates = new HashSet<>();
Set<SpeedWithBearingWithConfidence<Void>> resultCandidatesWithDistance = new HashSet<>();
for (Pair<Speed, SpeedWithBearingWithConfidence<Void>> averageSpeedForWindSpeed : averageBoatSpeedAndCourseForWindSpeed) {
Speed windSpeed = averageSpeedForWindSpeed.getA();
SpeedWithBearingWithConfidence<Void> averageBoatSpeedWithCourse = averageSpeedForWindSpeed.getB();
double currentBoatSpeedInKnots = averageBoatSpeedWithCourse.getObject().getKnots();
if (last != null) {
double lastBoatSpeedInKnots = last.getB().getObject().getKnots();
if (lastBoatSpeedInKnots <= requestedBoatSpeedInKnots
&& currentBoatSpeedInKnots >= requestedBoatSpeedInKnots) {
SpeedWithBearingWithConfidenceImpl<Void> resultCandidate = createWeightedResultCandidateForLowerLast(
requestedBoatSpeedInKnots, last, windSpeed, averageBoatSpeedWithCourse,
lastBoatSpeedInKnots, currentBoatSpeedInKnots);
resultCandidates.add(resultCandidate);
} else if (lastBoatSpeedInKnots >= requestedBoatSpeedInKnots
&& currentBoatSpeedInKnots <= requestedBoatSpeedInKnots) {
SpeedWithBearingWithConfidenceImpl<Void> resultCandidate = createWeightedResultCandidateForHigherLast(
requestedBoatSpeedInKnots, last, windSpeed, averageBoatSpeedWithCourse,
lastBoatSpeedInKnots, currentBoatSpeedInKnots);
resultCandidates.add(resultCandidate);
}
}
/*
* This will be run when there was no candidate found in between the two sampling points (or we are at the
* beginning) and add a Candidate if the requested point was maxDistanceToSamplingPointFunctionInKnots or
* less away from the last regarded sampling point. The candidate (if valid) is then added to the
* resultCandidatesWithDistance set. Confidences are halved and the set is only used if the standard result
* set is empty in the end
*/
if (resultCandidates.size() == 0 && last != null) {
addResultCandidateIfDistanceValid(requestedBoatSpeedInKnots, resultCandidatesWithDistance, last.getA(),
last.getB());
}
last = averageSpeedForWindSpeed;
}
// check last sampling point for distance match and return withDistance set if standard set is empty
if (last != null && resultCandidates.size() == 0) {
addResultCandidateIfDistanceValid(requestedBoatSpeedInKnots, resultCandidatesWithDistance, last.getA(),
last.getB());
resultCandidates = resultCandidatesWithDistance;
}
return resultCandidates;
}
/**
* Confidence will be halved in situations where there the requested speed does not lie between any sampling points.
* The speed and angle values are equal to the sampling point values.
*/
private void addResultCandidateIfDistanceValid(double requestedBoatSpeedInKnots,
Set<SpeedWithBearingWithConfidence<Void>> resultCandidates, Speed windSpeed,
SpeedWithBearingWithConfidence<Void> averageBoatSpeedWithCourse) {
if (Math.abs(averageBoatSpeedWithCourse.getObject().getKnots() - requestedBoatSpeedInKnots) <= maxDistanceToSamplingPointFunctionInKnots) {
SpeedWithBearingWithConfidence<Void> resultCandidate = new SpeedWithBearingWithConfidenceImpl<Void>(
new KnotSpeedWithBearingImpl(windSpeed.getKnots(), new DegreeBearingImpl(averageBoatSpeedWithCourse
.getObject().getBearing().getDegrees())), averageBoatSpeedWithCourse.getConfidence() * 0.5,
null);
resultCandidates.add(resultCandidate);
}
}
private SpeedWithBearingWithConfidenceImpl<Void> createWeightedResultCandidateForHigherLast(
double requestedBoatSpeedInKnots, Pair<Speed, SpeedWithBearingWithConfidence<Void>> last, Speed windSpeed,
SpeedWithBearingWithConfidence<Void> averageBoatSpeedWithCourse, double lastBoatSpeedInKnots,
double currentBoatSpeedInKnots) {
double differenceBetweenBothSpeedsInKnots = lastBoatSpeedInKnots - currentBoatSpeedInKnots;
double ratio;
if (differenceBetweenBothSpeedsInKnots < 0.00001) {
// Both sampling points have the same ratio. Use confidence to determine the ratio
ratio = last.getB().getConfidence()
/ (averageBoatSpeedWithCourse.getConfidence() + last.getB().getConfidence());
} else {
double differenceBetweenRequestedAndLower = requestedBoatSpeedInKnots - currentBoatSpeedInKnots;
ratio = differenceBetweenRequestedAndLower / differenceBetweenBothSpeedsInKnots;
}
double weightedAverageWindSpeedInKnots = ((1 - ratio) * windSpeed.getKnots())
+ (ratio * last.getA().getKnots());
double currentCourseInDeg = averageBoatSpeedWithCourse.getObject().getBearing().getDegrees();
double lastCourseInDeg = last.getB().getObject().getBearing().getDegrees();
double weightedAverageAngleInDeg = ((1 - ratio) * currentCourseInDeg) + (ratio * lastCourseInDeg);
double weightedAverageConfidence = ((1 - ratio) * averageBoatSpeedWithCourse.getConfidence())
+ (ratio * last.getB().getConfidence());
SpeedWithBearingWithConfidenceImpl<Void> resultCandidate = new SpeedWithBearingWithConfidenceImpl<Void>(
new KnotSpeedWithBearingImpl(weightedAverageWindSpeedInKnots, new DegreeBearingImpl(
weightedAverageAngleInDeg)), weightedAverageConfidence, null);
return resultCandidate;
}
private SpeedWithBearingWithConfidenceImpl<Void> createWeightedResultCandidateForLowerLast(
double requestedBoatSpeedInKnots, Pair<Speed, SpeedWithBearingWithConfidence<Void>> last, Speed windSpeed,
SpeedWithBearingWithConfidence<Void> averageBoatSpeedWithCourse, double lastBoatSpeedInKnots,
double currentBoatSpeedInKnots) {
double differenceBetweenBothSpeedsInKnots = currentBoatSpeedInKnots - lastBoatSpeedInKnots;
double ratio;
if (differenceBetweenBothSpeedsInKnots < 0.00001) {
// Both sampling points have the same ratio. Use confidence to determine the ratio
ratio = averageBoatSpeedWithCourse.getConfidence()
/ (averageBoatSpeedWithCourse.getConfidence() + last.getB().getConfidence());
} else {
double differenceBetweenRequestedAndLower = requestedBoatSpeedInKnots - lastBoatSpeedInKnots;
ratio = differenceBetweenRequestedAndLower / differenceBetweenBothSpeedsInKnots;
}
double weightedAverageWindSpeedInKnots = (ratio * windSpeed.getKnots())
+ ((1 - ratio) * last.getA().getKnots());
double currentCourseInDeg = averageBoatSpeedWithCourse.getObject().getBearing().getDegrees();
double lastCourseInDeg = last.getB().getObject().getBearing().getDegrees();
double weightedAverageAngleInDeg = (ratio * currentCourseInDeg) + ((1 - ratio) * lastCourseInDeg);
double weightedAverageConfidence = (ratio * averageBoatSpeedWithCourse.getConfidence())
+ ((1 - ratio) * last.getB().getConfidence());
SpeedWithBearingWithConfidenceImpl<Void> resultCandidate = new SpeedWithBearingWithConfidenceImpl<Void>(
new KnotSpeedWithBearingImpl(weightedAverageWindSpeedInKnots, new DegreeBearingImpl(
weightedAverageAngleInDeg)), weightedAverageConfidence, null);
return resultCandidate;
}
}