mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-13 22:15:26 +00:00
Clean up and documentation
removed unused classes in com.sap.sailing.polars[.test] and added documentation to used classes.
This commit is contained in:
1 parent
6628befff5
commit
705326bdee
23 files changed
+191
-536
No files matched your search
-260
@@ -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() {
|
||||
|
||||
|
||||
+7
-4
@@ -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();
|
||||
|
||||
+8
@@ -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
|
||||
|
||||
+18
@@ -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();
|
||||
|
||||
+6
@@ -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")
|
||||
|
||||
-21
@@ -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")
|
||||
|
||||
+19
@@ -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));
|
||||
}
|
||||
|
||||
+29
@@ -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);
|
||||
|
||||
+5
@@ -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;
|
||||
|
||||
+7
@@ -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;
|
||||
|
||||
+5
@@ -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")
|
||||
|
||||
+7
@@ -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;
|
||||
|
||||
+8
@@ -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());
|
||||
|
||||
+8
@@ -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;
|
||||
|
||||
+17
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+4
@@ -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
|
||||
|
||||
-197
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user