Merge remote-tracking branch 'server/master'

This commit is contained in:
Axel Uhl committed 2016-02-29 10:00:54 +01:00
commit 4f5ca6ceec
14 files changed
+338 -80

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:2.0.0-beta5'
classpath 'com.android.tools.build:gradle:2.0.0-beta6'
}
}
@@ -195,14 +195,14 @@ public class SwissTimingReplayAdapterServiceTest {
public void testStartPerformanceDetection() throws Exception {
SwissTimingReplayToDomainAdapter replayListener = new SwissTimingReplayToDomainAdapter(null, DomainFactory.INSTANCE,
new DummyTrackedRegattaRegistry(), /* useInternalMarkPassingAlgorithm */ false, mock(RaceLogResolver.class));
new SwissTimingReplayParserImpl().readData(getClass().getResourceAsStream("/SAW005906.20120805.replay"), replayListener);
new SwissTimingReplayParserImpl().readData(getClass().getResourceAsStream("/SAW005905.20120805.replay"), replayListener);
Iterable<? extends TrackedRace> trackedRaces = replayListener.getTrackedRaces();
TrackedRace trackedRace = trackedRaces.iterator().next();
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
assertSame(DomainFactory.INSTANCE.getBaseDomainFactory().getOrCreateBoatClass("470"), competitor.getBoat().getBoatClass());
Distance distanceToLineAtStart = trackedRace.getDistanceToStartLine(competitor, trackedRace.getStartOfRace());
assertTrue(distanceToLineAtStart.getMeters() > 0);
assertTrue(distanceToLineAtStart.getMeters() < 8.5);
assertTrue(distanceToLineAtStart.getMeters() < 20);
}
}
@@ -145,7 +145,10 @@ public class WaypointPositionAndDistanceCacheTest {
new DegreeBearingImpl(0), new KnotSpeedImpl(20).travel(now, now.plus(timeRangeResolution.times(3)))),
now.plus(timeRangeResolution.times(3))));
assertEquals(60, cache.getApproximateDistance(start, windwardWaypoint, now).getNauticalMiles(), 0.01);
assertEquals(60+new NauticalMileDistance(20).scale(timeRangeResolution.divide(Duration.ONE_HOUR)).getNauticalMiles(), cache.getApproximateDistance(start, windwardWaypoint, now.plus(timeRangeResolution)).getNauticalMiles(), 0.01);
// with no interpolation on the mark tracks of type GPSFixTrackImpl, asking for time point "timeRangeResolution"
// will base the result on the last fix *before* that point in time, in this case the fix set for "windward" at
// time point "timeRangeResolution.divide(10)"
assertEquals(60+new NauticalMileDistance(20).scale(timeRangeResolution.divide(10).divide(Duration.ONE_HOUR)).getNauticalMiles(), cache.getApproximateDistance(start, windwardWaypoint, now.plus(timeRangeResolution)).getNauticalMiles(), 0.01);
assertEquals(60+new NauticalMileDistance(20).scale(timeRangeResolution.times(2).divide(Duration.ONE_HOUR)).getNauticalMiles(), cache.getApproximateDistance(start, windwardWaypoint, now.plus(timeRangeResolution.times(2))).getNauticalMiles(), 0.01);
trackedRace.getOrCreateTrack(windward).add(new GPSFixImpl(new DegreePosition(1, 0).translateGreatCircle(
new DegreeBearingImpl(0), new KnotSpeedImpl(20).travel(now, now.plus(timeRangeResolution.minus(1)))),
@@ -51,6 +51,7 @@ import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.impl.DistanceCache;
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl;
import com.sap.sailing.domain.tracking.impl.GPSFixTrackImpl;
import com.sap.sailing.domain.tracking.impl.MaxSpeedCache;
import com.sap.sailing.domain.tracking.impl.TrackImpl;
import com.sap.sse.common.Duration;
@@ -854,18 +855,18 @@ public class TrackTest {
*/
@Test
public void testMoreFrequentDistanceComputationThanGPSFixReception() {
DynamicGPSFixTrack<Object, GPSFix> track = new DynamicGPSFixTrackImpl<Object>(new Object(), /* millisecondsOverWhichToAverage */ 30000l);
DynamicGPSFixTrack<Object, GPSFixMoving> track = new DynamicGPSFixMovingTrackImpl<Object>(new Object(), /* millisecondsOverWhichToAverage */ 30000l);
final int timeBetweenFixesInMillis = 1000;
Bearing bearing = new DegreeBearingImpl(123);
Speed speed = new KnotSpeedImpl(7);
SpeedWithBearing speed = new KnotSpeedWithBearingImpl(7, bearing);
Position p = new DegreePosition(0, 0);
final TimePoint now = MillisecondsTimePoint.now();
TimePoint start = now;
final int steps = 10;
TimePoint next = null;
GPSFix fix = null;
GPSFixMoving fix = null;
for (int i=0; i<steps; i++) {
fix = new GPSFixImpl(p, start);
fix = new GPSFixMovingImpl(p, start, speed);
track.addGPSFix(fix);
next = start.plus(timeBetweenFixesInMillis);
p = p.translateGreatCircle(bearing, speed.travel(start, next));
@@ -880,7 +881,7 @@ public class TrackTest {
assertEquals(distance1, distance3); // no progress after time point "next" because no further fixes are known
assertEquals(distance1, distance4); // no progress after time point "next" because no further fixes are known
// now add one more fix
fix = new GPSFixImpl(p, start);
fix = new GPSFixMovingImpl(p, start, speed);
track.addGPSFix(fix);
Distance distance1_new = track.getDistanceTraveled(now, next.minus(timeBetweenFixesInMillis));
Distance distance2_new = track.getDistanceTraveled(now, next.minus(2*timeBetweenFixesInMillis/3));
@@ -198,7 +198,7 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest
Wind wind = getTrackedRace().getWind(null, checkTime, getTrackedRace().getWindSources(WindSourceType.TRACK_BASED_ESTIMATION));
Position newWindwardMarkPosition = leewardGatePosition.translateGreatCircle(wind.getBearing(), d);
getTrackedRace().getOrCreateTrack(windwardMark.getMarks().iterator().next()).addGPSFix(
new GPSFixImpl(newWindwardMarkPosition, checkTime));
new GPSFixImpl(newWindwardMarkPosition, fixTime));
assertEquals(LegType.DOWNWIND, firstLeg.getLegType(fixTime));
Wind estimatedWindDirectionDownwind = track.getAveragedWind(/* position */ null, checkTime);
assertNotNull(estimatedWindDirectionDownwind);
@@ -0,0 +1,35 @@
package com.sap.sailing.domain.tracking.impl;
import static org.junit.Assert.assertEquals;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public abstract class PositionInterpolationAndExtrapolationTest<FixType extends GPSFix> {
private GPSFixTrackImpl<Object, FixType> track;
protected TimePoint now;
protected Position p1;
protected Position p2;
protected void setUp() {
now = MillisecondsTimePoint.now();
p1 = new DegreePosition(1, 1);
p2 = new DegreePosition(1.1, 1); // that's 6 nautical miles north of p1; to get there in one hour we need a speed of 6kts traveling north
}
protected void assertPos(Position p, boolean extrapolate) {
assertEquals(p, getTrack().getEstimatedPosition(now, extrapolate));
}
protected GPSFixTrack<Object, FixType> getTrack() {
return track;
}
protected void setTrack(GPSFixTrackImpl<Object, FixType> track) {
this.track = track;
}
}
@@ -0,0 +1,72 @@
package com.sap.sailing.domain.tracking.impl;
import static org.junit.Assert.assertNull;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sse.common.Duration;
public class PositionInterpolationAndExtrapolationWithSpeedInfoTest extends PositionInterpolationAndExtrapolationTest<GPSFixMoving> {
private SpeedWithBearing speed;
@Before
public void setUp() {
super.setUp();
setTrack(new DynamicGPSFixMovingTrackImpl<Object>(new Object(), /* millisecondsOverWhichToAverage */ 8000));
speed = new KnotSpeedWithBearingImpl(6 /* knots */, Bearing.NORTH);
}
@Override
protected DynamicGPSFixTrack<Object, GPSFixMoving> getTrack() {
return (DynamicGPSFixTrack<Object, GPSFixMoving>) super.getTrack();
}
@Test
public void testEmptyTrack() {
assertNull(getTrack().getEstimatedPosition(now, /* extrapolate */ true));
assertNull(getTrack().getEstimatedPosition(now, /* extrapolate */ false));
}
@Test
public void testFixBeforeNow() {
final GPSFixMoving fixBeforeNow = new GPSFixMovingImpl(p1, now.minus(Duration.ONE_HOUR), speed);
getTrack().add(fixBeforeNow);
assertPos(p1, /* extrapolate */ false);
assertPos(p1.translateGreatCircle(speed.getBearing(), speed.travel(now.minus(Duration.ONE_HOUR), now)), /* extrapolate */ true);
}
@Test
public void testFixAfterNow() {
GPSFixMoving fixAfterNow = new GPSFixMovingImpl(p1, now.plus(Duration.ONE_HOUR), speed);
getTrack().add(fixAfterNow);
assertPos(p1, /* extrapolate */ false);
assertPos(p1.translateGreatCircle(speed.getBearing().reverse() /* travel backwards */, speed.travel(now.minus(Duration.ONE_HOUR), now)), /* extrapolate */ true);
}
@Test
public void testExactMatch() {
GPSFixMoving fixNow = new GPSFixMovingImpl(p1, now, speed);
getTrack().add(fixNow);
assertPos(p1, /* extrapolate */ false);
assertPos(p1, /* extrapolate */ true);
}
@Test
public void testInBetween() {
GPSFixMoving fixBeforeNow = new GPSFixMovingImpl(p1, now.minus(Duration.ONE_HOUR), speed);
getTrack().add(fixBeforeNow);
GPSFixMoving fixAfterNow = new GPSFixMovingImpl(p2, now.plus(Duration.ONE_HOUR), speed);
getTrack().add(fixAfterNow);
Position middle = p1.translateGreatCircle(p1.getBearingGreatCircle(p2), p1.getDistance(p2).scale(0.5));
assertPos(middle, /* extrapolate */ false);
assertPos(middle, /* extrapolate */ true);
}
}
@@ -0,0 +1,63 @@
package com.sap.sailing.domain.tracking.impl;
import static org.junit.Assert.assertNull;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
import com.sap.sse.common.Duration;
public class PositionInterpolationAndExtrapolationWithoutSpeedInfoTest extends PositionInterpolationAndExtrapolationTest<GPSFix> {
@Before
public void setUp() {
super.setUp();
setTrack(new DynamicGPSFixTrackImpl<>(new Object(), /* millisecondsOverWhichToAverage */ 8000));
}
@Override
protected DynamicGPSFixTrackImpl<Object> getTrack() {
return (DynamicGPSFixTrackImpl<Object>) super.getTrack();
}
@Test
public void testEmptyTrack() {
assertNull(getTrack().getEstimatedPosition(now, /* extrapolate */ true));
assertNull(getTrack().getEstimatedPosition(now, /* extrapolate */ false));
}
@Test
public void testFixBeforeNow() {
GPSFix fixBeforeNow = new GPSFixImpl(p1, now.minus(Duration.ONE_HOUR));
getTrack().add(fixBeforeNow);
assertPos(p1, /* extrapolate */ false);
assertPos(p1, /* extrapolate */ true);
}
@Test
public void testFixAfterNow() {
GPSFix fixAfterNow = new GPSFixImpl(p1, now.plus(Duration.ONE_HOUR));
getTrack().add(fixAfterNow);
assertPos(p1, /* extrapolate */ false);
assertPos(p1, /* extrapolate */ true);
}
@Test
public void testExactMatch() {
GPSFix fixNow = new GPSFixImpl(p1, now);
getTrack().add(fixNow);
assertPos(p1, /* extrapolate */ false);
assertPos(p1, /* extrapolate */ true);
}
@Test
public void testInBetweenFallsBackToPreviousPosition() {
GPSFix fixBeforeNow = new GPSFixImpl(p1, now.minus(Duration.ONE_HOUR));
getTrack().add(fixBeforeNow);
GPSFix fixAfterNow = new GPSFixImpl(p2, now.plus(Duration.ONE_HOUR));
getTrack().add(fixAfterNow);
assertPos(p1, /* extrapolate */ false);
assertPos(p1, /* extrapolate */ true);
}
}
@@ -21,7 +21,9 @@ import com.sap.sse.common.Util;
* A track records the {@link GPSFix}es received for an object of type
* <code>ItemType</code>. It allows clients to ask for a position at any given
* {@link TimePoint} and interpolates the fixed positions to obtain an estimate
* of the position at the time requested.
* of the position at the time requested. The method used to interpolate may vary
* between different implementation classes. The default implementation is to
* fall back to the last fix at or before the time point requested.
*
* @author Axel Uhl (d043530)
*
@@ -9,6 +9,8 @@ import com.sap.sailing.domain.base.SpeedWithConfidence;
import com.sap.sailing.domain.base.impl.SpeedWithBearingWithConfidenceImpl;
import com.sap.sailing.domain.base.impl.SpeedWithConfidenceImpl;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.confidence.BearingWithConfidence;
@@ -53,6 +55,32 @@ public class DynamicGPSFixMovingTrackImpl<ItemType> extends GPSFixTrackImpl<Item
return super.add(new CompactGPSFixMovingImpl(fix));
}
/**
* Interpolates linearly between the two fixes based on their time difference and distance. This
* intentionally ignores the {@link GPSFixMoving#getSpeed() speed values} provided by the fixes
* themselves for performance reasons.
*/
@Override
protected Position getEstimatedPositionBetweenTwoValidFixes(TimePoint timePoint, GPSFixMoving lastFixAtOrBefore, GPSFixMoving firstFixAtOrAfter) {
assert lastFixAtOrBefore != null;
assert firstFixAtOrAfter != null;
assert !timePoint.before(lastFixAtOrBefore.getTimePoint());
assert !timePoint.after(firstFixAtOrAfter.getTimePoint());
final Position result;
final SpeedWithBearing estimatedSpeed = estimateSpeedOnTimeDifferenceAndDistanceOnly(lastFixAtOrBefore, firstFixAtOrAfter);
Distance distance = estimatedSpeed.travel(lastFixAtOrBefore.getTimePoint(), timePoint);
result = lastFixAtOrBefore.getPosition().translateGreatCircle(
estimatedSpeed.getBearing(), distance);
return result;
}
private SpeedWithBearing estimateSpeedOnTimeDifferenceAndDistanceOnly(GPSFixMoving fix1, GPSFixMoving fix2) {
assert fix1 != null && fix2 != null;
final Distance distance = fix1.getPosition().getDistance(fix2.getPosition());
return new KnotSpeedWithBearingImpl(distance.inTime(fix1.getTimePoint().until(fix2.getTimePoint())).getKnots(),
fix1.getPosition().getBearingGreatCircle(fix2.getPosition()));
}
@Override
protected SpeedWithBearingWithConfidence<TimePoint> getEstimatedSpeed(TimePoint at,
NavigableSet<GPSFixMoving> fixesToUseForSpeedEstimation, Weigher<TimePoint> weigher) {
@@ -437,42 +437,91 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
}
}
private Position getEstimatedPosition(TimePoint timePoint, boolean extrapolate, FixType lastFixAtOrBefore,
FixType firstFixAtOrAfter) {
lockForRead();
try {
// TODO bug #346: compute a confidence value for the position returned based on time difference between fix(es) and timePoint; consider using Taylor approximation of more fixes around timePoint to predict and weigh position
if (lastFixAtOrBefore != null && lastFixAtOrBefore == firstFixAtOrAfter) {
return lastFixAtOrBefore.getPosition(); // exact match; how unlikely is that?
/**
* Interpolates between two GPS fixes ({@code lastFixAtOrBefore} and {@code firstFixAtOrAfter}) for the time defined
* by {@code timePoint}. It is permissible for one or both of the fixes to be {@code null}. If both are {@code null}
* then so will be the result. If {@code timePoint} falls within the closed (inclusive) interval between the two
* fixes, linear interpolation based on the speed is used to estimate the position.
* <p>
*
* If one of the two fixes is missing ({@code null}) and no extrapolation is requested, the position determined by
* the fix that is present will be returned. If extrapolation is requested and the fix that is present is a
* {@link GPSFixMoving}, therefore having a {@link GPSFixMoving#getSpeed() speed}, that speed will be used to
* extrapolate forwards or backwards to the {@code timePoint} requested.
* <p>
*
* The speed between the two fixes is determined by the {@link #estimateSpeed(GPSFix, GPSFix)} method which, if both
* fixes are provided, uses the time difference and distance between the two fixes. If only one fix is provided and
* it was a {@link GPSFixMoving}, its {@link GPSFixMoving#getSpeed() speed} is used instead. Otherwise, no speed is
* available for extrapolation, and if extrapolation was requested, only the position of the single available fix
* will be returned.
* <p>
*
* The method has package scope so that test classes in the same package may access it.
*
* @param lastFixAtOrBefore
* a fix with a {@link Timed#getTimePoint() time point} that is not after {@code timePoint}; may be
* {@code null}
* @param firstFixAtOrAfter
* a fix with a {@link Timed#getTimePoint() time point} that is not before {@code timePoint}; may be
* {@code null}
*/
Position getEstimatedPosition(TimePoint timePoint, boolean extrapolate, FixType lastFixAtOrBefore, FixType firstFixAtOrAfter) {
assert timePoint != null;
assert lastFixAtOrBefore == null || !timePoint.before(lastFixAtOrBefore.getTimePoint());
assert firstFixAtOrAfter == null || !timePoint.after(firstFixAtOrAfter.getTimePoint());
final Position result;
// TODO bug #346: compute a confidence value for the position returned based on time difference between fix(es) and timePoint; consider using Taylor approximation of more fixes around timePoint to predict and weigh position
if (lastFixAtOrBefore == null) {
if (firstFixAtOrAfter == null) {
// no fix at all; cannot determine a position
result = null;
} else {
if (lastFixAtOrBefore == null && firstFixAtOrAfter != null) {
// TODO shouldn't this extrapolate into the past if extrapolate==true?
return firstFixAtOrAfter.getPosition(); // asking for time point before first fix: return first fix's position
}
if (firstFixAtOrAfter == null && !extrapolate) {
return lastFixAtOrBefore == null ? null : lastFixAtOrBefore.getPosition();
} else {
SpeedWithBearing estimatedSpeed = estimateSpeed(lastFixAtOrBefore, firstFixAtOrAfter);
if (estimatedSpeed == null) {
// TODO even if extrapolation is requested, if there is no firstFixAtOrAfter, and the GPSFixes are without speed, shouldn't this at least return the position at the lastFixAtOrBefore instead of null?
return null;
} else {
if (lastFixAtOrBefore != null) {
Distance distance = estimatedSpeed.travel(lastFixAtOrBefore.getTimePoint(), timePoint);
Position result = lastFixAtOrBefore.getPosition().translateGreatCircle(
estimatedSpeed.getBearing(), distance);
return result;
} else {
// firstFixAtOrAfter can't be null because otherwise no speed could have been estimated
// TODO shouldn't this extrapolate into the past if extrapolate==true?
return firstFixAtOrAfter.getPosition();
}
}
}
// only one fix valid; extrapolate or use unchanged
result = getEstimatedPosition(timePoint, firstFixAtOrAfter, extrapolate);
}
} else {
if (firstFixAtOrAfter == null) {
// only one fix valid; extrapolate or use unchanged
result = getEstimatedPosition(timePoint, lastFixAtOrBefore, extrapolate);
} else {
// both fixes are valid; interpolate between the two; extrapolate plays no role in this case
result = getEstimatedPositionBetweenTwoValidFixes(timePoint, lastFixAtOrBefore, firstFixAtOrAfter);
}
} finally {
unlockAfterRead();
}
return result;
}
/**
* This type of track returns a fix position up to the time point of the next fix. Therefore, {@code lastFixAtOrBefore.getPosition()}
* is returned by this implementation. Note that subclasses may choose to interpolate in some way.
*
* @param lastFixAtOrBefore must not be {@code null} and must have a time point at or before {@code timePoint}
* @param firstFixAtOrAfter must not be {@code null} and must have a time point at or after {@code timePoint}
*/
protected Position getEstimatedPositionBetweenTwoValidFixes(TimePoint timePoint, FixType lastFixAtOrBefore, FixType firstFixAtOrAfter) {
assert lastFixAtOrBefore != null;
assert firstFixAtOrAfter != null;
assert !timePoint.before(lastFixAtOrBefore.getTimePoint());
assert !timePoint.after(firstFixAtOrAfter.getTimePoint());
return lastFixAtOrBefore.getPosition();
}
/**
* When extrapolation is requested and the fix has a {@link GPSFixMoving#getSpeed() speed}, the fix's position is
* translated based on that speed to {@code timePoint}. Otherwise, the fix's position is returned unchanged.
*/
private Position getEstimatedPosition(TimePoint timePoint, FixType fix, boolean extrapolate) {
assert fix != null;
final Position result;
if (extrapolate && fix instanceof GPSFixMoving) {
final SpeedWithBearing estimatedSpeed = ((GPSFixMoving) fix).getSpeed();
Distance distance = estimatedSpeed.travel(fix.getTimePoint(), timePoint);
result = fix.getPosition().translateGreatCircle(estimatedSpeed.getBearing(), distance);
} else {
result = fix.getPosition();
}
return result;
}
@Override
@@ -480,34 +529,6 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
return maxSpeedCache.getMaxSpeed(from, to);
}
private SpeedWithBearing estimateSpeed(FixType fix1, FixType fix2) {
if (fix1 == null) {
if (fix2 instanceof GPSFixMoving) {
return ((GPSFixMoving) fix2).getSpeed();
} else {
return null;
}
} else if (fix2 == null) {
FixType lastBeforeFix1 = getLastFixBefore(fix1.getTimePoint());
if (lastBeforeFix1 != null) {
fix2 = fix1;
fix1 = lastBeforeFix1; // compute speed based on the last two fixes and assume constant speed
} else {
if (fix1 instanceof GPSFixMoving) {
return ((GPSFixMoving) fix1).getSpeed();
} else {
return null;
}
}
}
Distance distance = fix1.getPosition().getDistance(fix2.getPosition());
long millis = Math.abs(fix1.getTimePoint().asMillis() - fix2.getTimePoint().asMillis());
SpeedWithBearing speed = new KnotSpeedWithBearingImpl(distance.getNauticalMiles() / (millis / 1000. / 3600.),
fix1.getPosition().getBearingGreatCircle(fix2.getPosition()));
return speed;
}
/**
* Returns the smoothened fixes (see {@link #getInternalFixes()}), type-cast such that it's a set of {@link GPSFix}
* objects
@@ -177,18 +177,46 @@ public class RaceTimePanel extends TimePanel<RaceTimePanelSettings> implements R
/**
* If and only if the {@link #selectedRace}'s timing is described by the {@link #raceTimesInfoProvider} and
* according to the timing the current live time point is after the start of tracking or at least after three
* minutes before the race, and the current live time point is not after the end of tracking then live mode will be
* made possible (<code>true</code> will be returned).
* according to the timing the current live time point is after the start of tracking or at least after
* {@link RaceTimesCalculationUtil.MIN_TIME_BEFORE_RACE_START} minutes before the race, and the current live time
* point is not after the end of tracking then live mode will be made possible (<code>true</code> will be returned).<p>
*
* Bug 3482: when startOfTracking and endOfTracking are both {@code null} it would be good to check for the
* {@link RaceTimesInfoDTO#newestTrackingEvent} and {@link RaceTimesInfoDTO#endOfRace} values to see if---with
* a little leeway---an end for the live period may be determined. Without this logic, such races would remain
* live until the end of time.
*/
@Override
protected boolean isLiveModeToBeMadePossible() {
long liveTimePointInMillis = timer.getLiveTimePointInMillis();
RaceTimesInfoDTO lastRaceTimesInfo = raceTimesInfoProvider != null ? raceTimesInfoProvider.getRaceTimesInfo(selectedRace) : null;
return lastRaceTimesInfo != null &&
final boolean isLiveModeToBeMadePossible;
if (lastRaceTimesInfo != null &&
// check that we're after startOfTracking or startOfRace minus some leeway:
((lastRaceTimesInfo.startOfTracking != null && liveTimePointInMillis > lastRaceTimesInfo.startOfTracking.getTime()) ||
(lastRaceTimesInfo.startOfRace != null && liveTimePointInMillis > lastRaceTimesInfo.startOfRace.getTime() - RaceTimesCalculationUtil.MIN_TIME_BEFORE_RACE_START)) &&
(lastRaceTimesInfo.endOfTracking == null || liveTimePointInMillis <= lastRaceTimesInfo.endOfTracking.getTime());
(lastRaceTimesInfo.startOfRace != null && liveTimePointInMillis > lastRaceTimesInfo.startOfRace.getTime() - RaceTimesCalculationUtil.MIN_TIME_BEFORE_RACE_START))) {
// now check that we cannot know about an end time or are reasonably before it;
// we don't know about the end time if startOfTracking is valid but endOfTracking is not;
// neither do we know about the end time if both, startOfTracking and endOfTracking are null
// and we have neither a valid newestTrackingEvent nor an endOfRace value; if we do, we would
// have to be before the later one plus some leeway.
final Date endTimeOfLivePeriod;
if (lastRaceTimesInfo.startOfTracking == null && lastRaceTimesInfo.endOfTracking == null) {
Date latestOfNewestTrackingEventAndEndOfRace = lastRaceTimesInfo.newestTrackingEvent;
if (latestOfNewestTrackingEventAndEndOfRace == null || (lastRaceTimesInfo.endOfRace != null && lastRaceTimesInfo.endOfRace.after(latestOfNewestTrackingEventAndEndOfRace))) {
latestOfNewestTrackingEventAndEndOfRace = lastRaceTimesInfo.endOfRace;
}
endTimeOfLivePeriod = latestOfNewestTrackingEventAndEndOfRace == null ? null :
new Date(latestOfNewestTrackingEventAndEndOfRace.getTime() + RaceTimesCalculationUtil.TIME_AFTER_LIVE);
} else {
endTimeOfLivePeriod = lastRaceTimesInfo.endOfTracking == null ? null : lastRaceTimesInfo.endOfTracking;
}
isLiveModeToBeMadePossible = endTimeOfLivePeriod == null /* meaning we don't know an end time */ ||
endTimeOfLivePeriod.getTime() >= liveTimePointInMillis;
} else {
isLiveModeToBeMadePossible = false;
}
return isLiveModeToBeMadePossible;
}
@Override
@@ -8,8 +8,8 @@ import com.sap.sse.gwt.client.player.Timer;
public class RaceTimesCalculationUtil {
public static final long MIN_TIME_BEFORE_RACE_START = 3 * 60 * 1000; // 3 minutes
private static final long MIN_TIME_AFTER_RACE_START = 5 * 60 * 1000; // 5 minutes
private static final long TIME_AFTER_LIVE = 5 * 60 * 1000; // 5 minutes
public static final long MIN_TIME_AFTER_RACE_START = 5 * 60 * 1000; // 5 minutes
public static final long TIME_AFTER_LIVE = 5 * 60 * 1000; // 5 minutes
public static final long MAX_TIME_AFTER_RACE_END = 1 * 60 * 1000; // 1 minutes
public static Util.Pair<Date, Date> calculateRaceMinMax(Timer timer, RaceTimesInfoDTO raceTimesInfo) {
@@ -15,6 +15,11 @@
This parameter was used to allow users to trigger autoplay for live races on non-live points in time.
The permission "CAN_REPLAY_DURING_LIVE_RACES" and role "moderator" now grant this functionality to users.
</li>
<li>Mark positions are no longer interpolated between fixes. Instead, when a mark's track is asked for the
mark's position, the last known position at or before the time point requested will be used. The
"jumping" of marks in the UI that may otherwise be caused by this is ameliorated by introducing
the same CSS animation for the marks that is already used for the boats.
</li>
</ul>
<h5 class="articleSubheadline">January 2016</h2>