mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-15 14:56:41 +00:00
added Javadoc
Change-Id: I4fcd87e412439369fa6900a50b50d2ef2e26ca75
This commit is contained in:
1 parent
f0b9b64947
commit
654b7c4fa2
4 files changed
+198
-11
No files matched your search
+2
-2
@@ -167,8 +167,8 @@ public interface Track<FixType extends Timed> extends Serializable {
|
||||
|
||||
/**
|
||||
* Returns an iterator starting at the first fix after <code>startingAt</code> (or "at or after" in case
|
||||
* <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator are the smoothened fixes (see
|
||||
* also {@link #getFixes()}, without any smoothening or dampening applied.
|
||||
* <code>inclusive</code> is <code>true</code>). The fixes returned by the iterator exclude outliers (see
|
||||
* also {@link #getFixes()} and returns the remaining fixes without any smoothening or dampening applied.
|
||||
*
|
||||
* Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception
|
||||
* will be thrown in case the caller has failed to do so.
|
||||
|
||||
@@ -24,7 +24,6 @@ import com.sap.sailing.domain.base.SharedDomainFactory;
|
||||
import com.sap.sailing.domain.base.Sideline;
|
||||
import com.sap.sailing.domain.base.SpeedWithConfidence;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.base.impl.DouglasPeucker;
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
@@ -569,10 +568,13 @@ public interface TrackedRace extends Serializable, IsManagedByCache<SharedDomain
|
||||
Wind getDirectionFromStartToNextMark(TimePoint at);
|
||||
|
||||
/**
|
||||
* Uses a {@link DouglasPeucker Douglas-Peucker} algorithm to approximate this track's fixes starting at time
|
||||
* <code>from</code> until time point <code>to</code> such that the maximum distance between the track's fixes and
|
||||
* the approximation is at most <code>maxDistance</code>. The approximation's fixes are original fixes from
|
||||
* the competitor's {@link GPSFixTrack track}.
|
||||
* Traverses the competitor's {@link GPSFixTrack track} between {@code from} and {@code to} (both inclusive) and
|
||||
* returns those fixes where significant changes in the course over ground (COG) are observed, indicating a possibly
|
||||
* relevant maneuver. The {@link SpeedWithBearing#getBearing() COG} change is calculated over a time window the size
|
||||
* of the typical maneuver duration, but at least covering two fixes in order to also cover the case of low sampling
|
||||
* rates. If in any such window the COG change exceeds the threshold, the window is extended as far as the COG
|
||||
* change grows, then from the extended window the fix with the highest COG change to its successor is returned. The
|
||||
* next window analysis will start after the end of the current window, avoiding duplicates in the result.
|
||||
*/
|
||||
Iterable<GPSFixMoving> approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to);
|
||||
|
||||
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
package com.sap.sailing.domain.tracking.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.tracking.GPSFixTrack;
|
||||
import com.sap.sse.common.Bearing;
|
||||
import com.sap.sse.common.Duration;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
|
||||
/**
|
||||
* Given a {@link GPSFixTrack} containing {@link GPSFixMoving}, an instance of this class finds areas on the track where
|
||||
* the course changes sufficiently quickly to indicate a relevant maneuver. The basis for this is the
|
||||
* {@link BoatClass}'s {@link BoatClass#getApproximateManeuverDuration() maneuver duration}.
|
||||
*
|
||||
* TODO future versions of this class shall be stateful, keeping track of the interesting points identified so
|
||||
* far and only adjusting incrementally based on a listener pattern.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public class CourseChangeBasedTrackApproximation {
|
||||
private final GPSFixTrack<Competitor, GPSFixMoving> track;
|
||||
private final BoatClass boatClass;
|
||||
|
||||
/**
|
||||
* The fix window consists of the list of fixes, a corresponding list with the course
|
||||
* changes at each fix within the window, as well as the aggregated total course change from the beginning of the
|
||||
* window up to the respective fix; furthermore, the window duration is maintained to compare against the maximum
|
||||
* window length.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
private class FixWindow {
|
||||
private final LinkedList<GPSFixMoving> window;
|
||||
private final LinkedList<Double> courseChangeInDegreesForFixesInWindow; // one shorter than "window"; change[i] is from window[i] to window[i]+1
|
||||
// size the total course change list such that resizing should hardly be necessary
|
||||
private final List<Double> totalCourseChangeFromBeginningOfWindow;
|
||||
private final Duration maximumWindowLength;
|
||||
private final double maneuverAngleInDegreesThreshold;
|
||||
private Duration windowDuration;
|
||||
private double totalCourseChangeInWindowInDegrees;
|
||||
|
||||
FixWindow() {
|
||||
this.window = new LinkedList<>();
|
||||
this.courseChangeInDegreesForFixesInWindow = new LinkedList<>();
|
||||
this.windowDuration = Duration.NULL;
|
||||
this.totalCourseChangeInWindowInDegrees = 0.0;
|
||||
this.maximumWindowLength = boatClass.getApproximateManeuverDuration();
|
||||
this.maneuverAngleInDegreesThreshold = boatClass.getManeuverDegreeAngleThreshold();
|
||||
this.totalCourseChangeFromBeginningOfWindow = new ArrayList<>(((int) maximumWindowLength.divide(track.getAverageIntervalBetweenRawFixes()))+10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a fix to the end of this fix window. If this produces an interesting candidate within this window, the candidate is returned,
|
||||
* and the window is reset in such a way that the same candidate will not be returned a second time.<p>
|
||||
*
|
||||
* The window will have established its invariants when this method returns.
|
||||
*/
|
||||
public GPSFixMoving add(GPSFixMoving next) {
|
||||
final GPSFixMoving result;
|
||||
final GPSFixMoving previous = window.getLast();
|
||||
this.window.add(next);
|
||||
if (previous != null) {
|
||||
final double courseChangeBetweenPreviousAndNextInDegrees = previous.getSpeed().getBearing().getDifferenceTo(next.getSpeed().getBearing()).getDegrees();
|
||||
windowDuration = windowDuration.plus(previous.getTimePoint().until(next.getTimePoint()));
|
||||
courseChangeInDegreesForFixesInWindow.addLast(courseChangeBetweenPreviousAndNextInDegrees);
|
||||
totalCourseChangeFromBeginningOfWindow.add(totalCourseChangeFromBeginningOfWindow.get(totalCourseChangeFromBeginningOfWindow.size()-1)+courseChangeBetweenPreviousAndNextInDegrees);
|
||||
totalCourseChangeInWindowInDegrees += courseChangeBetweenPreviousAndNextInDegrees;
|
||||
if (windowDuration.compareTo(maximumWindowLength) > 0) {
|
||||
// analysis window has exceeded the typical maneuver duration for the boat class;
|
||||
result = getManeuverCandidate(window, courseChangeInDegreesForFixesInWindow, totalCourseChangeInWindowInDegrees);
|
||||
if (result != null) {
|
||||
// TODO a fix with significant course change was found in the window; reset
|
||||
}
|
||||
removeFirst();
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
assert window.size() == courseChangeInDegreesForFixesInWindow.size()+1;
|
||||
assert window.size() == totalCourseChangeFromBeginningOfWindow.size()+1;
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the first fix from the {@link #window} and adjusts all structures to re-establish all invariants.
|
||||
* In particular, the {@link #totalCourseChangeInWindow}, the {@link #totalCourseChangeFromBeginningOfWindow}
|
||||
* and the {@link #windowDuration}, as well as {@link #courseChangeInDegreesForFixesInWindow} are adjusted.
|
||||
*/
|
||||
private void removeFirst() {
|
||||
assert window.size() > 1;
|
||||
assert !courseChangeInDegreesForFixesInWindow.isEmpty();
|
||||
final GPSFixMoving removed = window.removeFirst();
|
||||
final double courseChangeOfFirstInDegrees = courseChangeInDegreesForFixesInWindow.removeFirst();
|
||||
totalCourseChangeInWindowInDegrees -= courseChangeOfFirstInDegrees;
|
||||
windowDuration = windowDuration.minus(removed.getTimePoint().until(window.getFirst().getTimePoint()));
|
||||
// adjust totalCourseChangeFromBeginningOfWindow by subtracting the first course change from all others
|
||||
// and shifting all by one position to the "left"
|
||||
for (int i=0; i<totalCourseChangeFromBeginningOfWindow.size()-1; i++) {
|
||||
// adjust all total course changes by subtracting the
|
||||
totalCourseChangeFromBeginningOfWindow.set(i, totalCourseChangeFromBeginningOfWindow.get(i+1)-courseChangeOfFirstInDegrees);
|
||||
}
|
||||
totalCourseChangeFromBeginningOfWindow.remove(totalCourseChangeFromBeginningOfWindow.size()-1);
|
||||
}
|
||||
|
||||
private GPSFixMoving getManeuverCandidate(LinkedList<GPSFixMoving> window,
|
||||
LinkedList<Double> courseChangeInDegreesForFixesInWindow, double totalCourseChangeInWindowInDegrees) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public CourseChangeBasedTrackApproximation(GPSFixTrack<Competitor, GPSFixMoving> track, BoatClass boatClass) {
|
||||
super();
|
||||
this.track = track;
|
||||
this.boatClass = boatClass;
|
||||
}
|
||||
|
||||
public Iterable<GPSFixMoving> approximate(TimePoint from, TimePoint to) {
|
||||
final List<GPSFixMoving> result = new ArrayList<>();
|
||||
final FixWindow window = new FixWindow();
|
||||
Duration windowDuration = Duration.NULL;
|
||||
Bearing totalCogChangeInWindow = Bearing.NORTH;
|
||||
track.lockForRead();
|
||||
try {
|
||||
// TODO try to keep locking intervals as short as possible
|
||||
final Iterator<GPSFixMoving> fixIterator = track.getFixesIterator(from, /* inclusive */ true);
|
||||
GPSFixMoving next;
|
||||
GPSFixMoving previous = null;
|
||||
GPSFixMoving fixInWindowWithMaximumCourseChange = null;
|
||||
Bearing previousCourseChange = null;
|
||||
int indexOfMaximumTotalCourseChange = -1; // -1 means undefined because window is empty
|
||||
do {
|
||||
if (fixIterator.hasNext()) {
|
||||
next = fixIterator.next();
|
||||
} else {
|
||||
next = null;
|
||||
}
|
||||
window.add(next);
|
||||
if (previous != null) {
|
||||
final Bearing courseChangeBetweenPreviousAndNext = previous.getSpeed().getBearing().getDifferenceTo(next.getSpeed().getBearing());
|
||||
windowDuration = windowDuration.plus(previous.getTimePoint().until(next.getTimePoint()));
|
||||
courseChangeInDegreesForFixesInWindow.addLast(courseChangeBetweenPreviousAndNext.getDegrees());
|
||||
totalCogChangeInWindow = totalCogChangeInWindow.add(courseChangeBetweenPreviousAndNext);
|
||||
if (windowDuration.compareTo(maximumWindowLength) > 0) {
|
||||
// analysis window has exceeded the typical maneuver duration for the boat class;
|
||||
final GPSFixMoving maneuverCandidateFix = getManeuverCandidate(window, courseChangeInDegreesForFixesInWindow, totalCogChangeInWindow);
|
||||
if (maneuverCandidateFix != null) {
|
||||
// a fix with significant course change was found in the window;
|
||||
result.add(maneuverCandidateFix);
|
||||
// TODO decide how far to skip; probably start with the fix at or after the maneuver candidate fix...
|
||||
}
|
||||
final GPSFixMoving removed = window.removeFirst();
|
||||
final double courseChangeOfFirst = courseChangeInDegreesForFixesInWindow.removeFirst();
|
||||
windowDuration = windowDuration.minus(removed.getTimePoint().until(window.getFirst().getTimePoint()));
|
||||
// adjust totalCourseChangeFromBeginningOfWindow by subtracting the first course change from all others
|
||||
// and shifting all by one position to the "left"
|
||||
for (int i=0; i<totalCourseChangeFromBeginningOfWindow.size()-1; i++) {
|
||||
// adjust all total course changes by subtracting the
|
||||
totalCourseChangeFromBeginningOfWindow.set(i, totalCourseChangeFromBeginningOfWindow.get(i+1)-courseChangeOfFirst);
|
||||
}
|
||||
totalCourseChangeFromBeginningOfWindow.remove(totalCourseChangeFromBeginningOfWindow.size()-1);
|
||||
}
|
||||
assert window.size() == courseChangeInDegreesForFixesInWindow.size()+1;
|
||||
previousCourseChange = courseChangeBetweenPreviousAndNext;
|
||||
}
|
||||
previous = next;
|
||||
} while (next != null && !next.getTimePoint().after(to));
|
||||
} finally {
|
||||
track.unlockAfterRead();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
+5
-4
@@ -67,7 +67,6 @@ import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
|
||||
import com.sap.sailing.domain.base.SpeedWithConfidence;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.base.impl.CourseImpl;
|
||||
import com.sap.sailing.domain.base.impl.DouglasPeucker;
|
||||
import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl;
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.ManeuverType;
|
||||
@@ -2660,9 +2659,11 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
|
||||
@Override
|
||||
public Iterable<GPSFixMoving> approximate(Competitor competitor, Distance maxDistance, TimePoint from, TimePoint to) {
|
||||
DouglasPeucker<Competitor, GPSFixMoving> douglasPeucker = new DouglasPeucker<Competitor, GPSFixMoving>(
|
||||
getTrack(competitor));
|
||||
return douglasPeucker.approximate(maxDistance, from, to);
|
||||
return new CourseChangeBasedTrackApproximation(getTrack(competitor), getBoatOfCompetitor(competitor).getBoatClass()).approximate(from, to);
|
||||
// The old DouglasPeucker-based code:
|
||||
// DouglasPeucker<Competitor, GPSFixMoving> douglasPeucker = new DouglasPeucker<Competitor, GPSFixMoving>(
|
||||
// track);
|
||||
// return douglasPeucker.approximate(maxDistance, from, to);
|
||||
}
|
||||
|
||||
protected void triggerManeuverCacheRecalculationForAllCompetitors() {
|
||||
|
||||
Reference in new issue
Block a user