mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-13 22:15:26 +00:00
a first two-dimensional clustering algorithm
This commit is contained in:
1 parent
f834e5637b
commit
95f6264ed4
5 files changed
+109
-6
No files matched your search
+1
-1
@@ -14,7 +14,7 @@ public class ScalablePosition implements ScalableValueWithDistance<ScalablePosit
|
||||
Math.sin(position.getLatRad()));
|
||||
}
|
||||
|
||||
public ScalablePosition(double x, double y, double z) {
|
||||
private ScalablePosition(double x, double y, double z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
|
||||
+37
-2
@@ -1,4 +1,4 @@
|
||||
package com.sap.sailing.polars.windestimation.test;
|
||||
package com.sap.sailing.polars.windestimation;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -9,18 +9,31 @@ import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.text.ParseException;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.common.Bearing;
|
||||
import com.sap.sailing.domain.common.confidence.impl.ScalableDouble;
|
||||
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
||||
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
|
||||
import com.sap.sailing.domain.test.OnlineTracTracBasedTest;
|
||||
import com.sap.sailing.domain.tracking.Maneuver;
|
||||
import com.sap.sailing.domain.tracking.Wind;
|
||||
import com.sap.sailing.domain.tracking.impl.ScalableWind;
|
||||
import com.sap.sailing.domain.tractracadapter.ReceiverType;
|
||||
import com.sap.sailing.polars.impl.PolarDataServiceImpl;
|
||||
import com.sap.sailing.polars.regression.NotEnoughDataHasBeenAddedException;
|
||||
import com.sap.sailing.polars.windestimation.ManeuverBasedWindEstimationTrackImpl;
|
||||
import com.sap.sailing.polars.windestimation.ManeuverBasedWindEstimationTrackImpl.ManeuverClassification;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.util.kmeans.Cluster;
|
||||
import com.sap.sse.util.kmeans.KMeansMappingClusterer;
|
||||
import com.tractrac.model.lib.api.event.CreateModelException;
|
||||
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
|
||||
|
||||
@@ -67,6 +80,28 @@ public class TestWindEstimationFromManeuversOnAFew505Races extends OnlineTracTra
|
||||
Wind average = getManeuverBasedAverageWind();
|
||||
assertEquals(275, average.getFrom().getDegrees(), 5.0); // wind in this race was from 075deg on average
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoDimensionalClustering() throws NotEnoughDataHasBeenAddedException, MalformedURLException,
|
||||
IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException,
|
||||
CreateModelException {
|
||||
setUp("event_20110609_KielerWoch-505_Race_3");
|
||||
ManeuverBasedWindEstimationTrackImpl windTrack = new ManeuverBasedWindEstimationTrackImpl(new PolarDataServiceImpl(Executors.newFixedThreadPool(4)),
|
||||
getTrackedRace(), /* millisecondsOverWhichToAverage */ 30000, /* waitForLatest */ true);
|
||||
final Map<Maneuver, Competitor> maneuvers = windTrack.getAllManeuvers(/* waitForLatest */ true);
|
||||
final int numberOfClusters = 16;
|
||||
KMeansMappingClusterer<ManeuverClassification, Pair<ScalableBearing, ScalableDouble>, Pair<Bearing, Double>, ScalableBearingAndScalableDouble> clusterer =
|
||||
new KMeansMappingClusterer<>(numberOfClusters,
|
||||
windTrack.getManeuverClassifications(maneuvers),
|
||||
(mc)->new ScalableBearingAndScalableDouble(mc.getMiddleManeuverCourse(), mc.getManeuverAngleDeg()), // maps maneuver classification to cluster metric
|
||||
// use an evenly distributed set of cluster seeds for clustering wind direction estimations
|
||||
Stream.concat(IntStream.range(0, numberOfClusters/2).mapToObj((i)->
|
||||
new Pair<>(new DegreeBearingImpl(((double) i)*360./(double) numberOfClusters/2), 45.)),
|
||||
IntStream.range(0, numberOfClusters/2).mapToObj((i)->
|
||||
new Pair<>(new DegreeBearingImpl(((double) i)*360./(double) numberOfClusters/2), -45.))));
|
||||
final Set<Cluster<ManeuverClassification, Pair<ScalableBearing, ScalableDouble>, Pair<Bearing, Double>, ScalableBearingAndScalableDouble>> clusters = clusterer.getClusters();
|
||||
assertEquals(8, clusters.size());
|
||||
}
|
||||
|
||||
private Wind getManeuverBasedAverageWind() throws NotEnoughDataHasBeenAddedException {
|
||||
ManeuverBasedWindEstimationTrackImpl windTrack = new ManeuverBasedWindEstimationTrackImpl(new PolarDataServiceImpl(Executors.newFixedThreadPool(4)),
|
||||
+5
-2
@@ -454,12 +454,15 @@ public class ManeuverBasedWindEstimationTrackImpl extends WindTrackImpl {
|
||||
/**
|
||||
* For each maneuver from <code>maneuvers</code>, a {@link ManeuverClassification object} is created.
|
||||
*/
|
||||
private Stream<ManeuverClassification> getManeuverClassifications(final Map<Maneuver, Competitor> maneuvers) {
|
||||
Stream<ManeuverClassification> getManeuverClassifications(final Map<Maneuver, Competitor> maneuvers) {
|
||||
return maneuvers.entrySet().stream().map((maneuverAndCompetitor)->
|
||||
new ManeuverClassification(maneuverAndCompetitor.getValue(), maneuverAndCompetitor.getKey()));
|
||||
}
|
||||
|
||||
private Map<Maneuver, Competitor> getAllManeuvers(boolean waitForLatest) {
|
||||
/**
|
||||
* Package scope to let test fragment access it
|
||||
*/
|
||||
Map<Maneuver, Competitor> getAllManeuvers(boolean waitForLatest) {
|
||||
Map<Maneuver, Competitor> maneuvers = new HashMap<>();
|
||||
final Waypoint firstWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint();
|
||||
final Waypoint lastWaypoint = trackedRace.getRace().getCourse().getLastWaypoint();
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.sap.sailing.polars.windestimation;
|
||||
|
||||
import com.sap.sailing.domain.common.Bearing;
|
||||
import com.sap.sailing.domain.common.confidence.impl.ScalableDouble;
|
||||
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.common.scalablevalue.ScalableValue;
|
||||
import com.sap.sse.common.scalablevalue.ScalableValueWithDistance;
|
||||
|
||||
/**
|
||||
* Two {@link Bearings} combined in one scalable object, like a vector of two bearings. {@link #add(ScalableValue)
|
||||
* Addition} works component wise, and so does {@link #multiply(double) multiplication} and {@link #divide(double)
|
||||
* division}. The {@link #getDistance(Pair) distance} is calculated as the Euklidian two-dimensional distance between
|
||||
* the two bearing pairs, with "degrees" as conceptual unit. For example, another object of this type that is 2 and 3 degrees
|
||||
* apart from this object will have a distance of {@link Math#sqrt(double) Math.sqrt(13)}.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public class ScalableBearingAndScalableDouble implements
|
||||
ScalableValueWithDistance<Pair<ScalableBearing, ScalableDouble>, Pair<Bearing, Double>> {
|
||||
private final ScalableBearing b1;
|
||||
private final ScalableDouble b2;
|
||||
|
||||
public ScalableBearingAndScalableDouble(Bearing b1, double b2) {
|
||||
this(new ScalableBearing(b1), new ScalableDouble(b2));
|
||||
}
|
||||
|
||||
private ScalableBearingAndScalableDouble(ScalableBearing b1, ScalableDouble b2) {
|
||||
super();
|
||||
this.b1 = b1;
|
||||
this.b2 = b2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<Bearing, Double> divide(double divisor) {
|
||||
return new Pair<>(b1.divide(divisor), b2.divide(divisor));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<ScalableBearing, ScalableDouble> getValue() {
|
||||
return new Pair<>(b1, b2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDistance(Pair<Bearing, Double> other) {
|
||||
final double b1DiffDeg = b1.divide(1).getDifferenceTo(other.getA()).getDegrees();
|
||||
final double b2DiffDeg = Math.abs(b2.divide(1)-other.getB());
|
||||
return Math.sqrt(b1DiffDeg*b1DiffDeg + b2DiffDeg*b2DiffDeg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScalableBearingAndScalableDouble add(ScalableValue<Pair<ScalableBearing, ScalableDouble>, Pair<Bearing, Double>> t) {
|
||||
return new ScalableBearingAndScalableDouble(b1.add(t.getValue().getA()), b2.add(t.getValue().getB()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScalableBearingAndScalableDouble multiply(double factor) {
|
||||
return new ScalableBearingAndScalableDouble(b1.multiply(factor), b2.multiply(factor));
|
||||
}
|
||||
}
|
||||
@@ -115,7 +115,11 @@ public class Cluster<E, ValueType, AveragesTo, T extends ScalableValueWithDistan
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{mean: "+getMean()+", centroid: "+getCentroid()+", elements: "+elements+"}";
|
||||
return "{mean: "+getMean()+", centroid: "+getCentroid()+", size: "+size()+", variance: "+getVariance()+", elements: "+elements+"}";
|
||||
}
|
||||
|
||||
private double getVariance() {
|
||||
return stream().mapToDouble((e)->getDistanceFromMean(e)*getDistanceFromMean(e)).average().getAsDouble();
|
||||
}
|
||||
|
||||
public Stream<E> stream() {
|
||||
|
||||
Reference in new issue
Block a user