SailingService.getWind(...) methods now also add the COMBINED WindSource(Type) by default

This commit is contained in:
Axel Uhl committed 2012-03-08 16:46:40 +01:00
1 parent a52a0e5645
commit 2150629d7b
3 files changed
+247 -175

No files matched your search

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