resolved the deep maneuver analysis recursion by setting strict time bounds for start/end of maneuver in case of recursions

This commit is contained in:
Axel Uhl committed 2014-05-16 00:57:01 +02:00
1 parent efe9fb6295
commit d1abac0c8d
2 files changed
+66 -28

No files matched your search

@@ -59,7 +59,7 @@ public class ManeuverAnalysisQingdao2014Test extends AbstractManeuverDetectionTe
* a penalty although between the jibe and the tack there is a mark passing * a penalty although between the jibe and the tack there is a mark passing
*/ */
@Test @Test
public void testManeuversForFindelCriticalDetection() throws ParseException, NoWindException { public void testManeuversForAlinghiCriticalDetection() throws ParseException, NoWindException {
Competitor competitor = getCompetitorByName("Alinghi"); Competitor competitor = getCompetitorByName("Alinghi");
assertNotNull(competitor); assertNotNull(competitor);
Date fromDate = dateFormat.parse("05/01/2014-09:07:00"); Date fromDate = dateFormat.parse("05/01/2014-09:07:00");
@@ -5,6 +5,7 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.ConcurrentModificationException; import java.util.ConcurrentModificationException;
import java.util.Date; import java.util.Date;
@@ -1927,7 +1928,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
} }
if (extendedTo != null) { if (extendedTo != null) {
try { try {
List<Maneuver> extendedResultForCache = detectManeuvers(competitor, extendedFrom, extendedTo); List<Maneuver> extendedResultForCache = detectManeuvers(competitor, extendedFrom, extendedTo, /* ignoreMarkPassings */ false);
result = new Triple<TimePoint, TimePoint, List<Maneuver>>(extendedFrom, extendedTo, result = new Triple<TimePoint, TimePoint, List<Maneuver>>(extendedFrom, extendedTo,
extendedResultForCache); extendedResultForCache);
} catch (NoWindException ex) { } catch (NoWindException ex) {
@@ -1949,19 +1950,26 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
} }
/** /**
* @param ignoreMarkPassings
* When <code>true</code>, no {@link ManeuverType#MARK_PASSING} maneuvers will be identified, and the
* fact that a mark passing would split up what else may be a penalty circle is ignored. This is helpful
* for recursive calls, e.g., after identifying a tack and a jibe around a mark passing and trying to
* identify for the time before and after the mark passing which maneuvers exist on which side of the
* passing.
* @return a valid but possibly empty list * @return a valid but possibly empty list
* @see #detectManeuvers(Competitor, List) * @see #detectManeuvers(Competitor, List, boolean, TimePoint, TimePoint)
*/ */
private List<Maneuver> detectManeuvers(Competitor competitor, TimePoint from, TimePoint to) throws NoWindException { private List<Maneuver> detectManeuvers(Competitor competitor, TimePoint from, TimePoint to, boolean ignoreMarkPassings) throws NoWindException {
return detectManeuvers( return detectManeuvers(
competitor, approximate(competitor, getRace().getBoatClass().getMaximumDistanceForCourseApproximation(), competitor, approximate(competitor, getRace().getBoatClass().getMaximumDistanceForCourseApproximation(),
from, to)); from, to), ignoreMarkPassings, from, to);
} }
/** /**
* Tries to detect maneuvers on the <code>competitor</code>'s track based on a number of approximating fixes. The * Tries to detect maneuvers on the <code>competitor</code>'s track based on a number of approximating fixes. The
* fixes contain bearing information, but this is not the bearing leading to the next approximation fix but the * fixes contain bearing information, but this is not the bearing leading to the next approximation fix but the
* bearing the boat had at the time of the approximating fix which is taken from the original track.<p> * bearing the boat had at the time of the approximating fix which is taken from the original track.
* <p>
* *
* The time period assumed for a maneuver duration is taken from the * The time period assumed for a maneuver duration is taken from the
* {@link BoatClass#getApproximateManeuverDurationInMilliseconds() boat class}. If no maneuver is detected, an empty * {@link BoatClass#getApproximateManeuverDurationInMilliseconds() boat class}. If no maneuver is detected, an empty
@@ -1972,10 +1980,28 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
* timely distance than {@link #getApproximateManeuverDurationInMilliseconds()} (including single course changes * timely distance than {@link #getApproximateManeuverDurationInMilliseconds()} (including single course changes
* that have no surrounding other course changes to group) are grouped into one {@link Maneuver}. * that have no surrounding other course changes to group) are grouped into one {@link Maneuver}.
* *
* @param ignoreMarkPassings
* When <code>true</code>, no {@link ManeuverType#MARK_PASSING} maneuvers will be identified, and the
* fact that a mark passing would split up what else may be a penalty circle is ignored. This is helpful
* for recursive calls, e.g., after identifying a tack and a jibe around a mark passing and trying to
* identify for the time before and after the mark passing which maneuvers exist on which side of the
* passing.
* @param earliestManeuverStart
* maneuver start will not be before this time point; if a maneuver is found whose time point is at or
* after this time point, no matter how close it is, its start regarding speed and course into the
* maneuver and the leg before the maneuver is not taken from an earlier time point, even if half the
* maneuver duration before the maneuver time point were before this time point.
* @param latestManeuverEnd
* maneuver end will not be after this time point; if a maneuver is found whose time point is at or
* before this time point, no matter how close it is, its end regarding speed and course out of the
* maneuver and the leg after the maneuver is not taken from a later time point, even if half the
* maneuver duration after the maneuver time point were after this time point.
*
* @return an empty list if no maneuver is detected for <code>competitor</code> between <code>from</code> and * @return an empty list if no maneuver is detected for <code>competitor</code> between <code>from</code> and
* <code>to</code>, or else the list of maneuvers detected. * <code>to</code>, or else the list of maneuvers detected.
*/ */
private List<Maneuver> detectManeuvers(Competitor competitor, List<GPSFixMoving> approximatingFixesToAnalyze) private List<Maneuver> detectManeuvers(Competitor competitor, List<GPSFixMoving> approximatingFixesToAnalyze,
boolean ignoreMarkPassings, TimePoint earliestManeuverStart, TimePoint latestManeuverEnd)
throws NoWindException { throws NoWindException {
List<Maneuver> result = new ArrayList<Maneuver>(); List<Maneuver> result = new ArrayList<Maneuver>();
if (approximatingFixesToAnalyze.size() > 2) { if (approximatingFixesToAnalyze.size() > 2) {
@@ -2004,7 +2030,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
// start new list // start new list
List<Maneuver> maneuvers = groupChangesInSameDirectionIntoManeuvers(competitor, List<Maneuver> maneuvers = groupChangesInSameDirectionIntoManeuvers(competitor,
speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges, speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges,
courseChangeSequenceInSameDirection); courseChangeSequenceInSameDirection, ignoreMarkPassings, earliestManeuverStart, latestManeuverEnd);
result.addAll(maneuvers); result.addAll(maneuvers);
courseChangeSequenceInSameDirection.clear(); courseChangeSequenceInSameDirection.clear();
speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges = speedWithBearingOnApproximationFromPreviousToCurrent; speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges = speedWithBearingOnApproximationFromPreviousToCurrent;
@@ -2017,7 +2043,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
if (!courseChangeSequenceInSameDirection.isEmpty()) { if (!courseChangeSequenceInSameDirection.isEmpty()) {
result.addAll(groupChangesInSameDirectionIntoManeuvers(competitor, result.addAll(groupChangesInSameDirectionIntoManeuvers(competitor,
speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges, speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges,
courseChangeSequenceInSameDirection)); courseChangeSequenceInSameDirection, ignoreMarkPassings, earliestManeuverStart, latestManeuverEnd));
} }
} }
return result; return result;
@@ -2068,18 +2094,34 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
* direction which may, e.g., represent a penalty circle or a mark rounding maneuver. As the maneuver's time point, * direction which may, e.g., represent a penalty circle or a mark rounding maneuver. As the maneuver's time point,
* the average time point of the course changes that went into the maneuver construction is used. * the average time point of the course changes that went into the maneuver construction is used.
* <p> * <p>
*
* @param speedWithBearingOnApproximationAtBeginning * @param speedWithBearingOnApproximationAtBeginning
* the speed/bearing before the first approximating fix passed in * the speed/bearing before the first approximating fix passed in
* <code>courseChangeSequenceInSameDirection</code> * <code>courseChangeSequenceInSameDirection</code>
* @param courseChangeSequenceInSameDirection * @param courseChangeSequenceInSameDirection
* all expected to have equal {@link CourseChange#to()} values * all expected to have equal {@link CourseChange#to()} values
* @param ignoreMarkPassings
* When <code>true</code>, no {@link ManeuverType#MARK_PASSING} maneuvers will be identified, and the
* fact that a mark passing would split up what else may be a penalty circle is ignored. This is helpful
* for recursive calls, e.g., after identifying a tack and a jibe around a mark passing and trying to
* identify for the time before and after the mark passing which maneuvers exist on which side of the
* passing.
* @param earliestManeuverStart
* maneuver start will not be before this time point; if a maneuver is found whose time point is at or
* after this time point, no matter how close it is, its start regarding speed and course into the
* maneuver and the leg before the maneuver is not taken from an earlier time point, even if half the
* maneuver duration before the maneuver time point were before this time point.
* @param latestManeuverEnd
* maneuver end will not be after this time point; if a maneuver is found whose time point is at or
* before this time point, no matter how close it is, its end regarding speed and course out of the
* maneuver and the leg after the maneuver is not taken from a later time point, even if half the
* maneuver duration after the maneuver time point were after this time point.
* *
* @return a non-<code>null</code> list * @return a non-<code>null</code> list
*/ */
private List<Maneuver> groupChangesInSameDirectionIntoManeuvers(Competitor competitor, private List<Maneuver> groupChangesInSameDirectionIntoManeuvers(Competitor competitor,
SpeedWithBearing speedWithBearingOnApproximationAtBeginning, SpeedWithBearing speedWithBearingOnApproximationAtBeginning,
List<Pair<GPSFixMoving, CourseChange>> courseChangeSequenceInSameDirection) throws NoWindException { List<Pair<GPSFixMoving, CourseChange>> courseChangeSequenceInSameDirection, boolean ignoreMarkPassings,
TimePoint earliestManeuverStart, TimePoint latestManeuverEnd) throws NoWindException {
List<Maneuver> result = new ArrayList<Maneuver>(); List<Maneuver> result = new ArrayList<Maneuver>();
List<Pair<GPSFixMoving, CourseChange>> group = new ArrayList<Pair<GPSFixMoving, CourseChange>>(); List<Pair<GPSFixMoving, CourseChange>> group = new ArrayList<Pair<GPSFixMoving, CourseChange>>();
if (!courseChangeSequenceInSameDirection.isEmpty()) { if (!courseChangeSequenceInSameDirection.isEmpty()) {
@@ -2110,7 +2152,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
// if next is more then approximate maneuver duration later or further apart than two hull lengths, // if next is more then approximate maneuver duration later or further apart than two hull lengths,
// turn the current group into a maneuver and add to result // turn the current group into a maneuver and add to result
Util.addAll(createManeuverFromGroupOfCourseChanges(competitor, beforeGroupOnApproximation, Util.addAll(createManeuverFromGroupOfCourseChanges(competitor, beforeGroupOnApproximation,
group, afterCurrentCourseChange, totalCourseChangeInDegrees, totalMilliseconds), result); group, afterCurrentCourseChange, totalCourseChangeInDegrees, totalMilliseconds, earliestManeuverStart, latestManeuverEnd), result);
group.clear(); group.clear();
totalCourseChangeInDegrees = 0.0; totalCourseChangeInDegrees = 0.0;
totalMilliseconds = 0l; totalMilliseconds = 0l;
@@ -2126,7 +2168,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
} while (iter.hasNext()); } while (iter.hasNext());
if (!group.isEmpty()) { if (!group.isEmpty()) {
Util.addAll(createManeuverFromGroupOfCourseChanges(competitor, beforeGroupOnApproximation, group, Util.addAll(createManeuverFromGroupOfCourseChanges(competitor, beforeGroupOnApproximation, group,
afterCurrentCourseChange, totalCourseChangeInDegrees, totalMilliseconds), result); afterCurrentCourseChange, totalCourseChangeInDegrees, totalMilliseconds, earliestManeuverStart, latestManeuverEnd), result);
} }
} }
return result; return result;
@@ -2135,19 +2177,15 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
private Iterable<Maneuver> createManeuverFromGroupOfCourseChanges(Competitor competitor, private Iterable<Maneuver> createManeuverFromGroupOfCourseChanges(Competitor competitor,
SpeedWithBearing speedWithBearingOnApproximationAtBeginning, List<Pair<GPSFixMoving, CourseChange>> group, SpeedWithBearing speedWithBearingOnApproximationAtBeginning, List<Pair<GPSFixMoving, CourseChange>> group,
SpeedWithBearing speedWithBearingOnApproximationAtEnd, double totalCourseChangeInDegrees, SpeedWithBearing speedWithBearingOnApproximationAtEnd, double totalCourseChangeInDegrees,
long totalMilliseconds) throws NoWindException { long totalMilliseconds, TimePoint earliestManeuverStart, TimePoint latestManeuverEnd) throws NoWindException {
List<Maneuver> result = new ArrayList<>(); List<Maneuver> result = new ArrayList<>();
MillisecondsTimePoint timePointBeforeManeuver = new MillisecondsTimePoint(group.get(0).getA().getTimePoint() TimePoint timePointBeforeManeuver = Collections.max(Arrays.asList(new MillisecondsTimePoint(group.get(0).getA().getTimePoint()
.asMillis() .asMillis() - getApproximateManeuverDurationInMilliseconds() / 2), earliestManeuverStart));
- getApproximateManeuverDurationInMilliseconds() / 2); TimePoint timePointAfterManeuver = Collections.min(Arrays.asList(new MillisecondsTimePoint(group.get(group.size() - 1).getA()
MillisecondsTimePoint timePointAfterManeuver = new MillisecondsTimePoint(group.get(group.size() - 1).getA() .getTimePoint().asMillis() + getApproximateManeuverDurationInMilliseconds() / 2), latestManeuverEnd));
.getTimePoint().asMillis() TimePoint maneuverTimePoint = computeManeuverTimepoint(competitor, timePointBeforeManeuver, timePointAfterManeuver);
+ getApproximateManeuverDurationInMilliseconds() / 2);
TimePoint maneuverTimePoint = computeManeuverTimepoint(competitor, timePointBeforeManeuver,
timePointAfterManeuver);
final GPSFixTrack<Competitor, GPSFixMoving> competitorTrack = getTrack(competitor); final GPSFixTrack<Competitor, GPSFixMoving> competitorTrack = getTrack(competitor);
Position maneuverPosition = competitorTrack Position maneuverPosition = competitorTrack.getEstimatedPosition(maneuverTimePoint, /* extrapolate */false);
.getEstimatedPosition(maneuverTimePoint, /* extrapolate */false);
Tack tackAfterManeuver = getTack(maneuverPosition, timePointAfterManeuver, Tack tackAfterManeuver = getTack(maneuverPosition, timePointAfterManeuver,
speedWithBearingOnApproximationAtEnd.getBearing()); speedWithBearingOnApproximationAtEnd.getBearing());
ManeuverType maneuverType; ManeuverType maneuverType;
@@ -2196,8 +2234,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
// kiwi drop. // kiwi drop.
// Therefore, we recursively detect the maneuvers for the segment before and the segment after the // Therefore, we recursively detect the maneuvers for the segment before and the segment after the
// mark passing and add the results to our result. // mark passing and add the results to our result.
result.addAll(detectManeuvers(competitor, timePointBeforeManeuver, markPassingTimePoint.minus(1))); result.addAll(detectManeuvers(competitor, timePointBeforeManeuver, markPassingTimePoint.minus(1), /* ignoreMarkPassings */ true));
result.addAll(detectManeuvers(competitor, markPassingTimePoint.plus(1), timePointAfterManeuver)); result.addAll(detectManeuvers(competitor, markPassingTimePoint.plus(1), timePointAfterManeuver, /* ignoreMarkPassings */ true));
} else { } else {
// Either there was no mark passing, or the mark passing was not accompanied by a tack or a jibe // Either there was no mark passing, or the mark passing was not accompanied by a tack or a jibe
if (tacked && jibed && markPassingTimePoint == null) { if (tacked && jibed && markPassingTimePoint == null) {
@@ -2241,8 +2279,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
* Computes the maneuver time point as the time point along between maneuver start and end where the competitor's * Computes the maneuver time point as the time point along between maneuver start and end where the competitor's
* track has greatest change in course. * track has greatest change in course.
*/ */
private TimePoint computeManeuverTimepoint(Competitor competitor, MillisecondsTimePoint timePointBeforeManeuver, private TimePoint computeManeuverTimepoint(Competitor competitor, TimePoint timePointBeforeManeuver,
MillisecondsTimePoint timePointAfterManeuver) { TimePoint timePointAfterManeuver) {
TimePoint result = timePointBeforeManeuver; TimePoint result = timePointBeforeManeuver;
GPSFixTrack<Competitor, GPSFixMoving> track = getTrack(competitor); GPSFixTrack<Competitor, GPSFixMoving> track = getTrack(competitor);
GPSFixMoving lastFix = null; GPSFixMoving lastFix = null;