Merge remote-tracking branch 'origin/master'

This commit is contained in:
fmittag
2012-03-08 16:19:14 +01:00
20 changed files with 496 additions and 356 deletions
@@ -29,7 +29,13 @@ public enum WindSourceType {
/**
* Inferred from the race course layout if the course is known to have its first leg be an upwind leg
*/
COURSE_BASED(false);
COURSE_BASED(false),
/**
* Wind estimation combined from all other wind sources, using <code>TrackedRace.getWind(...)</code>, based on
* confidences
*/
COMBINED(false);
private final boolean canBeStored;
@@ -6,6 +6,16 @@ import java.util.Iterator;
import java.util.List;
public class Util {
/**
* Adds all elements from <code>what</code> to <code>addTo</code> and returns <code>addTo</code> for chained use.
*/
public static <T> Collection<T> addAll(Iterable<T> what, Collection<T> addTo) {
for (T t : what) {
addTo.add(t);
}
return addTo;
}
public static <T> int size(Iterable<T> i) {
if (i instanceof Collection<?>) {
return ((Collection<?>) i).size();
@@ -116,14 +116,14 @@ public class ConfidenceTest {
@Test
public void testLinearWeigherHalfTime() {
Weigher<TimePoint> w = ConfidenceFactory.INSTANCE.createLinearTimeDifferenceWeigher(1000);
Weigher<TimePoint> w = ConfidenceFactory.INSTANCE.createHyperbolicTimeDifferenceWeigher(1000);
double confidence = w.getConfidence(new MillisecondsTimePoint(1000), new MillisecondsTimePoint(0));
assertEquals(0.5, confidence, 0.00000001);
}
@Test
public void testLinearWeigherZeroTimeDifference() {
Weigher<TimePoint> w = ConfidenceFactory.INSTANCE.createLinearTimeDifferenceWeigher(1000);
Weigher<TimePoint> w = ConfidenceFactory.INSTANCE.createHyperbolicTimeDifferenceWeigher(1000);
double confidence = w.getConfidence(new MillisecondsTimePoint(1000), new MillisecondsTimePoint(1000));
assertEquals(1.0, confidence, 0.00000001);
}
@@ -150,7 +150,7 @@ public class ConfidenceTest {
WindWithConfidence<TimePoint> d2 = new WindWithConfidenceImpl<TimePoint>(new WindImpl(new DegreePosition(1, 0), new MillisecondsTimePoint(20),
new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(180))), /* confidence */ 0.5, /* relativeTo */ new MillisecondsTimePoint(20));
ConfidenceBasedAverager<ScalableWind, Wind, TimePoint> averager = ConfidenceFactory.INSTANCE
.createAverager(ConfidenceFactory.INSTANCE.createLinearTimeDifferenceWeigher(1000));
.createAverager(ConfidenceFactory.INSTANCE.createHyperbolicTimeDifferenceWeigher(1000));
List<WindWithConfidence<TimePoint>> list = Arrays.asList(d1, d2);
HasConfidence<ScalableWind, Wind, TimePoint> average = averager.getAverage(list, new MillisecondsTimePoint(10));
assertEquals(0.5, average.getObject().getPosition().getLatDeg(), 0.00000001);
@@ -82,9 +82,8 @@ public abstract class TrackBasedTest {
trackedRace.getOrCreateTrack(left).addGPSFix(new GPSFixImpl(new DegreePosition(54.4680424, 10.234451), MillisecondsTimePoint.now()));
trackedRace.getOrCreateTrack(right).addGPSFix(new GPSFixImpl(new DegreePosition(54.4680424, 10.24), MillisecondsTimePoint.now()));
trackedRace.getOrCreateTrack(windwardMark).addGPSFix(new GPSFixImpl(topPosition, MillisecondsTimePoint.now()));
trackedRace.getOrCreateWindTrack(new WindSourceImpl(WindSourceType.WEB)).add(
new WindImpl(topPosition, timePointForFixes,
new KnotSpeedWithBearingImpl(/* speedInKnots */14.7, new DegreeBearingImpl(180))));
trackedRace.recordWind(new WindImpl(topPosition, timePointForFixes, new KnotSpeedWithBearingImpl(
/* speedInKnots */14.7, new DegreeBearingImpl(180))), new WindSourceImpl(WindSourceType.WEB));
return trackedRace;
}
@@ -92,8 +92,8 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
}
private void setBearingForCompetitor(Competitor competitor, TimePoint timePoint, double bearingDeg) {
DynamicGPSFixTrack<Competitor, GPSFixMoving> hungersTrack = getTrackedRace().getTrack(competitor);
hungersTrack.addGPSFix(new GPSFixMovingImpl(new DegreePosition(54.4680424, 10.234451), timePoint,
DynamicGPSFixTrack<Competitor, GPSFixMoving> competitorTrack = getTrackedRace().getTrack(competitor);
competitorTrack.addGPSFix(new GPSFixMovingImpl(new DegreePosition(54.4680424, 10.234451), timePoint,
new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(bearingDeg))));
}
@@ -104,11 +104,13 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
public void testWindEstimationCacheInvalidationAfterLegTypeChange() throws NoWindException {
TimePoint fixTime = new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime());
TimePoint checkTime = new MillisecondsTimePoint(fixTime.asMillis()+60000); // one minute later
initRace(2, new int[] { 1, 1 }, fixTime);
initRace(4, new int[] { 1, 1, 1, 1 }, fixTime);
getTrackedRace().setRaceIsKnownToStartUpwind(false); // use only WEB wind to determine leg type
TimePoint now = checkTime;
setBearingForCompetitor(competitors.get(0), now, 320);
setBearingForCompetitor(competitors.get(1), now, 50);
setBearingForCompetitor(competitors.get(2), now, 140);
setBearingForCompetitor(competitors.get(3), now, 230);
TrackedLeg firstLeg = getTrackedRace().getTrackedLeg(getTrackedRace().getRace().getCourse().getLegs().iterator().next());
assertEquals(LegType.UPWIND, firstLeg.getLegType(new MillisecondsTimePoint(MillisecondsTimePoint.now().asMillis())));
final Map<TimePoint, Wind> cachedFixes = new HashMap<TimePoint, Wind>();
@@ -140,7 +142,7 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
assertEquals(LegType.DOWNWIND, firstLeg.getLegType(fixTime));
Wind estimatedWindDirectionDownwind = track.getAveragedWind(/* position */ null, checkTime);
assertNotNull(estimatedWindDirectionDownwind);
assertEquals(5., estimatedWindDirectionDownwind.getBearing().getDegrees(), 0.00000001);
assertEquals(185., estimatedWindDirectionDownwind.getBearing().getDegrees(), 0.00000001);
}
@Test
@@ -6,6 +6,7 @@ import java.util.Map;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.TimePoint;
@@ -68,4 +69,10 @@ public class TracTracTrackedLegImpl implements TrackedLeg {
return null;
}
@Override
public Bearing getLegBearing(TimePoint at) {
// TODO Auto-generated method stub
return null;
}
}
@@ -28,4 +28,8 @@ public class HasConfidenceImpl<ValueType, BaseType, RelativeTo> implements HasCo
return object;
}
@Override
public String toString() {
return ""+getObject()+"@"+getConfidence();
}
}
@@ -28,5 +28,5 @@ public interface ConfidenceFactory {
*/
Weigher<TimePoint> createExponentialTimeDifferenceWeigher(long halfConfidenceAfterMilliseconds, double minimumConfidence);
Weigher<TimePoint> createLinearTimeDifferenceWeigher(long halfConfidenceAfterMilliseconds);
Weigher<TimePoint> createHyperbolicTimeDifferenceWeigher(long halfConfidenceAfterMilliseconds);
}
@@ -22,7 +22,7 @@ public class ConfidenceBasedAveragerFactoryImpl implements ConfidenceFactory {
}
@Override
public Weigher<TimePoint> createLinearTimeDifferenceWeigher(long halfConfidenceAfterMilliseconds) {
public Weigher<TimePoint> createHyperbolicTimeDifferenceWeigher(long halfConfidenceAfterMilliseconds) {
return new HyperbolicTimeDifferenceWeigher(halfConfidenceAfterMilliseconds);
}
@@ -4,6 +4,7 @@ import java.util.LinkedHashMap;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.TimePoint;
@@ -38,4 +39,6 @@ public interface TrackedLeg {
*/
LinkedHashMap<Competitor, Integer> getRanks(TimePoint timePoint);
Bearing getLegBearing(TimePoint at);
}
@@ -183,9 +183,8 @@ public interface TrackedRace {
/**
* Obtains estimated interpolated wind information for a given position and time point. The information is taken
* from all wind sources available except for those listed in <code>windSourcesToExclude</code>, with preferences
* controlled by the {@link #getWindSource() current wind source} which can be selected using {@link #setWindSource},
* and by the order of the {@link WindSource} literals.
* from all wind sources available except for those listed in <code>windSourcesToExclude</code>, using the confidences
* of the wind values provided by the various sources during averaging.
*/
Wind getWind(Position p, TimePoint at, Iterable<WindSource> windSourcesToExclude);
@@ -1,17 +1,12 @@
package com.sap.sailing.domain.tracking.impl;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NavigableSet;
import java.util.SortedSet;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.impl.WindTrackImpl.DummyWind;
import com.sap.sailing.util.impl.AbstractUnmodifiableNavigableSet;
import com.sap.sailing.util.impl.DescendingNavigableSet;
import com.sap.sailing.domain.tracking.WindTrack;
/**
* Emulates a collection of {@link Wind} fixes for a {@link TrackedRace}, computed using
@@ -26,294 +21,32 @@ import com.sap.sailing.util.impl.DescendingNavigableSet;
* @author Axel Uhl (d043530)
*
*/
public class EstimatedWindFixesAsNavigableSet extends AbstractUnmodifiableNavigableSet<Wind> {
/**
* The time resolution is one second.
*/
private static final long RESOLUTION_IN_MILLISECONDS = 1000l;
private final TrackedRace trackedRace;
private final TimePoint from;
private final TimePoint to;
private final TrackBasedEstimationWindTrackImpl track;
public class EstimatedWindFixesAsNavigableSet extends VirtualWindFixesAsNavigableSet {
public EstimatedWindFixesAsNavigableSet(TrackBasedEstimationWindTrackImpl track, TrackedRace trackedRace) {
this(track, trackedRace, null, null);
}
public long getResolutionInMilliseconds() {
return RESOLUTION_IN_MILLISECONDS;
protected TrackBasedEstimationWindTrackImpl getTrack() {
return (TrackBasedEstimationWindTrackImpl) super.getTrack();
}
/**
* @param from expected to be an integer multiple of {@link #RESOLUTION_IN_MILLISECONDS} or <code>null</code>
* @param to expected to be an integer multiple of {@link #RESOLUTION_IN_MILLISECONDS} or <code>null</code>
* @param from expected to be an integer multiple of {@link #getResolutionInMilliseconds()} or <code>null</code>
* @param to expected to be an integer multiple of {@link #getResolutionInMilliseconds()} or <code>null</code>
*/
private EstimatedWindFixesAsNavigableSet(TrackBasedEstimationWindTrackImpl track, TrackedRace trackedRace,
private EstimatedWindFixesAsNavigableSet(WindTrack track, TrackedRace trackedRace,
TimePoint from, TimePoint to) {
this.track = track;
this.trackedRace = trackedRace;
assert from == null || from.asMillis() % RESOLUTION_IN_MILLISECONDS == 0;
this.from = from;
assert to == null || to.asMillis() % RESOLUTION_IN_MILLISECONDS == 0;
this.to = to;
super(track, trackedRace, from, to, /* resolution in milliseconds */ 1000l);
}
private TimePoint lowerToResolution(Wind w) {
TimePoint result;
final TimePoint timePointOfLastEvent = trackedRace.getTimePointOfLastEvent();
if (timePointOfLastEvent == null) {
// nothing received yet; "lowering" to end of time
result = new MillisecondsTimePoint((Long.MAX_VALUE - 1) / RESOLUTION_IN_MILLISECONDS
* RESOLUTION_IN_MILLISECONDS);
} else if (w.getTimePoint().compareTo(timePointOfLastEvent) > 0) {
result = lowerToResolution(new DummyWind(timePointOfLastEvent));
} else {
result = new MillisecondsTimePoint((w.getTimePoint().asMillis() - 1) / RESOLUTION_IN_MILLISECONDS
* RESOLUTION_IN_MILLISECONDS);
}
return result;
}
private TimePoint floorToResolution(Wind w) {
TimePoint result;
final TimePoint timePointOfLastEvent = trackedRace.getTimePointOfLastEvent();
if (timePointOfLastEvent == null) {
// nothing received yet; "lowering" to end of time
result = new MillisecondsTimePoint((Long.MAX_VALUE - 1) / RESOLUTION_IN_MILLISECONDS
* RESOLUTION_IN_MILLISECONDS);
} else if (w.getTimePoint().compareTo(timePointOfLastEvent) > 0) {
result = floorToResolution(new DummyWind(timePointOfLastEvent));
} else {
result = new MillisecondsTimePoint(w.getTimePoint().asMillis() / RESOLUTION_IN_MILLISECONDS
* RESOLUTION_IN_MILLISECONDS);
}
return result;
}
private TimePoint ceilingToResolution(Wind w) {
TimePoint result;
final TimePoint startOfTracking = trackedRace.getStartOfTracking();
if (startOfTracking == null) {
// no start of tracking yet; "ceiling" to beginning of time
result = new MillisecondsTimePoint(0);
} else if (w.getTimePoint().compareTo(startOfTracking) < 0) {
result = ceilingToResolution(new DummyWind(startOfTracking));
} else {
result = new MillisecondsTimePoint(((w.getTimePoint().asMillis() - 1) / RESOLUTION_IN_MILLISECONDS + 1)
* RESOLUTION_IN_MILLISECONDS);
}
return result;
}
private TimePoint higherToResolution(Wind w) {
TimePoint result;
final TimePoint startOfTracking = trackedRace.getStartOfTracking();
if (startOfTracking == null) {
// no start of tracking yet; "ceiling" to beginning of time
result = new MillisecondsTimePoint(0);
} else if (w.getTimePoint().compareTo(startOfTracking) < 0) {
result = higherToResolution(new DummyWind(startOfTracking));
} else {
result = new MillisecondsTimePoint((w.getTimePoint().asMillis() / RESOLUTION_IN_MILLISECONDS + 1)
* RESOLUTION_IN_MILLISECONDS);
}
return result;
}
/**
* The time point starting from and including which the GPS fixes are considered in the race's tracks. Returns the
* value of {@link #from} unless it is <code>null</code>. In this case, the time point of the
* {@link TrackedRace#getStart() race start}, {@link #floorToResolution(Wind) floored to the resolution of this set}
* will be returned instead. If no valid start time can be obtained from the race, <code>Long.MAX_VALUE</code> is
* returned instead.
*/
private TimePoint getFrom() {
return from == null ? trackedRace.getStart() == null ? new MillisecondsTimePoint(Long.MAX_VALUE)
: floorToResolution(new DummyWind(trackedRace.getStart())) : from;
}
/**
* Time point up to and including which the GPS fixes are considered in the race's tracks. Returns the value of
* {@link #to} unless it is <code>null</code>. In this case, the time point of the
* {@link TrackedRace#getTimePointOfNewestEvent() time point of the newest event},
* {@link #ceilingToResolution(Wind) ceiled to the resolution of this set} will be returned instead. If no valid
* time of a newest event can be obtained from the race, <code>MillisecondsTimePoint(1)</code> is returned instead.
*/
private TimePoint getTo() {
return to == null ? trackedRace.getTimePointOfNewestEvent() == null ? new MillisecondsTimePoint(1)
: ceilingToResolution(new DummyWind(trackedRace.getTimePointOfNewestEvent())) : to;
protected Wind getWind(Position p, TimePoint timePoint) {
return getTrack().getEstimatedWindDirection(p, timePoint);
}
@Override
public Wind lower(Wind w) {
TimePoint timePoint = lowerToResolution(w);
return timePoint.compareTo(getFrom()) < 0 ? null : track.getEstimatedWindDirection(w.getPosition(), timePoint);
protected NavigableSet<Wind> createSubset(WindTrack track, TrackedRace trackedRace, TimePoint from, TimePoint to) {
return new EstimatedWindFixesAsNavigableSet(track, trackedRace, from, to);
}
@Override
public Wind floor(Wind w) {
TimePoint timePoint = floorToResolution(w);
return timePoint.compareTo(getFrom()) < 0 ? null : track.getEstimatedWindDirection(w.getPosition(), timePoint);
}
@Override
public Wind ceiling(Wind w) {
TimePoint timePoint = ceilingToResolution(w);
return timePoint.compareTo(getTo()) > 0 ? null : track.getEstimatedWindDirection(w.getPosition(), timePoint);
}
@Override
public Wind higher(Wind w) {
TimePoint timePoint = higherToResolution(w);
return timePoint.compareTo(getTo()) > 0 ? null : track.getEstimatedWindDirection(w.getPosition(), timePoint);
}
@Override
public Iterator<Wind> iterator() {
return new Iterator<Wind>() {
private TimePoint timePoint = getFrom();
@Override
public boolean hasNext() {
return timePoint.compareTo(getTo()) <= 0;
}
@Override
public Wind next() {
Wind result = floor(new DummyWind(timePoint));
timePoint = new MillisecondsTimePoint(timePoint.asMillis() + RESOLUTION_IN_MILLISECONDS);
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
public NavigableSet<Wind> descendingSet() {
return new DescendingNavigableSet<Wind>(this);
}
@Override
public Iterator<Wind> descendingIterator() {
return new Iterator<Wind>() {
private TimePoint timePoint = lowerToResolution(new DummyWind(getTo()));
@Override
public boolean hasNext() {
return timePoint.compareTo(getFrom()) >= 0;
}
@Override
public Wind next() {
Wind result = floor(new DummyWind(timePoint));
timePoint = new MillisecondsTimePoint(timePoint.asMillis() - RESOLUTION_IN_MILLISECONDS);
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
public NavigableSet<Wind> subSet(Wind fromElement, boolean fromInclusive, Wind toElement, boolean toInclusive) {
return new EstimatedWindFixesAsNavigableSet(track, trackedRace, fromInclusive ? ceilingToResolution(fromElement)
: higherToResolution(fromElement), toInclusive ? floorToResolution(toElement)
: lowerToResolution(toElement));
}
@Override
public NavigableSet<Wind> headSet(Wind toElement, boolean inclusive) {
return new EstimatedWindFixesAsNavigableSet(track, trackedRace, /* from */ null,
inclusive ? ceilingToResolution(toElement) : lowerToResolution(toElement));
}
@Override
public NavigableSet<Wind> tailSet(Wind fromElement, boolean inclusive) {
return new EstimatedWindFixesAsNavigableSet(track, trackedRace, inclusive ? floorToResolution(fromElement)
: higherToResolution(fromElement),
/* to */ null);
}
@Override
public SortedSet<Wind> subSet(Wind fromElement, Wind toElement) {
return subSet(fromElement, true, toElement, false);
}
@Override
public SortedSet<Wind> headSet(Wind toElement) {
return headSet(toElement, false);
}
@Override
public SortedSet<Wind> tailSet(Wind fromElement) {
return tailSet(fromElement, true);
}
@Override
public Comparator<? super Wind> comparator() {
return WindComparator.INSTANCE;
}
@Override
public Wind first() {
return floor(new DummyWind(getFrom()));
}
@Override
public Wind last() {
return ceiling(new DummyWind(getTo()));
}
@Override
public int size() {
return (int) ((getTo().asMillis() - getFrom().asMillis()) / RESOLUTION_IN_MILLISECONDS);
}
@Override
public boolean contains(Object o) {
boolean result = false;
if (o instanceof Wind) {
Wind wind = (Wind) o;
result = wind.getTimePoint().asMillis() % RESOLUTION_IN_MILLISECONDS == 0
&& wind.getTimePoint().compareTo(getFrom()) >= 0 && wind.getTimePoint().compareTo(getTo()) < 0;
}
return result;
}
@Override
public Object[] toArray() {
Object[] result = new Object[size()];
int i = 0;
for (Wind w : this) {
result[i++] = w;
}
return result;
}
@Override
public <T> T[] toArray(T[] a) {
Object[] result = a;
if (result.length < size()) {
result = new Object[size()];
}
int i = 0;
for (Wind w : this) {
result[i++] = w;
}
@SuppressWarnings("unchecked")
T[] tResult = (T[]) result;
return tResult;
}
}
@@ -211,7 +211,7 @@ public class TrackBasedEstimationWindTrackImpl extends WindTrackImpl implements
if (adjustedAt != null) {
// we can use the unsynchronized version here because our getInternalFixes() method operates
// only on a virtual sequence of wind fixes where no concurrency issues have to be observed
final WindWithConfidence<Pair<Position, TimePoint>> estimatedWindUnsynchronized = getEstimatedWindUnsynchronized(p, adjustedAt);
final WindWithConfidence<Pair<Position, TimePoint>> estimatedWindUnsynchronized = getAveragedWindUnsynchronized(p, adjustedAt);
result = estimatedWindUnsynchronized == null ? null : estimatedWindUnsynchronized.getObject();
}
return result;
@@ -132,26 +132,29 @@ public class TrackedLegImpl implements TrackedLeg, RaceChangeListener {
throw new NoWindException("Need to know wind direction to determine whether leg "+getLeg()+
" is an upwind or downwind leg");
}
// check for all combinations of start/end waypoint buoys and respond for the first that's within bounds
for (Buoy startBuoy : getLeg().getFrom().getBuoys()) {
Position startBuoyPos = getTrackedRace().getOrCreateTrack(startBuoy).getEstimatedPosition(at, false);
for (Buoy endBuoy : getLeg().getTo().getBuoys()) {
Position endBuoyPos = getTrackedRace().getOrCreateTrack(endBuoy).getEstimatedPosition(at, false);
Bearing legBearing = startBuoyPos.getBearingGreatCircle(endBuoyPos);
double deltaDeg = legBearing.getDifferenceTo(wind.getBearing()).getDegrees();
if (Math.abs(deltaDeg) < UPWIND_DOWNWIND_TOLERANCE_IN_DEG) {
return LegType.DOWNWIND;
} else {
double deltaDegOpposite = legBearing.getDifferenceTo(wind.getBearing().reverse()).getDegrees();
if (Math.abs(deltaDegOpposite) < UPWIND_DOWNWIND_TOLERANCE_IN_DEG) {
return LegType.UPWIND;
}
Bearing legBearing = getLegBearing(at);
if (legBearing != null) {
double deltaDeg = legBearing.getDifferenceTo(wind.getBearing()).getDegrees();
if (Math.abs(deltaDeg) < UPWIND_DOWNWIND_TOLERANCE_IN_DEG) {
return LegType.DOWNWIND;
} else {
double deltaDegOpposite = legBearing.getDifferenceTo(wind.getBearing().reverse()).getDegrees();
if (Math.abs(deltaDegOpposite) < UPWIND_DOWNWIND_TOLERANCE_IN_DEG) {
return LegType.UPWIND;
}
}
}
return LegType.REACHING;
}
@Override
public Bearing getLegBearing(TimePoint at) {
Position startBuoyPos = getTrackedRace().getApproximatePosition(getLeg().getFrom(), at);
Position endBuoyPos = getTrackedRace().getApproximatePosition(getLeg().getTo(), at);
Bearing legBearing = (startBuoyPos != null && endBuoyPos != null) ? startBuoyPos.getBearingGreatCircle(endBuoyPos) : null;
return legBearing;
}
@Override
public boolean isUpOrDownwindLeg(TimePoint at) throws NoWindException {
return getLegType(at) != LegType.REACHING;
@@ -500,11 +500,14 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
}
@Override
public synchronized WindTrack getOrCreateWindTrack(WindSource windSource) {
WindTrack result = windTracks.get(windSource);
if (result == null) {
result = createWindTrack(windSource);
windTracks.put(windSource, result);
public WindTrack getOrCreateWindTrack(WindSource windSource) {
WindTrack result;
synchronized (windTracks) {
result = windTracks.get(windSource);
if (result == null) {
result = createWindTrack(windSource);
windTracks.put(windSource, result);
}
}
return result;
}
@@ -531,7 +534,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
@Override
public WindWithConfidence<Pair<Position, TimePoint>> getWindWithConfidence(Position p, TimePoint at, Iterable<WindSource> windSourcesToExclude) {
final Weigher<TimePoint> weigher = ConfidenceFactory.INSTANCE.createLinearTimeDifferenceWeigher(/*halfConfidenceAfterMilliseconds*/ 10000l);
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) {
@@ -748,7 +751,7 @@ 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 = clusterBearingsForWindEstimation(timePoint,
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;
@@ -792,7 +795,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
}
// 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>> clusterBearingsForWindEstimation(TimePoint timePoint,
private Map<LegType, BearingWithConfidenceCluster<TimePoint>> clusterBearingsByLegType(TimePoint timePoint,
Position position, DummyMarkPassingWithTimePointOnly dummyMarkPassingForNow, Weigher<TimePoint> weigher) {
Weigher<TimePoint> weigherForMarkPassingProximity = new HyperbolicTimeDifferenceWeigher(getMillisecondsOverWhichToAverageSpeed()*5);
Map<LegType, BearingWithConfidenceCluster<TimePoint>> bearings = new HashMap<LegType, BearingWithConfidenceCluster<TimePoint>>();
@@ -813,26 +816,34 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
if (legType != LegType.REACHING) {
GPSFixTrack<Competitor, GPSFixMoving> track = getTrack(competitor);
if (!track.hasDirectionChange(timePoint, getManeuverDegreeAngleThreshold())) {
// reduce confidence around mark passings:
NavigableSet<MarkPassing> markPassings = getMarkPassings(competitor);
double markPassingProximityConfidenceReduction = 1.0;
synchronized (markPassings) {
NavigableSet<MarkPassing> prevMarkPassing = markPassings.headSet(dummyMarkPassingForNow, /* inclusive */ true);
NavigableSet<MarkPassing> nextMarkPassing = markPassings.tailSet(dummyMarkPassingForNow, /* inclusive */ true);
if (prevMarkPassing != null && !prevMarkPassing.isEmpty()) {
markPassingProximityConfidenceReduction *= Math.max(0.0,
1.0-weigherForMarkPassingProximity.getConfidence(prevMarkPassing.last().getTimePoint(), timePoint));
}
if (nextMarkPassing != null && !nextMarkPassing.isEmpty()) {
markPassingProximityConfidenceReduction *= Math.max(0.0,
1.0-weigherForMarkPassingProximity.getConfidence(nextMarkPassing.first().getTimePoint(), timePoint));
}
}
SpeedWithBearingWithConfidence<TimePoint> estimatedSpeedWithConfidence = track.getEstimatedSpeed(timePoint, weigher);
if (estimatedSpeedWithConfidence != null) {
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;
synchronized (markPassings) {
NavigableSet<MarkPassing> prevMarkPassing = markPassings.headSet(
dummyMarkPassingForNow, /* inclusive */true);
NavigableSet<MarkPassing> nextMarkPassing = markPassings.tailSet(
dummyMarkPassingForNow, /* inclusive */true);
if (prevMarkPassing != null && !prevMarkPassing.isEmpty()) {
markPassingProximityConfidenceReduction *= Math.max(0.0,
1.0 - weigherForMarkPassingProximity.getConfidence(prevMarkPassing.last()
.getTimePoint(), timePoint));
}
if (nextMarkPassing != null && !nextMarkPassing.isEmpty()) {
markPassingProximityConfidenceReduction *= Math.max(0.0,
1.0 - weigherForMarkPassingProximity.getConfidence(nextMarkPassing.first()
.getTimePoint(), timePoint));
}
}
BearingWithConfidence<TimePoint> bearing = new BearingWithConfidenceImpl<TimePoint>(
estimatedSpeedWithConfidence.getObject() == null ? null : estimatedSpeedWithConfidence.getObject().getBearing(),
markPassingProximityConfidenceReduction*estimatedSpeedWithConfidence.getConfidence(),
estimatedSpeedWithConfidence.getObject() == null ? null
: estimatedSpeedWithConfidence.getObject().getBearing(),
markPassingProximityConfidenceReduction
* estimatedSpeedWithConfidence.getConfidence(),
estimatedSpeedWithConfidence.getRelativeTo());
BearingWithConfidenceCluster<TimePoint> bearingClusterForLegType = bearings.get(legType);
bearingClusterForLegType.add(bearing);
@@ -844,6 +855,15 @@ 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>.
*/
private boolean isNavigatingForward(Bearing bearing, TrackedLeg trackedLeg, TimePoint at) {
Bearing legBearing = trackedLeg.getLegBearing(at);
return Math.abs(bearing.getDifferenceTo(legBearing).getDegrees()) < 90;
}
/**
* This is probably best explained by example. If the wind bearing is from port to starboard, the situation looks
* like this:
@@ -0,0 +1,340 @@
package com.sap.sailing.domain.tracking.impl;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NavigableSet;
import java.util.SortedSet;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.impl.WindTrackImpl.DummyWind;
import com.sap.sailing.util.impl.AbstractUnmodifiableNavigableSet;
import com.sap.sailing.util.impl.DescendingNavigableSet;
/**
* Emulates a collection of {@link Wind} fixes for a {@link TrackedRace}. Subclasses have to specify a resolution and a
* way to compute a wind fix for a given time point. If not constrained by a {@link #from} and/or a {@link #to} time
* point, an equidistant time field is assumed, starting at {@link TrackedRace#getStart()} and leading up to
* {@link TrackedRace#getTimePointOfNewestEvent()}. If {@link TrackedRace#getStart()} returns <code>null</code>,
* {@link Long#MAX_VALUE} is used as the {@link #from} time point, pushing the start to the more or less infinite future
* ("end of the universe"). If no event was received yet and hence {@link TrackedRace#getTimePointOfNewestEvent()}
* returns <code>null</code>, the {@link #to} end is assumed to be the beginning of the epoch (1970-01-01T00:00:00).
*
* @author Axel Uhl (d043530)
*
*/
public abstract class VirtualWindFixesAsNavigableSet extends AbstractUnmodifiableNavigableSet<Wind> {
/**
* The time resolution is one second.
*/
private final long resolutionInMilliseconds;
private final TrackedRace trackedRace;
private final TimePoint from;
private final TimePoint to;
private final WindTrack track;
protected VirtualWindFixesAsNavigableSet(WindTrack track, TrackedRace trackedRace, long resolutionInMilliseconds) {
this(track, trackedRace, null, null, resolutionInMilliseconds);
}
public long getResolutionInMilliseconds() {
return resolutionInMilliseconds;
}
/**
* @param from expected to be an integer multiple of {@link #resolutionInMilliseconds} or <code>null</code>
* @param to expected to be an integer multiple of {@link #resolutionInMilliseconds} or <code>null</code>
* @param resolutionInMilliseconds TODO
*/
protected VirtualWindFixesAsNavigableSet(WindTrack track, TrackedRace trackedRace,
TimePoint from, TimePoint to, long resolutionInMilliseconds) {
this.track = track;
this.trackedRace = trackedRace;
assert from == null || from.asMillis() % resolutionInMilliseconds == 0;
this.from = from;
assert to == null || to.asMillis() % resolutionInMilliseconds == 0;
this.to = to;
this.resolutionInMilliseconds = resolutionInMilliseconds;
}
protected WindTrack getTrack() {
return track;
}
protected TrackedRace getTrackedRace() {
return trackedRace;
}
protected TimePoint lowerToResolution(Wind w) {
TimePoint result;
final TimePoint timePointOfLastEvent = trackedRace.getTimePointOfLastEvent();
if (timePointOfLastEvent == null) {
// nothing received yet; "lowering" to end of time
result = new MillisecondsTimePoint((Long.MAX_VALUE - 1) / resolutionInMilliseconds
* resolutionInMilliseconds);
} else if (w.getTimePoint().compareTo(timePointOfLastEvent) > 0) {
result = lowerToResolution(new DummyWind(timePointOfLastEvent));
} else {
result = new MillisecondsTimePoint((w.getTimePoint().asMillis() - 1) / resolutionInMilliseconds
* resolutionInMilliseconds);
}
return result;
}
protected TimePoint floorToResolution(Wind w) {
TimePoint result;
final TimePoint timePointOfLastEvent = trackedRace.getTimePointOfLastEvent();
if (timePointOfLastEvent == null) {
// nothing received yet; "lowering" to end of time
result = new MillisecondsTimePoint((Long.MAX_VALUE - 1) / resolutionInMilliseconds
* resolutionInMilliseconds);
} else if (w.getTimePoint().compareTo(timePointOfLastEvent) > 0) {
result = floorToResolution(new DummyWind(timePointOfLastEvent));
} else {
result = new MillisecondsTimePoint(w.getTimePoint().asMillis() / resolutionInMilliseconds
* resolutionInMilliseconds);
}
return result;
}
protected TimePoint ceilingToResolution(Wind w) {
TimePoint result;
final TimePoint startOfTracking = trackedRace.getStartOfTracking();
if (startOfTracking == null) {
// no start of tracking yet; "ceiling" to beginning of time
result = new MillisecondsTimePoint(0);
} else if (w.getTimePoint().compareTo(startOfTracking) < 0) {
result = ceilingToResolution(new DummyWind(startOfTracking));
} else {
result = new MillisecondsTimePoint(((w.getTimePoint().asMillis() - 1) / resolutionInMilliseconds + 1)
* resolutionInMilliseconds);
}
return result;
}
protected TimePoint higherToResolution(Wind w) {
TimePoint result;
final TimePoint startOfTracking = trackedRace.getStartOfTracking();
if (startOfTracking == null) {
// no start of tracking yet; "ceiling" to beginning of time
result = new MillisecondsTimePoint(0);
} else if (w.getTimePoint().compareTo(startOfTracking) < 0) {
result = higherToResolution(new DummyWind(startOfTracking));
} else {
result = new MillisecondsTimePoint((w.getTimePoint().asMillis() / resolutionInMilliseconds + 1)
* resolutionInMilliseconds);
}
return result;
}
/**
* The time point starting from and including which the GPS fixes are considered in the race's tracks. Returns the
* value of {@link #from} unless it is <code>null</code>. In this case, the time point of the
* {@link TrackedRace#getStart() race start}, {@link #floorToResolution(Wind) floored to the resolution of this set}
* will be returned instead. If no valid start time can be obtained from the race, <code>Long.MAX_VALUE</code> is
* returned instead.
*/
private TimePoint getFrom() {
return from == null ? trackedRace.getStart() == null ? new MillisecondsTimePoint(Long.MAX_VALUE)
: floorToResolution(new DummyWind(trackedRace.getStart())) : from;
}
/**
* Time point up to and including which the GPS fixes are considered in the race's tracks. Returns the value of
* {@link #to} unless it is <code>null</code>. In this case, the time point of the
* {@link TrackedRace#getTimePointOfNewestEvent() time point of the newest event},
* {@link #ceilingToResolution(Wind) ceiled to the resolution of this set} will be returned instead. If no valid
* time of a newest event can be obtained from the race, <code>MillisecondsTimePoint(1)</code> is returned instead.
*/
private TimePoint getTo() {
return to == null ? trackedRace.getTimePointOfNewestEvent() == null ? new MillisecondsTimePoint(1)
: ceilingToResolution(new DummyWind(trackedRace.getTimePointOfNewestEvent())) : to;
}
@Override
public Wind lower(Wind w) {
TimePoint timePoint = lowerToResolution(w);
return timePoint.compareTo(getFrom()) < 0 ? null : getWind(w.getPosition(), timePoint);
}
/**
* Compute the {@link Wind} fix for the specified position and time point to deliver in this virtual track.
*/
abstract protected Wind getWind(Position p, TimePoint timePoint);
@Override
public Wind floor(Wind w) {
TimePoint timePoint = floorToResolution(w);
return timePoint.compareTo(getFrom()) < 0 ? null : getWind(w.getPosition(), timePoint);
}
@Override
public Wind ceiling(Wind w) {
TimePoint timePoint = ceilingToResolution(w);
return timePoint.compareTo(getTo()) > 0 ? null : getWind(w.getPosition(), timePoint);
}
@Override
public Wind higher(Wind w) {
TimePoint timePoint = higherToResolution(w);
return timePoint.compareTo(getTo()) > 0 ? null : getWind(w.getPosition(), timePoint);
}
@Override
public Iterator<Wind> iterator() {
return new Iterator<Wind>() {
private TimePoint timePoint = getFrom();
@Override
public boolean hasNext() {
return timePoint.compareTo(getTo()) <= 0;
}
@Override
public Wind next() {
Wind result = floor(new DummyWind(timePoint));
timePoint = new MillisecondsTimePoint(timePoint.asMillis() + resolutionInMilliseconds);
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
public NavigableSet<Wind> descendingSet() {
return new DescendingNavigableSet<Wind>(this);
}
@Override
public Iterator<Wind> descendingIterator() {
return new Iterator<Wind>() {
private TimePoint timePoint = lowerToResolution(new DummyWind(getTo()));
@Override
public boolean hasNext() {
return timePoint.compareTo(getFrom()) >= 0;
}
@Override
public Wind next() {
Wind result = floor(new DummyWind(timePoint));
timePoint = new MillisecondsTimePoint(timePoint.asMillis() - resolutionInMilliseconds);
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
public NavigableSet<Wind> subSet(Wind fromElement, boolean fromInclusive, Wind toElement, boolean toInclusive) {
return createSubset(track, trackedRace, fromInclusive ? ceilingToResolution(fromElement)
: higherToResolution(fromElement), toInclusive ? floorToResolution(toElement)
: lowerToResolution(toElement));
}
/**
* Create an instance of the same class as <code>this</code> object
*/
abstract protected NavigableSet<Wind> createSubset(WindTrack track, TrackedRace trackedRace, TimePoint from, TimePoint to);
@Override
public NavigableSet<Wind> headSet(Wind toElement, boolean inclusive) {
return createSubset(track, trackedRace, /* from */ null,
inclusive ? ceilingToResolution(toElement) : lowerToResolution(toElement));
}
@Override
public NavigableSet<Wind> tailSet(Wind fromElement, boolean inclusive) {
return createSubset(track, trackedRace, inclusive ? floorToResolution(fromElement)
: higherToResolution(fromElement),
/* to */ null);
}
@Override
public SortedSet<Wind> subSet(Wind fromElement, Wind toElement) {
return subSet(fromElement, true, toElement, false);
}
@Override
public SortedSet<Wind> headSet(Wind toElement) {
return headSet(toElement, false);
}
@Override
public SortedSet<Wind> tailSet(Wind fromElement) {
return tailSet(fromElement, true);
}
@Override
public Comparator<? super Wind> comparator() {
return WindComparator.INSTANCE;
}
@Override
public Wind first() {
return floor(new DummyWind(getFrom()));
}
@Override
public Wind last() {
return ceiling(new DummyWind(getTo()));
}
@Override
public int size() {
return (int) ((getTo().asMillis() - getFrom().asMillis()) / resolutionInMilliseconds);
}
@Override
public boolean contains(Object o) {
boolean result = false;
if (o instanceof Wind) {
Wind wind = (Wind) o;
result = wind.getTimePoint().asMillis() % resolutionInMilliseconds == 0
&& wind.getTimePoint().compareTo(getFrom()) >= 0 && wind.getTimePoint().compareTo(getTo()) < 0;
}
return result;
}
@Override
public Object[] toArray() {
Object[] result = new Object[size()];
int i = 0;
for (Wind w : this) {
result[i++] = w;
}
return result;
}
@Override
public <T> T[] toArray(T[] a) {
Object[] result = a;
if (result.length < size()) {
result = new Object[size()];
}
int i = 0;
for (Wind w : this) {
result[i++] = w;
}
@SuppressWarnings("unchecked")
T[] tResult = (T[]) result;
return tResult;
}
}
@@ -121,13 +121,13 @@ public class WindTrackImpl extends TrackImpl<Wind> implements WindTrack {
*/
@Override
public synchronized Wind getAveragedWind(Position p, TimePoint at) {
final WindWithConfidence<Pair<Position, TimePoint>> estimatedWindUnsynchronized = getEstimatedWindUnsynchronized(p, at);
final WindWithConfidence<Pair<Position, TimePoint>> estimatedWindUnsynchronized = getAveragedWindUnsynchronized(p, at);
return estimatedWindUnsynchronized == null ? null : estimatedWindUnsynchronized.getObject();
}
@Override
public synchronized WindWithConfidence<Pair<Position, TimePoint>> getAveragedWindWithConfidence(Position p, TimePoint at) {
return getEstimatedWindUnsynchronized(p, at);
return getAveragedWindUnsynchronized(p, at);
}
/**
@@ -136,16 +136,15 @@ public class WindTrackImpl extends TrackImpl<Wind> implements WindTrack {
* may use this carefully if they can guarantee there are no concurrency issues with the internal fixes
* while iterating over the result of {@link #getInternalFixes()}.
*/
protected WindWithConfidence<Pair<Position, TimePoint>> getEstimatedWindUnsynchronized(Position p, TimePoint at) {
// TODO aggregate confidences, using a weigher
protected WindWithConfidence<Pair<Position, TimePoint>> getAveragedWindUnsynchronized(Position p, TimePoint at) {
DummyWind atTimed = new DummyWind(at);
NavigableSet<Wind> beforeSet = getInternalFixes().headSet(atTimed, /* inclusive */ false);
NavigableSet<Wind> afterSet = getInternalFixes().tailSet(atTimed, /* inclusive */ true);
Iterator<Wind> beforeIter = beforeSet.descendingIterator();
Iterator<Wind> afterIter = afterSet.iterator();
double knotSum = 0;
// TODO bug #345: also measure speed with confidence; return confidence
Weigher<TimePoint> weigher = ConfidenceFactory.INSTANCE.createLinearTimeDifferenceWeigher(getMillisecondsOverWhichToAverageWind()/10);
// don't measure speed with separate confidence; return confidence obtained from averaging bearings
Weigher<TimePoint> weigher = ConfidenceFactory.INSTANCE.createHyperbolicTimeDifferenceWeigher(getMillisecondsOverWhichToAverageWind()/10);
BearingWithConfidenceCluster<TimePoint> bearingCluster = new BearingWithConfidenceCluster<TimePoint>(weigher);
int count = 0;
long beforeDistanceToAt = 0;
@@ -170,8 +169,7 @@ public class WindTrackImpl extends TrackImpl<Wind> implements WindTrack {
beforeIntervalEnd = beforeWind.getTimePoint();
}
knotSum += beforeWind.getKnots();
// TODO bug #346: replace confidence with passed-through confidence of beforeWind fix's confidence
bearingCluster.add(new BearingWithConfidenceImpl<TimePoint>(beforeWind.getBearing(), /* confidence */ 0.9, beforeWind.getTimePoint()));
bearingCluster.add(new BearingWithConfidenceImpl<TimePoint>(beforeWind.getBearing(), getBaseConfidence(), beforeWind.getTimePoint()));
count++;
if (beforeIter.hasNext()) {
beforeWind = beforeIter.next();
@@ -185,8 +183,7 @@ public class WindTrackImpl extends TrackImpl<Wind> implements WindTrack {
afterIntervalStart = afterWind.getTimePoint();
}
knotSum += afterWind.getKnots();
// TODO bug #346: replace confidence with passed-through confidence of beforeWind fix's confidence
bearingCluster.add(new BearingWithConfidenceImpl<TimePoint>(afterWind.getBearing(), /* confidence */ 0.9, afterWind.getTimePoint()));
bearingCluster.add(new BearingWithConfidenceImpl<TimePoint>(afterWind.getBearing(), getBaseConfidence(), afterWind.getTimePoint()));
count++;
if (afterIter.hasNext()) {
afterWind = afterIter.next();
@@ -200,13 +197,20 @@ public class WindTrackImpl extends TrackImpl<Wind> implements WindTrack {
if (count == 0) {
return null;
} else {
// TODO bug #346: pass on confidence
BearingWithConfidence<TimePoint> average = bearingCluster.getAverage(at);
SpeedWithBearing avgWindSpeed = new KnotSpeedWithBearingImpl(knotSum / count, average == null ? null : average.getObject());
return new WindWithConfidenceImpl<Pair<Position,TimePoint>>(new WindImpl(p, at, avgWindSpeed), /* TODO confidence */ 0.5,
return new WindWithConfidenceImpl<Pair<Position,TimePoint>>(new WindImpl(p, at, avgWindSpeed), average.getConfidence(),
new Pair<Position, TimePoint>(p, at));
}
}
/**
* The base confidence attributed to this track. 1.0 would mean that the individual fixes stored by this track
* represent <em>the truth</em>. 0.0 means "no relevance at all."
*/
private double getBaseConfidence() {
return /* confidence */ 0.9;
}
@Override
public String toString() {
@@ -73,6 +73,7 @@ import com.sap.sailing.domain.common.impl.KilometersPerHourSpeedImpl;
import com.sap.sailing.domain.common.impl.Util;
import com.sap.sailing.domain.common.impl.Util.Pair;
import com.sap.sailing.domain.common.impl.Util.Triple;
import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.Leaderboard.Entry;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
@@ -544,12 +545,18 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
Map<WindSource, WindTrackInfoDTO> windTrackInfoDTOs = new HashMap<WindSource, WindTrackInfoDTO>();
result.windTrackInfoByWindSource = windTrackInfoDTOs;
if (from != null && to != null) {
for (WindSource windSource : (windSources == null ? trackedRace.getWindSources() : Arrays.asList(windSources))) {
List<WindSource> windSourcesToDeliver = new ArrayList<WindSource>();
if (windSources == null) {
windSourcesToDeliver.addAll(Arrays.asList(windSources));
} else {
Util.addAll(trackedRace.getWindSources(), windSourcesToDeliver);
}
windSourcesToDeliver.add(new WindSourceImpl(WindSourceType.COMBINED));
for (WindSource windSource : windSourcesToDeliver) {
WindTrackInfoDTO windTrackInfoDTO = new WindTrackInfoDTO();
windTrackInfoDTO.windFixes = new ArrayList<WindDTO>();
WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
windTrackInfoDTO.dampeningIntervalInMilliseconds = windTrack
.getMillisecondsOverWhichToAverageWind();
windTrackInfoDTO.dampeningIntervalInMilliseconds = windTrack.getMillisecondsOverWhichToAverageWind();
Iterator<Wind> windIter = windTrack.getFixesIterator(from, /* inclusive */true);
while (windIter.hasNext()) {
Wind wind = windIter.next();
@@ -19,10 +19,12 @@ import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.TimePoint;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.persistence.impl.DomainObjectFactoryImpl;
import com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl;
import com.sap.sailing.domain.test.AbstractTracTracLiveTest;
@@ -85,15 +87,16 @@ public class TestStoringAndRetrievingWindTracksTest extends AbstractTracTracLive
public void addRaceDefinition(RaceDefinition race) {
}
});
WindSource windSource = trackedRace.getWindSources(WindSourceType.WEB).iterator().next();
WindSource windSource = new WindSourceImpl(WindSourceType.WEB);
Mongo myFirstMongo = newMongo();
DB firstDatabase = myFirstMongo.getDB(dbConfiguration.getDatabaseName());
new MongoObjectFactoryImpl(firstDatabase).addWindTrackDumper(trackedEvent, trackedRace, windSource);
WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
Position pos = new DegreePosition(54, 9);
TimePoint timePoint = MillisecondsTimePoint.now();
for (double bearingDeg = 123.4; bearingDeg<140; bearingDeg += 1.1) {
windTrack.add(new WindImpl(pos, MillisecondsTimePoint.now(), new KnotSpeedWithBearingImpl(10., new DegreeBearingImpl(bearingDeg))));
Thread.sleep(10); // ensure that the next now() call is distinguishably later
windTrack.add(new WindImpl(pos, timePoint, new KnotSpeedWithBearingImpl(10., new DegreeBearingImpl(bearingDeg))));
timePoint = new MillisecondsTimePoint(timePoint.asMillis()+1);
}
Thread.sleep(2000); // give MongoDB some time to make written data available to other connections