mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-30 17:36:40 +00:00
SailingService.getWind(...) methods now also add the COMBINED WindSource(Type) by default
This commit is contained in:
1 parent
a52a0e5645
commit
2150629d7b
3 files changed
+247
-175
No files matched your search
+1
-2
@@ -16,8 +16,7 @@ import com.sap.sailing.domain.tracking.WindTrack;
|
||||
*/
|
||||
public class CombinedWindAsNavigableSet extends VirtualWindFixesAsNavigableSet {
|
||||
|
||||
public CombinedWindAsNavigableSet(WindTrack track, TrackedRace trackedRace,
|
||||
long resolutionInMilliseconds) {
|
||||
public CombinedWindAsNavigableSet(WindTrack track, TrackedRace trackedRace, long resolutionInMilliseconds) {
|
||||
super(track, trackedRace, resolutionInMilliseconds);
|
||||
}
|
||||
|
||||
|
||||
+242
-172
@@ -77,74 +77,75 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
private static final Logger logger = Logger.getLogger(TrackedRaceImpl.class.getName());
|
||||
|
||||
private static final double PENALTY_CIRCLE_DEGREES_THRESHOLD = 320;
|
||||
|
||||
|
||||
/**
|
||||
* Used in {@link #getStartFinishPlacemarks()} to calculate the radius for the
|
||||
* {@link ReverseGeocoder#getPlacemarksNear(Position, double) GetPlacemarksNear-Service}.
|
||||
*/
|
||||
private static final double GEONAMES_RADIUS_CACLCULATION_FACTOR = 10.0;
|
||||
|
||||
// TODO observe the race course; if it changes, update leg structures; consider fine-grained update events that tell what changed
|
||||
// TODO observe the race course; if it changes, update leg structures; consider fine-grained update events that tell
|
||||
// what changed
|
||||
private final RaceDefinition race;
|
||||
|
||||
|
||||
private final TrackedEvent trackedEvent;
|
||||
|
||||
|
||||
/**
|
||||
* Keeps the oldest timestamp that is fed into this tracked race, either from a boat fix, a buoy
|
||||
* fix, a race start/finish or a coarse definition.
|
||||
* Keeps the oldest timestamp that is fed into this tracked race, either from a boat fix, a buoy fix, a race
|
||||
* start/finish or a coarse definition.
|
||||
*/
|
||||
private TimePoint startOfTracking;
|
||||
|
||||
|
||||
/**
|
||||
* Race start time as announced by the tracking infrastructure
|
||||
*/
|
||||
private TimePoint startTimeReceived;
|
||||
|
||||
|
||||
private TimePoint timePointOfNewestEvent;
|
||||
private TimePoint timePointOfLastEvent;
|
||||
private long updateCount;
|
||||
|
||||
private final Map<TimePoint, List<Competitor>> competitorRankings;
|
||||
|
||||
|
||||
private final Map<TimePoint, List<Competitor>> competitorRankings;
|
||||
|
||||
/**
|
||||
* legs appear in the order in which they appear in the race's course
|
||||
*/
|
||||
private final LinkedHashMap<Leg, TrackedLeg> trackedLegs;
|
||||
|
||||
|
||||
private final Map<Competitor, GPSFixTrack<Competitor, GPSFixMoving>> tracks;
|
||||
private final Map<Competitor, NavigableSet<MarkPassing>> markPassingsForCompetitor;
|
||||
|
||||
|
||||
/**
|
||||
* The mark passing sets used as values are ordered by time stamp.
|
||||
*/
|
||||
private final Map<Waypoint, NavigableSet<MarkPassing>> markPassingsForWaypoint;
|
||||
|
||||
|
||||
/**
|
||||
* Values are the <code>from</code> and <code>to</code> time points between which the maneuvers have been previously
|
||||
* computed. Clients wanting to know maneuvers for the competitor outside of this time interval need to (re-)compute them.
|
||||
* computed. Clients wanting to know maneuvers for the competitor outside of this time interval need to (re-)compute
|
||||
* them.
|
||||
*/
|
||||
private final Map<Competitor, Triple<TimePoint, TimePoint, List<Maneuver>>> maneuverCache;
|
||||
|
||||
|
||||
/**
|
||||
* A tracked race can maintain a number of sources for wind information from which a client
|
||||
* can select. As all intra-leg computations are done dynamically based on wind information,
|
||||
* selecting a different wind information source can alter the intra-leg results. See
|
||||
* {@link #currentWindSource}.
|
||||
* A tracked race can maintain a number of sources for wind information from which a client can select. As all
|
||||
* intra-leg computations are done dynamically based on wind information, selecting a different wind information
|
||||
* source can alter the intra-leg results. See {@link #currentWindSource}.
|
||||
*/
|
||||
private final Map<WindSource, WindTrack> windTracks;
|
||||
|
||||
|
||||
private Wind directionFromStartToNextMarkCache;
|
||||
|
||||
|
||||
private final Map<Buoy, GPSFixTrack<Buoy, GPSFix>> buoyTracks;
|
||||
|
||||
|
||||
protected long millisecondsOverWhichToAverageSpeed;
|
||||
|
||||
private final Map<Buoy, StartToNextMarkCacheInvalidationListener> startToNextMarkCacheInvalidationListeners;
|
||||
|
||||
protected long millisecondsOverWhichToAverageWind;
|
||||
|
||||
|
||||
private final WindStore windStore;
|
||||
|
||||
|
||||
public TrackedRaceImpl(TrackedEvent trackedEvent, RaceDefinition race, WindStore windStore,
|
||||
long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed) {
|
||||
super();
|
||||
@@ -154,14 +155,15 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
this.millisecondsOverWhichToAverageSpeed = millisecondsOverWhichToAverageSpeed;
|
||||
this.millisecondsOverWhichToAverageWind = millisecondsOverWhichToAverageWind;
|
||||
this.startToNextMarkCacheInvalidationListeners = new HashMap<Buoy, TrackedRaceImpl.StartToNextMarkCacheInvalidationListener>();
|
||||
this.maneuverCache = new HashMap<Competitor, Util.Triple<TimePoint,TimePoint,List<Maneuver>>>();
|
||||
this.maneuverCache = new HashMap<Competitor, Util.Triple<TimePoint, TimePoint, List<Maneuver>>>();
|
||||
this.buoyTracks = new HashMap<Buoy, GPSFixTrack<Buoy, GPSFix>>();
|
||||
int i = 0;
|
||||
for (Waypoint waypoint : race.getCourse().getWaypoints()) {
|
||||
for (Buoy buoy : waypoint.getBuoys()) {
|
||||
getOrCreateTrack(buoy);
|
||||
if (i<2) {
|
||||
// add cache invalidation listeners for first and second waypoint's buoys for directionFromStartToNextMarkCache
|
||||
if (i < 2) {
|
||||
// add cache invalidation listeners for first and second waypoint's buoys for
|
||||
// directionFromStartToNextMarkCache
|
||||
addStartToNextMarkCacheInvalidationListener(buoy);
|
||||
}
|
||||
}
|
||||
@@ -177,44 +179,50 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
markPassingsForCompetitor = new HashMap<Competitor, NavigableSet<MarkPassing>>();
|
||||
tracks = new HashMap<Competitor, GPSFixTrack<Competitor, GPSFixMoving>>();
|
||||
for (Competitor competitor : race.getCompetitors()) {
|
||||
markPassingsForCompetitor.put(competitor, new ConcurrentSkipListSet<MarkPassing>(MarkPassingByTimeComparator.INSTANCE));
|
||||
tracks.put(competitor, new DynamicGPSFixMovingTrackImpl<Competitor>(competitor, millisecondsOverWhichToAverageSpeed));
|
||||
markPassingsForCompetitor.put(competitor, new ConcurrentSkipListSet<MarkPassing>(
|
||||
MarkPassingByTimeComparator.INSTANCE));
|
||||
tracks.put(competitor, new DynamicGPSFixMovingTrackImpl<Competitor>(competitor,
|
||||
millisecondsOverWhichToAverageSpeed));
|
||||
}
|
||||
markPassingsForWaypoint = new HashMap<Waypoint, NavigableSet<MarkPassing>>();
|
||||
for (Waypoint waypoint : race.getCourse().getWaypoints()) {
|
||||
markPassingsForWaypoint.put(waypoint, new ConcurrentSkipListSet<MarkPassing>(MarkPassingByTimeComparator.INSTANCE));
|
||||
markPassingsForWaypoint.put(waypoint, new ConcurrentSkipListSet<MarkPassing>(
|
||||
MarkPassingByTimeComparator.INSTANCE));
|
||||
}
|
||||
windTracks = new HashMap<WindSource, WindTrack>();
|
||||
windTracks.putAll(windStore.loadWindTracks(trackedEvent, this, millisecondsOverWhichToAverageWind));
|
||||
// by default, a tracked race offers one course-based wind estimation, one track-based wind estimation track and
|
||||
// one "WEB" track for manual or REST-based wind reception; other wind tracks may be added as fixes are received for them.
|
||||
// one "WEB" track for manual or REST-based wind reception; other wind tracks may be added as fixes are received
|
||||
// for them.
|
||||
WindSource courseBasedWindSource = new WindSourceImpl(WindSourceType.COURSE_BASED);
|
||||
windTracks.put(courseBasedWindSource, windStore.getWindTrack(trackedEvent, this, courseBasedWindSource, millisecondsOverWhichToAverageWind));
|
||||
windTracks.put(courseBasedWindSource,
|
||||
windStore.getWindTrack(trackedEvent, this, courseBasedWindSource, millisecondsOverWhichToAverageWind));
|
||||
WindSource trackBasedWindSource = new WindSourceImpl(WindSourceType.TRACK_BASED_ESTIMATION);
|
||||
windTracks.put(trackBasedWindSource, windStore.getWindTrack(trackedEvent, this, trackBasedWindSource, millisecondsOverWhichToAverageWind));
|
||||
windTracks.put(trackBasedWindSource,
|
||||
windStore.getWindTrack(trackedEvent, this, trackBasedWindSource, millisecondsOverWhichToAverageWind));
|
||||
this.trackedEvent = trackedEvent;
|
||||
competitorRankings = new HashMap<TimePoint, List<Competitor>>();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Precondition: race has already been set, e.g., in constructor before this methocd is called
|
||||
*/
|
||||
abstract protected TrackedLeg createTrackedLeg(Leg leg);
|
||||
|
||||
|
||||
public RaceIdentifier getRaceIdentifier() {
|
||||
return new EventNameAndRaceName(getTrackedEvent().getEvent().getName(), getRace().getName());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NavigableSet<MarkPassing> getMarkPassings(Competitor competitor) {
|
||||
return markPassingsForCompetitor.get(competitor);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Iterable<MarkPassing> getMarkPassingsInOrder(Waypoint waypoint) {
|
||||
return markPassingsForWaypoint.get(waypoint);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TimePoint getStartOfTracking() {
|
||||
return startOfTracking;
|
||||
@@ -234,10 +242,11 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
if (firstMarkPassingFirstMark != null) {
|
||||
TimePoint timeOfFirstMarkPassingFirstMark = firstMarkPassingFirstMark.getTimePoint();
|
||||
if (startTimeReceived != null) {
|
||||
long startTimeReceived2timeOfFirstMarkPassingFirstMark = timeOfFirstMarkPassingFirstMark.asMillis() -
|
||||
startTimeReceived.asMillis();
|
||||
long startTimeReceived2timeOfFirstMarkPassingFirstMark = timeOfFirstMarkPassingFirstMark.asMillis()
|
||||
- startTimeReceived.asMillis();
|
||||
if (startTimeReceived2timeOfFirstMarkPassingFirstMark > MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS) {
|
||||
result = new MillisecondsTimePoint(timeOfFirstMarkPassingFirstMark.asMillis() - MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS);
|
||||
result = new MillisecondsTimePoint(timeOfFirstMarkPassingFirstMark.asMillis()
|
||||
- MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS);
|
||||
} else {
|
||||
result = startTimeReceived;
|
||||
}
|
||||
@@ -249,7 +258,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TimePoint getAssumedEnd() {
|
||||
TimePoint result = null;
|
||||
@@ -288,8 +297,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
return null;
|
||||
} else {
|
||||
TimePoint end = timePoint;
|
||||
if (markPassings.last().getWaypoint() == getRace().getCourse().getLastWaypoint() &&
|
||||
timePoint.compareTo(markPassings.last().getTimePoint()) > 0) {
|
||||
if (markPassings.last().getWaypoint() == getRace().getCourse().getLastWaypoint()
|
||||
&& timePoint.compareTo(markPassings.last().getTimePoint()) > 0) {
|
||||
// competitor has finished race; use time point of crossing the finish line
|
||||
end = markPassings.last().getTimePoint();
|
||||
}
|
||||
@@ -301,25 +310,27 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
public GPSFixTrack<Competitor, GPSFixMoving> getTrack(Competitor competitor) {
|
||||
return tracks.get(competitor);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TrackedLeg getTrackedLegFinishingAt(Waypoint endOfLeg) {
|
||||
int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(endOfLeg);
|
||||
if (indexOfWaypoint == -1) {
|
||||
throw new IllegalArgumentException("Waypoint "+endOfLeg+" not found in "+getRace().getCourse());
|
||||
throw new IllegalArgumentException("Waypoint " + endOfLeg + " not found in " + getRace().getCourse());
|
||||
} else if (indexOfWaypoint == 0) {
|
||||
throw new IllegalArgumentException("Waypoint "+endOfLeg+" isn't start of any leg in "+getRace().getCourse());
|
||||
throw new IllegalArgumentException("Waypoint " + endOfLeg + " isn't start of any leg in "
|
||||
+ getRace().getCourse());
|
||||
}
|
||||
return trackedLegs.get(race.getCourse().getLegs().get(indexOfWaypoint-1));
|
||||
return trackedLegs.get(race.getCourse().getLegs().get(indexOfWaypoint - 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackedLeg getTrackedLegStartingAt(Waypoint startOfLeg) {
|
||||
int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(startOfLeg);
|
||||
if (indexOfWaypoint == -1) {
|
||||
throw new IllegalArgumentException("Waypoint "+startOfLeg+" not found in "+getRace().getCourse());
|
||||
} else if (indexOfWaypoint == Util.size(getRace().getCourse().getWaypoints())-1) {
|
||||
throw new IllegalArgumentException("Waypoint "+startOfLeg+" isn't start of any leg in "+getRace().getCourse());
|
||||
throw new IllegalArgumentException("Waypoint " + startOfLeg + " not found in " + getRace().getCourse());
|
||||
} else if (indexOfWaypoint == Util.size(getRace().getCourse().getWaypoints()) - 1) {
|
||||
throw new IllegalArgumentException("Waypoint " + startOfLeg + " isn't start of any leg in "
|
||||
+ getRace().getCourse());
|
||||
}
|
||||
return trackedLegs.get(race.getCourse().getLegs().get(indexOfWaypoint));
|
||||
}
|
||||
@@ -356,7 +367,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public TrackedLeg getTrackedLeg(Leg leg) {
|
||||
return trackedLegs.get(leg);
|
||||
}
|
||||
@@ -384,7 +395,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
int currentRank = getTrackedLeg(competitor, leg).getRank(timePoint);
|
||||
return currentRank - previousRank;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public synchronized int getRank(Competitor competitor) throws NoWindException {
|
||||
return getRank(competitor, MillisecondsTimePoint.now());
|
||||
@@ -404,7 +415,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
Collections.sort(rankedCompetitors, comparator);
|
||||
competitorRankings.put(timePoint, rankedCompetitors);
|
||||
}
|
||||
return rankedCompetitors.indexOf(competitor)+1;
|
||||
return rankedCompetitors.indexOf(competitor) + 1;
|
||||
}
|
||||
} catch (NoWindError e) {
|
||||
throw e.getCause();
|
||||
@@ -428,7 +439,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TrackedLeg getCurrentLeg(TimePoint timePoint) {
|
||||
Waypoint lastWaypointPassed = null;
|
||||
@@ -489,32 +500,46 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint) {
|
||||
Position result = null;
|
||||
for (Buoy buoy : waypoint.getBuoys()) {
|
||||
Position nextPos = getOrCreateTrack(buoy).getEstimatedPosition(timePoint, /* extrapolate */ false);
|
||||
Position nextPos = getOrCreateTrack(buoy).getEstimatedPosition(timePoint, /* extrapolate */false);
|
||||
if (result == null) {
|
||||
result = nextPos;
|
||||
} else {
|
||||
result = result.translateGreatCircle(result.getBearingGreatCircle(nextPos), result.getDistance(nextPos).scale(0.5));
|
||||
result = result.translateGreatCircle(result.getBearingGreatCircle(nextPos), result.getDistance(nextPos)
|
||||
.scale(0.5));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* For wind sources of the special type {@link WindSourceType#COMBINED}, emits a new {@link CombinedWindTrackImpl}
|
||||
* which will not be added to {@link #windTracks} and will not lead to the wind source being listed in
|
||||
* {@link #getWindSources()} or {@link #getWindSources(WindSourceType)}. For all other wind sources, checks
|
||||
* {@link #windTracks} for the respective source. If found, it's returned; otherwise the wind track is created
|
||||
* through the {@link #windStore} using {@link #createWindTrack(WindSource)} and added to {@link #windTracks} before
|
||||
* being returned.
|
||||
*/
|
||||
@Override
|
||||
public WindTrack getOrCreateWindTrack(WindSource windSource) {
|
||||
WindTrack result;
|
||||
synchronized (windTracks) {
|
||||
result = windTracks.get(windSource);
|
||||
if (result == null) {
|
||||
result = createWindTrack(windSource);
|
||||
windTracks.put(windSource, result);
|
||||
if (windSource.getType() == WindSourceType.COMBINED) {
|
||||
result = new CombinedWindTrackImpl(this, getMillisecondsOverWhichToAverageWind());
|
||||
} else {
|
||||
synchronized (windTracks) {
|
||||
result = windTracks.get(windSource);
|
||||
if (result == null) {
|
||||
result = createWindTrack(windSource);
|
||||
windTracks.put(windSource, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a wind track for the <code>windSource</code> specified and stores it in {@link #windTracks}. The averaging interval is
|
||||
* set according to the averaging interval set for all other wind sources, or the default if no other wind source exists yet.
|
||||
* Creates a wind track for the <code>windSource</code> specified and stores it in {@link #windTracks}. The
|
||||
* averaging interval is set according to the averaging interval set for all other wind sources, or the default if
|
||||
* no other wind source exists yet.
|
||||
*/
|
||||
protected WindTrack createWindTrack(WindSource windSource) {
|
||||
return windStore.getWindTrack(trackedEvent, this, windSource, millisecondsOverWhichToAverageWind);
|
||||
@@ -528,26 +553,30 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
|
||||
@Override
|
||||
public Wind getWind(Position p, TimePoint at, Iterable<WindSource> windSourcesToExclude) {
|
||||
final WindWithConfidence<Pair<Position, TimePoint>> windWithConfidence = getWindWithConfidence(p, at, windSourcesToExclude);
|
||||
final WindWithConfidence<Pair<Position, TimePoint>> windWithConfidence = getWindWithConfidence(p, at,
|
||||
windSourcesToExclude);
|
||||
return windWithConfidence == null ? null : windWithConfidence.getObject();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public WindWithConfidence<Pair<Position, TimePoint>> getWindWithConfidence(Position p, TimePoint at, Iterable<WindSource> windSourcesToExclude) {
|
||||
final Weigher<TimePoint> weigher = ConfidenceFactory.INSTANCE.createHyperbolicTimeDifferenceWeigher(/*halfConfidenceAfterMilliseconds*/ 10000l);
|
||||
Weigher<Pair<Position, TimePoint>> timeWeigherThatPretendsToAlsoWeighPositions = new Weigher<Util.Pair<Position,TimePoint>>() {
|
||||
public WindWithConfidence<Pair<Position, TimePoint>> getWindWithConfidence(Position p, TimePoint at,
|
||||
Iterable<WindSource> windSourcesToExclude) {
|
||||
final Weigher<TimePoint> weigher = ConfidenceFactory.INSTANCE
|
||||
.createHyperbolicTimeDifferenceWeigher(/* halfConfidenceAfterMilliseconds */10000l);
|
||||
Weigher<Pair<Position, TimePoint>> timeWeigherThatPretendsToAlsoWeighPositions = new Weigher<Util.Pair<Position, TimePoint>>() {
|
||||
@Override
|
||||
public double getConfidence(Pair<Position, TimePoint> fix, Pair<Position, TimePoint> request) {
|
||||
return weigher.getConfidence(fix.getB(), request.getB());
|
||||
}
|
||||
};
|
||||
ConfidenceBasedAverager<ScalableWind, Wind, Pair<Position, TimePoint>> averager =
|
||||
ConfidenceFactory.INSTANCE.createAverager(timeWeigherThatPretendsToAlsoWeighPositions);
|
||||
ConfidenceBasedAverager<ScalableWind, Wind, Pair<Position, TimePoint>> averager = ConfidenceFactory.INSTANCE
|
||||
.createAverager(timeWeigherThatPretendsToAlsoWeighPositions);
|
||||
List<WindWithConfidence<Pair<Position, TimePoint>>> windFixesWithConfidences = new ArrayList<WindWithConfidence<Pair<Position, TimePoint>>>();
|
||||
for (WindSource windSource : getWindSources()) {
|
||||
if (!Util.contains(windSourcesToExclude, windSource)) {
|
||||
WindTrack track = getOrCreateWindTrack(windSource);
|
||||
WindWithConfidence<Pair<Position, TimePoint>> windWithConfidence = track.getAveragedWindWithConfidence(p, at);
|
||||
WindWithConfidence<Pair<Position, TimePoint>> windWithConfidence = track.getAveragedWindWithConfidence(
|
||||
p, at);
|
||||
if (windWithConfidence != null) {
|
||||
windFixesWithConfidences.add(windWithConfidence);
|
||||
}
|
||||
@@ -555,7 +584,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
HasConfidence<ScalableWind, Wind, Pair<Position, TimePoint>> average = averager.getAverage(
|
||||
windFixesWithConfidences, new Pair<Position, TimePoint>(p, at));
|
||||
WindWithConfidence<Pair<Position, TimePoint>> result = new WindWithConfidenceImpl<Pair<Position,TimePoint>>(
|
||||
WindWithConfidence<Pair<Position, TimePoint>> result = new WindWithConfidenceImpl<Pair<Position, TimePoint>>(
|
||||
average.getObject(), average.getConfidence(), new Pair<Position, TimePoint>(p, at));
|
||||
return result;
|
||||
}
|
||||
@@ -578,19 +607,20 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TimePoint getTimePointOfNewestEvent() {
|
||||
return timePointOfNewestEvent;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TimePoint getTimePointOfLastEvent() {
|
||||
return timePointOfLastEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param timeOfEvent may be <code>null</code> meaning to only unblock waiters but not update any time points
|
||||
* @param timeOfEvent
|
||||
* may be <code>null</code> meaning to only unblock waiters but not update any time points
|
||||
*/
|
||||
protected synchronized void updated(TimePoint timeOfEvent) {
|
||||
updateCount++;
|
||||
@@ -622,12 +652,12 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
wait(); // ...until updated(...) notifies us
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public synchronized void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) {
|
||||
updateStartToNextMarkCacheInvalidationCacheListenersAfterWaypointAdded(zeroBasedIndex, waypointThatGotAdded);
|
||||
markPassingsForWaypoint.put(waypointThatGotAdded, new ConcurrentSkipListSet<MarkPassing>(MarkPassingByTimeComparator.INSTANCE));
|
||||
markPassingsForWaypoint.put(waypointThatGotAdded, new ConcurrentSkipListSet<MarkPassing>(
|
||||
MarkPassingByTimeComparator.INSTANCE));
|
||||
for (Buoy buoy : waypointThatGotAdded.getBuoys()) {
|
||||
getOrCreateTrack(buoy);
|
||||
}
|
||||
@@ -648,7 +678,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
for (Map.Entry<Leg, TrackedLeg> entry : reorderedTrackedLegs.entrySet()) {
|
||||
trackedLegs.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
updated(/* time point*/ null);
|
||||
updated(/* time point */null);
|
||||
}
|
||||
|
||||
private void updateStartToNextMarkCacheInvalidationCacheListenersAfterWaypointAdded(int zeroBasedIndex,
|
||||
@@ -669,7 +699,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
addStartToNextMarkCacheInvalidationListener(waypointThatGotAdded);
|
||||
}
|
||||
|
||||
|
||||
private void addStartToNextMarkCacheInvalidationListener(Waypoint waypoint) {
|
||||
for (Buoy buoy : waypoint.getBuoys()) {
|
||||
addStartToNextMarkCacheInvalidationListener(buoy);
|
||||
@@ -702,7 +732,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
updateStartToNextMarkCacheInvalidationCacheListenersAfterWaypointRemoved(zeroBasedIndex, waypointThatGotRemoved);
|
||||
Leg toRemove = null;
|
||||
Leg last = null;
|
||||
int i=0;
|
||||
int i = 0;
|
||||
for (Map.Entry<Leg, TrackedLeg> e : trackedLegs.entrySet()) {
|
||||
last = e.getKey();
|
||||
if (i == zeroBasedIndex) {
|
||||
@@ -717,10 +747,10 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
if (toRemove != null) {
|
||||
trackedLegs.remove(toRemove);
|
||||
updated(/* time point*/ null);
|
||||
updated(/* time point */null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void updateStartToNextMarkCacheInvalidationCacheListenersAfterWaypointRemoved(int zeroBasedIndex,
|
||||
Waypoint waypointThatGotRemoved) {
|
||||
if (zeroBasedIndex < 2) {
|
||||
@@ -751,8 +781,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
Weigher<TimePoint> weigher = ConfidenceFactory.INSTANCE.createExponentialTimeDifferenceWeigher(
|
||||
// use a minimum confidence to avoid the bearing to flip to 270deg in case all is zero
|
||||
getMillisecondsOverWhichToAverageSpeed());
|
||||
Map<LegType, BearingWithConfidenceCluster<TimePoint>> bearings = clusterBearingsByLegType(timePoint,
|
||||
position, dummyMarkPassingForNow, weigher);
|
||||
Map<LegType, BearingWithConfidenceCluster<TimePoint>> bearings = clusterBearingsByLegType(timePoint, position,
|
||||
dummyMarkPassingForNow, weigher);
|
||||
// use the minimum confidence of the four "quadrants" as the result's confidence
|
||||
BearingWithConfidenceImpl<TimePoint> reversedUpwindAverage = null;
|
||||
int upwindNumberOfRelevantBoats = 0;
|
||||
@@ -763,8 +793,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
BearingWithConfidence<TimePoint> average0 = bearingClustersUpwind[0].getAverage(timePoint);
|
||||
BearingWithConfidence<TimePoint> average1 = bearingClustersUpwind[1].getAverage(timePoint);
|
||||
confidence = Math.min(average0.getConfidence(), average1.getConfidence());
|
||||
reversedUpwindAverage = new BearingWithConfidenceImpl<TimePoint>(average0.getObject().middle(average1.getObject()).reverse(),
|
||||
confidence, timePoint);
|
||||
reversedUpwindAverage = new BearingWithConfidenceImpl<TimePoint>(average0.getObject()
|
||||
.middle(average1.getObject()).reverse(), confidence, timePoint);
|
||||
upwindNumberOfRelevantBoats = Math.min(bearingClustersUpwind[0].size(), bearingClustersUpwind[1].size());
|
||||
}
|
||||
BearingWithConfidenceImpl<TimePoint> downwindAverage = null;
|
||||
@@ -776,8 +806,10 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
BearingWithConfidence<TimePoint> average1 = bearingClustersDownwind[1].getAverage(timePoint);
|
||||
double downwindConfidence = Math.min(average0.getConfidence(), average1.getConfidence());
|
||||
confidence = Math.min(confidence, downwindConfidence);
|
||||
downwindAverage = new BearingWithConfidenceImpl<TimePoint>(average0.getObject().middle(average1.getObject()), downwindConfidence, timePoint);
|
||||
downwindNumberOfRelevantBoats = Math.min(bearingClustersDownwind[0].size(), bearingClustersDownwind[1].size());
|
||||
downwindAverage = new BearingWithConfidenceImpl<TimePoint>(average0.getObject()
|
||||
.middle(average1.getObject()), downwindConfidence, timePoint);
|
||||
downwindNumberOfRelevantBoats = Math.min(bearingClustersDownwind[0].size(),
|
||||
bearingClustersDownwind[1].size());
|
||||
}
|
||||
int numberOfBoatsRelevantForEstimate = upwindNumberOfRelevantBoats + downwindNumberOfRelevantBoats;
|
||||
BearingWithConfidenceCluster<TimePoint> resultCluster = new BearingWithConfidenceCluster<TimePoint>(weigher);
|
||||
@@ -790,14 +822,16 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
resultCluster.add(downwindAverage);
|
||||
}
|
||||
BearingWithConfidence<TimePoint> resultBearing = resultCluster.getAverage(timePoint);
|
||||
return resultBearing == null ? null : new WindImpl(null, timePoint,
|
||||
new KnotSpeedWithBearingImpl(/* speedInKnots */ numberOfBoatsRelevantForEstimate, resultBearing.getObject()));
|
||||
return resultBearing == null ? null : new WindImpl(null, timePoint, new KnotSpeedWithBearingImpl(
|
||||
/* speedInKnots */numberOfBoatsRelevantForEstimate, resultBearing.getObject()));
|
||||
}
|
||||
|
||||
// TODO confidences need to be computed not only based on timePoint but also on position: boats far away don't contribute as confidently as boats close by
|
||||
// TODO confidences need to be computed not only based on timePoint but also on position: boats far away don't
|
||||
// contribute as confidently as boats close by
|
||||
private Map<LegType, BearingWithConfidenceCluster<TimePoint>> clusterBearingsByLegType(TimePoint timePoint,
|
||||
Position position, DummyMarkPassingWithTimePointOnly dummyMarkPassingForNow, Weigher<TimePoint> weigher) {
|
||||
Weigher<TimePoint> weigherForMarkPassingProximity = new HyperbolicTimeDifferenceWeigher(getMillisecondsOverWhichToAverageSpeed()*5);
|
||||
Weigher<TimePoint> weigherForMarkPassingProximity = new HyperbolicTimeDifferenceWeigher(
|
||||
getMillisecondsOverWhichToAverageSpeed() * 5);
|
||||
Map<LegType, BearingWithConfidenceCluster<TimePoint>> bearings = new HashMap<LegType, BearingWithConfidenceCluster<TimePoint>>();
|
||||
for (LegType legType : LegType.values()) {
|
||||
bearings.put(legType, new BearingWithConfidenceCluster<TimePoint>(weigher));
|
||||
@@ -810,16 +844,22 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
try {
|
||||
legType = trackedLeg.getLegType(timePoint);
|
||||
} catch (NoWindException e) {
|
||||
logger.warning("Unable to determine leg type for race "+getRace().getName()+" while trying to estimate wind");
|
||||
logger.warning("Unable to determine leg type for race " + getRace().getName()
|
||||
+ " while trying to estimate wind");
|
||||
return null;
|
||||
}
|
||||
if (legType != LegType.REACHING) {
|
||||
GPSFixTrack<Competitor, GPSFixMoving> track = getTrack(competitor);
|
||||
if (!track.hasDirectionChange(timePoint, getManeuverDegreeAngleThreshold())) {
|
||||
SpeedWithBearingWithConfidence<TimePoint> estimatedSpeedWithConfidence = track.getEstimatedSpeed(timePoint, weigher);
|
||||
if (estimatedSpeedWithConfidence != null && estimatedSpeedWithConfidence.getObject() != null &&
|
||||
// Mark passings may be missing or far off. This can lead to boats apparently going "backwards" regarding the leg's direction; ignore those
|
||||
isNavigatingForward(estimatedSpeedWithConfidence.getObject().getBearing(), trackedLeg, timePoint)) {
|
||||
SpeedWithBearingWithConfidence<TimePoint> estimatedSpeedWithConfidence = track
|
||||
.getEstimatedSpeed(timePoint, weigher);
|
||||
if (estimatedSpeedWithConfidence != null
|
||||
&& estimatedSpeedWithConfidence.getObject() != null
|
||||
&&
|
||||
// Mark passings may be missing or far off. This can lead to boats apparently going
|
||||
// "backwards" regarding the leg's direction; ignore those
|
||||
isNavigatingForward(estimatedSpeedWithConfidence.getObject().getBearing(), trackedLeg,
|
||||
timePoint)) {
|
||||
// additionally to generally excluding maneuvers, reduce confidence around mark passings:
|
||||
NavigableSet<MarkPassing> markPassings = getMarkPassings(competitor);
|
||||
double markPassingProximityConfidenceReduction = 1.0;
|
||||
@@ -854,10 +894,10 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
return bearings;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the <code>bearing</code> generally moves in the direction that the <code>trackedLeg</code> has at time point
|
||||
* <code>at</code>.
|
||||
* Checks if the <code>bearing</code> generally moves in the direction that the <code>trackedLeg</code> has at time
|
||||
* point <code>at</code>.
|
||||
*/
|
||||
private boolean isNavigatingForward(Bearing bearing, TrackedLeg trackedLeg, TimePoint at) {
|
||||
Bearing legBearing = trackedLeg.getLegBearing(at);
|
||||
@@ -878,11 +918,14 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
*
|
||||
* In this case, the boat's sails will be on the starboard side, so the result has to be {@link Tack#STARBOARD}. The
|
||||
* angle between the boat's heading (which we can only approximate by the boat's bearing) and the wind bearing in
|
||||
* this case is 90 degrees. <code>wind.{@link Bearing#getDifferenceTo(Bearing) getDifferenceTo}(boat)</code>
|
||||
* in this case will return a bearing representing -90 degrees.<p>
|
||||
* this case is 90 degrees. <code>wind.{@link Bearing#getDifferenceTo(Bearing) getDifferenceTo}(boat)</code> in this
|
||||
* case will return a bearing representing -90 degrees.
|
||||
* <p>
|
||||
*
|
||||
* If the wind is blowing the other way, the angle returned by {@link Bearing#getDifferenceTo(Bearing)} will correspond
|
||||
* to +90 degrees. In other words, a negative angle means starboard tack, a positive angle represents port tack.<p>
|
||||
* If the wind is blowing the other way, the angle returned by {@link Bearing#getDifferenceTo(Bearing)} will
|
||||
* correspond to +90 degrees. In other words, a negative angle means starboard tack, a positive angle represents
|
||||
* port tack.
|
||||
* <p>
|
||||
*
|
||||
* For the unlikely case of 0 degrees difference, {@link Tack#STARBOARD} will result.
|
||||
*/
|
||||
@@ -891,10 +934,10 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
return getTack(getTrack(competitor).getEstimatedPosition(timePoint, /* extrapolate */false), timePoint,
|
||||
getTrack(competitor).getEstimatedSpeed(timePoint).getBearing());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Based on the wind direction at <code>timePoint</code> and at position <code>where</code>, compares the <code>boatBearing</code>
|
||||
* to the wind's bearing at that time and place and determined the tack.
|
||||
* Based on the wind direction at <code>timePoint</code> and at position <code>where</code>, compares the
|
||||
* <code>boatBearing</code> to the wind's bearing at that time and place and determined the tack.
|
||||
*/
|
||||
private Tack getTack(Position where, TimePoint timePoint, Bearing boatBearing) {
|
||||
Bearing wind = getWind(where, timePoint).getBearing();
|
||||
@@ -904,7 +947,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TrackedRace for "+getRace();
|
||||
return "TrackedRace for " + getRace();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -913,16 +956,17 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
getTrack(competitor));
|
||||
return douglasPeucker.approximate(maxDistance, from, to);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Caches results in {@link #maneuverCache}. The cache is {@link #clearAllCaches() invalidated} by any
|
||||
* {@link #updated(TimePoint) update}. Therefore, it is mainly useful for completed races. The cache tries to grow
|
||||
* the time interval for which the maneuvers of a competitor have been computed. If <code>from</code> and
|
||||
* <code>to</code> are within an interval already cached, the interval requested is
|
||||
* {@link #extractInterval(TimePoint, TimePoint, List) extracted} from the maneuver list cached. Otherwise,
|
||||
* the cached interval (empty in case no maneuvers were cached for <code>competitor</code> yet) is extended to
|
||||
* include <code>from..to</code> by computing and caching the maneuvers for the new, extended interval. From the resulting
|
||||
* extended maneuver list the interval requested is then {@link #extractInterval(TimePoint, TimePoint, List) extracted}.
|
||||
* {@link #extractInterval(TimePoint, TimePoint, List) extracted} from the maneuver list cached. Otherwise, the
|
||||
* cached interval (empty in case no maneuvers were cached for <code>competitor</code> yet) is extended to include
|
||||
* <code>from..to</code> by computing and caching the maneuvers for the new, extended interval. From the resulting
|
||||
* extended maneuver list the interval requested is then {@link #extractInterval(TimePoint, TimePoint, List)
|
||||
* extracted}.
|
||||
*/
|
||||
@Override
|
||||
public List<Maneuver> getManeuvers(Competitor competitor, TimePoint from, TimePoint to) throws NoWindException {
|
||||
@@ -940,7 +984,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
: from.compareTo(fromToAndManeuvers.getA()) <= 0 ? from : fromToAndManeuvers.getA();
|
||||
TimePoint extendedTo = fromToAndManeuvers == null ? to : to.compareTo(fromToAndManeuvers.getB()) >= 0 ? to
|
||||
: fromToAndManeuvers.getB();
|
||||
List<Maneuver> extendedResultForCache = detectManeuvers(competitor,
|
||||
List<Maneuver> extendedResultForCache = detectManeuvers(
|
||||
competitor,
|
||||
approximate(competitor, getRace().getBoatClass().getMaximumDistanceForCourseApproximation(),
|
||||
extendedFrom, extendedTo));
|
||||
result = extractInterval(from, to, extendedResultForCache);
|
||||
@@ -962,7 +1007,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -980,7 +1025,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
* @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.
|
||||
*/
|
||||
private List<Maneuver> detectManeuvers(Competitor competitor, List<GPSFixMoving> approximatingFixesToAnalyze) throws NoWindException {
|
||||
private List<Maneuver> detectManeuvers(Competitor competitor, List<GPSFixMoving> approximatingFixesToAnalyze)
|
||||
throws NoWindException {
|
||||
List<Maneuver> result = new ArrayList<Maneuver>();
|
||||
if (approximatingFixesToAnalyze.size() > 2) {
|
||||
List<Pair<GPSFixMoving, CourseChange>> courseChangeSequenceInSameDirection = new ArrayList<Pair<GPSFixMoving, CourseChange>>();
|
||||
@@ -988,22 +1034,27 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
GPSFixMoving previous = approximationPointsIter.next();
|
||||
GPSFixMoving current = approximationPointsIter.next();
|
||||
// the bearings in these variables are between approximation points
|
||||
SpeedWithBearing speedWithBearingOnApproximationFromPreviousToCurrent = previous.getSpeedAndBearingRequiredToReach(current);
|
||||
SpeedWithBearing speedWithBearingOnApproximationFromPreviousToCurrent = previous
|
||||
.getSpeedAndBearingRequiredToReach(current);
|
||||
SpeedWithBearing speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges = speedWithBearingOnApproximationFromPreviousToCurrent;
|
||||
SpeedWithBearing speedWithBearingOnApproximationFromCurrentToNext; // will certainly be assigned because iter's collection's size > 2
|
||||
SpeedWithBearing speedWithBearingOnApproximationFromCurrentToNext; // will certainly be assigned because
|
||||
// iter's collection's size > 2
|
||||
do {
|
||||
GPSFixMoving next = approximationPointsIter.next();
|
||||
speedWithBearingOnApproximationFromCurrentToNext = current.getSpeedAndBearingRequiredToReach(next);
|
||||
// compute course change on "approximation track"
|
||||
CourseChange courseChange = speedWithBearingOnApproximationFromPreviousToCurrent
|
||||
.getCourseChangeRequiredToReach(speedWithBearingOnApproximationFromCurrentToNext);
|
||||
Pair<GPSFixMoving, CourseChange> courseChangeAtFix = new Pair<GPSFixMoving, CourseChange>(current, courseChange);
|
||||
if (!courseChangeSequenceInSameDirection.isEmpty() &&
|
||||
Math.signum(courseChangeSequenceInSameDirection.get(0).getB().getCourseChangeInDegrees()) !=
|
||||
Math.signum(courseChange.getCourseChangeInDegrees())) {
|
||||
// course change in different direction; cluster the course changes in same direction so far, then start new list
|
||||
Pair<GPSFixMoving, CourseChange> courseChangeAtFix = new Pair<GPSFixMoving, CourseChange>(current,
|
||||
courseChange);
|
||||
if (!courseChangeSequenceInSameDirection.isEmpty()
|
||||
&& Math.signum(courseChangeSequenceInSameDirection.get(0).getB().getCourseChangeInDegrees()) != Math
|
||||
.signum(courseChange.getCourseChangeInDegrees())) {
|
||||
// course change in different direction; cluster the course changes in same direction so far, then
|
||||
// start new list
|
||||
List<Maneuver> maneuvers = groupChangesInSameDirectionIntoManeuvers(competitor,
|
||||
speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges, courseChangeSequenceInSameDirection);
|
||||
speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges,
|
||||
courseChangeSequenceInSameDirection);
|
||||
result.addAll(maneuvers);
|
||||
courseChangeSequenceInSameDirection.clear();
|
||||
speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges = speedWithBearingOnApproximationFromPreviousToCurrent;
|
||||
@@ -1014,7 +1065,8 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
speedWithBearingOnApproximationFromPreviousToCurrent = speedWithBearingOnApproximationFromCurrentToNext;
|
||||
} while (approximationPointsIter.hasNext());
|
||||
if (!courseChangeSequenceInSameDirection.isEmpty()) {
|
||||
result.addAll(groupChangesInSameDirectionIntoManeuvers(competitor, speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges,
|
||||
result.addAll(groupChangesInSameDirectionIntoManeuvers(competitor,
|
||||
speedWithBearingOnApproximationAtBeginningOfUnidirectionalCourseChanges,
|
||||
courseChangeSequenceInSameDirection));
|
||||
}
|
||||
}
|
||||
@@ -1046,8 +1098,11 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
List<Pair<GPSFixMoving, CourseChange>> group = new ArrayList<Pair<GPSFixMoving, CourseChange>>();
|
||||
if (!courseChangeSequenceInSameDirection.isEmpty()) {
|
||||
Distance twoHullLengths = competitor.getBoat().getBoatClass().getHullLength().scale(2);
|
||||
SpeedWithBearing beforeGroupOnApproximation = speedWithBearingOnApproximationAtBeginning; // speed/bearing before group
|
||||
SpeedWithBearing beforeCurrentCourseChangeOnApproximation = beforeGroupOnApproximation; // speed/bearing before current course change
|
||||
SpeedWithBearing beforeGroupOnApproximation = speedWithBearingOnApproximationAtBeginning; // speed/bearing
|
||||
// before group
|
||||
SpeedWithBearing beforeCurrentCourseChangeOnApproximation = beforeGroupOnApproximation; // speed/bearing
|
||||
// before current
|
||||
// course change
|
||||
Iterator<Pair<GPSFixMoving, CourseChange>> iter = courseChangeSequenceInSameDirection.iterator();
|
||||
double totalCourseChangeInDegrees = 0.0;
|
||||
long totalMilliseconds = 0l;
|
||||
@@ -1058,8 +1113,9 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
if (!group.isEmpty()
|
||||
&& currentFixAndCourseChange.getA().getTimePoint().asMillis()
|
||||
- group.get(group.size() - 1).getA().getTimePoint().asMillis() > getApproximateManeuverDurationInMilliseconds()
|
||||
&& currentFixAndCourseChange.getA().getPosition().getDistance(
|
||||
group.get(group.size() - 1).getA().getPosition()).compareTo(twoHullLengths) > 0) {
|
||||
&& currentFixAndCourseChange.getA().getPosition()
|
||||
.getDistance(group.get(group.size() - 1).getA().getPosition())
|
||||
.compareTo(twoHullLengths) > 0) {
|
||||
// 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
|
||||
Maneuver maneuver = createManeuverFromGroupOfCourseChanges(competitor, beforeGroupOnApproximation,
|
||||
@@ -1070,15 +1126,17 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
totalMilliseconds = 0l;
|
||||
beforeGroupOnApproximation = beforeCurrentCourseChangeOnApproximation;
|
||||
}
|
||||
afterCurrentCourseChange = beforeCurrentCourseChangeOnApproximation.applyCourseChange(currentFixAndCourseChange.getB());
|
||||
afterCurrentCourseChange = beforeCurrentCourseChangeOnApproximation
|
||||
.applyCourseChange(currentFixAndCourseChange.getB());
|
||||
totalMilliseconds += currentFixAndCourseChange.getA().getTimePoint().asMillis();
|
||||
totalCourseChangeInDegrees += currentFixAndCourseChange.getB().getCourseChangeInDegrees();
|
||||
group.add(currentFixAndCourseChange);
|
||||
beforeCurrentCourseChangeOnApproximation = afterCurrentCourseChange; // speed/bearing after course change
|
||||
beforeCurrentCourseChangeOnApproximation = afterCurrentCourseChange; // speed/bearing after course
|
||||
// change
|
||||
} while (iter.hasNext());
|
||||
if (!group.isEmpty()) {
|
||||
result.add(createManeuverFromGroupOfCourseChanges(competitor, beforeGroupOnApproximation,
|
||||
group, afterCurrentCourseChange, totalCourseChangeInDegrees, totalMilliseconds));
|
||||
result.add(createManeuverFromGroupOfCourseChanges(competitor, beforeGroupOnApproximation, group,
|
||||
afterCurrentCourseChange, totalCourseChangeInDegrees, totalMilliseconds));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -1086,31 +1144,38 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
|
||||
private Maneuver createManeuverFromGroupOfCourseChanges(Competitor competitor,
|
||||
SpeedWithBearing speedWithBearingOnApproximationAtBeginning, List<Pair<GPSFixMoving, CourseChange>> group,
|
||||
SpeedWithBearing speedWithBearingOnApproximationAtEnd, double totalCourseChangeInDegrees, long totalMilliseconds)
|
||||
throws NoWindException {
|
||||
TimePoint maneuverTimePoint = new MillisecondsTimePoint(totalMilliseconds/group.size());
|
||||
Position maneuverPosition = getTrack(competitor).getEstimatedPosition(maneuverTimePoint, /* extrapolate */ false);
|
||||
SpeedWithBearing speedWithBearingOnApproximationAtEnd, double totalCourseChangeInDegrees,
|
||||
long totalMilliseconds) throws NoWindException {
|
||||
TimePoint maneuverTimePoint = new MillisecondsTimePoint(totalMilliseconds / group.size());
|
||||
Position maneuverPosition = getTrack(competitor)
|
||||
.getEstimatedPosition(maneuverTimePoint, /* extrapolate */false);
|
||||
MillisecondsTimePoint timePointBeforeManeuver = new MillisecondsTimePoint(group.get(0).getA().getTimePoint()
|
||||
.asMillis() - getApproximateManeuverDurationInMilliseconds()/2);
|
||||
.asMillis()
|
||||
- getApproximateManeuverDurationInMilliseconds() / 2);
|
||||
MillisecondsTimePoint timePointAfterManeuver = new MillisecondsTimePoint(group.get(group.size() - 1).getA()
|
||||
.getTimePoint().asMillis() + getApproximateManeuverDurationInMilliseconds()/2);
|
||||
Tack tackBeforeManeuver = getTack(maneuverPosition, timePointBeforeManeuver, speedWithBearingOnApproximationAtBeginning.getBearing());
|
||||
Tack tackAfterManeuver = getTack(maneuverPosition, timePointAfterManeuver, speedWithBearingOnApproximationAtEnd.getBearing());
|
||||
.getTimePoint().asMillis()
|
||||
+ getApproximateManeuverDurationInMilliseconds() / 2);
|
||||
Tack tackBeforeManeuver = getTack(maneuverPosition, timePointBeforeManeuver,
|
||||
speedWithBearingOnApproximationAtBeginning.getBearing());
|
||||
Tack tackAfterManeuver = getTack(maneuverPosition, timePointAfterManeuver,
|
||||
speedWithBearingOnApproximationAtEnd.getBearing());
|
||||
// the TrackedLegOfCompetitor variables may be null, e.g., in case the time points are before or after the race
|
||||
TrackedLegOfCompetitor legBeforeManeuver = getTrackedLeg(competitor, timePointBeforeManeuver);
|
||||
TrackedLegOfCompetitor legAfterManeuver = getTrackedLeg(competitor, timePointAfterManeuver);
|
||||
ManeuverType maneuverType;
|
||||
if (Math.abs(totalCourseChangeInDegrees) > PENALTY_CIRCLE_DEGREES_THRESHOLD) {
|
||||
maneuverType = ManeuverType.PENALTY_CIRCLE;
|
||||
} else if (legBeforeManeuver != legAfterManeuver &&
|
||||
} else if (legBeforeManeuver != legAfterManeuver
|
||||
&&
|
||||
// a maneuver at the start line is not to be considered a MARK_PASSING maneuver; show a tack as a tack
|
||||
legAfterManeuver != null && legAfterManeuver.getLeg().getFrom() != getRace().getCourse().getFirstWaypoint()) {
|
||||
legAfterManeuver != null
|
||||
&& legAfterManeuver.getLeg().getFrom() != getRace().getCourse().getFirstWaypoint()) {
|
||||
maneuverType = ManeuverType.MARK_PASSING;
|
||||
} else {
|
||||
if (tackBeforeManeuver != tackAfterManeuver) {
|
||||
LegType legType = legBeforeManeuver!=null ?
|
||||
getTrackedLeg(legBeforeManeuver.getLeg()).getLegType(timePointBeforeManeuver) :
|
||||
legAfterManeuver!=null ? getTrackedLeg(legAfterManeuver.getLeg()).getLegType(timePointAfterManeuver) : null;
|
||||
LegType legType = legBeforeManeuver != null ? getTrackedLeg(legBeforeManeuver.getLeg()).getLegType(
|
||||
timePointBeforeManeuver) : legAfterManeuver != null ? getTrackedLeg(legAfterManeuver.getLeg())
|
||||
.getLegType(timePointAfterManeuver) : null;
|
||||
if (legType != null) {
|
||||
// tack or jibe
|
||||
switch (legType) {
|
||||
@@ -1124,7 +1189,9 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
maneuverType = ManeuverType.UNKNOWN;
|
||||
if (logger.isLoggable(Level.FINE)) {
|
||||
logger.fine("Unknown maneuver for "
|
||||
+ competitor + " at " + maneuverTimePoint
|
||||
+ competitor
|
||||
+ " at "
|
||||
+ maneuverTimePoint
|
||||
+ (legBeforeManeuver != null ? " on reaching leg " + legBeforeManeuver.getLeg()
|
||||
: " before start"));
|
||||
}
|
||||
@@ -1132,22 +1199,25 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
} else {
|
||||
maneuverType = ManeuverType.UNKNOWN;
|
||||
logger.fine("Can't determine leg type because tracked legs for competitor "+competitor+
|
||||
" cannot be determined for time points "+timePointBeforeManeuver+" and "+
|
||||
timePointAfterManeuver);
|
||||
logger.fine("Can't determine leg type because tracked legs for competitor " + competitor
|
||||
+ " cannot be determined for time points " + timePointBeforeManeuver + " and "
|
||||
+ timePointAfterManeuver);
|
||||
}
|
||||
} else {
|
||||
// heading up or bearing away
|
||||
Wind wind = getWind(maneuverPosition, maneuverTimePoint);
|
||||
Bearing windBearing = wind.getBearing();
|
||||
Bearing toWindBeforeManeuver = windBearing.getDifferenceTo(speedWithBearingOnApproximationAtBeginning.getBearing());
|
||||
Bearing toWindAfterManeuver = windBearing.getDifferenceTo(speedWithBearingOnApproximationAtEnd.getBearing());
|
||||
maneuverType = Math.abs(toWindBeforeManeuver.getDegrees()) < Math.abs(toWindAfterManeuver.getDegrees()) ?
|
||||
ManeuverType.HEAD_UP : ManeuverType.BEAR_AWAY;
|
||||
Bearing toWindBeforeManeuver = windBearing.getDifferenceTo(speedWithBearingOnApproximationAtBeginning
|
||||
.getBearing());
|
||||
Bearing toWindAfterManeuver = windBearing.getDifferenceTo(speedWithBearingOnApproximationAtEnd
|
||||
.getBearing());
|
||||
maneuverType = Math.abs(toWindBeforeManeuver.getDegrees()) < Math.abs(toWindAfterManeuver.getDegrees()) ? ManeuverType.HEAD_UP
|
||||
: ManeuverType.BEAR_AWAY;
|
||||
}
|
||||
}
|
||||
Maneuver maneuver = new ManeuverImpl(maneuverType, tackAfterManeuver, maneuverPosition, maneuverTimePoint, speedWithBearingOnApproximationAtBeginning,
|
||||
speedWithBearingOnApproximationAtEnd, totalCourseChangeInDegrees);
|
||||
Maneuver maneuver = new ManeuverImpl(maneuverType, tackAfterManeuver, maneuverPosition, maneuverTimePoint,
|
||||
speedWithBearingOnApproximationAtBeginning, speedWithBearingOnApproximationAtEnd,
|
||||
totalCourseChangeInDegrees);
|
||||
return maneuver;
|
||||
}
|
||||
|
||||
@@ -1165,22 +1235,22 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
private double getMinimumAngleBetweenDifferentTacksUpwind() {
|
||||
return getRace().getBoatClass().getMinimumAngleBetweenDifferentTacksUpwind();
|
||||
}
|
||||
|
||||
|
||||
private long getApproximateManeuverDurationInMilliseconds() {
|
||||
return getRace().getBoatClass().getApproximateManeuverDurationInMilliseconds();
|
||||
}
|
||||
|
||||
private class StartToNextMarkCacheInvalidationListener implements GPSTrackListener<Buoy> {
|
||||
private final GPSFixTrack<Buoy, GPSFix> listeningTo;
|
||||
|
||||
|
||||
public StartToNextMarkCacheInvalidationListener(GPSFixTrack<Buoy, GPSFix> listeningTo) {
|
||||
this.listeningTo = listeningTo;
|
||||
}
|
||||
|
||||
|
||||
public void stopListening() {
|
||||
listeningTo.removeListener(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void gpsFixReceived(GPSFix fix, Buoy buoy) {
|
||||
directionFromStartToNextMarkCache = null;
|
||||
@@ -1190,13 +1260,13 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Pair<Placemark, Placemark> getStartFinishPlacemarks() {
|
||||
Pair<Placemark, Placemark> placemarks = new Pair<Placemark, Placemark>(null, null);
|
||||
Placemark startBest = null;
|
||||
Placemark finishBest = null;
|
||||
|
||||
|
||||
// Get start postition
|
||||
Iterator<Buoy> startBuoys = getRace().getCourse().getFirstWaypoint().getBuoys().iterator();
|
||||
GPSFix startBuoyFix = startBuoys.hasNext() ? getOrCreateTrack(startBuoys.next()).getLastRawFix() : null;
|
||||
@@ -1224,8 +1294,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
Iterator<Buoy> finishBuoys = getRace().getCourse().getFirstWaypoint().getBuoys().iterator();
|
||||
GPSFix finishBuoyFix = finishBuoys.hasNext() ? getOrCreateTrack(finishBuoys.next()).getLastRawFix() : null;
|
||||
Position finishPosition = finishBuoyFix != null ? finishBuoyFix.getPosition() : null;
|
||||
if (startPosition != null
|
||||
&& finishPosition != null) {
|
||||
if (startPosition != null && finishPosition != null) {
|
||||
if (startPosition.getDistance(finishPosition).getKilometers() <= ReverseGeocoder.POSITION_CACHE_DISTANCE_LIMIT_IN_KM) {
|
||||
finishBest = startBest;
|
||||
} else {
|
||||
@@ -1245,7 +1314,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (startBest != null) {
|
||||
placemarks.setA(startBest);
|
||||
}
|
||||
@@ -1254,9 +1323,10 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
return placemarks;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Distance getWindwardDistanceToOverallLeader(Competitor competitor, TimePoint timePoint) throws NoWindException {
|
||||
public Distance getWindwardDistanceToOverallLeader(Competitor competitor, TimePoint timePoint)
|
||||
throws NoWindException {
|
||||
final TrackedLegOfCompetitor trackedLeg = getTrackedLeg(competitor, timePoint);
|
||||
return trackedLeg == null ? null : trackedLeg.getWindwardDistanceToOverallLeader(timePoint);
|
||||
}
|
||||
@@ -1271,10 +1341,10 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Iterable<WindSource> getWindSources() {
|
||||
return windTracks.keySet();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+4
-1
@@ -609,7 +609,10 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
|
||||
result.raceIsKnownToStartUpwind = trackedRace.raceIsKnownToStartUpwind();
|
||||
Map<WindSource, WindTrackInfoDTO> windTrackInfoDTOs = new HashMap<WindSource, WindTrackInfoDTO>();
|
||||
result.windTrackInfoByWindSource = windTrackInfoDTOs;
|
||||
for (WindSource windSource : trackedRace.getWindSources()) {
|
||||
List<WindSource> windSourcesToDeliver = new ArrayList<WindSource>();
|
||||
Util.addAll(trackedRace.getWindSources(), windSourcesToDeliver);
|
||||
windSourcesToDeliver.add(new WindSourceImpl(WindSourceType.COMBINED));
|
||||
for (WindSource windSource : windSourcesToDeliver) {
|
||||
if (windSourceTypeNames == null || windSourceTypeNames.contains(windSource.getType().name())) {
|
||||
TimePoint fromTimePoint = new MillisecondsTimePoint(from);
|
||||
WindTrackInfoDTO windTrackInfoDTO = new WindTrackInfoDTO();
|
||||
|
||||
Reference in new issue
Block a user