mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-21 03:30:12 +00:00
Merge remote-tracking branch 'server/master'
This commit is contained in:
commit
4f5ca6ceec
14 files changed
+338
-80
No files matched your search
+1
-1
@@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -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));
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+35
@@ -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;
|
||||
}
|
||||
}
|
||||
+72
@@ -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);
|
||||
}
|
||||
}
|
||||
+63
@@ -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)
|
||||
*
|
||||
|
||||
+28
@@ -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) {
|
||||
|
||||
+82
-61
@@ -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
|
||||
|
||||
+34
-6
@@ -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
|
||||
|
||||
+2
-2
@@ -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) {
|
||||
|
||||
+5
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user