added Javadoc

Change-Id: I4fcd87e412439369fa6900a50b50d2ef2e26ca75
This commit is contained in:
Axel Uhl committed 2019-02-05 00:08:25 +01:00
1 parent f0b9b64947
commit 654b7c4fa2
4 files changed
+198 -11

No files matched your search

@@ -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);
@@ -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;
}
}
@@ -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() {