Merge branch 'master' into translation

Change-Id: Ic9c7792a57a6a60e64e58b737b86d0f7489978bd
This commit is contained in:
Axel Uhl
2018-07-13 09:34:39 +02:00
54 changed files with 2003 additions and 889 deletions
+1 -1
View File
@@ -1 +1 @@
1.0.22
1.0.23
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store</string>
<key>provisioningProfiles</key>
<dict>
<key>com.sap.sailing.ios.SAPTracker.release</key> <!--bundle identifier of project read note below (Make sure to include the .release appended at the end)!-->
<string>SAP Sail InSight</string>
</dict>
</dict>
</plist>
@@ -11,8 +11,9 @@ import com.sap.sailing.datamining.impl.data.CompleteManeuverCurveWithEstimationD
import com.sap.sailing.datamining.shared.ManeuverSettings;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimationData;
import com.sap.sailing.domain.maneuverdetection.ManeuverDetector;
import com.sap.sailing.domain.maneuverdetection.ManeuverDetectorWithEstimationDataSupport;
import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorImpl;
import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorWithEstimationDataSupportDecoratorImpl;
import com.sap.sailing.domain.tracking.CompleteManeuverCurve;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
@@ -45,7 +46,9 @@ public class CompleteManeuverCurveWithEstimationDataRetrievalProcessor extends
List<HasCompleteManeuverCurveWithEstimationDataContext> result = new ArrayList<>();
TrackedRace trackedRace = element.getTrackedRaceContext().getTrackedRace();
Competitor competitor = element.getCompetitor();
ManeuverDetector maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor);
ManeuverDetectorWithEstimationDataSupport maneuverDetector = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl(
new ManeuverDetectorImpl(trackedRace, competitor),
element.getTrackedRaceContext().getLeaderboardContext().getPolarDataService());
Iterable<Maneuver> maneuvers = trackedRace.getManeuvers(competitor, false);
Iterable<CompleteManeuverCurve> maneuverCurves = maneuverDetector.getCompleteManeuverCurves(maneuvers);
Iterable<CompleteManeuverCurveWithEstimationData> maneuversWithEstimationData = maneuverDetector
@@ -259,22 +259,29 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext {
}
private int getNumberOf(ManeuverType maneuverType) {
TrackedRace trackedRace = getTrackedRace();
int number = 0;
if (trackedRace != null && trackedRace.getStartOfRace() != null) {
final TimePoint end;
final TimePoint endOfTracking = trackedRace.getEndOfTracking();
if (trackedRace.getEndOfRace() != null) {
end = trackedRace.getEndOfRace();
TrackedRace trackedRace = getTrackedRace();
if (trackedRace != null) {
Course course = trackedRace.getRace().getCourse();
Waypoint startWaypoint = course.getFirstWaypoint();
MarkPassing startPassing = trackedRace.getMarkPassing(getCompetitor(), startWaypoint);
TimePoint start = startPassing != null ? startPassing.getTimePoint() : trackedRace.getStartOfRace();
Waypoint finishWaypoint = course.getLastWaypoint();
MarkPassing finishPassing = trackedRace.getMarkPassing(getCompetitor(), finishWaypoint);
TimePoint end;
if (finishPassing != null) {
end = finishPassing.getTimePoint();
} else {
final TimePoint now = MillisecondsTimePoint.now();
if (endOfTracking != null && endOfTracking.before(now)) {
end = endOfTracking;
} else {
end = now;
end = trackedRace.getEndOfRace();
if (end == null) {
TimePoint endOfTracking = trackedRace.getEndOfTracking();
TimePoint now = MillisecondsTimePoint.now();
end = endOfTracking != null && endOfTracking.before(now) ? endOfTracking : now;
}
}
for (Maneuver maneuver : trackedRace.getManeuvers(getCompetitor(), trackedRace.getStartOfRace(), end, false)) {
for (Maneuver maneuver : trackedRace.getManeuvers(getCompetitor(), start, end, false)) {
if (maneuver.getType() == maneuverType) {
number++;
}
@@ -18,7 +18,8 @@ public enum AvailableWindFinderSpotCollections {
SZCZECIN("szczecin"),
SANKT_PETERSBURG("sankt_petersburg"),
SANKT_MORITZ("sankt_moritz"),
PORTO_CERVO("porto_cervo");
PORTO_CERVO("porto_cervo"),
MUEGGELSEE("mueggelsee");
private final String name;
@@ -619,9 +619,10 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
private void startTracking() {
setStatusAndProgress(TrackedRaceStatusEnum.TRACKING, 0.0);
trackedRace.addListener(raceChangeListener);
this.deviceMappings = new FixLoaderDeviceMappings(trackedRace.getAttachedRegattaLogs(),
trackedRace.getRace().getName());
trackedRace.addListener(raceChangeListener);
this.deviceMappings.updateMappings();
}
private void loadFixesForExtendedTimeRange(final TimeRange extendedTimeRange) {
@@ -146,7 +146,7 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
});
}
private void updateMappings() {
public void updateMappings() {
try {
updateMappingsInternal();
} catch (Exception e) {
@@ -144,7 +144,7 @@ public class TimeRangeCache<T> {
// no writer can be active because we're holding the read lock; read access on the lruCache is synchronized using
// the lruCache's mutex; this is necessary because we're using access-based LRU pinging where even getting an entry
// modifies the internal parts of the data structure which is not thread safe.
synchronized (lruCache) {
synchronized (lruCache) { // ping the "perfect match" although it may not even have existed in the cache
lruCache.get(new Util.Pair<TimePoint, TimePoint>(from, to));
}
return result;
@@ -506,12 +506,14 @@ public class TrackImpl<FixType extends Timed> implements Track<FixType> {
result = nullElement;
}
}
// run the cache update while still holding the read lock; this avoids bug4629 where a cache invalidation
// caused by fix insertions can come after the result calculation and before the cache update
if (!perfectCacheHit && recursionDepth == 0) {
cache.cache(from, to, result);
}
} finally {
unlockAfterRead();
}
if (!perfectCacheHit && recursionDepth == 0) {
cache.cache(from, to, result);
}
}
return result;
}
@@ -0,0 +1,229 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.impl.CourseAreaImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.NauticalMileDistance;
import com.sap.sailing.domain.common.sensordata.BravoExtendedSensorDataMetadata;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.impl.BravoExtendedFixImpl;
import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.tracking.BravoFixTrack;
import com.sap.sailing.domain.tracking.DynamicBravoFixTrack;
import com.sap.sailing.domain.tracking.impl.BravoFixTrackImpl;
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
import com.sap.sailing.domain.tracking.impl.TimeRangeCache;
import com.sap.sse.common.Distance;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.DegreeBearingImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
/**
* See bug4629. This test reproduces an order of fix insertion into a {@link BravoFixTrack}, cache invalidation,
* cache value calculation and cache value insertion that with bug 4629 existing will lead to an inconsistent
* cache entry that should have been invalidated by the fix insertion.
*
* @author Axel Uhl (d043530)
*
*/
public class BravoFixTrackFoiledDistanceCacheTest {
private DynamicBravoFixTrack<CourseArea> track;
private DynamicGPSFixMovingTrackImpl<CourseArea> gpsTrack;
private TimeRangeCacheWithParallelTestSupport<CourseArea> foilingDistanceCache;
/**
* Supports blocking and releasing calls to {@link #invalidateAllAtOrLaterThan(TimePoint)} and
* {@link #cache(TimePoint, TimePoint, Object)}, so as to force lock acquisition and release
* in a specific order.
*
* @author Axel Uhl (d043530)
*
* @param <T>
*/
private static class TimeRangeCacheWithParallelTestSupport<T> extends TimeRangeCache<T> {
private static Map<String, TimeRangeCacheWithParallelTestSupport<?>> caches = new HashMap<>();
private int callsToCache;
private int callsToInvalidateAllAtOrLaterThan;
private CyclicBarrier cacheBarrier;
private CyclicBarrier invalidateBarrier;
private CyclicBarrier cacheInformBarrier;
public TimeRangeCacheWithParallelTestSupport(String nameForLockLogging) {
super(nameForLockLogging);
caches.put(nameForLockLogging, this);
}
static public <T> TimeRangeCacheWithParallelTestSupport<T> getCacheByName(String nameForLockLogging) {
@SuppressWarnings("unchecked")
TimeRangeCacheWithParallelTestSupport<T> timeRangeCacheWithParallelTestSupport = (TimeRangeCacheWithParallelTestSupport<T>) caches.get(nameForLockLogging);
return timeRangeCacheWithParallelTestSupport;
}
@Override
public void invalidateAllAtOrLaterThan(TimePoint timePoint) {
super.invalidateAllAtOrLaterThan(timePoint);
callsToInvalidateAllAtOrLaterThan++;
if (invalidateBarrier != null) {
try {
invalidateBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void cache(TimePoint from, TimePoint to, T result) {
try {
if (cacheInformBarrier != null) {
cacheInformBarrier.await();
}
if (cacheBarrier != null) {
cacheBarrier.await();
}
} catch (InterruptedException | BrokenBarrierException e) {
throw new RuntimeException(e);
}
super.cache(from, to, result);
callsToCache++;
}
public int getCallsToCache() {
return callsToCache;
}
public int getCallsToInvalidateAllAtOrLaterThan() {
return callsToInvalidateAllAtOrLaterThan;
}
public void waitForCacheInvalidation() throws InterruptedException, BrokenBarrierException {
invalidateBarrier.await();
invalidateBarrier = null;
}
public void allowWaitingForCacheInvalidation() {
invalidateBarrier = new CyclicBarrier(2);
}
public void letFoilingDistanceCacheContinueWithCaching() throws InterruptedException, BrokenBarrierException {
cacheBarrier.await();
cacheBarrier = null;
}
public void letFoilingDistanceCacheWaitBeforeCaching() {
cacheBarrier = new CyclicBarrier(2);
}
public void letFoilingDistanceCacheInformUsBeforeCaching() {
cacheInformBarrier = new CyclicBarrier(2);
}
public void waitForCacheToBeEntered() throws InterruptedException, BrokenBarrierException {
cacheInformBarrier.await();
cacheInformBarrier = null;
}
}
@Before
public void setUp() {
final CourseAreaImpl courseArea = new CourseAreaImpl("Test", UUID.randomUUID());
gpsTrack = new DynamicGPSFixMovingTrackImpl<>(courseArea, /* millisecondsOverWhichToAverage */ 15000);
track = new BravoFixTrackImpl<CourseArea>(courseArea, "test", /* hasExtendedFixes */ true, gpsTrack) {
private static final long serialVersionUID = 1473560197177750211L;
@Override
protected <T> TimeRangeCache<T> createTimeRangeCache(CourseArea trackedItem, final String cacheName) {
return new TimeRangeCacheWithParallelTestSupport<>(cacheName);
}
};
track.add(createFix(1000l, /* rideHeightPort */ 0.6, /* rideHeightStarboard */ 0.6, /* heel */ 10., /* pitch */ 5.));
track.add(createFix(2000l, /* rideHeightPort */ 0.6, /* rideHeightStarboard */ 0.6, /* heel */ 10., /* pitch */ 5.));
track.add(createFix(3000l, /* rideHeightPort */ 0.6, /* rideHeightStarboard */ 0.6, /* heel */ 10., /* pitch */ 5.));
gpsTrack.add(createGPSFix(1000l, 0, 0, 0, 1));
gpsTrack.add(createGPSFix(2000l, 1./3600./60., 0, 0, 1));
gpsTrack.add(createGPSFix(3000l, 2./3600./60., 0, 0, 1));
foilingDistanceCache = TimeRangeCacheWithParallelTestSupport.getCacheByName("foilingDistanceCache");
}
@Test
public void testDistanceSpentFoiling() throws InterruptedException, ExecutionException, BrokenBarrierException {
assertEquals(new NauticalMileDistance(2./3600.).getMeters(), track.getDistanceSpentFoiling(t(1000l), t(3000l)).getMeters(), 0.01);
assertEquals(1, foilingDistanceCache.getCallsToCache());
assertEquals(6, foilingDistanceCache.getCallsToInvalidateAllAtOrLaterThan()); // the three sensor and three GPS fixes
assertEquals(new NauticalMileDistance(2./3600.).getMeters(), track.getDistanceSpentFoiling(t(1000l), t(3000l)).getMeters(), 0.01);
assertEquals(1, foilingDistanceCache.getCallsToCache()); // still the same perfect cache hit, no new cached value
track.add(createFix(2500l, /* rideHeightPort */ 0.6, /* rideHeightStarboard */ 0.6, /* heel */ 10., /* pitch */ 5.));
assertEquals(7, foilingDistanceCache.getCallsToInvalidateAllAtOrLaterThan()); // now one more sensor fix
assertEquals(new NauticalMileDistance(2./3600.).getMeters(), track.getDistanceSpentFoiling(t(1000l), t(3000l)).getMeters(), 0.01);
assertEquals(2, foilingDistanceCache.getCallsToCache()); // had to be re-calculated and then was expected to be put to cache
gpsTrack.add(createGPSFix(4000l, 3./3600./60., 0, 0, 1));
assertEquals(8, foilingDistanceCache.getCallsToInvalidateAllAtOrLaterThan()); // now one more GPS fix
// now modify the cache such that it will stop before updating the cache
foilingDistanceCache.letFoilingDistanceCacheWaitBeforeCaching();
foilingDistanceCache.letFoilingDistanceCacheInformUsBeforeCaching();
FutureTask<Distance> getDistanceFuture = new FutureTask<>(()->track.getDistanceSpentFoiling(t(1000l), t(4000l)));
new Thread(getDistanceFuture).start();
foilingDistanceCache.waitForCacheToBeEntered();
// now insert another sensor fix at t(4000l) that will have to invalidate the result of the previous request;
// adding the fix will trigger a cache invalidation; the TimeRangeCache.cache(...) call caused by the query above
// is still blocked:
FutureTask<Boolean> addFuture = new FutureTask<>(()->track.add(createFix(4000l, /* rideHeightPort */ 0.6, /* rideHeightStarboard */ 0.6, /* heel */ 10., /* pitch */ 5.)));
foilingDistanceCache.allowWaitingForCacheInvalidation();
new Thread(addFuture).start();
// with the fix for bug4629 the cache invalidation won't be reached because fix addition will require the write lock
// which isn't possible until the cache update has succeeded which now happens under the track's read lock.
Thread.sleep(500); // to continue to let the old broken version fail more or less reliably by waiting for track.add(...) to reach the invalidation
// So let the request from above continue with its call to cache(...) which eventually will release the track's read lock...
foilingDistanceCache.letFoilingDistanceCacheContinueWithCaching();
// ...so that now the cache invalidation will finally get on its way
foilingDistanceCache.waitForCacheInvalidation();
// wait until the caching has completed:
getDistanceFuture.get();
// and until adding the fixes has completed
addFuture.get();
// Now for the getDistanceFuture, either it has delivered the new value already because it was passed by
// the addition of the fix, or it delivered the old value, but then the cache entry will have been invalidated.
// Now ask again; if the invalidation worked correctly, we should get a greater result now:
assertEquals(new NauticalMileDistance(3./3600.).getMeters(), track.getDistanceSpentFoiling(t(1000l), t(4000l)).getMeters(), 0.01);
}
private BravoExtendedFixImpl createFix(long timePointAsMillis, Double rideHeightPort, Double rideHeightStarboard, Double heel, Double pitch) {
final Double[] fixData = new Double[Collections.max(Arrays.asList(
BravoExtendedSensorDataMetadata.HEEL.getColumnIndex()+1,
BravoExtendedSensorDataMetadata.PITCH.getColumnIndex()+1,
BravoExtendedSensorDataMetadata.RIDE_HEIGHT_PORT_HULL.getColumnIndex()+1,
BravoExtendedSensorDataMetadata.RIDE_HEIGHT_STBD_HULL.getColumnIndex()+1))];
fixData[BravoExtendedSensorDataMetadata.HEEL.getColumnIndex()] = heel;
fixData[BravoExtendedSensorDataMetadata.PITCH.getColumnIndex()] = pitch;
fixData[BravoExtendedSensorDataMetadata.RIDE_HEIGHT_PORT_HULL.getColumnIndex()] = rideHeightPort;
fixData[BravoExtendedSensorDataMetadata.RIDE_HEIGHT_STBD_HULL.getColumnIndex()] = rideHeightStarboard;
return new BravoExtendedFixImpl(new DoubleVectorFixImpl(t(timePointAsMillis), fixData));
}
private GPSFixMoving createGPSFix(long timePointAsMillis, double lat, double lng, double cogInDeg, double sogInKnots) {
return new GPSFixMovingImpl(new DegreePosition(lat, lng), new MillisecondsTimePoint(timePointAsMillis),
new KnotSpeedWithBearingImpl(sogInKnots, new DegreeBearingImpl(cogInDeg)));
}
private MillisecondsTimePoint t(long timePointAsMillis) {
return new MillisecondsTimePoint(timePointAsMillis);
}
}
@@ -5,6 +5,7 @@ import com.sap.sailing.domain.common.Positioned;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.tracking.CompleteManeuverCurve;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Distance;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Timed;
import com.sap.sse.datamining.annotations.Connector;
@@ -96,6 +97,12 @@ public interface CompleteManeuverCurveWithEstimationData extends Timed, Position
* boundaries, the boundaries of the curve with unstable course and speed are used.
*/
Bearing getRelativeBearingToNextMarkAfterManeuver();
Distance getDistanceToClosestMark();
Double getDeviationOfManeuverAngleFromTargetTackAngleInDegrees();
Double getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees();
/**
* Gets whether a mark was crossed within the maneuver curve.
@@ -0,0 +1,20 @@
package com.sap.sailing.domain.maneuverdetection;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Distance;
/**
*
* @author Vladislav Chumak (D069712)
*
*/
public interface GpsFixWithEstimationData extends GPSFixMoving {
Wind getWind();
Bearing getRelativeBearingToNextMarkAfterManeuver();
Distance getDistanceToClosestMark();
}
@@ -36,50 +36,4 @@ public interface ManeuverDetector {
*/
List<Maneuver> detectManeuvers();
/**
* Derives maneuvers from the provided {@code maneuverCurves}. Since the provided complete maneuver curves already
* include the calculated boundaries of each complete maneuvering spot, this method operates in a very
* performance-efficient manner.
*
* @param maneuverCurves
* The maneuver curves from which the maneuvers shall be derived
* @return The maneuvers derived from the provided maneuver curves. The list gets empty, if the provided maneuver
* curves list is also empty.
*/
List<Maneuver> detectManeuvers(Iterable<CompleteManeuverCurve> maneuverCurves);
/**
* Detects the complete maneuver curves performed within a GPS-track of the competitor associated with this
* {@link ManeuverDetector}-instance. In contrast to maneuvers determined by {@link #detectManeuvers()}, the
* complete maneuver curves are not subject to any splitting logic for maneuvers with multiple "tacking" and
* "jibing". See {@link ManeuverDetector} description for more info regarding the detection strategy.
*
* @return an empty list if no maneuver spots were detected, otherwise the list with detected maneuver curves.
* @see CompleteManeuverCurve
* @see ManeuverDetector
*/
List<CompleteManeuverCurve> detectCompleteManeuverCurves();
/**
* Parses {@link CompleteManeuverCurve}-instances from provided {@link Maneuver}-instances. This method performs
* significantly faster than {@link #detectCompleteManeuverCurves()}.
*
* @param maneuvers
* The maneuvers to parse into complete maneuver curves
* @return an empty list if provided maneuvers list is empty, otherwise the list with complete maneuver curves
* derived from provided maneuvers.
* @see CompleteManeuverCurve
* @see Maneuver
*/
List<CompleteManeuverCurve> getCompleteManeuverCurves(Iterable<Maneuver> maneuvers);
/**
* Converts provided {@link CompleteManeuverCurve}-instances into
* {@link CompleteManeuverCurveWithEstimationData}-instances. For this, additional information to
* {@code maneuverCurves} is computed. This computation is regarded as complex as the computation within
* {@link #detectManeuvers()}.
*/
List<CompleteManeuverCurveWithEstimationData> getCompleteManeuverCurvesWithEstimationData(
Iterable<CompleteManeuverCurve> maneuverCurves);
}
@@ -0,0 +1,61 @@
package com.sap.sailing.domain.maneuverdetection;
import java.util.List;
import com.sap.sailing.domain.tracking.CompleteManeuverCurve;
import com.sap.sailing.domain.tracking.Maneuver;
/**
* A maneuver detector which additional support for management of estimation data for wind estimation.
*
* @author Vladislav Chumak (D069712)
* @see ManeuverDetector
*
*/
public interface ManeuverDetectorWithEstimationDataSupport extends ManeuverDetector {
/**
* Derives maneuvers from the provided {@code maneuverCurves}. Since the provided complete maneuver curves already
* include the calculated boundaries of each complete maneuvering spot, this method operates in a very
* performance-efficient manner.
*
* @param maneuverCurves
* The maneuver curves from which the maneuvers shall be derived
* @return The maneuvers derived from the provided maneuver curves. The list gets empty, if the provided maneuver
* curves list is also empty.
*/
List<Maneuver> detectManeuvers(Iterable<CompleteManeuverCurve> maneuverCurves);
/**
* Detects the complete maneuver curves performed within a GPS-track of the competitor associated with this
* {@link ManeuverDetector}-instance. In contrast to maneuvers determined by {@link #detectManeuvers()}, the
* complete maneuver curves are not subject to any splitting logic for maneuvers with multiple "tacking" and
* "jibing". See {@link ManeuverDetector} description for more info regarding the detection strategy.
*
* @return an empty list if no maneuver spots were detected, otherwise the list with detected maneuver curves.
* @see CompleteManeuverCurve
* @see ManeuverDetector
*/
List<CompleteManeuverCurve> detectCompleteManeuverCurves();
/**
* Parses {@link CompleteManeuverCurve}-instances from provided {@link Maneuver}-instances. This method performs
* significantly faster than {@link #detectCompleteManeuverCurves()}.
*
* @param maneuvers
* The maneuvers to parse into complete maneuver curves
* @return an empty list if provided maneuvers list is empty, otherwise the list with complete maneuver curves
* derived from provided maneuvers.
* @see CompleteManeuverCurve
* @see Maneuver
*/
List<CompleteManeuverCurve> getCompleteManeuverCurves(Iterable<Maneuver> maneuvers);
/**
* Converts provided {@link CompleteManeuverCurve}-instances into
* {@link CompleteManeuverCurveWithEstimationData}-instances. For this, additional information to
* {@code maneuverCurves} is computed. This computation is regarded as complex as the computation within
* {@link #detectManeuvers()}.
*/
List<CompleteManeuverCurveWithEstimationData> getCompleteManeuverCurvesWithEstimationData(
Iterable<CompleteManeuverCurve> maneuverCurves);
}
@@ -0,0 +1,155 @@
package com.sap.sailing.domain.maneuverdetection.impl;
import java.util.NavigableSet;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.BearingChangeAnalyzer;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.maneuverdetection.ManeuverDetector;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
public abstract class AbstractManeuverDetectorImpl implements ManeuverDetector {
/**
* Tracked race whose tracks are being processed for maneuver detection.
*/
protected final TrackedRace trackedRace;
/**
* The competitor, whose maneuvers are being discovered
*/
protected final Competitor competitor;
/**
* The track of competitor
*/
protected final GPSFixTrack<Competitor, GPSFixMoving> track;
/**
* Constructs maneuver detector which is supposed to be used for maneuver detection within the provided tracked race
* for provided competitor.
*
* @param trackedRace
* The tracked race whose maneuvers are supposed to be detected
* @param competitor
* The competitor, whose maneuvers shall be discovered
*/
public AbstractManeuverDetectorImpl(TrackedRace trackedRace, Competitor competitor) {
this.trackedRace = trackedRace;
this.competitor = competitor;
this.track = trackedRace != null ? trackedRace.getTrack(competitor) : null;
}
/**
* Gets track's start time point, end time point and the time point of last raw fix.
*
* @return {@code null} when there are no appropriate fixes contained within the analyzed track
*/
public TrackTimeInfo getTrackTimeInfo() {
NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
TimePoint earliestTrackRecord = null;
TimePoint latestRawFixTimePoint = null;
MarkPassing crossedFinishLine = null;
// getLastWaypoint() will wait for a read lock on the course; do this outside the synchronized block to avoid
// deadlocks
final Waypoint lastWaypoint = trackedRace.getRace().getCourse().getLastWaypoint();
if (lastWaypoint != null) {
trackedRace.lockForRead(markPassings);
try {
if (markPassings != null && !markPassings.isEmpty()) {
earliestTrackRecord = markPassings.iterator().next().getTimePoint();
crossedFinishLine = trackedRace.getMarkPassing(competitor, lastWaypoint);
}
} finally {
trackedRace.unlockAfterRead(markPassings);
}
}
if (earliestTrackRecord == null) {
GPSFixMoving firstRawFix = track.getFirstRawFix();
if (firstRawFix != null) {
earliestTrackRecord = firstRawFix.getTimePoint();
}
}
if (earliestTrackRecord != null) {
TimePoint latestTrackRecord;
if (crossedFinishLine != null) {
latestTrackRecord = crossedFinishLine.getTimePoint();
} else {
final GPSFixMoving lastRawFix = track.getLastRawFix();
if (lastRawFix != null) {
latestTrackRecord = lastRawFix.getTimePoint();
latestRawFixTimePoint = latestTrackRecord;
} else {
latestTrackRecord = null;
}
}
if (latestTrackRecord != null) {
if (latestRawFixTimePoint == null) {
final GPSFixMoving lastRawFix = track.getLastRawFix();
if (lastRawFix != null) {
latestRawFixTimePoint = lastRawFix.getTimePoint();
}
}
if (latestRawFixTimePoint != null) {
if (!earliestTrackRecord.equals(latestTrackRecord)) {
return new TrackTimeInfo(earliestTrackRecord, latestTrackRecord, latestRawFixTimePoint);
}
GPSFixMoving firstRawFix = track.getFirstRawFix();
if (firstRawFix != null) {
return new TrackTimeInfo(firstRawFix.getTimePoint(), latestRawFixTimePoint,
latestRawFixTimePoint);
}
}
}
}
return null;
}
/**
* Gets the number of cases, when the boats bow was headed through the wind coming from behind.
*/
protected int getNumberOfJibes(ManeuverCurveBoundaries maneuverBoundaries, Wind wind) {
BearingChangeAnalyzer bearingChangeAnalyzer = BearingChangeAnalyzer.INSTANCE;
int numberOfJibes = wind == null ? 0
: bearingChangeAnalyzer.didPass(maneuverBoundaries.getSpeedWithBearingBefore().getBearing(),
maneuverBoundaries.getDirectionChangeInDegrees(),
maneuverBoundaries.getSpeedWithBearingAfter().getBearing(), wind.getBearing());
return numberOfJibes;
}
/**
* Gets the number of cases, when the boats bow was headed through the wind coming from the front.
*/
protected int getNumberOfTacks(ManeuverCurveBoundaries maneuverBoundaries, Wind wind) {
BearingChangeAnalyzer bearingChangeAnalyzer = BearingChangeAnalyzer.INSTANCE;
int numberOfTacks = wind == null ? 0
: bearingChangeAnalyzer.didPass(maneuverBoundaries.getSpeedWithBearingBefore().getBearing(),
maneuverBoundaries.getDirectionChangeInDegrees(),
maneuverBoundaries.getSpeedWithBearingAfter().getBearing(), wind.getFrom());
return numberOfTacks;
}
/**
* Maps the provided {@code courseChangeInDegrees} from {@link Bearing} to {@link NauticalSide}.
*/
protected NauticalSide getDirectionOfCourseChange(double courseChangeInDegrees) {
return courseChangeInDegrees < 0 ? NauticalSide.PORT : NauticalSide.STARBOARD;
}
/**
* Gets the approximated duration of the maneuver main curve considering the boat class of the competitor.
*/
protected Duration getApproximateManeuverDuration() {
return trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass().getApproximateManeuverDuration();
}
}
@@ -6,6 +6,7 @@ import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimat
import com.sap.sailing.domain.maneuverdetection.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData;
import com.sap.sailing.domain.maneuverdetection.ManeuverMainCurveWithEstimationData;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Distance;
/**
*
@@ -24,13 +25,16 @@ public class CompleteManeuverCurveWithEstimationDataImpl implements CompleteMane
private final Bearing relativeBearingToNextMarkBeforeManeuver;
private final Bearing relativeBearingToNextMarkAfterManeuver;
private final boolean markPassing;
private Position position;
private final Position position;
private final Distance distanceToClosestMark;
private final Double deviationOfManeuverAngleFromTargetTackAngleInDegrees;
private final Double deviationOfManeuverAngleFromTargetJibeAngleInDegrees;
public CompleteManeuverCurveWithEstimationDataImpl(Position position, ManeuverMainCurveWithEstimationData mainCurve,
ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData curveWithUnstableCourseAndSpeed, Wind wind,
int tackingCount, int jibingCount, boolean maneuverStartsByRunningAwayFromWind,
Bearing relativeBearingToNextMarkBeforeManeuver, Bearing relativeBearingToNextMarkAfterManeuver,
boolean markPassing) {
boolean markPassing, Distance distanceToClosestMark, Double deviationOfManeuverAngleFromTargetTackAngleInDegrees, Double deviationOfManeuverAngleFromTargetJibeAngleInDegrees) {
this.position = position;
this.mainCurve = mainCurve;
this.curveWithUnstableCourseAndSpeed = curveWithUnstableCourseAndSpeed;
@@ -41,6 +45,9 @@ public class CompleteManeuverCurveWithEstimationDataImpl implements CompleteMane
this.relativeBearingToNextMarkBeforeManeuver = relativeBearingToNextMarkBeforeManeuver;
this.relativeBearingToNextMarkAfterManeuver = relativeBearingToNextMarkAfterManeuver;
this.markPassing = markPassing;
this.distanceToClosestMark = distanceToClosestMark;
this.deviationOfManeuverAngleFromTargetTackAngleInDegrees = deviationOfManeuverAngleFromTargetTackAngleInDegrees;
this.deviationOfManeuverAngleFromTargetJibeAngleInDegrees = deviationOfManeuverAngleFromTargetJibeAngleInDegrees;
}
@Override
@@ -92,5 +99,20 @@ public class CompleteManeuverCurveWithEstimationDataImpl implements CompleteMane
public Position getPosition() {
return position;
}
@Override
public Distance getDistanceToClosestMark() {
return distanceToClosestMark;
}
@Override
public Double getDeviationOfManeuverAngleFromTargetTackAngleInDegrees() {
return deviationOfManeuverAngleFromTargetTackAngleInDegrees;
}
@Override
public Double getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees() {
return deviationOfManeuverAngleFromTargetJibeAngleInDegrees;
}
}
@@ -0,0 +1,48 @@
package com.sap.sailing.domain.maneuverdetection.impl;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.maneuverdetection.GpsFixWithEstimationData;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Distance;
import com.sap.sse.common.TimePoint;
/**
*
* @author Vladislav Chumak (D069712)
*
*/
public class GpsFixWithEstimationDataImpl extends GPSFixMovingImpl implements GpsFixWithEstimationData {
private static final long serialVersionUID = -6952863819352430365L;
private Wind wind;
private Bearing relativeBearingToNextMarkAfterManeuver;
private Distance distanceToClosestMark;
public GpsFixWithEstimationDataImpl(Position position, TimePoint timePoint, SpeedWithBearing speedWithBearing,
Wind wind, Bearing relativeBearingToNextMarkAfterManeuver, Distance distanceToClosestMark) {
super(position, timePoint, speedWithBearing);
this.wind = wind;
this.relativeBearingToNextMarkAfterManeuver = relativeBearingToNextMarkAfterManeuver;
this.distanceToClosestMark = distanceToClosestMark;
}
@Override
public Wind getWind() {
return wind;
}
@Override
public Bearing getRelativeBearingToNextMarkAfterManeuver() {
return relativeBearingToNextMarkAfterManeuver;
}
@Override
public Distance getDistanceToClosestMark() {
return distanceToClosestMark;
}
}
@@ -0,0 +1,120 @@
package com.sap.sailing.domain.maneuverdetection.impl;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.CourseChange;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.maneuverdetection.ApproximatedFixesCalculator;
import com.sap.sailing.domain.maneuverdetection.ManeuverDetector;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.impl.ManeuverCurveBoundariesImpl;
import com.sap.sailing.domain.tracking.impl.ManeuverWithCoarseGrainedBoundariesImpl;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
/**
* Maneuver detector implementation for GPS tracks with extremely low sampling rate such as 1 fix per 30 seconds.
*
* @author Vladislav Chumak (D069712)
*
*/
public class LowGPSSamplingRateManeuverDetectorImpl extends AbstractManeuverDetectorImpl implements ManeuverDetector {
public LowGPSSamplingRateManeuverDetectorImpl(TrackedRace trackedRace, Competitor competitor) {
super(trackedRace, competitor);
}
@Override
public List<Maneuver> detectManeuvers() {
List<Maneuver> result = new ArrayList<>();
TrackTimeInfo startAndEndTimePoints = getTrackTimeInfo();
if (startAndEndTimePoints != null) {
ApproximatedFixesCalculator approximatedFixesCalculator = new ApproximatedFixesCalculatorImpl(trackedRace,
competitor);
Iterable<GPSFixMoving> approximatedFixes = approximatedFixesCalculator.approximate(
startAndEndTimePoints.getTrackStartTimePoint(), startAndEndTimePoints.getTrackEndTimePoint());
if (Util.size(approximatedFixes) > 2) {
Iterator<GPSFixMoving> approximationPointsIter = approximatedFixes.iterator();
GPSFixMoving previous = approximationPointsIter.next();
GPSFixMoving current = approximationPointsIter.next();
// the bearings in these variables are between approximation points
do {
GPSFixMoving next = approximationPointsIter.next();
SpeedWithBearing speedWithBearingOnApproximationFromPreviousToCurrent = previous
.getSpeedAndBearingRequiredToReach(current);
SpeedWithBearing speedWithBearingOnApproximationFromCurrentToNext = current
.getSpeedAndBearingRequiredToReach(next);
CourseChange courseChange = speedWithBearingOnApproximationFromPreviousToCurrent
.getCourseChangeRequiredToReach(speedWithBearingOnApproximationFromCurrentToNext);
speedWithBearingOnApproximationFromPreviousToCurrent = speedWithBearingOnApproximationFromCurrentToNext;
Maneuver maneuver = createManeuverFromGroupOfCourseChanges(competitor,
speedWithBearingOnApproximationFromPreviousToCurrent, current,
speedWithBearingOnApproximationFromCurrentToNext, courseChange.getCourseChangeInDegrees());
result.add(maneuver);
previous = current;
current = next;
} while (approximationPointsIter.hasNext());
}
}
return result;
}
private Maneuver createManeuverFromGroupOfCourseChanges(Competitor competitor,
SpeedWithBearing speedWithBearingOnApproximationAtBeginning, GPSFixMoving currentFix,
SpeedWithBearing speedWithBearingOnApproximationAtEnd, double totalCourseChangeInDegrees) {
TimePoint maneuverTimePoint = currentFix.getTimePoint();
Position maneuverPosition = currentFix.getPosition();
final Wind wind = trackedRace.getWind(maneuverPosition, maneuverTimePoint);
Tack tackAfterManeuver = null;
try {
tackAfterManeuver = wind == null ? null
: trackedRace.getTack(maneuverPosition, maneuverTimePoint,
speedWithBearingOnApproximationAtEnd.getBearing());
} catch (NoWindException e) {
}
ManeuverType maneuverType;
ManeuverCurveBoundaries maneuverCurve = new ManeuverCurveBoundariesImpl(
maneuverTimePoint.minus(getApproximateManeuverDuration().divide(2)),
maneuverTimePoint.plus(getApproximateManeuverDuration().times(3.0)),
speedWithBearingOnApproximationAtBeginning, speedWithBearingOnApproximationAtEnd,
totalCourseChangeInDegrees,
speedWithBearingOnApproximationAtBeginning.compareTo(speedWithBearingOnApproximationAtBeginning) < 0
? speedWithBearingOnApproximationAtBeginning : speedWithBearingOnApproximationAtEnd);
if (wind != null) {
if (getNumberOfTacks(maneuverCurve, wind) > 0) {
maneuverType = ManeuverType.TACK;
} else if (getNumberOfJibes(maneuverCurve, wind) > 0) {
maneuverType = ManeuverType.JIBE;
} else {
// heading up or bearing away
Bearing windBearing = wind.getBearing();
Bearing toWindBeforeManeuver = windBearing
.getDifferenceTo(speedWithBearingOnApproximationAtBeginning.getBearing());
Bearing toWindAfterManeuver = windBearing
.getDifferenceTo(speedWithBearingOnApproximationAtEnd.getBearing());
maneuverType = Math.abs(toWindBeforeManeuver.getDegrees()) < Math.abs(toWindAfterManeuver.getDegrees())
? ManeuverType.HEAD_UP : ManeuverType.BEAR_AWAY;
}
} else {
// no wind information; marking as UNKNOWN
maneuverType = ManeuverType.UNKNOWN;
}
Maneuver maneuver = new ManeuverWithCoarseGrainedBoundariesImpl(maneuverType, tackAfterManeuver,
maneuverPosition, maneuverTimePoint, maneuverCurve);
return maneuver;
}
}
@@ -5,10 +5,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableSet;
import java.util.function.Predicate;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
@@ -21,12 +19,9 @@ import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimationData;
import com.sap.sailing.domain.maneuverdetection.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData;
import com.sap.sailing.domain.maneuverdetection.ApproximatedFixesCalculator;
import com.sap.sailing.domain.maneuverdetection.ManeuverDetector;
import com.sap.sailing.domain.maneuverdetection.ManeuverMainCurveWithEstimationData;
import com.sap.sailing.domain.tracking.CompleteManeuverCurve;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.Maneuver;
@@ -56,7 +51,7 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
* @see ManeuverDetector
*
*/
public class ManeuverDetectorImpl implements ManeuverDetector {
public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl {
private static final Logger logger = Logger.getLogger(ManeuverDetectorImpl.class.getName());
@@ -64,7 +59,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
* Defines the maximal absolute course change velocity in degrees per second that shall be regarded as a stable
* course.
*/
private static final double MAX_ABS_COURSE_CHANGE_IN_DEGREES_PER_SECOND_FOR_STABLE_BEARING_ANALYSIS = 2;
private static final double MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS = 1;
/**
* Defines the absolute course change in degrees between bearing steps to ignore in order to shorten the
@@ -72,35 +67,11 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
*/
private static final double MIN_ANGULAR_VELOCITY_FOR_MAIN_CURVE_BOUNDARIES_IN_DEGREES_PER_SECOND = 0.2;
/**
* Defines the course change limit toward opposite direction related to the direction of maneuver main curve. If
* speed maxima or stable bearing analysis produce a curve extension which exceeds this limit, the extension gets
* rejected.
*/
private static final double MAX_COURSE_CHANGE_TOWARD_MANEUVER_OPPOSITE_DIRECTION_FOR_CURVE_EXTENSION_IN_DEGREES = 15.0;
/**
* Tracked race whose tracks are being processed for maneuver detection.
*/
protected final TrackedRace trackedRace;
/**
* The competitor, whose maneuvers are being discovered
*/
protected final Competitor competitor;
/**
* The track of competitor
*/
protected final GPSFixTrack<Competitor, GPSFixMoving> track;
/**
* Constructor for unit tests only.
*/
public ManeuverDetectorImpl() {
trackedRace = null;
competitor = null;
track = null;
super(null, null);
}
/**
@@ -113,9 +84,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
* The competitor, whose maneuvers shall be discovered
*/
public ManeuverDetectorImpl(TrackedRace trackedRace, Competitor competitor) {
this.trackedRace = trackedRace;
this.competitor = competitor;
this.track = trackedRace.getTrack(competitor);
super(trackedRace, competitor);
}
@Override
@@ -123,355 +92,6 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
return getAllManeuversFromManeuverSpots(detectManeuverSpots());
}
@Override
public List<Maneuver> detectManeuvers(Iterable<CompleteManeuverCurve> maneuverCurves) {
List<Maneuver> maneuvers = new ArrayList<>();
for (CompleteManeuverCurve maneuverCurve : maneuverCurves) {
TimePoint maneuverTimePoint = maneuverCurve.getMainCurveBoundaries().getTimePoint();
Position maneuverPosition = track.getEstimatedPosition(maneuverTimePoint, /* extrapolate */false);
Wind wind = trackedRace.getWind(maneuverPosition, maneuverTimePoint);
maneuvers.addAll(determineManeuversFromManeuverCurve(maneuverCurve.getMainCurveBoundaries(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries(), wind,
maneuverCurve.getMarkPassing()));
}
return maneuvers;
}
@Override
public List<CompleteManeuverCurve> detectCompleteManeuverCurves() {
List<ManeuverSpot> maneuverSpots = detectManeuverSpots();
return maneuverSpots.stream().filter(maneuverSpot -> maneuverSpot.getManeuverCurve() != null)
.map(maneuverSpot -> maneuverSpot.getManeuverCurve()).collect(Collectors.toList());
}
@Override
public List<CompleteManeuverCurve> getCompleteManeuverCurves(Iterable<Maneuver> maneuvers) {
List<CompleteManeuverCurve> result = new ArrayList<>();
CompleteManeuverCurve curveToAdd = null;
boolean previousManeuverCouldBelongToSameCurve = false;
Maneuver previousManeuver = null;
for (Maneuver maneuver : maneuvers) {
boolean maneuverCouldBelongToSameCurve = maneuver.getType() == ManeuverType.PENALTY_CIRCLE
|| maneuver.isMarkPassing()
&& (maneuver.getType() == ManeuverType.TACK || maneuver.getType() == ManeuverType.JIBE);
if (previousManeuverCouldBelongToSameCurve && maneuverCouldBelongToSameCurve
&& previousManeuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter()
.equals(maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore())
&& previousManeuver.getToSide() == maneuver.getToSide()) {
curveToAdd = extendCompleteManeuverCurveWithManeuver(curveToAdd, maneuver);
} else {
if (curveToAdd != null) {
result.add(curveToAdd);
}
curveToAdd = convertManeuverToCompleteManeuverCurve(maneuver);
}
previousManeuver = maneuver;
previousManeuverCouldBelongToSameCurve = maneuverCouldBelongToSameCurve;
}
if (curveToAdd != null) {
result.add(curveToAdd);
}
return result;
}
/**
* Converts the provided maneuver into {@link CompleteManeuverCurve}. The boundaries of provided maneuver are reused
* for the resulting complete maneuver curve.
*
* @see CompleteManeuverCurve
* @see Maneuver
*/
private CompleteManeuverCurve convertManeuverToCompleteManeuverCurve(Maneuver maneuver) {
ManeuverMainCurveDetailsWithBearingSteps mainCurveBoundaries = new ManeuverMainCurveDetailsWithBearingSteps(
maneuver.getMainCurveBoundaries().getTimePointBefore(),
maneuver.getMainCurveBoundaries().getTimePointAfter(), maneuver.getTimePoint(),
maneuver.getMainCurveBoundaries().getSpeedWithBearingBefore(),
maneuver.getMainCurveBoundaries().getSpeedWithBearingAfter(),
maneuver.getMainCurveBoundaries().getDirectionChangeInDegrees(),
maneuver.getMaxTurningRateInDegreesPerSecond(), maneuver.getMainCurveBoundaries().getLowestSpeed(),
getSpeedWithBearingSteps(maneuver.getMainCurveBoundaries().getTimePointBefore(),
maneuver.getMainCurveBoundaries().getTimePointAfter()));
return new CompleteManeuverCurveImpl(mainCurveBoundaries,
maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries(), maneuver.getMarkPassing());
}
/**
* Extends the end of provided maneuver curve with the end of provided maneuver. For this, the curve boundaries with
* unstable course and speed are merged by appending, whereas the maneuver main curve gets recalculated completely
* from scratch. The additional attributes such as, direction change and lowest speed get adjusted accordingly.
*/
private CompleteManeuverCurve extendCompleteManeuverCurveWithManeuver(CompleteManeuverCurve maneuverCurve,
Maneuver maneuver) {
ManeuverMainCurveDetailsWithBearingSteps mainCurveDetails = computeManeuverMainCurveDetails(
maneuverCurve.getMainCurveBoundaries().getTimePointBefore(),
maneuver.getMainCurveBoundaries().getTimePointAfter(), maneuver.getToSide());
if (mainCurveDetails == null) {
return maneuverCurve;
}
ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries = new ManeuverCurveBoundariesImpl(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingBefore(),
maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDirectionChangeInDegrees()
+ maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDirectionChangeInDegrees(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed()
.compareTo(maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed()) > 0
? maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed()
: maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed());
return new CompleteManeuverCurveImpl(mainCurveDetails, maneuverCurveWithStableSpeedAndCourseBoundaries,
maneuverCurve.getMarkPassing() == null ? maneuver.getMarkPassing() : maneuverCurve.getMarkPassing());
}
@Override
public List<CompleteManeuverCurveWithEstimationData> getCompleteManeuverCurvesWithEstimationData(
Iterable<CompleteManeuverCurve> maneuverCurves) {
List<CompleteManeuverCurveWithEstimationData> result = new ArrayList<>();
CompleteManeuverCurve previousManeuverCurve = null;
CompleteManeuverCurve currentManeuverCurve = null;
for (CompleteManeuverCurve nextManeuverCurve : maneuverCurves) {
if (currentManeuverCurve != null) {
CompleteManeuverCurveWithEstimationData maneuverCurveWithEstimationData = calculateCompleteManeuverCurveWithEstimationData(
currentManeuverCurve, previousManeuverCurve, nextManeuverCurve);
result.add(maneuverCurveWithEstimationData);
}
previousManeuverCurve = currentManeuverCurve;
currentManeuverCurve = nextManeuverCurve;
}
if (currentManeuverCurve != null) {
CompleteManeuverCurveWithEstimationData maneuverCurveWithEstimationData = calculateCompleteManeuverCurveWithEstimationData(
currentManeuverCurve, previousManeuverCurve, null);
result.add(maneuverCurveWithEstimationData);
}
return result;
}
/**
* Calculates a {@link CompleteManeuverCurveWithEstimationData}-instance for the provided {@code maneuverCurve}. The
* computation of additional information required by {@link CompleteManeuverCurveWithEstimationData} is regarded as
* computationally-intensive.
*/
private CompleteManeuverCurveWithEstimationData calculateCompleteManeuverCurveWithEstimationData(
CompleteManeuverCurve maneuverCurve, CompleteManeuverCurve previousManeuverCurve,
CompleteManeuverCurve nextManeuverCurve) {
Bearing courseAtMaxTurningRate = null;
SpeedWithBearingStep stepWithLowestSpeed = null;
SpeedWithBearingStep stepWithHighestSpeed = null;
SpeedWithBearingStep stepWithMaxTurningRate = null;
SpeedWithBearingStep previousStep = null;
for (SpeedWithBearingStep step : maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingSteps()) {
if (stepWithLowestSpeed == null
|| stepWithLowestSpeed.getSpeedWithBearing().compareTo(step.getSpeedWithBearing()) > 0) {
stepWithLowestSpeed = step;
}
if (stepWithHighestSpeed == null
|| stepWithHighestSpeed.getSpeedWithBearing().compareTo(step.getSpeedWithBearing()) < 0) {
stepWithHighestSpeed = step;
}
if (previousStep != null && (stepWithMaxTurningRate == null || stepWithMaxTurningRate
.getTurningRateInDegreesPerSecond() < step.getTurningRateInDegreesPerSecond())) {
stepWithMaxTurningRate = step;
courseAtMaxTurningRate = previousStep.getSpeedWithBearing().getBearing()
.add(new DegreeBearingImpl(step.getCourseChangeInDegrees() / 2));
}
previousStep = step;
}
int gpsFixCountWithinMainCurve = 0;
int gpsFixCountWithinWholeCurve = 0;
int gpsFixesCountFromPreviousManeuver = 0;
int gpsFixesCountToNextManeuver = 0;
try {
track.lockForRead();
boolean considerPreviousManeuver = previousManeuverCurve != null && previousManeuverCurve
.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter()
.before(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore());
boolean considerNextManeuver = nextManeuverCurve != null && nextManeuverCurve
.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore()
.after(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter());
for (GPSFixMoving fix : track.getFixes(
considerPreviousManeuver
? previousManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries()
.getTimePointAfter()
: maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
!considerPreviousManeuver,
considerNextManeuver
? nextManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries()
.getTimePointBefore()
: maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(),
!considerNextManeuver)) {
if (fix.getTimePoint().before(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore())) {
++gpsFixesCountFromPreviousManeuver;
} else if (fix.getTimePoint().after(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter())) {
++gpsFixesCountToNextManeuver;
} else {
if (!fix.getTimePoint().before(maneuverCurve.getMainCurveBoundaries().getTimePointBefore())
&& !fix.getTimePoint().after(maneuverCurve.getMainCurveBoundaries().getTimePointAfter())) {
++gpsFixCountWithinMainCurve;
}
++gpsFixCountWithinWholeCurve;
}
}
} finally {
track.unlockAfterRead();
}
ManeuverLoss projectedManeuverLoss = getManeuverLoss(maneuverCurve.getMainCurveBoundaries());
Distance distanceSailedIfNotManeuvering = maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingBefore()
.travel(maneuverCurve.getMainCurveBoundaries().getDuration());
Distance distanceSailedWithinManeuver = track.getDistanceTraveled(
maneuverCurve.getMainCurveBoundaries().getTimePointBefore(),
maneuverCurve.getMainCurveBoundaries().getTimePointAfter());
Duration longestGpsFixIntervalBetweenTwoFixes = track.getLongestIntervalBetweenTwoFixes(
maneuverCurve.getMainCurveBoundaries().getTimePointBefore(),
maneuverCurve.getMainCurveBoundaries().getTimePointAfter());
ManeuverMainCurveWithEstimationData mainCurve = new ManeuverMainCurveWithEstimationDataImpl(
maneuverCurve.getMainCurveBoundaries().getTimePointBefore(),
maneuverCurve.getMainCurveBoundaries().getTimePointAfter(),
maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingBefore(),
maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingAfter(),
maneuverCurve.getMainCurveBoundaries().getDirectionChangeInDegrees(),
stepWithLowestSpeed.getSpeedWithBearing(), stepWithLowestSpeed.getTimePoint(),
stepWithHighestSpeed.getSpeedWithBearing(), stepWithHighestSpeed.getTimePoint(),
maneuverCurve.getMainCurveBoundaries().getTimePoint(),
maneuverCurve.getMainCurveBoundaries().getMaxTurningRateInDegreesPerSecond(), courseAtMaxTurningRate,
distanceSailedWithinManeuver, projectedManeuverLoss.getDistanceSailed(), distanceSailedIfNotManeuvering,
projectedManeuverLoss.getDistanceSailedIfNotManeuvering(),
Math.abs(maneuverCurve.getMainCurveBoundaries().getDirectionChangeInDegrees())
/ maneuverCurve.getMainCurveBoundaries().getDuration().asSeconds(),
gpsFixCountWithinMainCurve, longestGpsFixIntervalBetweenTwoFixes);
projectedManeuverLoss = getManeuverLoss(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries());
distanceSailedIfNotManeuvering = maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries()
.getSpeedWithBearingBefore()
.travel(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDuration());
distanceSailedWithinManeuver = track.getDistanceTraveled(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter());
longestGpsFixIntervalBetweenTwoFixes = track.getLongestIntervalBetweenTwoFixes(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter());
TrackTimeInfo trackTimeInfo = previousManeuverCurve == null || nextManeuverCurve == null ? getTrackTimeInfo()
: null;
Pair<Duration, SpeedWithBearing> durationAndAvgSpeedWithBearingBefore = calculateDurationAndAvgSpeedWithBearingBetweenTimePoints(
previousManeuverCurve == null ? trackTimeInfo.getTrackStartTimePoint()
: previousManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries()
.getTimePointAfter(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore());
Pair<Duration, SpeedWithBearing> durationAndAvgSpeedWithBearingAfter = calculateDurationAndAvgSpeedWithBearingBetweenTimePoints(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(),
nextManeuverCurve == null ? trackTimeInfo.getTrackEndTimePoint()
: nextManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore());
Duration intervalBetweenLastFixOfCurveAndNextFix = Duration.NULL;
GPSFixMoving lastManeuverFix = track.getLastFixAtOrBefore(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter());
if (lastManeuverFix != null) {
GPSFixMoving firstFixAfterLastManeuverFix = track.getFirstFixAfter(lastManeuverFix.getTimePoint());
if (firstFixAfterLastManeuverFix != null) {
intervalBetweenLastFixOfCurveAndNextFix = lastManeuverFix.getTimePoint()
.until(firstFixAfterLastManeuverFix.getTimePoint());
}
}
Duration intervalBetweenFirstFixOfCurveAndPreviousFix = Duration.NULL;
GPSFixMoving firstManeuverFix = track.getFirstFixAtOrAfter(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore());
if (firstManeuverFix != null) {
GPSFixMoving lastFixBeforeFirstManeuverFix = track.getLastFixBefore(firstManeuverFix.getTimePoint());
if (lastFixBeforeFirstManeuverFix != null) {
intervalBetweenFirstFixOfCurveAndPreviousFix = lastFixBeforeFirstManeuverFix.getTimePoint()
.until(firstManeuverFix.getTimePoint());
}
}
ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData curveWithUnstableCourseAndSpeed = new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataImpl(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingBefore(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDirectionChangeInDegrees(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed(),
durationAndAvgSpeedWithBearingBefore.getB(), durationAndAvgSpeedWithBearingBefore.getA(),
gpsFixesCountFromPreviousManeuver, durationAndAvgSpeedWithBearingAfter.getB(),
durationAndAvgSpeedWithBearingAfter.getA(), gpsFixesCountToNextManeuver, distanceSailedWithinManeuver,
projectedManeuverLoss.getDistanceSailed(), distanceSailedIfNotManeuvering,
projectedManeuverLoss.getDistanceSailedIfNotManeuvering(), gpsFixCountWithinWholeCurve,
longestGpsFixIntervalBetweenTwoFixes, intervalBetweenLastFixOfCurveAndNextFix,
intervalBetweenFirstFixOfCurveAndPreviousFix);
TimePoint maneuverTimePoint = maneuverCurve.getMainCurveBoundaries().getTimePoint();
Position maneuverPosition = track.getEstimatedPosition(maneuverTimePoint, /* extrapolate */false);
Wind wind = trackedRace.getWind(maneuverPosition, maneuverTimePoint);
int numberOfJibes = getNumberOfJibes(mainCurve, wind);
int numberOfTacks = getNumberOfTacks(mainCurve, wind);
boolean maneuverStartsByRunningAwayFromWind = (mainCurve.getSpeedWithBearingBefore().getBearing().getDegrees()
- 180) * mainCurve.getDirectionChangeInDegrees() < 0;
Bearing relativeBearingToNextMarkPassingBeforeManeuver = getRelativeBearingToNextMark(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(), maneuverCurve
.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingBefore().getBearing());
Bearing relativeBearingToNextMarkPassingAfterManeuver = getRelativeBearingToNextMark(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(), maneuverCurve
.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter().getBearing());
return new CompleteManeuverCurveWithEstimationDataImpl(maneuverPosition, mainCurve,
curveWithUnstableCourseAndSpeed, wind, numberOfTacks, numberOfJibes,
maneuverStartsByRunningAwayFromWind, relativeBearingToNextMarkPassingBeforeManeuver,
relativeBearingToNextMarkPassingAfterManeuver, maneuverCurve.isMarkPassing());
}
/**
* Calculates the duration and avg speed with avg course based on the competitor's track within the provided time
* range.
*/
private Pair<Duration, SpeedWithBearing> calculateDurationAndAvgSpeedWithBearingBetweenTimePoints(TimePoint from,
TimePoint to) {
Duration duration = from.until(to);
Position fromPosition = track.getEstimatedPosition(from, false);
Position toPosition = track.getEstimatedPosition(to, false);
Distance distance = fromPosition.getDistance(toPosition);
Bearing bearing = fromPosition.getBearingGreatCircle(toPosition);
Speed speed = distance.inTime(Math.abs(duration.asMillis()));
SpeedWithBearing avgSpeedWithBearing = new KnotSpeedWithBearingImpl(speed.getKnots(), bearing);
return new Pair<>(duration, avgSpeedWithBearing);
}
/**
* Gets the relative bearing of the next mark from the boat's position and course at {@code timePoint}. The relative
* bearing is calculated by absolute bearing of next mark from the boat's position minus the boat's course.
*/
private Bearing getRelativeBearingToNextMark(TimePoint timePoint, Bearing boatCourse) {
Bearing result = null;
TrackedLegOfCompetitor legAfter = trackedRace.getTrackedLeg(competitor, timePoint);
if (legAfter != null && legAfter.getLeg().getTo() != null) {
Position nextMarkPosition = trackedRace.getApproximatePosition(legAfter.getLeg().getTo(), timePoint);
Position maneuverEndPosition = track.getEstimatedPosition(timePoint, false);
Bearing absoluteBearing = maneuverEndPosition.getBearingGreatCircle(nextMarkPosition);
result = absoluteBearing.getDifferenceTo(boatCourse);
}
return result;
}
/**
* Gets the number of cases, when the boats bow was headed through the wind coming from behind.
*/
private int getNumberOfJibes(ManeuverCurveBoundaries maneuverBoundaries, Wind wind) {
BearingChangeAnalyzer bearingChangeAnalyzer = BearingChangeAnalyzer.INSTANCE;
int numberOfJibes = wind == null ? 0
: bearingChangeAnalyzer.didPass(maneuverBoundaries.getSpeedWithBearingBefore().getBearing(),
maneuverBoundaries.getDirectionChangeInDegrees(),
maneuverBoundaries.getSpeedWithBearingAfter().getBearing(), wind.getBearing());
return numberOfJibes;
}
/**
* Gets the number of cases, when the boats bow was headed through the wind coming from the front.
*/
private int getNumberOfTacks(ManeuverCurveBoundaries maneuverBoundaries, Wind wind) {
BearingChangeAnalyzer bearingChangeAnalyzer = BearingChangeAnalyzer.INSTANCE;
int numberOfTacks = wind == null ? 0
: bearingChangeAnalyzer.didPass(maneuverBoundaries.getSpeedWithBearingBefore().getBearing(),
maneuverBoundaries.getDirectionChangeInDegrees(),
maneuverBoundaries.getSpeedWithBearingAfter().getBearing(), wind.getFrom());
return numberOfTacks;
}
/**
* Detects maneuver spots performed within a GPS-track of the competitor associated with this
* {@link ManeuverDetector}-instance. See {@link ManeuverDetector} description for more info regarding the detection
@@ -490,64 +110,6 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
return Collections.emptyList();
}
/**
* Gets track's start time point, end time point and the time point of last raw fix.
*
* @return {@code null} when there are no appropriate fixes contained within the analyzed track
*/
public TrackTimeInfo getTrackTimeInfo() {
NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
TimePoint earliestTrackRecord = null;
TimePoint latestRawFixTimePoint = null;
MarkPassing crossedFinishLine = null;
// getLastWaypoint() will wait for a read lock on the course; do this outside the synchronized block to avoid
// deadlocks
final Waypoint lastWaypoint = trackedRace.getRace().getCourse().getLastWaypoint();
if (lastWaypoint != null) {
trackedRace.lockForRead(markPassings);
try {
if (markPassings != null && !markPassings.isEmpty()) {
earliestTrackRecord = markPassings.iterator().next().getTimePoint();
crossedFinishLine = trackedRace.getMarkPassing(competitor, lastWaypoint);
}
} finally {
trackedRace.unlockAfterRead(markPassings);
}
}
if (earliestTrackRecord == null) {
GPSFixMoving firstRawFix = track.getFirstRawFix();
if (firstRawFix != null) {
earliestTrackRecord = firstRawFix.getTimePoint();
}
}
if (earliestTrackRecord != null) {
TimePoint latestTrackRecord;
if (crossedFinishLine != null) {
latestTrackRecord = crossedFinishLine.getTimePoint();
} else {
final GPSFixMoving lastRawFix = track.getLastRawFix();
if (lastRawFix != null) {
latestTrackRecord = lastRawFix.getTimePoint();
latestRawFixTimePoint = latestTrackRecord;
} else {
latestTrackRecord = null;
}
}
if (latestTrackRecord != null) {
if (latestRawFixTimePoint == null) {
final GPSFixMoving lastRawFix = track.getLastRawFix();
if (lastRawFix != null) {
latestRawFixTimePoint = lastRawFix.getTimePoint();
}
}
if (latestRawFixTimePoint != null) {
return new TrackTimeInfo(earliestTrackRecord, latestTrackRecord, latestRawFixTimePoint);
}
}
}
return null;
}
/**
* Detects the maneuver spots with corresponding maneuvers within provided time frame. See step 1ff. in
* {@link ManeuverDetector} description.
@@ -566,13 +128,12 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
* @return an empty list if no maneuver spots are detected for <code>competitor</code> between <code>from</code> and
* <code>to</code>, or else the list of maneuver spots with corresponding maneuvers detected.
*/
protected List<ManeuverSpot> detectManeuvers(TimePoint earliestManeuverStart, TimePoint latestManeuverEnd) {
return detectManeuvers(
trackedRace.approximate(competitor,
trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass()
.getMaximumDistanceForCourseApproximation(),
earliestManeuverStart, latestManeuverEnd),
earliestManeuverStart, latestManeuverEnd);
public List<ManeuverSpot> detectManeuvers(TimePoint earliestManeuverStart, TimePoint latestManeuverEnd) {
ApproximatedFixesCalculator approximatedFixesCalculator = new ApproximatedFixesCalculatorImpl(trackedRace,
competitor);
Iterable<GPSFixMoving> approximatedFixes = approximatedFixesCalculator.approximate(earliestManeuverStart,
latestManeuverEnd);
return detectManeuvers(approximatedFixes, earliestManeuverStart, latestManeuverEnd);
}
/**
@@ -622,13 +183,6 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
}
/**
* Maps the provided {@code courseChangeInDegrees} from {@link Bearing} to {@link NauticalSide}.
*/
protected NauticalSide getDirectionOfCourseChange(double courseChangeInDegrees) {
return courseChangeInDegrees < 0 ? NauticalSide.PORT : NauticalSide.STARBOARD;
}
/**
* Checks whether {@code currentFix} can be grouped together with the previous fixes in order to be regarded as a
* single maneuver spot. For this, the {@code newCourseChangeDirection must match the direction of provided
@@ -661,6 +215,16 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
return true;
}
protected List<Maneuver> getAllManeuversFromManeuverSpots(List<ManeuverSpot> maneuverSpots) {
List<Maneuver> maneuvers = new ArrayList<>();
for (ManeuverSpot maneuverSpot : maneuverSpots) {
for (Maneuver maneuver : maneuverSpot.getManeuvers()) {
maneuvers.add(maneuver);
}
}
return maneuvers;
}
/**
* Determines course change direction around the provided {@code fix} by means of
* {@link #getSpeedWithBearingSteps(TimePoint, TimePoint)}. The course change analysis considers fixes within
@@ -895,14 +459,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
} else {
Pair<ManeuverMainCurveDetailsWithBearingSteps, ManeuverMainCurveDetailsWithBearingSteps> mainCurves = splitManeuverMainCurveByTimePoint(
maneuverMainCurveDetails, firstPenaltyCircleCompletedAt);
if (mainCurves.getA() == null || mainCurves.getB() == null) {
// This should really not happen!
logger.warning(
"Maneuver detection has failed to process penalty circle maneuver correctly, because refinedPenaltyMainCurveDetails computation returned null. Race-Id: "
+ trackedRace.getRace().getId() + ", Competitor: " + competitor.getName()
+ ", Time point before maneuver: "
+ maneuverUnstableCourseAndSpeedBoundaries.getTimePointBefore());
} else {
if (mainCurves.getA() != null && mainCurves.getB() != null) {
maneuversAlreadyAdded = true;
Pair<ManeuverCurveBoundaries, ManeuverCurveBoundaries> maneuverUnstableCourseAndSpeedBoundariesPair = splitManeuverCurveWithStableSpeedAndCourseByTimePoint(
maneuverUnstableCourseAndSpeedBoundaries, mainCurves.getA(), mainCurves.getB(),
@@ -990,19 +547,50 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
SpeedWithBearingStepsIterable speedWithBearingSteps, TimePoint timePoint) {
List<SpeedWithBearingStep> stepsBefore = new ArrayList<>();
List<SpeedWithBearingStep> stepsAfter = new ArrayList<>();
SpeedWithBearingStep lastEntry = null;
for (SpeedWithBearingStep entry : speedWithBearingSteps) {
if (!entry.getTimePoint().after(timePoint)) {
if (stepsBefore.isEmpty()) {
// First bearing step supposed to have 0 as course change as
// it does not have any previous steps with bearings to compute bearing difference.
// If the condition is not met, the existing code which uses ManeuverBearingStep class will break.
entry = new SpeedWithBearingStepImpl(entry.getTimePoint(), entry.getSpeedWithBearing(), 0.0, 0.0);
}
stepsBefore.add(entry);
}
if (!entry.getTimePoint().before(timePoint)) {
if (stepsAfter.isEmpty()) {
entry = new SpeedWithBearingStepImpl(entry.getTimePoint(), entry.getSpeedWithBearing(), 0.0, 0.0);
// First step supposed to have 0 as course change as it does not have any previous steps to compute
// bearing difference. If the condition is not met, the existing code which uses
// SpeedWithBearingStepsIterable class will break.
if (lastEntry != null && lastEntry.getTimePoint().before(timePoint)) {
// If there is not any step located at the splitting time point, we need to retrieve the
// interpolated speed with bearing at time point in order to produce boundary step for both step
// sets at time point. If we will not do it, the course change between the steps split by
// splitting time point will be lost.
SpeedWithBearing speedWithBearing = track.getEstimatedSpeed(timePoint);
if (speedWithBearing != null) {
double courseChangeAngleInDegrees = lastEntry.getSpeedWithBearing().getBearing()
.getDifferenceTo(speedWithBearing.getBearing(),
new DegreeBearingImpl(lastEntry.getCourseChangeInDegrees()))
.getDegrees();
double turningRateInDegreesPerSecond = Math.abs(
courseChangeAngleInDegrees / lastEntry.getTimePoint().until(timePoint).asSeconds());
SpeedWithBearingStep lastStepBefore = new SpeedWithBearingStepImpl(timePoint,
speedWithBearing, courseChangeAngleInDegrees, turningRateInDegreesPerSecond);
stepsBefore.add(lastStepBefore);
SpeedWithBearingStep firstStepAfter = new SpeedWithBearingStepImpl(timePoint,
speedWithBearing, 0.0, 0.0);
stepsAfter.add(firstStepAfter);
courseChangeAngleInDegrees = firstStepAfter.getSpeedWithBearing().getBearing()
.getDifferenceTo(speedWithBearing.getBearing(),
new DegreeBearingImpl(firstStepAfter.getCourseChangeInDegrees()))
.getDegrees();
turningRateInDegreesPerSecond = Math.abs(
courseChangeAngleInDegrees / timePoint.until(entry.getTimePoint()).asSeconds());
entry = new SpeedWithBearingStepImpl(entry.getTimePoint(), entry.getSpeedWithBearing(),
courseChangeAngleInDegrees, turningRateInDegreesPerSecond);
}
}
if (stepsAfter.isEmpty()) {
entry = new SpeedWithBearingStepImpl(entry.getTimePoint(), entry.getSpeedWithBearing(), 0.0,
0.0);
}
}
stepsAfter.add(entry);
}
@@ -1125,7 +713,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
* distance" is compared to the competitor's actual position at that time. This distance is returned as the result
* of this method.
*/
private ManeuverLoss getManeuverLoss(ManeuverCurveBoundaries maneuverBoundaries) {
protected ManeuverLoss getManeuverLoss(ManeuverCurveBoundaries maneuverBoundaries) {
final GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
SpeedWithBearing speedWhenSpeedStartedToDrop = maneuverBoundaries.getSpeedWithBearingBefore();
SpeedWithBearing speedAfterManeuver = maneuverBoundaries.getSpeedWithBearingAfter();
@@ -1162,16 +750,6 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
return approximateManeuverDuration.divide(2);
}
protected List<Maneuver> getAllManeuversFromManeuverSpots(List<ManeuverSpot> maneuverSpots) {
List<Maneuver> maneuvers = new ArrayList<>();
for (ManeuverSpot maneuverSpot : maneuverSpots) {
for (Maneuver maneuver : maneuverSpot.getManeuvers()) {
maneuvers.add(maneuver);
}
}
return maneuvers;
}
/**
* Starting at <code>timePointBeforeManeuver</code>, and assuming that the group of
* <code>approximatedFixesAndCourseChanges</code> contains at least a tack and a jibe, finds the approximated fix's
@@ -1241,8 +819,8 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
* The target course change direction for the main curve to determine
* @return The details of the maneuver main curve
*/
private ManeuverMainCurveDetailsWithBearingSteps computeManeuverMainCurveDetails(TimePoint timePointBeforeManeuver,
TimePoint timePointAfterManeuver, NauticalSide maneuverDirection) {
protected ManeuverMainCurveDetailsWithBearingSteps computeManeuverMainCurveDetails(
TimePoint timePointBeforeManeuver, TimePoint timePointAfterManeuver, NauticalSide maneuverDirection) {
SpeedWithBearingStepsIterable stepsToAnalyze = getSpeedWithBearingSteps(timePointBeforeManeuver,
timePointAfterManeuver);
ManeuverMainCurveDetailsWithBearingSteps maneuverMainCurveDetails = computeManeuverMainCurve(stepsToAnalyze,
@@ -1255,7 +833,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
* the steps, performance costy call to {@link GPSFixTrack#getSpeedWithBearingSteps(TimePoint, TimePoint, Duration)}
* is made.
*/
private SpeedWithBearingStepsIterable getSpeedWithBearingSteps(TimePoint timePointBeforeManeuver,
protected SpeedWithBearingStepsIterable getSpeedWithBearingSteps(TimePoint timePointBeforeManeuver,
TimePoint timePointAfterManeuver) {
SpeedWithBearingStepsIterable stepsToAnalyze = track.getSpeedWithBearingSteps(timePointBeforeManeuver,
timePointAfterManeuver);
@@ -1272,10 +850,10 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
* approximate the beginning time point of the maneuver, the speed maximum is determined throughout forward in time
* iteration of speed steps starting from time point of main curve beginning. From the determined speed maximum, the
* iteration continues until the point, when the bearing changes occur only with a maximum of
* {@value #MAX_ABS_COURSE_CHANGE_IN_DEGREES_PER_SECOND_FOR_STABLE_BEARING_ANALYSIS} degrees per second, which is
* regarded as a stable course. The exiting time point of maneuver is approximated analogously by speed maximum
* determination throughout backward in time iteration of speed steps starting from time of main curve end, followed
* by a search for a point with stable course.
* {@value #MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS} degrees per second, which is regarded as
* a stable course. The exiting time point of maneuver is approximated analogously by speed maximum determination
* throughout backward in time iteration of speed steps starting from time of main curve end, followed by a search
* for a point with stable course.
*
* @param maneuverMainCurveDetails
* The details of the main curve, ideally computed by
@@ -1324,8 +902,8 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
* determined, the course changes get analyzed starting from {@code t'} until {@code (t -}
* {@link BoatClass#getApproximateManeuverDurationInMilliseconds() approx. maneuver duration}{@code )} in order to
* locate the point where the bearing starts to change with a rate of maximal
* {@value #MAX_ABS_COURSE_CHANGE_IN_DEGREES_PER_SECOND_FOR_STABLE_BEARING_ANALYSIS} degrees per second, which is
* regarded as a stable course.
* {@value #MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS} degrees per second, which is regarded as
* a stable course.
*
* @param maneuverMainCurveDetails
* The details of the main curve, ideally computed by
@@ -1356,25 +934,11 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
if (isCourseChangeLimitExceededForCurveExtension(maneuverMainCurveDetails, maneuverStart)) {
maneuverStart = null;
}
TimePoint stableBearingAnalysisUntil = maneuverStart == null ? maneuverMainCurveDetails.getTimePointBefore()
: maneuverStart.getExtensionTimePoint();
// Stable course analysis is considered as not necessary for preparation phase of maneuver because no
// oversteering is usually performed before maneuver
Speed lowestSpeed = maneuverStart == null ? null : maneuverStart.getLowestSpeedWithinExtensionArea();
double courseChangeSinceManeuverMainCurveInDegrees = maneuverStart == null ? 0
: maneuverStart.getCourseChangeInDegreesWithinExtensionArea();
stepsToAnalyze = getSpeedWithBearingStepsWithinTimeRange(stepsToAnalyze, earliestTimePointForSpeedTrendAnalysis,
stableBearingAnalysisUntil);
ManeuverCurveBoundaryExtension stableBearingExtension = findStableBearingWithMaxAbsCourseChangeSpeed(
stepsToAnalyze, true, MAX_ABS_COURSE_CHANGE_IN_DEGREES_PER_SECOND_FOR_STABLE_BEARING_ANALYSIS);
if (stableBearingExtension != null
&& !isCourseChangeLimitExceededForCurveExtension(maneuverMainCurveDetails, stableBearingExtension)) {
maneuverStart = stableBearingExtension;
courseChangeSinceManeuverMainCurveInDegrees += stableBearingExtension
.getCourseChangeInDegreesWithinExtensionArea();
if (lowestSpeed == null
|| lowestSpeed.compareTo(stableBearingExtension.getLowestSpeedWithinExtensionArea()) > 0) {
lowestSpeed = stableBearingExtension.getLowestSpeedWithinExtensionArea();
}
}
return maneuverStart != null
? new ManeuverCurveBoundaryExtension(maneuverStart.getExtensionTimePoint(),
maneuverStart.getSpeedWithBearingAtExtensionTimePoint(),
@@ -1391,10 +955,8 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
if (curveBoundaryExtension == null) {
return false;
}
return curveBoundaryExtension.getCourseChangeInDegreesWithinExtensionArea()
* maneuverMainCurveDetails.getDirectionChangeInDegrees() < 0
&& Math.abs(curveBoundaryExtension
.getCourseChangeInDegreesWithinExtensionArea()) > MAX_COURSE_CHANGE_TOWARD_MANEUVER_OPPOSITE_DIRECTION_FOR_CURVE_EXTENSION_IN_DEGREES;
return Math.abs(curveBoundaryExtension.getCourseChangeInDegreesWithinExtensionArea()) > Math
.abs(curveBoundaryExtension.getCourseChangeInDegreesWithinExtensionArea()) / 3.0;
}
/**
@@ -1409,8 +971,8 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
* determined, the course changes get analyzed starting from {@code t'} until {@code (t +}
* {@link BoatClass#getApproximateManeuverDurationInMilliseconds() approx. maneuver duration} {@code * 3)} in order
* to locate the point where the bearing starts to change with a rate of maximal
* {@value #MAX_ABS_COURSE_CHANGE_IN_DEGREES_PER_SECOND_FOR_STABLE_BEARING_ANALYSIS} degrees per second, which is
* regarded as a stable course.
* {@value #MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS} degrees per second, which is regarded as
* a stable course.
*
* @param maneuverMainCurveDetails
* The details of the main curve, ideally computed by
@@ -1450,7 +1012,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
stepsToAnalyze = getSpeedWithBearingStepsWithinTimeRange(stepsToAnalyze, stableBearingAnalysisFrom,
latestTimePointForSpeedTrendAnalysis);
ManeuverCurveBoundaryExtension stableBearingExtension = findStableBearingWithMaxAbsCourseChangeSpeed(
stepsToAnalyze, false, MAX_ABS_COURSE_CHANGE_IN_DEGREES_PER_SECOND_FOR_STABLE_BEARING_ANALYSIS);
stepsToAnalyze, false, MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS);
if (stableBearingExtension != null
&& !isCourseChangeLimitExceededForCurveExtension(maneuverMainCurveDetails, stableBearingExtension)) {
maneuverEnd = stableBearingExtension;
@@ -1762,11 +1324,4 @@ public class ManeuverDetectorImpl implements ManeuverDetector {
return new SpeedWithBearingStepsIterable(maneuverBearingSteps);
}
/**
* Gets the approximated duration of the maneuver main curve considering the boat class of the competitor.
*/
protected Duration getApproximateManeuverDuration() {
return trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass().getApproximateManeuverDuration();
}
}
@@ -0,0 +1,472 @@
package com.sap.sailing.domain.maneuverdetection.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimationData;
import com.sap.sailing.domain.maneuverdetection.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData;
import com.sap.sailing.domain.maneuverdetection.ManeuverDetector;
import com.sap.sailing.domain.maneuverdetection.ManeuverDetectorWithEstimationDataSupport;
import com.sap.sailing.domain.maneuverdetection.ManeuverMainCurveWithEstimationData;
import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sailing.domain.tracking.CompleteManeuverCurve;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
import com.sap.sailing.domain.tracking.SpeedWithBearingStep;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.impl.CompleteManeuverCurveImpl;
import com.sap.sailing.domain.tracking.impl.ManeuverCurveBoundariesImpl;
import com.sap.sailing.domain.tracking.impl.NonCachingMarkPositionAtTimePointCache;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Distance;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Speed;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.DegreeBearingImpl;
/**
* A decorator which adds support for management of estimation data for wind estimation to an existing maneuver detector
* implementation.
*
* @author Vladislav Chumak (D069712)
* @see ManeuverDetector
*
*/
public class ManeuverDetectorWithEstimationDataSupportDecoratorImpl
implements ManeuverDetectorWithEstimationDataSupport {
private final ManeuverDetectorImpl maneuverDetector;
private final PolarDataService polarDataService;
public ManeuverDetectorWithEstimationDataSupportDecoratorImpl(ManeuverDetectorImpl maneuverDetector,
PolarDataService polarDataService) {
this.maneuverDetector = maneuverDetector;
this.polarDataService = polarDataService;
}
@Override
public List<Maneuver> detectManeuvers() {
return maneuverDetector.detectManeuvers();
}
@Override
public List<Maneuver> detectManeuvers(Iterable<CompleteManeuverCurve> maneuverCurves) {
List<Maneuver> maneuvers = new ArrayList<>();
for (CompleteManeuverCurve maneuverCurve : maneuverCurves) {
TimePoint maneuverTimePoint = maneuverCurve.getMainCurveBoundaries().getTimePoint();
Position maneuverPosition = maneuverDetector.track.getEstimatedPosition(maneuverTimePoint,
/* extrapolate */false);
Wind wind = maneuverDetector.trackedRace.getWind(maneuverPosition, maneuverTimePoint);
maneuvers
.addAll(maneuverDetector.determineManeuversFromManeuverCurve(maneuverCurve.getMainCurveBoundaries(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries(), wind,
maneuverCurve.getMarkPassing()));
}
return maneuvers;
}
@Override
public List<CompleteManeuverCurve> detectCompleteManeuverCurves() {
List<ManeuverSpot> maneuverSpots = maneuverDetector.detectManeuverSpots();
return maneuverSpots.stream().filter(maneuverSpot -> maneuverSpot.getManeuverCurve() != null)
.map(maneuverSpot -> maneuverSpot.getManeuverCurve()).collect(Collectors.toList());
}
@Override
public List<CompleteManeuverCurve> getCompleteManeuverCurves(Iterable<Maneuver> maneuvers) {
List<CompleteManeuverCurve> result = new ArrayList<>();
CompleteManeuverCurve curveToAdd = null;
boolean previousManeuverCouldBelongToSameCurve = false;
Maneuver previousManeuver = null;
for (Maneuver maneuver : maneuvers) {
boolean maneuverCouldBelongToSameCurve = maneuver.getType() == ManeuverType.PENALTY_CIRCLE
|| maneuver.isMarkPassing()
&& (maneuver.getType() == ManeuverType.TACK || maneuver.getType() == ManeuverType.JIBE);
if (previousManeuverCouldBelongToSameCurve && maneuverCouldBelongToSameCurve
&& previousManeuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter()
.equals(maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore())
&& previousManeuver.getToSide() == maneuver.getToSide()) {
curveToAdd = extendCompleteManeuverCurveWithManeuver(curveToAdd, maneuver);
} else {
if (curveToAdd != null) {
result.add(curveToAdd);
}
curveToAdd = convertManeuverToCompleteManeuverCurve(maneuver);
}
previousManeuver = maneuver;
previousManeuverCouldBelongToSameCurve = maneuverCouldBelongToSameCurve;
}
if (curveToAdd != null) {
result.add(curveToAdd);
}
return result;
}
/**
* Converts the provided maneuver into {@link CompleteManeuverCurve}. The boundaries of provided maneuver are reused
* for the resulting complete maneuver curve.
*
* @see CompleteManeuverCurve
* @see Maneuver
*/
private CompleteManeuverCurve convertManeuverToCompleteManeuverCurve(Maneuver maneuver) {
ManeuverMainCurveDetailsWithBearingSteps mainCurveBoundaries = new ManeuverMainCurveDetailsWithBearingSteps(
maneuver.getMainCurveBoundaries().getTimePointBefore(),
maneuver.getMainCurveBoundaries().getTimePointAfter(), maneuver.getTimePoint(),
maneuver.getMainCurveBoundaries().getSpeedWithBearingBefore(),
maneuver.getMainCurveBoundaries().getSpeedWithBearingAfter(),
maneuver.getMainCurveBoundaries().getDirectionChangeInDegrees(),
maneuver.getMaxTurningRateInDegreesPerSecond(), maneuver.getMainCurveBoundaries().getLowestSpeed(),
maneuverDetector.getSpeedWithBearingSteps(maneuver.getMainCurveBoundaries().getTimePointBefore(),
maneuver.getMainCurveBoundaries().getTimePointAfter()));
return new CompleteManeuverCurveImpl(mainCurveBoundaries,
maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries(), maneuver.getMarkPassing());
}
/**
* Extends the end of provided maneuver curve with the end of provided maneuver. For this, the curve boundaries with
* unstable course and speed are merged by appending, whereas the maneuver main curve gets recalculated completely
* from scratch. The additional attributes such as, direction change and lowest speed get adjusted accordingly.
*/
private CompleteManeuverCurve extendCompleteManeuverCurveWithManeuver(CompleteManeuverCurve maneuverCurve,
Maneuver maneuver) {
ManeuverMainCurveDetailsWithBearingSteps mainCurveDetails = maneuverDetector.computeManeuverMainCurveDetails(
maneuverCurve.getMainCurveBoundaries().getTimePointBefore(),
maneuver.getMainCurveBoundaries().getTimePointAfter(), maneuver.getToSide());
if (mainCurveDetails == null) {
return maneuverCurve;
}
ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries = new ManeuverCurveBoundariesImpl(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingBefore(),
maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDirectionChangeInDegrees()
+ maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDirectionChangeInDegrees(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed()
.compareTo(maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed()) > 0
? maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed()
: maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed());
return new CompleteManeuverCurveImpl(mainCurveDetails, maneuverCurveWithStableSpeedAndCourseBoundaries,
maneuverCurve.getMarkPassing() == null ? maneuver.getMarkPassing() : maneuverCurve.getMarkPassing());
}
@Override
public List<CompleteManeuverCurveWithEstimationData> getCompleteManeuverCurvesWithEstimationData(
Iterable<CompleteManeuverCurve> maneuverCurves) {
List<CompleteManeuverCurveWithEstimationData> result = new ArrayList<>();
CompleteManeuverCurve previousManeuverCurve = null;
CompleteManeuverCurve currentManeuverCurve = null;
for (CompleteManeuverCurve nextManeuverCurve : maneuverCurves) {
if (currentManeuverCurve != null) {
CompleteManeuverCurveWithEstimationData maneuverCurveWithEstimationData = calculateCompleteManeuverCurveWithEstimationData(
currentManeuverCurve, previousManeuverCurve, nextManeuverCurve);
result.add(maneuverCurveWithEstimationData);
}
previousManeuverCurve = currentManeuverCurve;
currentManeuverCurve = nextManeuverCurve;
}
if (currentManeuverCurve != null) {
CompleteManeuverCurveWithEstimationData maneuverCurveWithEstimationData = calculateCompleteManeuverCurveWithEstimationData(
currentManeuverCurve, previousManeuverCurve, null);
result.add(maneuverCurveWithEstimationData);
}
return result;
}
/**
* Calculates a {@link CompleteManeuverCurveWithEstimationData}-instance for the provided {@code maneuverCurve}. The
* computation of additional information required by {@link CompleteManeuverCurveWithEstimationData} is regarded as
* computationally-intensive.
*/
private CompleteManeuverCurveWithEstimationData calculateCompleteManeuverCurveWithEstimationData(
CompleteManeuverCurve maneuverCurve, CompleteManeuverCurve previousManeuverCurve,
CompleteManeuverCurve nextManeuverCurve) {
Bearing courseAtMaxTurningRate = null;
SpeedWithBearingStep stepWithLowestSpeed = null;
SpeedWithBearingStep stepWithHighestSpeed = null;
SpeedWithBearingStep stepWithMaxTurningRate = null;
SpeedWithBearingStep previousStep = null;
for (SpeedWithBearingStep step : maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingSteps()) {
if (stepWithLowestSpeed == null
|| stepWithLowestSpeed.getSpeedWithBearing().compareTo(step.getSpeedWithBearing()) > 0) {
stepWithLowestSpeed = step;
}
if (stepWithHighestSpeed == null
|| stepWithHighestSpeed.getSpeedWithBearing().compareTo(step.getSpeedWithBearing()) < 0) {
stepWithHighestSpeed = step;
}
if (previousStep != null && (stepWithMaxTurningRate == null || stepWithMaxTurningRate
.getTurningRateInDegreesPerSecond() < step.getTurningRateInDegreesPerSecond())) {
stepWithMaxTurningRate = step;
courseAtMaxTurningRate = previousStep.getSpeedWithBearing().getBearing()
.add(new DegreeBearingImpl(step.getCourseChangeInDegrees() / 2));
}
previousStep = step;
}
int gpsFixCountWithinMainCurve = 0;
int gpsFixCountWithinWholeCurve = 0;
int gpsFixesCountFromPreviousManeuver = 0;
int gpsFixesCountToNextManeuver = 0;
try {
maneuverDetector.track.lockForRead();
boolean considerPreviousManeuver = previousManeuverCurve != null && previousManeuverCurve
.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter()
.before(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore());
boolean considerNextManeuver = nextManeuverCurve != null && nextManeuverCurve
.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore()
.after(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter());
for (GPSFixMoving fix : maneuverDetector.track.getFixes(
considerPreviousManeuver
? previousManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries()
.getTimePointAfter()
: maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
!considerPreviousManeuver,
considerNextManeuver
? nextManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries()
.getTimePointBefore()
: maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(),
!considerNextManeuver)) {
if (fix.getTimePoint().before(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore())) {
++gpsFixesCountFromPreviousManeuver;
} else if (fix.getTimePoint().after(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter())) {
++gpsFixesCountToNextManeuver;
} else {
if (!fix.getTimePoint().before(maneuverCurve.getMainCurveBoundaries().getTimePointBefore())
&& !fix.getTimePoint().after(maneuverCurve.getMainCurveBoundaries().getTimePointAfter())) {
++gpsFixCountWithinMainCurve;
}
++gpsFixCountWithinWholeCurve;
}
}
} finally {
maneuverDetector.track.unlockAfterRead();
}
ManeuverLoss projectedManeuverLoss = maneuverDetector.getManeuverLoss(maneuverCurve.getMainCurveBoundaries());
Distance distanceSailedIfNotManeuvering = maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingBefore()
.travel(maneuverCurve.getMainCurveBoundaries().getDuration());
Distance distanceSailedWithinManeuver = maneuverDetector.track.getDistanceTraveled(
maneuverCurve.getMainCurveBoundaries().getTimePointBefore(),
maneuverCurve.getMainCurveBoundaries().getTimePointAfter());
Duration longestGpsFixIntervalBetweenTwoFixes = maneuverDetector.track.getLongestIntervalBetweenTwoFixes(
maneuverCurve.getMainCurveBoundaries().getTimePointBefore(),
maneuverCurve.getMainCurveBoundaries().getTimePointAfter());
ManeuverMainCurveWithEstimationData mainCurve = new ManeuverMainCurveWithEstimationDataImpl(
maneuverCurve.getMainCurveBoundaries().getTimePointBefore(),
maneuverCurve.getMainCurveBoundaries().getTimePointAfter(),
maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingBefore(),
maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingAfter(),
maneuverCurve.getMainCurveBoundaries().getDirectionChangeInDegrees(),
stepWithLowestSpeed.getSpeedWithBearing(), stepWithLowestSpeed.getTimePoint(),
stepWithHighestSpeed.getSpeedWithBearing(), stepWithHighestSpeed.getTimePoint(),
maneuverCurve.getMainCurveBoundaries().getTimePoint(),
maneuverCurve.getMainCurveBoundaries().getMaxTurningRateInDegreesPerSecond(), courseAtMaxTurningRate,
distanceSailedWithinManeuver, projectedManeuverLoss.getDistanceSailed(), distanceSailedIfNotManeuvering,
projectedManeuverLoss.getDistanceSailedIfNotManeuvering(),
Math.abs(maneuverCurve.getMainCurveBoundaries().getDirectionChangeInDegrees())
/ maneuverCurve.getMainCurveBoundaries().getDuration().asSeconds(),
gpsFixCountWithinMainCurve, longestGpsFixIntervalBetweenTwoFixes);
projectedManeuverLoss = maneuverDetector
.getManeuverLoss(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries());
distanceSailedIfNotManeuvering = maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries()
.getSpeedWithBearingBefore()
.travel(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDuration());
distanceSailedWithinManeuver = maneuverDetector.track.getDistanceTraveled(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter());
longestGpsFixIntervalBetweenTwoFixes = maneuverDetector.track.getLongestIntervalBetweenTwoFixes(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter());
TrackTimeInfo trackTimeInfo = previousManeuverCurve == null || nextManeuverCurve == null
? maneuverDetector.getTrackTimeInfo() : null;
Pair<Duration, SpeedWithBearing> durationAndAvgSpeedWithBearingBefore = calculateDurationAndAvgSpeedWithBearingBetweenTimePoints(
previousManeuverCurve == null ? trackTimeInfo.getTrackStartTimePoint()
: previousManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries()
.getTimePointAfter(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore());
Pair<Duration, SpeedWithBearing> durationAndAvgSpeedWithBearingAfter = calculateDurationAndAvgSpeedWithBearingBetweenTimePoints(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(),
nextManeuverCurve == null ? trackTimeInfo.getTrackEndTimePoint()
: nextManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore());
Duration intervalBetweenLastFixOfCurveAndNextFix = Duration.NULL;
GPSFixMoving lastManeuverFix = maneuverDetector.track.getLastFixAtOrBefore(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter());
if (lastManeuverFix != null) {
GPSFixMoving firstFixAfterLastManeuverFix = maneuverDetector.track
.getFirstFixAfter(lastManeuverFix.getTimePoint());
if (firstFixAfterLastManeuverFix != null) {
intervalBetweenLastFixOfCurveAndNextFix = lastManeuverFix.getTimePoint()
.until(firstFixAfterLastManeuverFix.getTimePoint());
}
}
Duration intervalBetweenFirstFixOfCurveAndPreviousFix = Duration.NULL;
GPSFixMoving firstManeuverFix = maneuverDetector.track.getFirstFixAtOrAfter(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore());
if (firstManeuverFix != null) {
GPSFixMoving lastFixBeforeFirstManeuverFix = maneuverDetector.track
.getLastFixBefore(firstManeuverFix.getTimePoint());
if (lastFixBeforeFirstManeuverFix != null) {
intervalBetweenFirstFixOfCurveAndPreviousFix = lastFixBeforeFirstManeuverFix.getTimePoint()
.until(firstManeuverFix.getTimePoint());
}
}
ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData curveWithUnstableCourseAndSpeed = new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataImpl(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingBefore(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDirectionChangeInDegrees(),
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed(),
durationAndAvgSpeedWithBearingBefore.getB(), durationAndAvgSpeedWithBearingBefore.getA(),
gpsFixesCountFromPreviousManeuver, durationAndAvgSpeedWithBearingAfter.getB(),
durationAndAvgSpeedWithBearingAfter.getA(), gpsFixesCountToNextManeuver, distanceSailedWithinManeuver,
projectedManeuverLoss.getDistanceSailed(), distanceSailedIfNotManeuvering,
projectedManeuverLoss.getDistanceSailedIfNotManeuvering(), gpsFixCountWithinWholeCurve,
longestGpsFixIntervalBetweenTwoFixes, intervalBetweenLastFixOfCurveAndNextFix,
intervalBetweenFirstFixOfCurveAndPreviousFix);
TimePoint maneuverTimePoint = maneuverCurve.getMainCurveBoundaries().getTimePoint();
Position maneuverPosition = maneuverDetector.track.getEstimatedPosition(maneuverTimePoint,
/* extrapolate */false);
Wind wind = maneuverDetector.trackedRace.getWind(maneuverPosition, maneuverTimePoint);
int numberOfJibes = maneuverDetector.getNumberOfJibes(mainCurve, wind);
int numberOfTacks = maneuverDetector.getNumberOfTacks(mainCurve, wind);
boolean maneuverStartsByRunningAwayFromWind = (mainCurve.getSpeedWithBearingBefore().getBearing().getDegrees()
- 180) * mainCurve.getDirectionChangeInDegrees() < 0;
Bearing relativeBearingToNextMarkPassingBeforeManeuver = getRelativeBearingToNextMark(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(), maneuverCurve
.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingBefore().getBearing());
Bearing relativeBearingToNextMarkPassingAfterManeuver = getRelativeBearingToNextMark(
maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(), maneuverCurve
.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter().getBearing());
BoatClass boatClass = maneuverDetector.trackedRace.getRace().getBoatOfCompetitor(maneuverDetector.competitor)
.getBoatClass();
Double deviationFromTackAngle = null;
Double deviationFromJibeAngle = null;
Speed boatSpeed = curveWithUnstableCourseAndSpeed.getSpeedWithBearingBefore()
.compareTo(curveWithUnstableCourseAndSpeed.getSpeedWithBearingAfter()) < 0
? curveWithUnstableCourseAndSpeed.getSpeedWithBearingBefore()
: curveWithUnstableCourseAndSpeed.getSpeedWithBearingAfter();
if (polarDataService.getAllBoatClassesWithPolarSheetsAvailable().contains(boatClass)) {
SpeedWithBearingWithConfidence<Void> closestTackTwa = polarDataService.getClosestTwaTws(ManeuverType.TACK,
boatSpeed, curveWithUnstableCourseAndSpeed.getDirectionChangeInDegrees(), boatClass);
SpeedWithBearingWithConfidence<Void> closestJibeTwa = polarDataService.getClosestTwaTws(ManeuverType.JIBE,
boatSpeed, curveWithUnstableCourseAndSpeed.getDirectionChangeInDegrees(), boatClass);
if (closestTackTwa != null) {
deviationFromTackAngle = polarDataService.getManeuverAngleInDegreesFromTwa(
closestTackTwa.getObject().getBearing().getDegrees(), ManeuverType.TACK);
}
if (closestJibeTwa != null) {
deviationFromJibeAngle = polarDataService.getManeuverAngleInDegreesFromTwa(
closestJibeTwa.getObject().getBearing().getDegrees(), ManeuverType.JIBE);
}
}
Distance closestDistanceToMark = getClosestDistanceToMark(mainCurve.getTimePointOfMaxTurningRate());
return new CompleteManeuverCurveWithEstimationDataImpl(maneuverPosition, mainCurve,
curveWithUnstableCourseAndSpeed, wind, numberOfTacks, numberOfJibes,
maneuverStartsByRunningAwayFromWind, relativeBearingToNextMarkPassingBeforeManeuver,
relativeBearingToNextMarkPassingAfterManeuver, maneuverCurve.isMarkPassing(), closestDistanceToMark,
deviationFromTackAngle, deviationFromJibeAngle);
}
public Distance getClosestDistanceToMark(TimePoint timePoint) {
NonCachingMarkPositionAtTimePointCache markPositionAtTimePointCache = new NonCachingMarkPositionAtTimePointCache(
maneuverDetector.trackedRace, timePoint);
Distance result = null;
TrackedLegOfCompetitor legAfter = maneuverDetector.trackedRace.getTrackedLeg(maneuverDetector.competitor,
timePoint);
if (legAfter != null) {
Position maneuverPosition = maneuverDetector.track.getEstimatedPosition(timePoint, false);
if (legAfter.getLeg().getTo() != null) {
result = getClosestDistanceToMarkInternal(markPositionAtTimePointCache, legAfter.getLeg().getTo(),
maneuverPosition);
}
if (legAfter.getLeg().getFrom() != null) {
Distance distance = getClosestDistanceToMarkInternal(markPositionAtTimePointCache,
legAfter.getLeg().getFrom(), maneuverPosition);
if (result == null || distance != null && distance.compareTo(result) < 0) {
result = distance;
}
}
}
return result;
}
private Distance getClosestDistanceToMarkInternal(
NonCachingMarkPositionAtTimePointCache markPositionAtTimePointCache, Waypoint waypoint,
Position maneuverPosition) {
Distance result = null;
for (Mark mark : waypoint.getMarks()) {
Position markPosition = markPositionAtTimePointCache.getEstimatedPosition(mark);
Distance distance = markPosition.getDistance(maneuverPosition);
if (result == null || distance.compareTo(result) < 0) {
result = distance;
}
}
return result;
}
/**
* Calculates the duration and avg speed with avg course based on the competitor's track within the provided time
* range.
*/
private Pair<Duration, SpeedWithBearing> calculateDurationAndAvgSpeedWithBearingBetweenTimePoints(TimePoint from,
TimePoint to) {
Duration duration = from.until(to);
Position fromPosition = maneuverDetector.track.getEstimatedPosition(from, false);
Position toPosition = maneuverDetector.track.getEstimatedPosition(to, false);
Distance distance = fromPosition.getDistance(toPosition);
Bearing bearing = fromPosition.getBearingGreatCircle(toPosition);
Speed speed = distance.inTime(Math.abs(duration.asMillis()));
SpeedWithBearing avgSpeedWithBearing = new KnotSpeedWithBearingImpl(speed.getKnots(), bearing);
return new Pair<>(duration, avgSpeedWithBearing);
}
/**
* Gets the relative bearing of the next mark from the boat's position and course at {@code timePoint}. The relative
* bearing is calculated by absolute bearing of next mark from the boat's position minus the boat's course.
*/
public Bearing getRelativeBearingToNextMark(TimePoint timePoint, Bearing boatCourse) {
NonCachingMarkPositionAtTimePointCache markPositionAtTimePointCache = new NonCachingMarkPositionAtTimePointCache(
maneuverDetector.trackedRace, timePoint);
Bearing result = null;
TrackedLegOfCompetitor legAfter = maneuverDetector.trackedRace.getTrackedLeg(maneuverDetector.competitor,
timePoint);
if (legAfter != null && legAfter.getLeg().getTo() != null) {
Position maneuverEndPosition = maneuverDetector.track.getEstimatedPosition(timePoint, false);
Waypoint nextWaypoint = legAfter.getLeg().getTo();
for (Mark mark : nextWaypoint.getMarks()) {
Position nextMarkPosition = markPositionAtTimePointCache.getEstimatedPosition(mark);
Bearing absoluteBearing = maneuverEndPosition.getBearingGreatCircle(nextMarkPosition);
Bearing resultCandidate = absoluteBearing.getDifferenceTo(boatCourse);
if (result == null) {
result = resultCandidate;
} else if (Math.signum(result.getDegrees()) != Math.signum(resultCandidate.getDegrees())) {
result = new DegreeBearingImpl(0);
break;
} else if (Math.abs(result.getDegrees()) > Math.abs(resultCandidate.getDegrees())) {
result = resultCandidate;
}
}
}
return result;
}
}
@@ -1,5 +1,6 @@
package com.sap.sailing.domain.polars;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
@@ -181,4 +182,11 @@ public interface PolarDataService {
* with this service, then lets the {@code consumer} accept that domain factory.
*/
void runWithDomainFactory(Consumer<DomainFactory> consumer) throws InterruptedException;
Map<BoatClass, Long> getFixCountPerBoatClass();
SpeedWithBearingWithConfidence<Void> getClosestTwaTws(ManeuverType type, Speed speedAtManeuverStart,
double courseChangeDeg, BoatClass boatClass);
double getManeuverAngleInDegreesFromTwa(double twa, ManeuverType maneuverType);
}
@@ -36,7 +36,6 @@ public class ShardingContext {
*/
public static ShardingType identifyAndSetShardingConstraint(String shardingInfo) {
if (shardingInfo == null || shardingInfo.isEmpty()) {
logger.warning("Empty sharding constraint");
return null;
}
ThreadLocal<String> identifiedShardingHolder = null;
@@ -175,11 +175,13 @@ public interface GPSFixTrack<ItemType, FixType extends GPSFix> extends MappedTra
void resumeValidityCaching();
/**
* Gets a list of bearings between the provided time range (inclusive the boundaries). The bearings are retrieved by
* means of {@link GPSFixTrack#getEstimatedSpeed(TimePoint)}. The first and last bearing steps will be always
* sampled at provided {@code fromTimePoint} and {@code toTimePoint}, whereas the steps between are sampled at time
* points of non-raw GPS fixes. The idea of this concept is to produce at least two bearing steps as result, in
* order to provide the caller at least a {@code totalCourseChangeAngleInDegrees > 0} between the given time range.
* Gets a list of speed with bearing steps considering the provided time range. The bearings are retrieved by means
* of {@link GPSFixTrack#getEstimatedSpeed(TimePoint)}. The steps are sampled at time points of non-raw GPS fixes.
* When there is no non-raw fix contained at {@code fromTimePoint}, the time point of the first step will be the
* time point of the first non-raw fix before {@code fromTimePoint}. Analogously, when there is no non-raw fix
* contained at {@code toTimePoint}, the last step's time point will be the first non-raw fix after
* {@code toTimePoint}. The idea of this concept is to produce at least two steps as part of the result, in order to
* provide the caller at least a {@code |totalCourseChangeAngleInDegrees > 0|} between the given time range.
*
* @param fromTimePoint
* The from time point (inclusive) for resulting bearing steps
@@ -174,5 +174,5 @@ public interface Maneuver extends GPSFix {
*/
@Statistic(messageKey = "AvgTurningRateInDegreesPerSecond", resultDecimals = 4)
double getAvgTurningRateInDegreesPerSecond();
}
@@ -170,7 +170,11 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
}
}
private <T> TimeRangeCache<T> createTimeRangeCache(ItemType trackedItem, final String cacheName) {
/**
* This method is protected in order to let test classes use other TimeRangeCache specializations
* that provide specific test support.
*/
protected <T> TimeRangeCache<T> createTimeRangeCache(ItemType trackedItem, final String cacheName) {
return new TimeRangeCache<>(cacheName+" for "+trackedItem);
}
@@ -1171,20 +1171,13 @@ public abstract class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends
Bearing lastCourse = null;
TimePoint lastTimePoint = null;
double lastCourseChangeAngleInDegrees = 0;
TimePoint fixTimePointAfterToTimePoint = null;
try {
lockForRead();
TimePoint timePoint = fromTimePoint;
for (Iterator<FixType> iterator = getFixesIterator(fromTimePoint, false); iterator
.hasNext(); timePoint = iterator.next().getTimePoint()) {
if (timePoint == null) {
continue;
}
if (timePoint.after(toTimePoint)) {
fixTimePointAfterToTimePoint = timePoint;
timePoint = toTimePoint;
}
SpeedWithBearing estimatedSpeed = getEstimatedSpeed(timePoint);
FixType firstFix = getLastFixAtOrBefore(fromTimePoint);
TimePoint currentTimePoint = firstFix == null ? fromTimePoint : firstFix.getTimePoint();
for (Iterator<FixType> iterator = getFixesIterator(currentTimePoint, false); iterator
.hasNext(); currentTimePoint = iterator.next().getTimePoint()) {
SpeedWithBearing estimatedSpeed = getEstimatedSpeed(currentTimePoint);
if (estimatedSpeed != null) {
Bearing course = estimatedSpeed.getBearing();
/*
@@ -1195,44 +1188,20 @@ public abstract class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends
double courseChangeAngleInDegrees = lastCourse == null ? 0
: lastCourse.getDifferenceTo(course, new DegreeBearingImpl(lastCourseChangeAngleInDegrees))
.getDegrees();
double turningRateInDegreesPerSecond = lastTimePoint == null ? 0
: Math.abs(courseChangeAngleInDegrees
/ lastTimePoint.until(currentTimePoint).asSeconds());
// Fix distorted turning rate due to inappropriate interpolation of getEstimatedSpeed() at first
// and last step
double courseChangeInDegreesForTurningRateCalculation = courseChangeAngleInDegrees;
Duration durationBetweenStepsForTurningRateCalculation = lastTimePoint == null ? null
: lastTimePoint.until(timePoint);
if (fromTimePoint.equals(lastTimePoint)) {
FixType firstFix = getLastFixAtOrBefore(fromTimePoint);
if (firstFix != null && !firstFix.getTimePoint().equals(fromTimePoint)) {
SpeedWithBearing firstFixEstimatedSpeed = getEstimatedSpeed(firstFix.getTimePoint());
if (firstFixEstimatedSpeed != null) {
durationBetweenStepsForTurningRateCalculation = firstFix.getTimePoint()
.until(timePoint);
courseChangeInDegreesForTurningRateCalculation = courseChangeAngleInDegrees
+ firstFixEstimatedSpeed.getBearing().getDifferenceTo(lastCourse).getDegrees();
}
}
} else if (fixTimePointAfterToTimePoint != null && lastCourse != null) {
SpeedWithBearing lastFixEstimatedSpeed = getEstimatedSpeed(fixTimePointAfterToTimePoint);
if (lastFixEstimatedSpeed != null) {
durationBetweenStepsForTurningRateCalculation = lastTimePoint == null ? null
: lastTimePoint.until(fixTimePointAfterToTimePoint);
courseChangeInDegreesForTurningRateCalculation = courseChangeAngleInDegrees + estimatedSpeed
.getBearing().getDifferenceTo(lastFixEstimatedSpeed.getBearing()).getDegrees();
}
}
double turningRateInDegreesPerSecond = durationBetweenStepsForTurningRateCalculation == null ? 0
: Math.abs(courseChangeInDegreesForTurningRateCalculation
/ durationBetweenStepsForTurningRateCalculation.asSeconds());
speedWithBearingSteps.add(new SpeedWithBearingStepImpl(timePoint, estimatedSpeed,
speedWithBearingSteps.add(new SpeedWithBearingStepImpl(currentTimePoint, estimatedSpeed,
courseChangeAngleInDegrees, turningRateInDegreesPerSecond));
if (currentTimePoint.after(toTimePoint)) {
break;
}
lastCourse = course;
lastCourseChangeAngleInDegrees = courseChangeAngleInDegrees;
lastTimePoint = timePoint;
lastTimePoint = currentTimePoint;
}
if (!timePoint.before(toTimePoint)) {
if (!currentTimePoint.before(toTimePoint)) {
break;
}
}
@@ -0,0 +1,37 @@
package com.sap.sailing.domain.tracking.impl;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
import com.sap.sse.common.TimePoint;
/**
* Maneuver implementation which were detected on tracks with extremely low GPS-sampling rate. The implementation
* suggests to ignore the following attributes:
* <ul>
* <li>Maneuver loss</li>
* <li>Max. and Avg. Turning rate</li>
* <li>Time point before and after of all maneuver boundaries</li>
* <li>Lowest speed within all maneuver boundaries</li>
* </ul>
* However, to provide capability with existing code, the attributes are filled with values.
*
* @author Vladislav Chumak (D069712)
*
*/
public class ManeuverWithCoarseGrainedBoundariesImpl extends ManeuverImpl {
private static final long serialVersionUID = -381990329349665889L;
public ManeuverWithCoarseGrainedBoundariesImpl(ManeuverType type, Tack newTack, Position position,
TimePoint timePoint, ManeuverCurveBoundaries maneuverBoundaries) {
super(type, newTack, position, null, timePoint, maneuverBoundaries, maneuverBoundaries, Math.abs(maneuverBoundaries.getDirectionChangeInDegrees()), null);
}
@Override
public ManeuverCurveBoundaries getManeuverBoundaries() {
return getMainCurveBoundaries();
}
}
@@ -113,6 +113,7 @@ import com.sap.sailing.domain.maneuverdetection.IncrementalManeuverDetector;
import com.sap.sailing.domain.maneuverdetection.ManeuverDetector;
import com.sap.sailing.domain.maneuverdetection.ShortTimeAfterLastHitCache;
import com.sap.sailing.domain.maneuverdetection.impl.IncrementalManeuverDetectorImpl;
import com.sap.sailing.domain.maneuverdetection.impl.LowGPSSamplingRateManeuverDetectorImpl;
import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.domain.polars.PolarDataService;
@@ -701,10 +702,21 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
@Override
public List<Maneuver> computeCacheUpdate(Competitor competitor, EmptyUpdateInterval updateInterval)
throws NoWindException {
IncrementalManeuverDetector maneuverDetector = maneuverDetectorPerCompetitorCache
.getValue(competitor);
List<Maneuver> maneuvers = computeManeuvers(competitor, maneuverDetector);
return maneuvers;
Duration averageIntervalBetweenRawFixes = getTrack(competitor)
.getAverageIntervalBetweenRawFixes();
if (averageIntervalBetweenRawFixes != null) {
ManeuverDetector maneuverDetector;
if (averageIntervalBetweenRawFixes.asSeconds() >= 30) {
maneuverDetector = new LowGPSSamplingRateManeuverDetectorImpl(TrackedRaceImpl.this,
competitor);
} else {
maneuverDetector = maneuverDetectorPerCompetitorCache.getValue(competitor);
}
List<Maneuver> maneuvers = computeManeuvers(competitor, maneuverDetector);
return maneuvers;
} else {
return Collections.emptyList();
}
}
}, /* nameForLocks */"Maneuver cache for race " + getRace().getName());
}
@@ -2,6 +2,10 @@ package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Date;
import com.google.gwt.dom.client.Style.FontStyle;
import com.google.gwt.dom.client.Style.FontWeight;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
@@ -14,44 +18,36 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog;
public class SetStartTimeReceivedDialog extends DataEntryDialogWithDateTimeBox<Date> {
private final StringMessages stringMessages;
private DateAndTimeInput timeBox;
public SetStartTimeReceivedDialog(StringMessages stringMessages, DataEntryDialog.DialogCallback<Date> callback) {
super(stringMessages.setStartTimeReceived(), stringMessages.setStartTimeReceivedDescription(), stringMessages.ok(), stringMessages.cancel(), new ReceivedStartTimeDialog(stringMessages), callback);
super(stringMessages.setStartTimeReceived(), stringMessages.setStartTimeReceivedDescription(),
stringMessages.ok(), stringMessages.cancel(), valueToValidate -> null, callback);
this.stringMessages = stringMessages;
}
@Override
protected Widget getAdditionalWidget() {
Grid content = new Grid(1, 2);
Label timeBoxLabel = new Label(stringMessages.startTime() + ":");
final FlowPanel panel = new FlowPanel();
final Label noticeLabel = new Label(stringMessages.setStartTimeReceivedNotice());
noticeLabel.getElement().getStyle().setFontWeight(FontWeight.BOLD);
noticeLabel.getElement().getStyle().setFontStyle(FontStyle.ITALIC);
panel.add(noticeLabel);
final Grid content = new Grid(1, 3);
final Label timeBoxLabel = new Label(stringMessages.startTime() + ":");
content.setWidget(0, 0, timeBoxLabel);
timeBox = createDateTimeBox(new Date(), Accuracy.SECONDS);
timeBox = createDateTimeBox(null, Accuracy.SECONDS);
content.setWidget(0, 1, timeBox);
return content;
final Button setNowButton = new Button(stringMessages.now());
setNowButton.addClickHandler(event -> timeBox.setValue(new Date(), true));
content.setWidget(0, 2, setNowButton);
panel.add(content);
return panel;
}
@Override
protected Date getResult() {
return timeBox.getValue();
}
private static class ReceivedStartTimeDialog implements Validator<Date> {
private StringMessages stringMessages;
public ReceivedStartTimeDialog(StringMessages stringMessages) {
this.stringMessages = stringMessages;
}
@Override
public String getErrorMessage(Date valueToValidate) {
return valueToValidate == null ? stringMessages.pleaseEnterAValue() : null;
}
}
}
@@ -47,11 +47,9 @@ public class TrackedRacesManagementPanel extends AbstractRaceManagementPanel {
}
@Override
public void onSuccess(RaceDTO result) {
if (result != null) {
selectedRaceDTO = result;
refreshSelectedRaceData();
TrackedRacesManagementPanel.this.regattaRefresher.fillRegattas();
}
selectedRaceDTO = result;
refreshSelectedRaceData();
TrackedRacesManagementPanel.this.regattaRefresher.fillRegattas();
}
});
}
@@ -1181,6 +1181,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String showUncorrectedTotalPoints();
String setStartTimeReceived();
String setStartTimeReceivedDescription();
String setStartTimeReceivedNotice();
String lastScoreCorrectionsTime();
String lastScoreCorrectionsComment();
String setTimeToNow();
@@ -1167,7 +1167,8 @@ racesScoredTooltip=Number of races the competitor has completed or gotten a scor
averageNumberOfOperationsPerMessage=Average number of operations per message
showUncorrectedTotalPoints=Show uncorrected total points
setStartTimeReceived=Set start time received
setStartTimeReceivedDescription=This sets the startTimeReceived of the selected TrackedRace that isn''t persistent. This means that the new value will be forgotten after the server has been restarted.
setStartTimeReceivedDescription=Sets the startTimeReceived of the selected TrackedRace to the provided value. Leave the value empty to remove the currently set startTimeReceived.
setStartTimeReceivedNotice=This change is not persistent, i.e. the new value will be forgotten when the server has been restarted.
lastScoreCorrectionsTime=Last score correction time
lastScoreCorrectionsComment=Last score correction comment
setTimeToNow=Set time to ''now''
@@ -1154,8 +1154,8 @@ racesScoredTooltip=Anzahl von Rennen, die der Segler vollendet oder für die er
averageNumberOfOperationsPerMessage=Durchschnittliche Anzahl Operationen pro Nachricht
showUncorrectedTotalPoints=Unkorrigierte Punkte anzeigen
setStartTimeReceived=Setze die erhaltene Startzeit
setStartTimeReceivedDescription=This sets the startTimeReceived of the selected TrackedRace, that isn''t persistent. This means, that the new value will be forgotten after the server has been restarted.
setStartTimeReceivedDescription=Dies setzt die startTimeReceived des selektierten TrackedRaces, welches nicht persistent ist. Das beudeutet, dass der neue Wert vergessen wird, sobald der Server neu gestartet wurde.
setStartTimeReceivedDescription=Setzt die startTimeReceived des selektierten TrackedRaces auf den angegebenen Wert. Den Wert leer lassen, um die aktuell gesetzte startTimeReceived zu entfernen.
setStartTimeReceivedNotice=Diese Änderung ist nicht persistent, d.h. der neue Wert wird beim Neustart des Servers vergessen.
lastScoreCorrectionsTime=Letzter Zeitpunkt der Punktzahl-Korrektur
lastScoreCorrectionsComment=Letzter Kommentar der Punktzahl-Korrektur
setTimeToNow=Setze den Zeitpunkt auf ''Jetzt''
@@ -9,11 +9,9 @@ import java.util.function.Function;
import java.util.function.Supplier;
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.Cell.Context;
import com.google.gwt.cell.client.DateCell;
import com.google.gwt.cell.client.TextCell;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.i18n.shared.DateTimeFormat;
import com.google.gwt.i18n.shared.DateTimeFormat.PredefinedFormat;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
@@ -42,14 +40,11 @@ import com.sap.sailing.gwt.ui.actions.GetManeuversForCompetitorsAction;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider;
import com.sap.sailing.gwt.ui.client.ManeuverTypeFormatter;
import com.sap.sailing.gwt.ui.client.NumberFormatterFactory;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.controls.AbstractSortableColumnWithMinMax;
import com.sap.sailing.gwt.ui.client.shared.controls.SortableColumn;
import com.sap.sailing.gwt.ui.leaderboard.HasStringAndDoubleValue;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel.LeaderBoardStyle;
import com.sap.sailing.gwt.ui.leaderboard.MinMaxRenderer;
import com.sap.sailing.gwt.ui.leaderboard.SortedCellTableWithStylableHeaders;
import com.sap.sailing.gwt.ui.shared.ManeuverDTO;
import com.sap.sse.common.TimeRange;
@@ -82,8 +77,6 @@ public class ManeuverTablePanel extends AbstractCompositeComponent<ManeuverTable
private final StringMessages stringMessages;
private final CompetitorSelectionProvider competitorSelectionModel;
private final NumberFormat towDigitAccuracy = NumberFormatterFactory.getDecimalFormat(2);
private final SimplePanel contentPanel = new SimplePanel();
private final Label importantMessageLabel = new Label();
private final SortedCellTableWithStylableHeaders<ManeuverTableData> maneuverCellTable;
@@ -162,84 +155,31 @@ public class ManeuverTablePanel extends AbstractCompositeComponent<ManeuverTable
this.stringMessages.avgTurningRate(), this.stringMessages.degreesPerSecondUnit()));
this.maneuverCellTable.addColumn(createSortableMinMaxColumn(ManeuverTableData::getManeuverLoss,
this.stringMessages.maneuverLoss(), stringMessages.metersUnit()));
this.maneuverCellTable.addColumn(createSortableMinMaxColumn(ManeuverTableData::getDirectionChange,
this.maneuverCellTable.addColumn(createSortableAbsMinMaxColumn(ManeuverTableData::getDirectionChange,
stringMessages.directionChange(), this.stringMessages.degreesShort()));
initWidget(rootPanel);
setVisible(false);
}
/**
* Creates a sortable column with the absolute value. Whereas {@link #createSortableMinMaxColumn()} creates a
* sortable column with signed values.
*/
private SortableColumn<ManeuverTableData, String> createSortableAbsMinMaxColumn(
Function<ManeuverTableData, Double> extractor, String title, String unit) {
return new SortableMinMaxColumn(extractor, title, unit, maneuverCellTable.getDataProvider(), /* absolute */ true);
}
/**
* Creates a sortable column with signed values.
*/
private SortableColumn<ManeuverTableData, String> createSortableMinMaxColumn(
Function<ManeuverTableData, Double> extractor, String title, String unit) {
final SortableColumn<ManeuverTableData, String> col = new AbstractSortableColumnWithMinMax<ManeuverTableData, String>(
new TextCell(), SortingOrder.ASCENDING) {
final InvertibleComparator<ManeuverTableData> comparatorWithAbs = new InvertibleComparatorAdapter<ManeuverTableData>() {
@Override
public int compare(ManeuverTableData o1, ManeuverTableData o2) {
Double o1v = extractor.apply(o1);
Double o2v = extractor.apply(o2);
if (o1v == null && o2v == null) {
return 0;
}
if (o1v == null && o2v != null) {
return -1;
}
if (o1v != null && o2v == null) {
return 1;
}
return Double.compare(Math.abs(o1v), Math.abs(o2v));
}
};
final HasStringAndDoubleValue<ManeuverTableData> dataProvider = new HasStringAndDoubleValue<ManeuverTableData>() {
@Override
public String getStringValueToRender(ManeuverTableData row) {
Double value = extractor.apply(row);
if (value == null) {
return null;
}
return towDigitAccuracy.format(value);
}
@Override
public Double getDoubleValue(ManeuverTableData row) {
Double value = extractor.apply(row);
return value == null ? null : Math.abs(value);
}
};
final MinMaxRenderer<ManeuverTableData> renderer = new MinMaxRenderer<ManeuverTableData>(dataProvider, comparatorWithAbs);
@Override
public InvertibleComparator<ManeuverTableData> getComparator() {
return comparatorWithAbs;
}
@Override
public void render(Context context, ManeuverTableData object, SafeHtmlBuilder sb) {
renderer.render(context, object, title, sb);
}
@Override
public Header<?> getHeader() {
return new TextHeader(title + " [" + unit + "]");
}
@Override
public String getValue(ManeuverTableData object) {
return dataProvider.getStringValueToRender(object);
}
@Override
public void updateMinMax() {
renderer.updateMinMax(maneuverCellTable.getDataProvider().getList());
}
};
col.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
return col;
return new SortableMinMaxColumn(extractor, title, unit, maneuverCellTable.getDataProvider(), /* absolute */ false);
}
private SortableColumn<ManeuverTableData, String> createManeuverTypeColumn() {
return new SortableColumn<ManeuverTableData, String>(new TextCell(), SortingOrder.ASCENDING) {
@Override
public InvertibleComparator<ManeuverTableData> getComparator() {
return new InvertibleComparatorAdapter<ManeuverTableData>() {
@@ -269,7 +209,6 @@ public class ManeuverTablePanel extends AbstractCompositeComponent<ManeuverTable
return o1.getTimePoint().compareTo(o2.getTimePoint());
}
};
final SortableColumn<ManeuverTableData, Date> col = new SortableColumn<ManeuverTableData, Date>(
new DateCell(DateTimeFormat.getFormat(PredefinedFormat.TIME_LONG)), SortingOrder.ASCENDING) {
@Override
@@ -297,7 +236,6 @@ public class ManeuverTablePanel extends AbstractCompositeComponent<ManeuverTable
return -Boolean.compare(o1.isMarkPassing(), o2.isMarkPassing());
}
};
final SortableColumn<ManeuverTableData, Boolean> column = new SortableColumn<ManeuverTableData, Boolean>(
new AbstractCell<Boolean>() {
@Override
@@ -332,9 +270,7 @@ public class ManeuverTablePanel extends AbstractCompositeComponent<ManeuverTable
return o1.getCompetitorName().compareTo(o2.getCompetitorName());
}
};
return new SortableColumn<ManeuverTableData, String>(new TextCell(), SortingOrder.ASCENDING) {
@Override
public InvertibleComparator<ManeuverTableData> getComparator() {
return comparator;
@@ -0,0 +1,94 @@
package com.sap.sailing.gwt.ui.client.shared.racemap.maneuver;
import java.util.Comparator;
import java.util.function.Function;
import com.google.gwt.cell.client.Cell.Context;
import com.google.gwt.cell.client.TextCell;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.cellview.client.Header;
import com.google.gwt.user.cellview.client.TextHeader;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.view.client.ListDataProvider;
import com.sap.sailing.domain.common.InvertibleComparator;
import com.sap.sailing.domain.common.SortingOrder;
import com.sap.sailing.domain.common.impl.InvertibleComparatorAdapter;
import com.sap.sailing.gwt.ui.client.NumberFormatterFactory;
import com.sap.sailing.gwt.ui.client.shared.controls.AbstractSortableColumnWithMinMax;
import com.sap.sailing.gwt.ui.leaderboard.HasStringAndDoubleValue;
import com.sap.sailing.gwt.ui.leaderboard.MinMaxRenderer;
public class SortableMinMaxColumn extends AbstractSortableColumnWithMinMax<ManeuverTableData, String> {
private final static NumberFormat TWO_DIGIT_ACCURACY = NumberFormatterFactory.getDecimalFormat(2);
private final String title;
private final String unit;
final InvertibleComparator<ManeuverTableData> comparator;
final HasStringAndDoubleValue<ManeuverTableData> dataProvider;
final MinMaxRenderer<ManeuverTableData> renderer;
final ListDataProvider<ManeuverTableData> maneuverTableListDataProvider;
public SortableMinMaxColumn(final Function<ManeuverTableData, Double> extractor, String title, String unit,
ListDataProvider<ManeuverTableData> maneuverTableListDataProvider, boolean absolute) {
super(new TextCell(), SortingOrder.ASCENDING);
this.title = title;
this.unit = unit;
this.maneuverTableListDataProvider = maneuverTableListDataProvider;
this.comparator = new InvertibleComparatorAdapter<ManeuverTableData>() {
@Override
public int compare(ManeuverTableData o1, ManeuverTableData o2) {
Double o1v = extractor.apply(o1);
Double o2v = extractor.apply(o2);
return Comparator.nullsFirst((Double v1, Double v2)->Double.compare(absolute?Math.abs(v1):v1, absolute?Math.abs(v2):v2)).compare(o1v, o2v);
}
};
this.dataProvider = new HasStringAndDoubleValue<ManeuverTableData>() {
@Override
public String getStringValueToRender(ManeuverTableData row) {
Double value = extractor.apply(row);
if (value == null) {
return null;
}
return TWO_DIGIT_ACCURACY.format(value);
}
@Override
public Double getDoubleValue(ManeuverTableData row) {
Double value = extractor.apply(row);
return value == null ? null : absolute ? Math.abs(value) : value;
}
};
this.renderer = new MinMaxRenderer<ManeuverTableData>(dataProvider, comparator);
this.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
}
@Override
public InvertibleComparator<ManeuverTableData> getComparator() {
return comparator;
}
@Override
public void render(Context context, ManeuverTableData object, SafeHtmlBuilder sb) {
renderer.render(context, object, title, sb);
}
@Override
public Header<?> getHeader() {
return new TextHeader(title + " [" + unit + "]");
}
@Override
public String getValue(ManeuverTableData object) {
return dataProvider.getStringValueToRender(object);
}
@Override
public void updateMinMax() {
renderer.updateMinMax(maneuverTableListDataProvider.getList());
}
}
@@ -26,6 +26,7 @@ public class MinMaxRenderer<T> {
protected static final String BACKGROUND_BAR_STYLE_GOOD = "minMaxBackgroundBarGood";
private final HasStringAndDoubleValue<T> valueProvider;
/** used to determine minimum and maximum values for the rendered bars.*/
private final Comparator<T> comparator;
private Double minimumValue;
private Double maximumValue;
@@ -119,7 +120,8 @@ public class MinMaxRenderer<T> {
* @param row
* The row to get the percentage for.
*/
protected int getPercentage(T row) {
protected int getPercentage(T row) {
int percentage = 0;
Double value = valueProvider.getDoubleValue(row);
if (value != null) {
@@ -128,10 +130,8 @@ public class MinMaxRenderer<T> {
percentage = (int) (minBarLength + (100. - minBarLength) * (value - getMinimumDouble())
/ (getMaximumDouble() - getMinimumDouble()));
}
}
}
return percentage;
}
private Double getMinimumDouble() {
@@ -149,23 +149,24 @@ public class MinMaxRenderer<T> {
* The values of {@link LeaderboardRowDTO}s to determine the minimum and maximum values for.
*/
public void updateMinMax(Iterable<T> displayedLeaderboardRowsProvider) {
T minimumRow = null;
T maximumRow = null;
T minimumOrderRow = null;
T maximumOrderRow = null;
for (T row : displayedLeaderboardRowsProvider) {
if (valueProvider.getDoubleValue(row) != null
&& (minimumRow == null || comparator.compare(minimumRow, row) > 0)) {
minimumRow = row;
&& (minimumOrderRow == null || comparator.compare(minimumOrderRow, row) > 0)) {
minimumOrderRow = row;
}
if (valueProvider.getDoubleValue(row) != null
&& (maximumRow == null || comparator.compare(maximumRow, row) < 0)) {
maximumRow = row;
&& (maximumOrderRow == null || comparator.compare(maximumOrderRow, row) < 0)) {
maximumOrderRow = row;
}
}
if (minimumOrderRow != null) {
minimumValue = valueProvider.getDoubleValue(minimumOrderRow);
}
if (minimumRow != null) {
minimumValue = valueProvider.getDoubleValue(minimumRow);
}
if (maximumRow != null) {
maximumValue = valueProvider.getDoubleValue(maximumRow);
if (maximumOrderRow != null) {
maximumValue = valueProvider.getDoubleValue(maximumOrderRow);
}
}
@@ -397,7 +397,9 @@ public class RaceBoardPanel
selectedRaceIdentifier, stringMessages, competitorSelectionProvider, errorReporter, timer,
maneuverTableSettings, timeRangeWithZoomModel, new ClassicLeaderboardStyle(), userService);
maneuverTablePanel.getEntryWidget().setTitle(stringMessages.maneuverTable());
componentsForSideBySideViewer.add(maneuverTablePanel);
if (showChartMarkEditMediaButtonsAndVideo) {
componentsForSideBySideViewer.add(maneuverTablePanel);
}
editMarkPassingPanel = new EditMarkPassingsPanel(this, getComponentContext(), sailingService,
selectedRaceIdentifier,
stringMessages,
@@ -5062,7 +5062,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
final MasterDataImporter importer = new MasterDataImporter(baseDomainFactory, getService());
importer.importFromStream(inputStream, importOperationId, override);
} catch (Exception e) {
} catch (Throwable e) {
// do not assume that RuntimeException is logged properly
logger.log(Level.SEVERE, e.getMessage(), e);
getService()
@@ -6444,15 +6444,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
@Override
public RaceDTO setStartTimeReceivedForRace(RaceIdentifier raceIdentifier, Date newStartTimeReceived) {
if (newStartTimeReceived != null) {
RegattaNameAndRaceName regattaAndRaceIdentifier = new RegattaNameAndRaceName(
raceIdentifier.getRegattaName(), raceIdentifier.getRaceName());
DynamicTrackedRace trackedRace = getService().getTrackedRace(regattaAndRaceIdentifier);
trackedRace.setStartTimeReceived(new MillisecondsTimePoint(newStartTimeReceived));
return baseDomainFactory.createRaceDTO(getService(), false, regattaAndRaceIdentifier, trackedRace);
}
return null;
RegattaNameAndRaceName regattaAndRaceIdentifier = new RegattaNameAndRaceName(raceIdentifier.getRegattaName(),
raceIdentifier.getRaceName());
DynamicTrackedRace trackedRace = getService().getTrackedRace(regattaAndRaceIdentifier);
trackedRace.setStartTimeReceived(
newStartTimeReceived == null ? null : new MillisecondsTimePoint(newStartTimeReceived));
return baseDomainFactory.createRaceDTO(getService(), false, regattaAndRaceIdentifier, trackedRace);
}
@Override
@@ -61,7 +61,7 @@ public class PolarDataResourceTest {
assertThat(polarService.getSpeedRegressionsPerAngle().size(), is(68));
assertThat(polarService.getCubicRegressionsPerCourse().size(), is(4));
assertThat(polarService.getFixCointPerBoatClass().get(boatClass), is(9330L));
assertThat(polarService.getFixCountPerBoatClass().get(boatClass), is(9330L));
// presuming that if downwind functions & regression collections' size are correct then any other thing is
// imported correctly
assertThat(polarService.getAngleRegressionFunction(boatClass, LegType.DOWNWIND), is(angleDownwindFunction));
@@ -177,7 +177,8 @@ public class PolarDataServiceImpl implements ReplicablePolarService, ClearStateT
return result;
}
private SpeedWithBearingWithConfidence<Void> getClosestTwaTws(ManeuverType type, Speed speedAtManeuverStart,
@Override
public SpeedWithBearingWithConfidence<Void> getClosestTwaTws(ManeuverType type, Speed speedAtManeuverStart,
double courseChangeDeg, BoatClass boatClass) {
assert type == ManeuverType.TACK || type == ManeuverType.JIBE;
double minDiff = Double.MAX_VALUE;
@@ -186,8 +187,9 @@ public class PolarDataServiceImpl implements ReplicablePolarService, ClearStateT
boatClass, speedAtManeuverStart, type == ManeuverType.TACK ? LegType.UPWIND : LegType.DOWNWIND,
type == ManeuverType.TACK ? courseChangeDeg >= 0 ? Tack.PORT : Tack.STARBOARD
: courseChangeDeg >= 0 ? Tack.STARBOARD : Tack.PORT)) {
double diff = Math.abs(trueWindSpeedAndAngle.getObject().getBearing().getDegrees() * 2)
- Math.abs(courseChangeDeg);
double targetManeuverAngle = getManeuverAngleInDegreesFromTwa(
trueWindSpeedAndAngle.getObject().getBearing().getDegrees(), type);
double diff = Math.abs(targetManeuverAngle) - Math.abs(courseChangeDeg);
if (diff < minDiff) {
minDiff = diff;
closestTwsTwa = trueWindSpeedAndAngle;
@@ -235,11 +237,21 @@ public class PolarDataServiceImpl implements ReplicablePolarService, ClearStateT
}
SpeedWithBearingWithConfidence<Void> speed = polarDataMiner.getAverageSpeedAndCourseOverGround(boatClass,
windSpeed, legType);
Bearing bearing = new DegreeBearingImpl(speed.getObject().getBearing().getDegrees() * 2);
Bearing bearing = new DegreeBearingImpl(getManeuverAngleInDegreesFromTwa(speed.getObject().getBearing().getDegrees(), maneuverType));
BearingWithConfidence<Void> bearingWithConfidence = new BearingWithConfidenceImpl<Void>(bearing,
speed.getConfidence(), null);
return bearingWithConfidence;
}
public double getManeuverAngleInDegreesFromTwa(double twa, ManeuverType maneuverType) {
if (maneuverType == ManeuverType.TACK) {
return Math.abs(twa) * 2;
}
if (maneuverType == ManeuverType.JIBE) {
return (180 - Math.abs(twa)) * 2;
}
throw new IllegalArgumentException("ManeuverType needs to be tack or jibe.");
}
@Override
public void insertExistingFixes(TrackedRace trackedRace) {
@@ -388,7 +400,8 @@ public class PolarDataServiceImpl implements ReplicablePolarService, ClearStateT
return polarDataMiner.getSpeedRegressionPerAngleClusterProcessor().getRegressionsImpl();
}
public Map<BoatClass, Long> getFixCointPerBoatClass() {
@Override
public Map<BoatClass, Long> getFixCountPerBoatClass() {
return polarDataMiner.getSpeedRegressionPerAngleClusterProcessor().getFixCountPerBoatClass();
}
@@ -107,8 +107,7 @@ public class AngleAndSpeedRegression implements Serializable {
boolean angleFound;
try {
angle = angleRegression.getOrCreatePolynomialFunction().value(windSpeedCandidateInKnots);
if ((tack == Tack.PORT && legType == LegType.UPWIND)
|| (tack == Tack.STARBOARD && legType == LegType.DOWNWIND)) {
if (tack == Tack.PORT) {
angle = -angle;
}
angleFound = true;
@@ -28,14 +28,14 @@ import com.sap.sailing.server.gateway.deserialization.impl.CompleteManeuverCurve
import com.sap.sailing.server.gateway.deserialization.impl.DetailedBoatClassJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.ManeuverMainCurveWithEstimationDataJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.ManeuverWindJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.PositionJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.WindJsonDeserializer;
import com.sap.sailing.server.gateway.serialization.impl.CompleteManeuverCurveWithEstimationDataJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.DetailedBoatClassJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.ManeuverMainCurveWithEstimationDataJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.ManeuverWindJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.PositionJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.WindJsonSerializer;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Distance;
import com.sap.sse.common.Duration;
@@ -120,34 +120,40 @@ public class EstimationDataSerializationDeserializationTest {
longestIntervalBetweenTwoFixes, intervalBetweenLastFixOfCurveAndNextFix,
intervalBetweenFirstFixOfCurveAndPreviousFix);
MillisecondsTimePoint windTimePoint = new MillisecondsTimePoint(dateFormat.parse("06/23/2011-15:28:25"));
SpeedWithBearing windSpeedWithBearing = new KnotSpeedWithBearingImpl(2, new DegreeBearingImpl(340));
DegreePosition windPosition = new DegreePosition(54.325246, 10.148556);
Wind wind = new WindImpl(windPosition, windTimePoint, windSpeedWithBearing);
DegreePosition maneuverPosition = new DegreePosition(50.325246, 11.148556);
Wind wind = new WindImpl(maneuverPosition, mainCurve.getTimePointOfMaxTurningRate(), windSpeedWithBearing);
int jibingCount = 203;
int tackingCount = 12345;
boolean maneuverStartsByRunningAwayFromWind = true;
Bearing relativeBearingToNextMarkBeforeManeuver = new DegreeBearingImpl(202.23);
Bearing relativeBearingToNextMarkAfterManeuver = new DegreeBearingImpl(10.01);
boolean markPassing = true;
Distance closestDistanceToMark = new MeterDistance(3.0);
Double deviationFromTargetTackAngle = 23.30;
Double deviationFromTargetJibeAngle = 22.30;
CompleteManeuverCurveWithEstimationData toSerialize = new CompleteManeuverCurveWithEstimationDataImpl(
maneuverPosition, mainCurve, curve, wind, tackingCount, jibingCount,
maneuverStartsByRunningAwayFromWind, relativeBearingToNextMarkBeforeManeuver,
relativeBearingToNextMarkAfterManeuver, markPassing);
relativeBearingToNextMarkAfterManeuver, markPassing, closestDistanceToMark,
deviationFromTargetTackAngle, deviationFromTargetJibeAngle);
CompleteManeuverCurveWithEstimationDataJsonSerializer serializer = new CompleteManeuverCurveWithEstimationDataJsonSerializer(
new ManeuverMainCurveWithEstimationDataJsonSerializer(),
new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer(),
new WindJsonSerializer(new PositionJsonSerializer()), new PositionJsonSerializer());
new ManeuverWindJsonSerializer(), new PositionJsonSerializer());
JSONObject json = serializer.serialize(toSerialize);
CompleteManeuverCurveWithEstimationDataJsonDeserializer deserializer = new CompleteManeuverCurveWithEstimationDataJsonDeserializer(
new ManeuverMainCurveWithEstimationDataJsonDeserializer(),
new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonDeserializer(),
new WindJsonDeserializer(new PositionJsonDeserializer()), new PositionJsonDeserializer());
new ManeuverWindJsonDeserializer(), new PositionJsonDeserializer());
CompleteManeuverCurveWithEstimationData deserialized = deserializer.deserialize(json);
assertEquals(deviationFromTargetJibeAngle,
deserialized.getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees());
assertEquals(deviationFromTargetTackAngle,
deserialized.getDeviationOfManeuverAngleFromTargetTackAngleInDegrees());
assertEquals(closestDistanceToMark, deserialized.getDistanceToClosestMark());
assertEquals(maneuverPosition, deserialized.getPosition());
assertEquals(tackingCount, deserialized.getTackingCount());
assertEquals(jibingCount, deserialized.getJibingCount());
@@ -156,8 +162,8 @@ public class EstimationDataSerializationDeserializationTest {
assertEquals(relativeBearingToNextMarkBeforeManeuver,
deserialized.getRelativeBearingToNextMarkBeforeManeuver());
assertEquals(relativeBearingToNextMarkAfterManeuver, deserialized.getRelativeBearingToNextMarkAfterManeuver());
assertEquals(windTimePoint, deserialized.getWind().getTimePoint());
assertEquals(windPosition, deserialized.getWind().getPosition());
assertEquals(wind.getTimePoint(), deserialized.getWind().getTimePoint());
assertEquals(wind.getPosition(), deserialized.getWind().getPosition());
assertEquals(windSpeedWithBearing.getBearing(), deserialized.getWind().getBearing());
assertEquals(windSpeedWithBearing.getMetersPerSecond(), deserialized.getWind().getMetersPerSecond(), DELTA);
@@ -303,22 +309,31 @@ public class EstimationDataSerializationDeserializationTest {
Bearing relativeBearingToNextMarkAfterManeuver = null;
boolean markPassing = false;
DegreePosition maneuverPosition = new DegreePosition(50.325246, 11.148556);
Distance closestDistanceToMark = null;
Double deviationFromTargetTackAngle = null;
Double deviationFromTargetJibeAngle = null;
CompleteManeuverCurveWithEstimationData toSerialize = new CompleteManeuverCurveWithEstimationDataImpl(
maneuverPosition, mainCurve, curve, wind, tackingCount, jibingCount,
maneuverStartsByRunningAwayFromWind, relativeBearingToNextMarkBeforeManeuver,
relativeBearingToNextMarkAfterManeuver, markPassing);
relativeBearingToNextMarkAfterManeuver, markPassing, closestDistanceToMark,
deviationFromTargetTackAngle, deviationFromTargetJibeAngle);
CompleteManeuverCurveWithEstimationDataJsonSerializer serializer = new CompleteManeuverCurveWithEstimationDataJsonSerializer(
new ManeuverMainCurveWithEstimationDataJsonSerializer(),
new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer(),
new WindJsonSerializer(new PositionJsonSerializer()), new PositionJsonSerializer());
new ManeuverWindJsonSerializer(), new PositionJsonSerializer());
JSONObject json = serializer.serialize(toSerialize);
CompleteManeuverCurveWithEstimationDataJsonDeserializer deserializer = new CompleteManeuverCurveWithEstimationDataJsonDeserializer(
new ManeuverMainCurveWithEstimationDataJsonDeserializer(),
new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonDeserializer(),
new WindJsonDeserializer(new PositionJsonDeserializer()), new PositionJsonDeserializer());
new ManeuverWindJsonDeserializer(), new PositionJsonDeserializer());
CompleteManeuverCurveWithEstimationData deserialized = deserializer.deserialize(json);
assertEquals(deviationFromTargetJibeAngle,
deserialized.getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees());
assertEquals(deviationFromTargetTackAngle,
deserialized.getDeviationOfManeuverAngleFromTargetTackAngleInDegrees());
assertEquals(closestDistanceToMark, deserialized.getDistanceToClosestMark());
assertEquals(maneuverPosition, deserialized.getPosition());
assertEquals(tackingCount, deserialized.getTackingCount());
assertEquals(jibingCount, deserialized.getJibingCount());
@@ -3,7 +3,10 @@ package com.sap.sailing.server.gateway.deserialization.impl;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.common.impl.WindImpl;
import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimationData;
import com.sap.sailing.domain.maneuverdetection.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData;
import com.sap.sailing.domain.maneuverdetection.ManeuverMainCurveWithEstimationData;
@@ -12,6 +15,7 @@ import com.sap.sailing.server.gateway.deserialization.JsonDeserializationExcepti
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
import com.sap.sailing.server.gateway.serialization.impl.CompleteManeuverCurveWithEstimationDataJsonSerializer;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Distance;
import com.sap.sse.common.impl.DegreeBearingImpl;
/**
@@ -24,13 +28,13 @@ public class CompleteManeuverCurveWithEstimationDataJsonDeserializer
private final ManeuverMainCurveWithEstimationDataJsonDeserializer mainCurveDeserializer;
private final ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonDeserializer curveWithUnstableCourseAndSpeedDeserializer;
private final WindJsonDeserializer windDeserializer;
private final ManeuverWindJsonDeserializer windDeserializer;
private final PositionJsonDeserializer positionDeserializer;
public CompleteManeuverCurveWithEstimationDataJsonDeserializer(
ManeuverMainCurveWithEstimationDataJsonDeserializer mainCurveDeserializer,
ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonDeserializer curveWithUnstableCourseAndSpeedDeserializer,
WindJsonDeserializer windDeserializer, PositionJsonDeserializer positionDeserializer) {
ManeuverWindJsonDeserializer windDeserializer, PositionJsonDeserializer positionDeserializer) {
this.mainCurveDeserializer = mainCurveDeserializer;
this.curveWithUnstableCourseAndSpeedDeserializer = curveWithUnstableCourseAndSpeedDeserializer;
this.windDeserializer = windDeserializer;
@@ -48,7 +52,9 @@ public class CompleteManeuverCurveWithEstimationDataJsonDeserializer
.deserialize((JSONObject) object.get(
CompleteManeuverCurveWithEstimationDataJsonSerializer.CURVE_WITH_UNSTABLE_COURSE_AND_SPEED));
JSONObject windJson = (JSONObject) object.get(CompleteManeuverCurveWithEstimationDataJsonSerializer.WIND);
Wind wind = windJson == null ? null : windDeserializer.deserialize(windJson);
SpeedWithBearing windSpeedWithBearing = windJson == null ? null : windDeserializer.deserialize(windJson);
Wind wind = windSpeedWithBearing == null ? null
: new WindImpl(position, mainCurve.getTimePointOfMaxTurningRate(), windSpeedWithBearing);
Integer tackingCount = getInteger(
object.get(CompleteManeuverCurveWithEstimationDataJsonSerializer.TACKING_COUNT));
Integer jibingCount = getInteger(
@@ -59,16 +65,28 @@ public class CompleteManeuverCurveWithEstimationDataJsonDeserializer
CompleteManeuverCurveWithEstimationDataJsonSerializer.RELATIVE_BEARING_TO_NEXT_MARK_BEFORE_MANEUVER);
Double relativeBearingToNextMarkAfterManeuver = (Double) object.get(
CompleteManeuverCurveWithEstimationDataJsonSerializer.RELATIVE_BEARING_TO_NEXT_MARK_AFTER_MANEUVER);
Double closestDistanceToMarkInMeters = (Double) object
.get(CompleteManeuverCurveWithEstimationDataJsonSerializer.CLOSEST_DISTANCE_TO_MARK);
Double deviationFromTargetTackAngle = (Double) object
.get(CompleteManeuverCurveWithEstimationDataJsonSerializer.DEVIATION_FROM_TARGET_TACK_ANGLE);
Double deviationFromTargetJibeAngle = (Double) object
.get(CompleteManeuverCurveWithEstimationDataJsonSerializer.DEVIATION_FROM_TARGET_JIBE_ANGLE);
return new CompleteManeuverCurveWithEstimationDataImpl(position, mainCurve, curveWithUnstableCourseAndSpeed,
wind, tackingCount, jibingCount, maneuverStartsByRunningAwayFromWind,
convertBearing(relativeBearingToNextMarkBeforeManeuver),
convertBearing(relativeBearingToNextMarkAfterManeuver), markPassing);
convertBearing(relativeBearingToNextMarkAfterManeuver), markPassing,
convertDistance(closestDistanceToMarkInMeters), deviationFromTargetTackAngle,
deviationFromTargetJibeAngle);
}
private Bearing convertBearing(Double degrees) {
return degrees == null ? null : new DegreeBearingImpl(degrees);
}
private Distance convertDistance(Double meters) {
return meters == null ? null : new MeterDistance(meters);
}
public static Integer getInteger(Object object) {
if (object == null) {
return null;
@@ -0,0 +1,29 @@
package com.sap.sailing.server.gateway.deserialization.impl;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
import com.sap.sailing.server.gateway.serialization.impl.ManeuverWindJsonSerializer;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.impl.DegreeBearingImpl;
/**
*
* @author Vladislav Chumak (D069712)
*
*/
public class ManeuverWindJsonDeserializer implements JsonDeserializer<SpeedWithBearing> {
public SpeedWithBearing deserialize(JSONObject object) throws JsonDeserializationException {
Double directionInTrueDegrees = (Double) object.get(ManeuverWindJsonSerializer.DIRECTION_IN_TRUE_DEGREES);
Double speedInKnots = (Double) object.get(ManeuverWindJsonSerializer.SPEED_IN_KNOTS);
Bearing degreeBearing = new DegreeBearingImpl(directionInTrueDegrees);
SpeedWithBearing speedBearing = new KnotSpeedWithBearingImpl(speedInKnots, degreeBearing);
return speedBearing;
}
}
@@ -0,0 +1,20 @@
package com.sap.sailing.server.gateway.serialization.impl;
import org.json.simple.JSONArray;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.maneuverdetection.impl.TrackTimeInfo;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
/**
*
* @author Vladislav Chumak (D069712)
* @see CompetitorTrackWithEstimationDataJsonSerializer
*/
public interface CompetitorTrackElementsJsonSerializer {
JSONArray serialize(TrackedRace trackedRace, Competitor competitor, TimePoint from, TimePoint to,
TrackTimeInfo trackTimeInfo);
}
@@ -0,0 +1,144 @@
package com.sap.sailing.server.gateway.serialization.impl;
import java.util.NavigableSet;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorImpl;
import com.sap.sailing.domain.maneuverdetection.impl.TrackTimeInfo;
import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsDurationImpl;
/**
*
* @author Vladislav Chumak (D069712)
*
*/
public class CompetitorTrackWithEstimationDataJsonSerializer extends AbstractTrackedRaceDataJsonSerializer {
public static final String elements = "elements";
public static final String BOAT_CLASS = "boatClass";
public static final String COMPETITOR_NAME = "competitorName";
public static final String AVG_INTERVAL_BETWEEN_FIXES_IN_SECONDS = "avgIntervalBetweenFixesInSeconds";
public static final String DISTANCE_TRAVELLED_IN_METERS = "distanceTravelledInMeters";
public static final String START_TIME_POINT = "startUnixTime";
public static final String END_TIME_POINT = "endUnixTime";
public static final String FIXES_COUNT_FOR_POLARS = "fixesCountForPolars";
public static final String MARK_PASSINGS_COUNT = "markPassingsCount";
public static final String WAYPOINTS_COUNT = "waypointsCount";
private final BoatClassJsonSerializer boatClassJsonSerializer;
private final CompetitorTrackElementsJsonSerializer elementsJsonSerializer;
private final PolarDataService polarDataService;
private final Integer startBeforeStartLineInSeconds;
private final Integer endBeforeStartLineInSeconds;
private final Integer startAfterFinishLineInSeconds;
private final Integer endAfterFinishLineInSeconds;
public CompetitorTrackWithEstimationDataJsonSerializer(PolarDataService polarDataService,
BoatClassJsonSerializer boatClassJsonSerializer,
CompetitorTrackElementsJsonSerializer elementsJsonSerializer, Integer startBeforeStartLineInSeconds,
Integer endBeforeStartLineInSeconds, Integer startAfterFinishLineInSeconds,
Integer endAfterFinishLineInSeconds) {
this.polarDataService = polarDataService;
this.boatClassJsonSerializer = boatClassJsonSerializer;
this.elementsJsonSerializer = elementsJsonSerializer;
this.startBeforeStartLineInSeconds = startBeforeStartLineInSeconds;
this.endBeforeStartLineInSeconds = endBeforeStartLineInSeconds;
this.startAfterFinishLineInSeconds = startAfterFinishLineInSeconds;
this.endAfterFinishLineInSeconds = endAfterFinishLineInSeconds;
}
@Override
public JSONObject serialize(TrackedRace trackedRace) {
final JSONObject result = new JSONObject();
JSONArray byCompetitorJson = new JSONArray();
result.put(BYCOMPETITOR, byCompetitorJson);
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
ManeuverDetectorImpl maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor);
TrackTimeInfo trackTimeInfo = maneuverDetector.getTrackTimeInfo();
TimePoint from = null;
TimePoint to = null;
if (startBeforeStartLineInSeconds != null) {
from = trackTimeInfo.getTrackStartTimePoint()
.minus(new MillisecondsDurationImpl(startBeforeStartLineInSeconds * 1000L));
} else if (startAfterFinishLineInSeconds != null) {
from = trackTimeInfo.getTrackEndTimePoint()
.plus(new MillisecondsDurationImpl(startAfterFinishLineInSeconds * 1000L));
} else {
from = trackTimeInfo.getTrackStartTimePoint();
}
if (endAfterFinishLineInSeconds != null) {
to = trackTimeInfo.getTrackEndTimePoint()
.plus(new MillisecondsDurationImpl(endAfterFinishLineInSeconds * 1000L));
} else if (endBeforeStartLineInSeconds != null) {
to = trackTimeInfo.getTrackStartTimePoint()
.minus(new MillisecondsDurationImpl(endBeforeStartLineInSeconds * 1000L));
} else {
to = trackTimeInfo.getTrackEndTimePoint();
}
if (trackTimeInfo != null) {
final JSONObject forCompetitorJson = new JSONObject();
byCompetitorJson.add(forCompetitorJson);
forCompetitorJson.put(COMPETITOR_NAME, competitor.getName());
forCompetitorJson.put(BOAT_CLASS, boatClassJsonSerializer
.serialize(trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass()));
forCompetitorJson.put(FIXES_COUNT_FOR_POLARS, getFixesCountForPolars(trackedRace, competitor));
Duration averageIntervalBetweenFixes = trackedRace.getTrack(competitor)
.getAverageIntervalBetweenFixes();
forCompetitorJson.put(AVG_INTERVAL_BETWEEN_FIXES_IN_SECONDS,
averageIntervalBetweenFixes == null ? 0 : averageIntervalBetweenFixes.asSeconds());
GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
Double distanceTravelledInMeters = null;
if (trackTimeInfo.getTrackStartTimePoint() != null && trackTimeInfo.getTrackEndTimePoint() != null) {
distanceTravelledInMeters = track.getDistanceTraveled(trackTimeInfo.getTrackStartTimePoint(),
trackTimeInfo.getTrackEndTimePoint()).getMeters();
}
forCompetitorJson.put(DISTANCE_TRAVELLED_IN_METERS, distanceTravelledInMeters);
forCompetitorJson.put(START_TIME_POINT, trackTimeInfo.getTrackStartTimePoint() == null ? null
: trackTimeInfo.getTrackStartTimePoint().asMillis());
forCompetitorJson.put(END_TIME_POINT, trackTimeInfo.getTrackEndTimePoint() == null ? null
: trackTimeInfo.getTrackEndTimePoint().asMillis());
forCompetitorJson.put(MARK_PASSINGS_COUNT, getMarkPassingsCount(trackedRace, competitor));
forCompetitorJson.put(WAYPOINTS_COUNT, getWaypointsCount(trackedRace));
forCompetitorJson.put(elements,
elementsJsonSerializer.serialize(trackedRace, competitor, from, to, trackTimeInfo));
}
}
return result;
}
private int getMarkPassingsCount(TrackedRace trackedRace, Competitor competitor) {
int markPassingsCount = 0;
NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor, false);
trackedRace.lockForRead(markPassings);
try {
markPassingsCount = Util.size(markPassings);
} finally {
trackedRace.unlockAfterRead(markPassings);
}
return markPassingsCount;
}
private long getFixesCountForPolars(TrackedRace trackedRace, Competitor competitor) {
BoatClass boatClass = trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass();
Long fixesCountForBoatPolars = polarDataService.getFixCountPerBoatClass().get(boatClass);
return fixesCountForBoatPolars == null ? 0L : fixesCountForBoatPolars;
}
private int getWaypointsCount(TrackedRace trackedRace) {
Iterable<Waypoint> waypoints = trackedRace.getRace().getCourse().getWaypoints();
return Util.size(waypoints);
}
}
@@ -23,16 +23,19 @@ public class CompleteManeuverCurveWithEstimationDataJsonSerializer
public static final String MANEUVER_STARTS_BY_RUNNING_AWAY_FROM_WIND = "maneuverStartsByRunningAwayFromWind";
public static final String RELATIVE_BEARING_TO_NEXT_MARK_BEFORE_MANEUVER = "relativeBearingToNextMarkBeforeManeuver";
public static final String RELATIVE_BEARING_TO_NEXT_MARK_AFTER_MANEUVER = "relativeBearingToNextMarkAfterManeuver";
public static final String CLOSEST_DISTANCE_TO_MARK = "closestDistanceToMarkInMeters";
public static final String DEVIATION_FROM_TARGET_TACK_ANGLE = "deviationFromTargetTackAngleInDegrees";
public static final String DEVIATION_FROM_TARGET_JIBE_ANGLE = "deviationFromTargetJibeAngleInDegrees";
private final ManeuverCurveBoundariesJsonSerializer mainCurveSerializer;
private final ManeuverCurveBoundariesJsonSerializer curveWithUnstableCourseAndSpeedSerializer;
private final WindJsonSerializer windSerializer;
private final ManeuverWindJsonSerializer windSerializer;
private final PositionJsonSerializer positionSerializer;
public CompleteManeuverCurveWithEstimationDataJsonSerializer(
ManeuverCurveBoundariesJsonSerializer mainCurveSerializer,
ManeuverCurveBoundariesJsonSerializer curveWithUnstableCourseAndSpeedSerializer,
WindJsonSerializer windSerializer, PositionJsonSerializer positionSerializer) {
ManeuverWindJsonSerializer windSerializer, PositionJsonSerializer positionSerializer) {
this.mainCurveSerializer = mainCurveSerializer;
this.curveWithUnstableCourseAndSpeedSerializer = curveWithUnstableCourseAndSpeedSerializer;
this.windSerializer = windSerializer;
@@ -59,6 +62,12 @@ public class CompleteManeuverCurveWithEstimationDataJsonSerializer
result.put(RELATIVE_BEARING_TO_NEXT_MARK_AFTER_MANEUVER,
maneuverWithEstimationData.getRelativeBearingToNextMarkAfterManeuver() == null ? null
: maneuverWithEstimationData.getRelativeBearingToNextMarkAfterManeuver().getDegrees());
result.put(CLOSEST_DISTANCE_TO_MARK, maneuverWithEstimationData.getDistanceToClosestMark() == null ? null
: maneuverWithEstimationData.getDistanceToClosestMark().getMeters());
result.put(DEVIATION_FROM_TARGET_TACK_ANGLE,
maneuverWithEstimationData.getDeviationOfManeuverAngleFromTargetTackAngleInDegrees());
result.put(DEVIATION_FROM_TARGET_JIBE_ANGLE,
maneuverWithEstimationData.getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees());
return result;
}
@@ -1,95 +1,75 @@
package com.sap.sailing.server.gateway.serialization.impl;
import java.util.List;
import java.util.stream.Collectors;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimationData;
import com.sap.sailing.domain.maneuverdetection.ManeuverDetector;
import com.sap.sailing.domain.maneuverdetection.ManeuverDetectorWithEstimationDataSupport;
import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorImpl;
import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorWithEstimationDataSupportDecoratorImpl;
import com.sap.sailing.domain.maneuverdetection.impl.ManeuverSpot;
import com.sap.sailing.domain.maneuverdetection.impl.TrackTimeInfo;
import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sailing.domain.tracking.CompleteManeuverCurve;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
/**
*
* @author Vladislav Chumak (D069712)
*
*/
public class CompleteManeuverCurvesWithEstimationDataJsonSerializer extends AbstractTrackedRaceDataJsonSerializer {
public static final String MANEUVER_CURVES = "maneuverCurves";
public static final String BOAT_CLASS = "boatClass";
public static final String COMPETITOR_NAME = "competitorName";
public static final String AVG_INTERVAL_BETWEEN_FIXES_IN_SECONDS = "avgIntervalBetweenFixesInSeconds";
public static final String DISTANCE_TRAVELLED_IN_METERS = "distanceTravelledInMeters";
public static final String START_TIME_POINT = "startUnixTime";
public static final String END_TIME_POINT = "endUnixTime";
private final BoatClassJsonSerializer boatClassJsonSerializer;
public class CompleteManeuverCurvesWithEstimationDataJsonSerializer implements CompetitorTrackElementsJsonSerializer {
private final PolarDataService polarDataService;
private final CompleteManeuverCurveWithEstimationDataJsonSerializer maneuverWithEstimationDataJsonSerializer;
public CompleteManeuverCurvesWithEstimationDataJsonSerializer(BoatClassJsonSerializer boatClassJsonSerializer,
public CompleteManeuverCurvesWithEstimationDataJsonSerializer(PolarDataService polarDataService,
CompleteManeuverCurveWithEstimationDataJsonSerializer maneuverWithEstimationDataJsonSerializer) {
this.boatClassJsonSerializer = boatClassJsonSerializer;
this.polarDataService = polarDataService;
this.maneuverWithEstimationDataJsonSerializer = maneuverWithEstimationDataJsonSerializer;
}
@Override
public JSONObject serialize(TrackedRace trackedRace) {
final JSONObject result = new JSONObject();
JSONArray byCompetitorJson = new JSONArray();
result.put(BYCOMPETITOR, byCompetitorJson);
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
ManeuverDetectorImpl maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor);
TrackTimeInfo trackTimeInfo = maneuverDetector.getTrackTimeInfo();
if (trackTimeInfo != null) {
final JSONObject forCompetitorJson = new JSONObject();
byCompetitorJson.add(forCompetitorJson);
forCompetitorJson.put(COMPETITOR_NAME, competitor.getName());
forCompetitorJson.put(BOAT_CLASS, boatClassJsonSerializer
.serialize(trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass()));
final JSONArray completeManeuverCurvesWithEstimationData = new JSONArray();
for (CompleteManeuverCurveWithEstimationData maneuver : getCompleteManeuverCurvesWithEstimationData(
trackedRace, competitor)) {
completeManeuverCurvesWithEstimationData
.add(maneuverWithEstimationDataJsonSerializer.serialize(maneuver));
}
forCompetitorJson.put(MANEUVER_CURVES, completeManeuverCurvesWithEstimationData);
Duration averageIntervalBetweenFixes = trackedRace.getTrack(competitor)
.getAverageIntervalBetweenFixes();
forCompetitorJson.put(AVG_INTERVAL_BETWEEN_FIXES_IN_SECONDS,
averageIntervalBetweenFixes == null ? 0 : averageIntervalBetweenFixes.asSeconds());
GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
Double distanceTravelledInMeters = null;
if (trackTimeInfo.getTrackStartTimePoint() != null && trackTimeInfo.getTrackEndTimePoint() != null) {
distanceTravelledInMeters = track.getDistanceTraveled(trackTimeInfo.getTrackStartTimePoint(),
trackTimeInfo.getTrackEndTimePoint()).getMeters();
}
forCompetitorJson.put(DISTANCE_TRAVELLED_IN_METERS, distanceTravelledInMeters);
forCompetitorJson.put(START_TIME_POINT, trackTimeInfo.getTrackStartTimePoint() == null ? null
: trackTimeInfo.getTrackStartTimePoint().asMillis());
forCompetitorJson.put(END_TIME_POINT, trackTimeInfo.getTrackEndTimePoint() == null ? null
: trackTimeInfo.getTrackEndTimePoint().asMillis());
}
public JSONArray serialize(TrackedRace trackedRace, Competitor competitor, TimePoint from, TimePoint to,
TrackTimeInfo trackTimeInfo) {
final JSONArray completeManeuverCurvesWithEstimationData = new JSONArray();
Iterable<CompleteManeuverCurveWithEstimationData> completeManeuvers = trackTimeInfo.getTrackStartTimePoint()
.equals(from) && trackTimeInfo.getTrackEndTimePoint().equals(to)
? getCompleteManeuverCurvesWithEstimationData(trackedRace, competitor)
: getCompleteManeuverCurvesWithEstimationData(trackedRace, competitor, from, to);
for (CompleteManeuverCurveWithEstimationData maneuver : completeManeuvers) {
completeManeuverCurvesWithEstimationData.add(maneuverWithEstimationDataJsonSerializer.serialize(maneuver));
}
return result;
return completeManeuverCurvesWithEstimationData;
}
private Iterable<CompleteManeuverCurveWithEstimationData> getCompleteManeuverCurvesWithEstimationData(
TrackedRace trackedRace, Competitor competitor, TimePoint from, TimePoint to) {
ManeuverDetectorImpl maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor);
List<ManeuverSpot> maneuverSpots = maneuverDetector.detectManeuvers(from, to);
List<CompleteManeuverCurve> maneuverCurves = maneuverSpots.stream()
.map(maneuverSpot -> maneuverSpot.getManeuverCurve()).collect(Collectors.toList());
ManeuverDetectorWithEstimationDataSupport maneuverDetectorWithEstimationData = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl(
maneuverDetector, polarDataService);
Iterable<CompleteManeuverCurveWithEstimationData> maneuversWithEstimationData = maneuverDetectorWithEstimationData
.getCompleteManeuverCurvesWithEstimationData(maneuverCurves);
return maneuversWithEstimationData;
}
private Iterable<CompleteManeuverCurveWithEstimationData> getCompleteManeuverCurvesWithEstimationData(
TrackedRace trackedRace, Competitor competitor) {
Iterable<Maneuver> maneuvers = trackedRace.getManeuvers(competitor, false);
ManeuverDetector maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor);
Iterable<CompleteManeuverCurveWithEstimationData> maneuversWithEstimationData = null;
try {
Iterable<CompleteManeuverCurve> maneuverCurves = maneuverDetector.getCompleteManeuverCurves(maneuvers);
maneuversWithEstimationData = maneuverDetector.getCompleteManeuverCurvesWithEstimationData(maneuverCurves);
} catch (Exception e) {
e.printStackTrace();
}
ManeuverDetectorImpl maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor);
ManeuverDetectorWithEstimationDataSupport maneuverDetectorWithEstimationData = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl(
maneuverDetector, polarDataService);
Iterable<CompleteManeuverCurve> maneuverCurves = maneuverDetectorWithEstimationData
.getCompleteManeuverCurves(maneuvers);
Iterable<CompleteManeuverCurveWithEstimationData> maneuversWithEstimationData = maneuverDetectorWithEstimationData
.getCompleteManeuverCurvesWithEstimationData(maneuverCurves);
return maneuversWithEstimationData;
}
@@ -0,0 +1,90 @@
package com.sap.sailing.server.gateway.serialization.impl;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorImpl;
import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorWithEstimationDataSupportDecoratorImpl;
import com.sap.sailing.domain.maneuverdetection.impl.TrackTimeInfo;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Distance;
import com.sap.sse.common.TimePoint;
/**
*
* @author Vladislav Chumak (D069712)
*
*/
public class GpsFixesWithEstimationDataJsonSerializer implements CompetitorTrackElementsJsonSerializer {
public static final String GPS_FIXES = "gpsFixes";
public static final String BOAT_CLASS = "boatClass";
public static final String COMPETITOR_NAME = "competitorName";
public static final String AVG_INTERVAL_BETWEEN_FIXES_IN_SECONDS = "avgIntervalBetweenFixesInSeconds";
public static final String DISTANCE_TRAVELLED_IN_METERS = "distanceTravelledInMeters";
public static final String START_TIME_POINT = "startUnixTime";
public static final String END_TIME_POINT = "endUnixTime";
public static final String WIND = "wind";
public static final String RELATIVE_BEARING_TO_NEXT_MARK = "relativeBearingToNextMark";
public static final String CLOSEST_DISTANCE_TO_MARK = "closestDistanceToMarkInMeters";
private final GPSFixMovingJsonSerializer gpsFixMovingJsonSerializer;
private final boolean addWind;
private final boolean addNextWaypoint;
private final ManeuverWindJsonSerializer windJsonSerializer;
private final Boolean smoothFixes;
public GpsFixesWithEstimationDataJsonSerializer(GPSFixMovingJsonSerializer gpsFixMovingJsonSerializer,
ManeuverWindJsonSerializer windJsonSerializer, boolean addWind, boolean addNextWaypoint,
Boolean smoothFixes) {
this.gpsFixMovingJsonSerializer = gpsFixMovingJsonSerializer;
this.windJsonSerializer = windJsonSerializer;
this.addWind = addWind;
this.addNextWaypoint = addNextWaypoint;
this.smoothFixes = smoothFixes;
}
@Override
public JSONArray serialize(TrackedRace trackedRace, Competitor competitor, TimePoint from, TimePoint to,
TrackTimeInfo trackTimeInfo) {
final JSONArray gpsFixesWithEstimationData = new JSONArray();
ManeuverDetectorImpl maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor);
ManeuverDetectorWithEstimationDataSupportDecoratorImpl estimationDataSupportDecoratorImpl = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl(
maneuverDetector, null);
GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
track.lockForRead();
try {
for (GPSFixMoving gpsFix : track.getFixes(from, true, to, true)) {
JSONObject serializedGpsFix = gpsFixMovingJsonSerializer.serialize(gpsFix);
if (addWind) {
Wind wind = trackedRace.getWind(gpsFix.getPosition(), gpsFix.getTimePoint());
JSONObject serializedWind = wind == null ? null : windJsonSerializer.serialize(wind);
serializedGpsFix.put(WIND, serializedWind);
}
if (addNextWaypoint) {
Distance closestDistanceToMark = estimationDataSupportDecoratorImpl
.getClosestDistanceToMark(gpsFix.getTimePoint());
SpeedWithBearing speedWithBearing = smoothFixes ? track.getEstimatedSpeed(gpsFix.getTimePoint())
: gpsFix.getSpeed();
Bearing relativeBearingToNextMark = speedWithBearing == null ? null
: estimationDataSupportDecoratorImpl.getRelativeBearingToNextMark(gpsFix.getTimePoint(),
speedWithBearing.getBearing());
serializedGpsFix.put(CLOSEST_DISTANCE_TO_MARK,
closestDistanceToMark == null ? null : closestDistanceToMark.getMeters());
serializedGpsFix.put(RELATIVE_BEARING_TO_NEXT_MARK,
relativeBearingToNextMark == null ? null : relativeBearingToNextMark.getDegrees());
}
gpsFixesWithEstimationData.add(serializedGpsFix);
}
} finally {
track.unlockAfterRead();
}
return gpsFixesWithEstimationData;
}
}
@@ -78,6 +78,7 @@ import com.sap.sailing.server.gateway.serialization.impl.BoatJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.ColorJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.CompetitorAndBoatJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.CompetitorJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.CompetitorTrackWithEstimationDataJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.CompleteManeuverCurveWithEstimationDataJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.CompleteManeuverCurvesWithEstimationDataJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.DefaultWindTrackJsonSerializer;
@@ -85,9 +86,12 @@ import com.sap.sailing.server.gateway.serialization.impl.DetailedBoatClassJsonSe
import com.sap.sailing.server.gateway.serialization.impl.DistanceJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.FleetJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.GPSFixJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.GPSFixMovingJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.GpsFixesWithEstimationDataJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.ManeuverJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.ManeuverMainCurveWithEstimationDataJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.ManeuverWindJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.ManeuversJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.MarkPassingsJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.NationalityJsonSerializer;
@@ -1027,7 +1031,15 @@ public class RegattasResource extends AbstractSailingServerResource {
@Produces("application/json;charset=UTF-8")
@Path("{regattaname}/races/{racename}/completeManeuverCurvesWithEstimationData")
public Response getCompleteManeuverCurvesWithEstimationData(@PathParam("regattaname") String regattaName,
@PathParam("racename") String raceName) {
@PathParam("racename") String raceName,
@QueryParam("startBeforeStartLineInSeconds") @DefaultValue(Integer.MIN_VALUE
+ "") Integer startBeforeStartLineInSeconds,
@QueryParam("endBeforeStartLineInSeconds") @DefaultValue(Integer.MIN_VALUE
+ "") Integer endBeforeStartLineInSeconds,
@QueryParam("startAfterFinishLineInSeconds") @DefaultValue(Integer.MIN_VALUE
+ "") Integer startAfterFinishLineInSeconds,
@QueryParam("endAfterFinishLineInSeconds") @DefaultValue(Integer.MIN_VALUE
+ "") Integer endAfterFinishLineInSeconds) {
Response response;
Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
@@ -1042,12 +1054,17 @@ public class RegattasResource extends AbstractSailingServerResource {
.type(MediaType.TEXT_PLAIN).build();
} else {
TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
CompleteManeuverCurvesWithEstimationDataJsonSerializer serializer = new CompleteManeuverCurvesWithEstimationDataJsonSerializer(
new DetailedBoatClassJsonSerializer(),
new CompleteManeuverCurveWithEstimationDataJsonSerializer(
new ManeuverMainCurveWithEstimationDataJsonSerializer(),
new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer(),
new WindJsonSerializer(new PositionJsonSerializer()), new PositionJsonSerializer()));
CompetitorTrackWithEstimationDataJsonSerializer serializer = new CompetitorTrackWithEstimationDataJsonSerializer(
getService().getPolarDataService(), new DetailedBoatClassJsonSerializer(),
new CompleteManeuverCurvesWithEstimationDataJsonSerializer(getService().getPolarDataService(),
new CompleteManeuverCurveWithEstimationDataJsonSerializer(
new ManeuverMainCurveWithEstimationDataJsonSerializer(),
new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer(),
new ManeuverWindJsonSerializer(), new PositionJsonSerializer())),
getNullableValueFromDefault(startBeforeStartLineInSeconds),
getNullableValueFromDefault(endBeforeStartLineInSeconds),
getNullableValueFromDefault(startAfterFinishLineInSeconds),
getNullableValueFromDefault(endAfterFinishLineInSeconds));
JSONObject jsonMarkPassings = serializer.serialize(trackedRace);
String json = jsonMarkPassings.toJSONString();
return Response.ok(json).header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build();
@@ -1056,6 +1073,55 @@ public class RegattasResource extends AbstractSailingServerResource {
return response;
}
@GET
@Produces("application/json;charset=UTF-8")
@Path("{regattaname}/races/{racename}/gpsFixesWithEstimationData")
public Response getGpsFixesWithEstimationData(@PathParam("regattaname") String regattaName,
@PathParam("racename") String raceName, @QueryParam("addWind") @DefaultValue("true") Boolean addWind,
@QueryParam("addNextWaypoint") @DefaultValue("true") Boolean addNextWaypoint,
@QueryParam("smoothFixes") @DefaultValue("true") Boolean smoothFixes,
@QueryParam("startBeforeStartLineInSeconds") @DefaultValue(Integer.MIN_VALUE
+ "") Integer startBeforeStartLineInSeconds,
@QueryParam("endBeforeStartLineInSeconds") @DefaultValue(Integer.MIN_VALUE
+ "") Integer endBeforeStartLineInSeconds,
@QueryParam("startAfterFinishLineInSeconds") @DefaultValue(Integer.MIN_VALUE
+ "") Integer startAfterFinishLineInSeconds,
@QueryParam("endAfterFinishLineInSeconds") @DefaultValue(Integer.MIN_VALUE
+ "") Integer endAfterFinishLineInSeconds) {
Response response;
Regatta regatta = findRegattaByName(regattaName);
if (regatta == null) {
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a regatta with name '" + StringEscapeUtils.escapeHtml(regattaName) + "'.")
.type(MediaType.TEXT_PLAIN).build();
} else {
RaceDefinition race = findRaceByName(regatta, raceName);
if (race == null) {
response = Response.status(Status.NOT_FOUND)
.entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.")
.type(MediaType.TEXT_PLAIN).build();
} else {
TrackedRace trackedRace = findTrackedRace(regattaName, raceName);
CompetitorTrackWithEstimationDataJsonSerializer serializer = new CompetitorTrackWithEstimationDataJsonSerializer(
getService().getPolarDataService(), new DetailedBoatClassJsonSerializer(),
new GpsFixesWithEstimationDataJsonSerializer(new GPSFixMovingJsonSerializer(),
new ManeuverWindJsonSerializer(), addWind, addNextWaypoint, smoothFixes),
getNullableValueFromDefault(startBeforeStartLineInSeconds),
getNullableValueFromDefault(endBeforeStartLineInSeconds),
getNullableValueFromDefault(startAfterFinishLineInSeconds),
getNullableValueFromDefault(endAfterFinishLineInSeconds));
JSONObject jsonMarkPassings = serializer.serialize(trackedRace);
String json = jsonMarkPassings.toJSONString();
return Response.ok(json).header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build();
}
}
return response;
}
private Integer getNullableValueFromDefault(Integer value) {
return Integer.MIN_VALUE == value ? null : value;
}
@GET
@Produces("application/json;charset=UTF-8")
@Path("{regattaname}/races")
@@ -27,6 +27,9 @@
<li>When adding a YouTube video for tracked races ("Manage Media" in <tt>RaceBoard.html</tt> or "Tracked Races > Audio & Video" in <tt>AdminConsole.html</tt>) the respective video metadata is now read using YouTube API v3.
This functionality used to work some years ago using API v2 but was broken since this API version was discontinued some time ago.
Due to limitations of the new API we can't read the start timepoint of videos by now. You still need to provide this value manually.</li>
<li>In the "Tracked races" tab, within the eponymous section of the AdminConsole, the "Set start
time received" dialog can be used to remove the currently set start time received, by simply
leaving the value empty and confirming the dialog.</li>
</ul>
<h2 class="articleSubheadline">June 2018</h2>
@@ -5,19 +5,19 @@ import com.google.gwt.resources.client.ImageResource;
public interface DataMiningResources extends ClientBundle {
@Source("com/sap/sailing/gwt/ui/client/images/close.png")
@Source("com/sap/sse/datamining/ui/images/close.png")
ImageResource closeIcon();
@Source("com/sap/sailing/gwt/ui/client/images/arrow_left.png")
@Source("com/sap/sse/datamining/ui/images/arrow_left.png")
ImageResource arrowLeftIcon();
@Source("com/sap/sailing/gwt/ui/client/images/arrow_right.png")
@Source("com/sap/sse/datamining/ui/images/arrow_right.png")
ImageResource arrowRightIcon();
@Source("com/sap/sailing/gwt/ui/client/images/plusicon_small.png")
@Source("com/sap/sse/datamining/ui/images/plusicon_small.png")
ImageResource plusIcon();
@Source("com/sap/sailing/gwt/ui/client/images/magnifier_small.png")
@Source("com/sap/sse/datamining/ui/images/magnifier_small.png")
ImageResource searchIcon();
}
@@ -40,6 +40,7 @@ public class Notification {
ress.css().ensureInjected();
snackBar.addStyleName(ress.css().snackbar());
snackBar.getElement().getStyle().setCursor(Cursor.POINTER);
snackBar.getElement().getStyle().setZIndex(99);
notificationAnimation = new Animation() {
@Override
+1 -1
View File
@@ -8,7 +8,7 @@ There are two ways to run the Selenium tests locally on your computer. Either, y
### Firefox Prerequisites
!Since old Firefox version do not work with WindowScaling, ensure that in windows the Font Scaling is set to 100%, else Firefox will not be able to click buttons. You can find this setting at "Settings>Display Settings>Change the size of text, apps and other items"!
!Since old Firefox version do not work with WindowScaling, ensure that in windows the Font Scaling is set to 100%, else Firefox will not be able to click buttons. You can find this setting at "Settings>Display Settings>Change the size of text, apps and other items". Also make sure that Firefox is running maximized (at least at Windows) as per default it is not running maximized!
You have to ensure that your Firefox browser has a profile called "Selenium" and that in this profile the latest version of the GWT plugin is installed. To ensure this, launch the server by choosing the "Sailing Server (Proxy)" or "Sailing Server (No Proxy)" launch config. Then, run the "SailingGWT" launch to start the GWT UI in hosted / development mode. Afterwards you can launch Firefox from the command line with the -p option. On Windows machines, you can do this by pressing the Windows key, then typing "firefox.exe -p". In the profile manager create a profile called Selenium and start Firefow with that profile. Hit the entry page of the AdminConsole by entering `http://127.0.0.1:8888/gwt/AdminConsole.html?gwt.codesvr=127.0.0.1:9997` into the address bar. This will ask you to install the GWT plugin into your Selenium profile. When done, exit the browser. You may use the profile manager again to set your default profile to your original profile.