mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-25 06:58:39 +00:00
Merge branch 'master' into bug4655
Conflicts: java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java
This commit is contained in:
@@ -12,7 +12,7 @@ export JAVA_HOME=/opt/sapjvm_8
|
||||
export JAVA_1_7_HOME=/opt/jdk1.7.0_75
|
||||
export ANDROID_HOME=/opt/android-sdk-linux
|
||||
|
||||
export PATH=$PATH:$JAVA_HOME/bin:/opt/amazon/ec2-api-tools-1.6.8.0/bin:/opt/amazon/bin
|
||||
export PATH=$PATH:$JAVA_HOME/bin:/opt/amazon/ec2-api-tools-1.6.8.0/bin:/opt/amazon/bin:/opt/apache-maven-3.2.1/bin
|
||||
|
||||
export DISPLAY=:2.0
|
||||
|
||||
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
// Aggregates settings by their frequency across all DBs.
|
||||
// Usage: mongo --host <hostname> --port <port> settings-agg.js
|
||||
allDatabases = db.adminCommand({ "listDatabases": 1 }).databases
|
||||
|
||||
collection = 'PREFERENCES'
|
||||
|
||||
acc = {}
|
||||
|
||||
allDatabases.forEach(function(d) {
|
||||
//database = connect('localhost:27017/' + d.name)
|
||||
database = db.getSiblingDB(d.name)
|
||||
collections = database.getCollectionNames()
|
||||
if (collections.indexOf(collection) >= 0) {
|
||||
usage = database.getCollection(collection).aggregate([{$unwind: "$KEYS_AND_VALUES"}, {$group: {_id:"$KEYS_AND_VALUES.VALUE", total:{$sum:1}}}])
|
||||
|
||||
usage.forEach(function(u) {
|
||||
if (u._id[0] == '{') {
|
||||
if (u._id in acc) {
|
||||
acc[u._id] += u.total
|
||||
} else {
|
||||
acc[u._id] = u.total
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
result = []
|
||||
for (var key in acc) {
|
||||
if (acc.hasOwnProperty(key)) {
|
||||
result.push({"setting": key, "count": acc[key]})
|
||||
}
|
||||
}
|
||||
|
||||
result.sort(function(a, b) { return b.count - a.count })
|
||||
|
||||
for (var i in result) {
|
||||
print("Setting: " + result[i].setting + "\nCount: " + result[i].count + "\n")
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
+5
-2
@@ -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
|
||||
|
||||
+19
-12
@@ -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) {
|
||||
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 {
|
||||
end = trackedRace.getEndOfRace();
|
||||
} else {
|
||||
final TimePoint now = MillisecondsTimePoint.now();
|
||||
if (endOfTracking != null && endOfTracking.before(now)) {
|
||||
end = endOfTracking;
|
||||
} else {
|
||||
end = now;
|
||||
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++;
|
||||
}
|
||||
|
||||
+1
@@ -117,6 +117,7 @@ public enum BoatClassMasterdata {
|
||||
TOM_28_MAX ("Tom 28 MAX", true, 8.48, 2.48, BoatHullType.MONOHULL, true, "Tom 28"),
|
||||
TRIAS ("Trias", true, 9.20, 2.12, BoatHullType.MONOHULL, true),
|
||||
TP52 ("TP52", true, 15.85, 4.35, BoatHullType.MONOHULL, true, "TP 52", "Transpac 52", "Transpac52"),
|
||||
VARIANTA ("Varianta", true, 6.40, 2.10, BoatHullType.MONOHULL, true),
|
||||
VAURIEN ("Vaurien", true, 4.08, 1.47, BoatHullType.MONOHULL, true),
|
||||
VENT_D_OUEST ("Vent d'Ouest", true, 5.85, 1.75, BoatHullType.MONOHULL, true, "VENTDOUEST", "VENTD'OUEST"),
|
||||
VIPER_640 ("Viper 640", true, 6.43, 2.49, BoatHullType.MONOHULL, true),
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.sap.sailing.domain.common.windfinder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public enum AvailableWindFinderSpotCollections {
|
||||
KIELERFOERDE("kielerfoerde"),
|
||||
CHIEMSEE("chiemsee"),
|
||||
STARNBERGERSEE("starnbergersee"),
|
||||
WANNSEE("wannsee"),
|
||||
TRAVEMUENDE("travemuende"),
|
||||
BALTIC_OFFSHORE("baltic_offshore"),
|
||||
LITAUEN("litauen"),
|
||||
HAMBURG_ALSTER("hamburg_alster"),
|
||||
GDANSK("gdansk"),
|
||||
BREST("brest"),
|
||||
SZCZECIN("szczecin"),
|
||||
SANKT_PETERSBURG("sankt_petersburg"),
|
||||
SANKT_MORITZ("sankt_moritz"),
|
||||
PORTO_CERVO("porto_cervo"),
|
||||
MUEGGELSEE("mueggelsee");
|
||||
|
||||
private final String name;
|
||||
|
||||
private AvailableWindFinderSpotCollections(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static List<String> getAllAvailableWindFinderSpotCollectionsInAlphabeticalOrder() {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (AvailableWindFinderSpotCollections awsc : values()) {
|
||||
result.add(awsc.getName());
|
||||
}
|
||||
Collections.sort(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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) {
|
||||
|
||||
+1
-1
@@ -146,7 +146,7 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
|
||||
});
|
||||
}
|
||||
|
||||
private void updateMappings() {
|
||||
public void updateMappings() {
|
||||
try {
|
||||
updateMappingsInternal();
|
||||
} catch (Exception e) {
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+5
-3
@@ -506,12 +506,14 @@ public class TrackImpl<FixType extends Timed> implements Track<FixType> {
|
||||
result = nullElement;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
unlockAfterRead();
|
||||
}
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+229
@@ -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);
|
||||
}
|
||||
}
|
||||
+7
@@ -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;
|
||||
@@ -97,6 +98,12 @@ public interface CompleteManeuverCurveWithEstimationData extends Timed, Position
|
||||
*/
|
||||
Bearing getRelativeBearingToNextMarkAfterManeuver();
|
||||
|
||||
Distance getDistanceToClosestMark();
|
||||
|
||||
Double getDeviationOfManeuverAngleFromTargetTackAngleInDegrees();
|
||||
|
||||
Double getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees();
|
||||
|
||||
/**
|
||||
* Gets whether a mark was crossed within the maneuver curve.
|
||||
*/
|
||||
|
||||
+20
@@ -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();
|
||||
}
|
||||
-46
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
+61
@@ -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);
|
||||
}
|
||||
+155
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+24
-2
@@ -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
|
||||
@@ -93,4 +100,19 @@ public class CompleteManeuverCurveWithEstimationDataImpl implements CompleteMane
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Distance getDistanceToClosestMark() {
|
||||
return distanceToClosestMark;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getDeviationOfManeuverAngleFromTargetTackAngleInDegrees() {
|
||||
return deviationOfManeuverAngleFromTargetTackAngleInDegrees;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees() {
|
||||
return deviationOfManeuverAngleFromTargetJibeAngleInDegrees;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+48
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+120
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+77
-522
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+472
@@ -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);
|
||||
}
|
||||
|
||||
+3
-4
@@ -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;
|
||||
@@ -77,12 +76,12 @@ public class ShardingContext {
|
||||
*/
|
||||
public static void checkConstraint(final ShardingType type, final String shardingInfo) {
|
||||
if (shardingInfo == null || shardingInfo.isEmpty()) {
|
||||
logger.severe("Empty sharding constraint");
|
||||
logger.warning("Empty sharding constraint");
|
||||
return;
|
||||
}
|
||||
final ThreadLocal<String> shardingHolder = shardingMap.get(type);
|
||||
if (shardingHolder == null) {
|
||||
logger.log(Level.SEVERE, "No current sharding context set for " + type.name(), new RuntimeException());
|
||||
logger.log(Level.WARNING, "No current sharding context set for " + type.name(), new RuntimeException());
|
||||
return;
|
||||
}
|
||||
String currentShardingInfo = shardingHolder.get();
|
||||
@@ -99,7 +98,7 @@ public class ShardingContext {
|
||||
}
|
||||
|
||||
public static void clearShardingConstraint(ShardingType type) {
|
||||
ThreadLocal<String> shardingHolder = shardingMap.remove(type);
|
||||
ThreadLocal<String> shardingHolder = shardingMap.get(type);
|
||||
if (shardingHolder != null) {
|
||||
shardingHolder.remove();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+5
-1
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+14
-45
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+37
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+14
-2
@@ -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);
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -57,8 +57,7 @@ Require-Bundle: com.sap.sailing.domain,
|
||||
com.sap.sailing.expeditionconnector,
|
||||
com.sap.sailing.expeditionconnector.common,
|
||||
org.mp4parser.isoparser;bundle-version="1.9.31",
|
||||
com.sap.sailing.domain.windfinderadapter,
|
||||
com.sap.sse.datamining.ui;bundle-version="1.0.0"
|
||||
com.sap.sse.datamining.ui
|
||||
Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Import-Package: javax.servlet;version="3.1.0",
|
||||
|
||||
+1
@@ -128,6 +128,7 @@ public class BoatClassImageResolver {
|
||||
boatClassIconsMap.put(BoatClassMasterdata.TOM_28_MAX.getDisplayName(), imageResources.Tom28MaxIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.TP52.getDisplayName(), imageResources.TP52Icon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.TRIAS.getDisplayName(), imageResources.TriasIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.VARIANTA.getDisplayName(), imageResources.VariantaIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.VAURIEN.getDisplayName(), imageResources.VaurienIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.VENT_D_OUEST.getDisplayName(), imageResources.VentdOuestIcon());
|
||||
boatClassIconsMap.put(BoatClassMasterdata.VIPER_640.getDisplayName(), imageResources.Viper640Icon());
|
||||
|
||||
+4
@@ -443,4 +443,8 @@ public interface BoatClassImageResources extends ClientBundle {
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/VAURIEN.png")
|
||||
@ImageOptions(preventInlining = true)
|
||||
ImageResource VaurienIcon();
|
||||
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/VARIANTA.png")
|
||||
@ImageOptions(preventInlining = true)
|
||||
ImageResource VariantaIcon();
|
||||
}
|
||||
|
||||
+5
-3
@@ -3,6 +3,7 @@ package com.sap.sailing.gwt.home.desktop.places.fakeseries;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.sap.sailing.domain.common.DetailType;
|
||||
@@ -103,9 +104,10 @@ public class EventSeriesAnalyticsDataManager {
|
||||
MultiRaceLeaderboardSettings leaderboardSettings,
|
||||
String preselectedLeaderboardName, String leaderboardGroupName,
|
||||
String metaLeaderboardName, boolean showRaceDetails, boolean autoExpandLastRaceColumn, Iterable<DetailType> availableDetailTypes) {
|
||||
if(multiLeaderboardPanel == null) {
|
||||
SailingServiceAsync sailingService = sailingCF.getSailingService(()-> metaLeaderboardName);
|
||||
multiLeaderboardPanel = new MultiLeaderboardProxyPanel(parent, context, sailingService, metaLeaderboardName,
|
||||
if (multiLeaderboardPanel == null) {
|
||||
final Function<String, SailingServiceAsync> sailingServiceFactory = leaderBoardName -> sailingCF
|
||||
.getSailingService(() -> leaderBoardName);
|
||||
multiLeaderboardPanel = new MultiLeaderboardProxyPanel(parent, context, sailingServiceFactory, metaLeaderboardName,
|
||||
asyncActionsExecutor, timer, true /* isEmbedded */,
|
||||
preselectedLeaderboardName, errorReporter, StringMessages.INSTANCE,
|
||||
showRaceDetails, autoExpandLastRaceColumn, leaderboardSettings, flagImageResolver, availableDetailTypes);
|
||||
|
||||
+5
@@ -4,6 +4,11 @@
|
||||
<div id="mainContent">
|
||||
<h4 class="articleHeadline">What's New - SAP Sailing Analytics</h4>
|
||||
<div class="innerContent">
|
||||
<h5 class="articleSubheadline">July 2018</h5>
|
||||
<ul class="bulletList">
|
||||
<li>In <tt>RaceBoard.html</tt> when zooming into a specific time range, the time slider will provide a "reset zoom" button in expanded mode.</li>
|
||||
</ul>
|
||||
|
||||
<h5 class="articleSubheadline">June 2018</h5>
|
||||
<ul class="bulletList">
|
||||
<li>In <tt>RaceBoard.html</tt> the availability of media is now visualized under the time slider via a bar overlay. When hovering the bar, the title of the video is shown.</li>
|
||||
|
||||
-1
@@ -15,7 +15,6 @@ public abstract class AbstractLeaderboardDialog<LD extends LeaderboardDescriptor
|
||||
protected LD leaderboardDescriptor;
|
||||
|
||||
protected DiscardThresholdBoxes discardThresholdBoxes;
|
||||
protected static final int MAX_NUMBER_OF_DISCARDED_RESULTS = 4;
|
||||
|
||||
public AbstractLeaderboardDialog(String title, LD leaderboardDescriptor, StringMessages stringMessages,
|
||||
Validator<LD> validator, DialogCallback<LD> callback) {
|
||||
|
||||
+13
-12
@@ -3,8 +3,7 @@ package com.sap.sailing.gwt.ui.adminconsole;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gwt.user.client.ui.HasVerticalAlignment;
|
||||
import com.google.gwt.user.client.ui.HorizontalPanel;
|
||||
import com.google.gwt.user.client.ui.Grid;
|
||||
import com.google.gwt.user.client.ui.Label;
|
||||
import com.google.gwt.user.client.ui.LongBox;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
@@ -23,10 +22,11 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog;
|
||||
*
|
||||
*/
|
||||
public class DiscardThresholdBoxes {
|
||||
private static final int MAX_NUMBER_OF_DISCARDED_RESULTS = 4;
|
||||
private static final int MAX_NUMBER_OF_DISCARDED_RESULTS = 15;
|
||||
|
||||
private static final int NUMBER_OF_BOXES_PER_LINE = 5;
|
||||
|
||||
private final LongBox[] discardThresholdBoxes;
|
||||
private final DataEntryDialog<?> parent;
|
||||
|
||||
/**
|
||||
* The widget used to represent the UI
|
||||
@@ -38,7 +38,6 @@ public class DiscardThresholdBoxes {
|
||||
}
|
||||
|
||||
public DiscardThresholdBoxes(DataEntryDialog<?> parent, int[] initialDiscardThresholds, StringMessages stringMessages) {
|
||||
this.parent = parent;
|
||||
discardThresholdBoxes = new LongBox[MAX_NUMBER_OF_DISCARDED_RESULTS];
|
||||
for (int i = 0; i < discardThresholdBoxes.length; i++) {
|
||||
if (initialDiscardThresholds != null && i < initialDiscardThresholds.length) {
|
||||
@@ -82,16 +81,18 @@ public class DiscardThresholdBoxes {
|
||||
|
||||
private Widget createDiscardThresholdBoxesPanel(StringMessages stringMessages) {
|
||||
assert discardThresholdBoxes != null && discardThresholdBoxes.length == MAX_NUMBER_OF_DISCARDED_RESULTS;
|
||||
VerticalPanel vp = new VerticalPanel();
|
||||
final VerticalPanel vp = new VerticalPanel();
|
||||
vp.add(new Label(stringMessages.discardRacesFromHowManyStartedRacesOn()));
|
||||
HorizontalPanel hp = new HorizontalPanel();
|
||||
vp.add(hp);
|
||||
hp.setSpacing(3);
|
||||
final Grid grid = new Grid(0, 2*NUMBER_OF_BOXES_PER_LINE);
|
||||
grid.setCellSpacing(3);
|
||||
vp.add(grid);
|
||||
for (int i = 0; i < discardThresholdBoxes.length; i++) {
|
||||
hp.add(new Label("" + (i + 1) + "."));
|
||||
hp.add(discardThresholdBoxes[i]);
|
||||
if (i%NUMBER_OF_BOXES_PER_LINE == 0) {
|
||||
grid.resizeRows(i/NUMBER_OF_BOXES_PER_LINE + 1);
|
||||
}
|
||||
grid.setWidget(i/NUMBER_OF_BOXES_PER_LINE, 2*(i%NUMBER_OF_BOXES_PER_LINE), new Label("" + (i + 1) + "."));
|
||||
grid.setWidget(i/NUMBER_OF_BOXES_PER_LINE, 2*(i%NUMBER_OF_BOXES_PER_LINE)+1, discardThresholdBoxes[i]);
|
||||
}
|
||||
parent.alignAllPanelWidgetsVertically(hp, HasVerticalAlignment.ALIGN_MIDDLE);
|
||||
return vp;
|
||||
}
|
||||
|
||||
|
||||
+8
-8
@@ -40,22 +40,20 @@ public class EventDetailsComposite extends Composite {
|
||||
private final SimpleAnchorListComposite imageURLList;
|
||||
private final SimpleAnchorListComposite videoURLList;
|
||||
private final SimpleStringListComposite leaderboardGroupList;
|
||||
private final SimpleStringListComposite windfinderSpotCollectionsList;
|
||||
|
||||
private final CaptionPanel mainPanel;
|
||||
|
||||
public EventDetailsComposite(final SailingServiceAsync sailingService, final ErrorReporter errorReporter, final StringMessages stringMessages) {
|
||||
super();
|
||||
this.stringMessages = stringMessages;
|
||||
|
||||
event = null;
|
||||
mainPanel = new CaptionPanel(stringMessages.regatta());
|
||||
VerticalPanel vPanel = new VerticalPanel();
|
||||
mainPanel.add(vPanel);
|
||||
|
||||
int rows = 17;
|
||||
Grid grid = new Grid(rows, 2);
|
||||
vPanel.add(grid);
|
||||
|
||||
int currentRow = 0;
|
||||
eventId = createLabelAndValueWidget(grid, currentRow++, stringMessages.id(), "IdLabel");
|
||||
eventName = createLabelAndValueWidget(grid, currentRow++, stringMessages.eventName(), "NameLabel");
|
||||
@@ -72,12 +70,11 @@ public class EventDetailsComposite extends Composite {
|
||||
imageURLList = createLabelAndAnchorListWidget(grid, currentRow++, stringMessages.images(), "ImageURLValueList");
|
||||
videoURLList = createLabelAndAnchorListWidget(grid, currentRow++, stringMessages.videos(), "VideoURLValueList");
|
||||
leaderboardGroupList = createLabelAndValueListWidget(grid, currentRow++, stringMessages.leaderboardGroups(), "LeaderboardGroupValueList");
|
||||
|
||||
for(int i=0; i < rows; i++) {
|
||||
windfinderSpotCollectionsList = createLabelAndValueListWidget(grid, currentRow++, stringMessages.windFinderSpotCollectionsList(), "WindFinderSpotCollectionsList");
|
||||
for (int i = 0; i < rows; i++) {
|
||||
grid.getCellFormatter().setVerticalAlignment(i, 0, HasVerticalAlignment.ALIGN_TOP);
|
||||
grid.getCellFormatter().setVerticalAlignment(i, 1, HasVerticalAlignment.ALIGN_TOP);
|
||||
}
|
||||
|
||||
initWidget(mainPanel);
|
||||
}
|
||||
|
||||
@@ -142,7 +139,6 @@ public class EventDetailsComposite extends Composite {
|
||||
.createRegattaOverviewLink(new RegattaOverviewContextDefinition(event.id));
|
||||
eventOverviewURL.setText(regattaOverviewLink);
|
||||
eventOverviewURL.setHref(UriUtils.fromString(regattaOverviewLink));
|
||||
|
||||
List<String> courseAreaNames = new ArrayList<>();
|
||||
if (event.venue.getCourseAreas() != null && event.venue.getCourseAreas().size() > 0) {
|
||||
for (CourseAreaDTO courseArea : event.venue.getCourseAreas()) {
|
||||
@@ -150,7 +146,6 @@ public class EventDetailsComposite extends Composite {
|
||||
}
|
||||
}
|
||||
courseAreaNamesList.setValues(courseAreaNames);
|
||||
|
||||
List<String> imageURLStringsAsList = new ArrayList<>();
|
||||
for(ImageDTO image: event.getImages()) {
|
||||
imageURLStringsAsList.add(image.getSourceRef());
|
||||
@@ -166,6 +161,11 @@ public class EventDetailsComposite extends Composite {
|
||||
leaderboardGroupNamesAsList.add(leaderboardGroupDTO.getName());
|
||||
}
|
||||
leaderboardGroupList.setValues(leaderboardGroupNamesAsList);
|
||||
List<String> windfinderSpotCollectionsNamesAsList = new ArrayList<>();
|
||||
for (String windfinderSpotCollection : event.getWindFinderReviewedSpotsCollectionIds()) {
|
||||
windfinderSpotCollectionsNamesAsList.add(windfinderSpotCollection);
|
||||
}
|
||||
windfinderSpotCollectionsList.setValues(windfinderSpotCollectionsNamesAsList);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-7
@@ -22,6 +22,7 @@ import com.google.gwt.user.client.ui.TextArea;
|
||||
import com.google.gwt.user.client.ui.TextBox;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
import com.sap.sailing.domain.common.windfinder.AvailableWindFinderSpotCollections;
|
||||
import com.sap.sailing.gwt.ui.client.DataEntryDialogWithDateTimeBox;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
@@ -32,6 +33,7 @@ import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.VenueDTO;
|
||||
import com.sap.sse.gwt.client.IconResources;
|
||||
import com.sap.sse.gwt.client.controls.datetime.DateAndTimeInput;
|
||||
import com.sap.sse.gwt.client.controls.listedit.GenericStringListEditorComposite;
|
||||
import com.sap.sse.gwt.client.controls.listedit.GenericStringListInlineEditorComposite;
|
||||
import com.sap.sse.gwt.client.controls.listedit.StringConstantsListEditorComposite;
|
||||
import com.sap.sse.gwt.client.controls.listedit.StringListInlineEditorComposite;
|
||||
@@ -123,10 +125,10 @@ public abstract class EventDialog extends DataEntryDialogWithDateTimeBox<EventDT
|
||||
* @param leaderboardGroupsOfEvent even though not editable in this dialog, this parameter gives an editing subclass a chance to "park" the leaderboard group
|
||||
* assignments for re-association with the new {@link EventDTO} created by the {@link #getResult} method.
|
||||
*/
|
||||
public EventDialog(EventParameterValidator validator, SailingServiceAsync sailingService, StringMessages stringMessages, List<LeaderboardGroupDTO> availableLeaderboardGroups,
|
||||
public EventDialog(EventParameterValidator validator, SailingServiceAsync sailingService,
|
||||
StringMessages stringMessages, List<LeaderboardGroupDTO> availableLeaderboardGroups,
|
||||
Iterable<LeaderboardGroupDTO> leaderboardGroupsOfEvent, DialogCallback<EventDTO> callback) {
|
||||
super(stringMessages.event(), null, stringMessages.ok(), stringMessages.cancel(), validator,
|
||||
callback);
|
||||
super(stringMessages.event(), null, stringMessages.ok(), stringMessages.cancel(), validator, callback);
|
||||
this.stringMessages = stringMessages;
|
||||
this.availableLeaderboardGroupsByName = new HashMap<>();
|
||||
for (final LeaderboardGroupDTO lgDTO : availableLeaderboardGroups) {
|
||||
@@ -145,7 +147,6 @@ public abstract class EventDialog extends DataEntryDialogWithDateTimeBox<EventDT
|
||||
validateAndUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
courseAreaNameList = new CourseAreaListInlineEditorComposite(Collections.<CourseAreaDTO> emptyList(),
|
||||
new GenericStringListInlineEditorComposite.ExpandedUi<CourseAreaDTO>(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */
|
||||
SuggestedCourseAreaNames.suggestedCourseAreaNames, stringMessages.enterCourseAreaName(), 50));
|
||||
@@ -158,13 +159,17 @@ public abstract class EventDialog extends DataEntryDialogWithDateTimeBox<EventDT
|
||||
new StringConstantsListEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(),
|
||||
leaderboardGroupNames, stringMessages.selectALeaderboardGroup()));
|
||||
leaderboardGroupList.addValueChangeHandler(valueChangeHandler);
|
||||
|
||||
imagesListComposite = new ImagesListComposite(sailingService, stringMessages);
|
||||
videosListComposite = new VideosListComposite(stringMessages);
|
||||
externalLinksComposite = new ExternalLinksComposite(stringMessages);
|
||||
final List<String> suggestedWindFinderSpotCollections = AvailableWindFinderSpotCollections
|
||||
.getAllAvailableWindFinderSpotCollectionsInAlphabeticalOrder() == null ? Collections.emptyList()
|
||||
: AvailableWindFinderSpotCollections
|
||||
.getAllAvailableWindFinderSpotCollectionsInAlphabeticalOrder();
|
||||
windFinderSpotCollectionIdsComposite = new StringListInlineEditorComposite(Collections.<String> emptyList(),
|
||||
new GenericStringListInlineEditorComposite.ExpandedUi<String>(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */
|
||||
Collections.emptyList(), stringMessages.enterIdOfWindfinderReviewedSpotCollection(), 80));
|
||||
new GenericStringListEditorComposite.ExpandedUi<String>(stringMessages,
|
||||
IconResources.INSTANCE.removeIcon(), /* suggestValues */
|
||||
suggestedWindFinderSpotCollections, stringMessages.enterIdOfWindFinderReviewedSpotCollection(), 35));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+19
-23
@@ -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;
|
||||
@@ -18,40 +22,32 @@ public class SetStartTimeReceivedDialog extends DataEntryDialogWithDateTimeBox<D
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+55
-9
@@ -5,7 +5,6 @@ import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.dom.client.Style.Unit;
|
||||
import com.google.gwt.event.dom.client.ChangeEvent;
|
||||
@@ -61,10 +60,15 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
|
||||
private final FlushableCellTable<SwissTimingRaceRecordDTO> raceTable;
|
||||
private final Map<String, SwissTimingConfigurationDTO> previousConfigurations;
|
||||
private final ListBox previousConfigurationsComboBox;
|
||||
private final TextBox eventIdBox;
|
||||
private final TextBox jsonUrlBox;
|
||||
private final TextBox hostnameTextbox;
|
||||
private final IntegerBox portIntegerbox;
|
||||
private final List<SwissTimingRaceRecordDTO> availableSwissTimingRaces = new ArrayList<SwissTimingRaceRecordDTO>();
|
||||
private final String manage2sailBaseAPIUrl = "http://manage2sail.com/api/public/links/event/";
|
||||
private final String manage2sailAPIaccessToken = "?accesstoken=bDAv8CwsTM94ujZ";
|
||||
private final String manage2sailUrlAppendix = "&mediaType=json&includeRaces=true";
|
||||
private final String eventIdPattern = "[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}";
|
||||
|
||||
public SwissTimingEventManagementPanel(final SailingServiceAsync sailingService,
|
||||
ErrorReporter errorReporter,
|
||||
@@ -84,7 +88,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
|
||||
captionPanelConnections.setContentWidget(verticalPanel);
|
||||
captionPanelConnections.setStyleName("bold");
|
||||
|
||||
Grid connectionsGrid = new Grid(5, 2);
|
||||
Grid connectionsGrid = new Grid(6, 2);
|
||||
verticalPanel.add(connectionsGrid);
|
||||
|
||||
previousConfigurations = new HashMap<String, SwissTimingConfigurationDTO>();
|
||||
@@ -109,20 +113,37 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
|
||||
|
||||
connectionsGrid.setWidget(0, 0, new Label(stringMessages.swissTimingEvents() + ":"));
|
||||
connectionsGrid.setWidget(0, 1, previousConfigurationsComboBox);
|
||||
connectionsGrid.setWidget(1, 0, new Label("Manage2Sail Event-URL (json):"));
|
||||
connectionsGrid.setWidget(1, 1, jsonUrlBox);
|
||||
|
||||
eventIdBox = new TextBox();
|
||||
eventIdBox.getElement().getStyle().setWidth(30, Unit.EM);
|
||||
eventIdBox.setTitle(stringMessages.manage2SailEventIdBoxTooltip());
|
||||
connectionsGrid.setWidget(1, 0, new Label(stringMessages.manage2SailEventIdBox() + ":"));
|
||||
connectionsGrid.setWidget(1, 1, eventIdBox);
|
||||
eventIdBox.addChangeHandler(event -> {
|
||||
if (eventIdBox.getValue() != "") {
|
||||
updateUrlFromEventId(eventIdBox.getValue());
|
||||
}
|
||||
});
|
||||
|
||||
connectionsGrid.setWidget(2, 0, new Label(stringMessages.manage2SailEventURLBox() + ":"));
|
||||
connectionsGrid.setWidget(2, 1, jsonUrlBox);
|
||||
jsonUrlBox.addChangeHandler(event -> {
|
||||
if (jsonUrlBox.getValue() != "") {
|
||||
updateEventIdFromUrl(jsonUrlBox.getValue());
|
||||
}
|
||||
});
|
||||
|
||||
hostnameTextbox = new TextBox();
|
||||
portIntegerbox = new IntegerBox();
|
||||
|
||||
connectionsGrid.setWidget(2, 0, new Label(stringConstants.hostname() + ":"));
|
||||
connectionsGrid.setWidget(2, 1, hostnameTextbox);
|
||||
connectionsGrid.setWidget(3, 0, new Label(stringConstants.hostname() + ":"));
|
||||
connectionsGrid.setWidget(3, 1, hostnameTextbox);
|
||||
|
||||
connectionsGrid.setWidget(3, 0, new Label(stringConstants.port() + ":"));
|
||||
connectionsGrid.setWidget(3, 1, portIntegerbox);
|
||||
connectionsGrid.setWidget(4, 0, new Label(stringMessages.manage2SailPort() + ":"));
|
||||
connectionsGrid.setWidget(4, 1, portIntegerbox);
|
||||
|
||||
Button btnListRaces = new Button(stringConstants.listRaces());
|
||||
connectionsGrid.setWidget(4, 1, btnListRaces);
|
||||
connectionsGrid.setWidget(5, 1, btnListRaces);
|
||||
btnListRaces.addClickHandler(new ClickHandler() {
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
@@ -327,6 +348,31 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function tries to infer a valid JsonUrl for any input given that matches the pattern of an event Id from
|
||||
* M2S. If there is an event id detected the Json Url gets updated and the event Id textbox is filled with the
|
||||
* detected event Id. The ID pattern is defined in {@link eventIdPattern}.
|
||||
*/
|
||||
private void updateUrlFromEventId(String eventIdTextbox) {
|
||||
if (eventIdTextbox.matches(".*" + eventIdPattern + ".*")) {
|
||||
final String inferredEventId = eventIdTextbox.replaceFirst(".*(" + eventIdPattern + ").*", "$1");
|
||||
jsonUrlBox.setValue(
|
||||
manage2sailBaseAPIUrl + inferredEventId + manage2sailAPIaccessToken + manage2sailUrlAppendix);
|
||||
eventIdBox.setValue(inferredEventId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #updateUrlFromEventId} this function tries to extract a M2S event Id by looking at the given
|
||||
* url in the Json Url Textbox. The value of {@link eventIdBox} is then set to the event ID inferred from the Json Url.
|
||||
*/
|
||||
private void updateEventIdFromUrl(String jsonUrlTextBox) {
|
||||
if (jsonUrlTextBox.matches("http://manage2sail.com/.*" + eventIdPattern + ".*")) {
|
||||
final String inferredEventId = jsonUrlTextBox.replaceFirst(".*(" + eventIdPattern + ").*", "$1");
|
||||
eventIdBox.setValue(inferredEventId);
|
||||
}
|
||||
}
|
||||
|
||||
private ListHandler<SwissTimingRaceRecordDTO> getRaceTableColumnSortHandler(List<SwissTimingRaceRecordDTO> raceRecords,
|
||||
Column<SwissTimingRaceRecordDTO, ?> regattaNameColumn, Column<SwissTimingRaceRecordDTO, ?> seriesNameColumn,
|
||||
Column<SwissTimingRaceRecordDTO, ?> nameColumn, Column<SwissTimingRaceRecordDTO, ?> trackingStartColumn,
|
||||
|
||||
-2
@@ -47,12 +47,10 @@ public class TrackedRacesManagementPanel extends AbstractRaceManagementPanel {
|
||||
}
|
||||
@Override
|
||||
public void onSuccess(RaceDTO result) {
|
||||
if (result != null) {
|
||||
selectedRaceDTO = result;
|
||||
refreshSelectedRaceData();
|
||||
TrackedRacesManagementPanel.this.regattaRefresher.fillRegattas();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@Override
|
||||
|
||||
+8
@@ -1,5 +1,7 @@
|
||||
package com.sap.sailing.gwt.ui.client;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import com.google.gwt.user.client.rpc.RemoteService;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.dto.VideoMetadataDTO;
|
||||
@@ -35,4 +37,10 @@ public interface MediaService extends RemoteService {
|
||||
* Obtains a MediaTrack for the given literal url, if one exists, {@code null} otherwise
|
||||
*/
|
||||
MediaTrack getMediaTrackByUrl(String url);
|
||||
|
||||
/**
|
||||
* Obtains metadata from the youtube api
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
VideoMetadataDTO checkYoutubeMetadata(String url) throws UnsupportedEncodingException;
|
||||
}
|
||||
|
||||
+2
@@ -35,4 +35,6 @@ public interface MediaServiceAsync {
|
||||
|
||||
void getMediaTrackByUrl(String url, AsyncCallback<MediaTrack> asyncCallback);
|
||||
|
||||
void checkYoutubeMetadata(String url, AsyncCallback<VideoMetadataDTO> asyncCallback);
|
||||
|
||||
}
|
||||
|
||||
+8
-2
@@ -153,7 +153,6 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
|
||||
String legDetailsToShow();
|
||||
String columnMoveUp();
|
||||
String columnMoveDown();
|
||||
String port();
|
||||
String raceStartTimeColumn();
|
||||
String showOnlySelectedCompetitors();
|
||||
String showSelectedCompetitorsInfo();
|
||||
@@ -1182,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();
|
||||
@@ -1972,7 +1972,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
|
||||
String windFinderWindSourceTypeTooltip();
|
||||
String windFinder();
|
||||
String enterTagsForTheVideo();
|
||||
String enterIdOfWindfinderReviewedSpotCollection();
|
||||
String enterIdOfWindFinderReviewedSpotCollection();
|
||||
String enterTagsForTheImage();
|
||||
String unableToResolveWindFinderSpotId(String id, String message);
|
||||
String windFinderWeatherData();
|
||||
@@ -2076,7 +2076,13 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
|
||||
String multiUrlChangeNewURL();
|
||||
String multiUrlNoPrefixWarning();
|
||||
String multiUrlChangeExplain();
|
||||
String resetZoom();
|
||||
String lastEvent(String locationOrVenue);
|
||||
String teaserOverallLinkToolTip();
|
||||
String gender();
|
||||
String manage2SailEventIdBox();
|
||||
String manage2SailEventURLBox();
|
||||
String manage2SailEventIdBoxTooltip();
|
||||
String manage2SailPort();
|
||||
String windFinderSpotCollectionsList();
|
||||
}
|
||||
|
||||
+9
-3
@@ -153,7 +153,6 @@ overallDetailsToShow=Overall Details
|
||||
legDetailsToShow=Leg Details
|
||||
columnMoveUp=Up
|
||||
columnMoveDown=Down
|
||||
port=Port
|
||||
raceStartTimeColumn=Race Started
|
||||
showOnlySelectedCompetitors=Show only selected competitors
|
||||
showSelectedCompetitorsInfo=Show info box for selected competitors
|
||||
@@ -1168,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''
|
||||
@@ -1977,7 +1977,8 @@ windFinderWindSourceTypeName=WindFinder
|
||||
windFinderWindSourceTypeTooltip=Measured wind from one or more spots of www.windfinder.com
|
||||
windFinder=WindFinder
|
||||
enterTagsForTheVideo=Enter tags for the video
|
||||
enterIdOfWindfinderReviewedSpotCollection=Enter ID of a reviewed WindFinder spot collection, e.g., "schilksee"
|
||||
enterIdOfWindFinderReviewedSpotCollection=Enter ID of a reviewed WindFinder spot collection, e.g., "schilksee"
|
||||
windFinderSpotCollectionsList=WindFinder spot collections
|
||||
enterTagsForTheImage=Enter tags for the image
|
||||
unableToResolveWindFinderSpotId=Unable to resolve WindFinder spot with ID {0}: {1}
|
||||
windFinderWeatherData=Weather Data
|
||||
@@ -2070,6 +2071,11 @@ multiUrlChangeSave=Save url changes
|
||||
multiUrlChangeNewURL=new url
|
||||
multiUrlNoPrefixWarning=No common prefix was found, this usually means that not all selected videos are hosted at the same location currently. Proceed at own risk!
|
||||
multiUrlChangeExplain=This dialog will bulk replace common parts at the start of mediatrack urls. Make sure all urls start with the same prefix! Also please take a look at the new url column, and test the resulting urls before pressing save!
|
||||
resetZoom=Reset zoom
|
||||
lastEvent=Last Event: {0}
|
||||
teaserOverallLinkToolTip=To see the overall series, click the yellow corner
|
||||
gender=Gender
|
||||
manage2SailEventIdBox=Manage2Sail Event ID
|
||||
manage2SailEventURLBox=Manage2Sail EventURL (json)
|
||||
manage2SailEventIdBoxTooltip=Event ID or a URL that contains the event ID from Manage2Sail
|
||||
manage2SailPort=Port
|
||||
+9
-4
@@ -155,7 +155,6 @@ overallDetailsToShow=Regatta-Details
|
||||
legDetailsToShow=Schenkeldetails
|
||||
columnMoveUp=Hoch
|
||||
columnMoveDown=Runter
|
||||
port=Backbord
|
||||
raceStartTimeColumn=Rennstart
|
||||
showOnlySelectedCompetitors=Nur ausgewählte Teilnehmer anzeigen
|
||||
showSelectedCompetitorsInfo=Infobox für ausgewählte Teilnehmer anzeigen
|
||||
@@ -1155,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''
|
||||
@@ -1973,8 +1972,9 @@ errorWhileSlicingARace=Ein Fehler ist beim Schneiden eines Rennens aufgetreten
|
||||
windFinderWindSourceTypeName=WindFinder
|
||||
windFinderWindSourceTypeTooltip=Gemessener Wind von einer oder mehreren Stationen von www.windfinder.com
|
||||
windFinder=WindFinder
|
||||
windFinderSpotCollectionsList=WindFinder Messstellen-Sammlungen
|
||||
enterTagsForTheVideo=Tags zum Video erfassen
|
||||
enterIdOfWindfinderReviewedSpotCollection=ID einer geprüften WindFinder Messstellen-Sammlung eingeben, z.B. "schilksee"
|
||||
enterIdOfWindFinderReviewedSpotCollection=ID einer geprüften WindFinder Messstellen-Sammlung eingeben, z.B. "schilksee"
|
||||
enterTagsForTheImage=Tags zum Bild erfassen
|
||||
unableToResolveWindFinderSpotId=WindFinder-Station mit Kennung {0} wurde nicht gefunden: {1}
|
||||
windFinderWeatherData=Wetter​daten
|
||||
@@ -2066,6 +2066,11 @@ multiUrlChangeSave=Änderungen der URLs speichern
|
||||
multiUrlChangeNewURL=Neue Url
|
||||
multiUrlNoPrefixWarning=Es wurde kein gemeinsamer Präfix gefunden, dies deutet darauf hin, dass nicht alle ausgewählten Videos vom gleichen Host kommen. Auf eigene Gefahr fortsetzen!
|
||||
multiUrlChangeExplain=Dieser Dialog ermöglicht es, mehrere URL-Anfänge gleichzeitig auszutauschen. Hierzu müssen diese den gleichen Präfix besitzen. Bitte unbedingt die Spalte mit den neuen URLs beachten und diese testen bevor gespeichert wird!
|
||||
resetZoom=Reset zoom
|
||||
lastEvent=Letztes Event: {0}
|
||||
teaserOverallLinkToolTip=Um die Serien-Übersicht zu sehen, in die gelbe Ecke klicken
|
||||
gender=Geschlecht
|
||||
manage2SailEventId=Manage2Sail Event ID
|
||||
manage2SailEventURL=Manage2Sail Event URL (json)
|
||||
manage2SailEventIdBoxTooltip=Event ID von Manage 2 Sail oder URL die die Event ID beinhaltet
|
||||
manage2SailPort=Port
|
||||
+13
-115
@@ -9,12 +9,10 @@ trackedBefore=Historial de eventos rastreados
|
||||
general=General
|
||||
listRaces=Listar carreras
|
||||
listRegattas=Listar regatas
|
||||
numberPairResultsPresenter=Diagrama de dispersión
|
||||
wind=Viento
|
||||
maneuverType=Maniobra
|
||||
windPanelLabel=Este es el panel de vientos, y hasta el momento está totalmente vacío.
|
||||
refresh=Refrescar
|
||||
remove=Eliminar
|
||||
removeNumber=Eliminar ({0})
|
||||
windSource=Fuente de vientos
|
||||
dampeningInterval=Intervalo de amortiguación
|
||||
@@ -53,7 +51,6 @@ trackedRaceConnectedToSelectedRaceName=Carrera rastreada conectada al nombre de
|
||||
linkToColumn=Enlace a columna
|
||||
unlink=Quitar enlace
|
||||
leaderboardName=Nombre de tabla de clasificación
|
||||
cancel=Cancelar
|
||||
pleaseEnterAName=Indique un nombre
|
||||
pleaseEnterABoatClass=Indique una clase de embarcación
|
||||
discardRacesFromHowManyStartedRacesOn=Descartar un inicio de carrera más con cuántas carreras iniciadas
|
||||
@@ -65,7 +62,6 @@ startingFromNumberOfRaces=Empezando por cuántas carreras
|
||||
renameLeaderboard=Cambiar nombre de tabla de clasificación
|
||||
addColumnToLeaderboard=Añadir columna a tabla de clasificación
|
||||
pleaseEnterNameForNewRaceColumn=Indique un nombre para la nueva columna de carrera
|
||||
ok=OK
|
||||
medalRace=Medal Race
|
||||
renameRace=Cambiar nombre de carrera
|
||||
openSelectedLeaderboard=Abrir tabla de clasificación seleccionada
|
||||
@@ -89,7 +85,6 @@ sapSailingAnalytics=Sailing Analytics
|
||||
leaderboard=Tabla de clasificación
|
||||
leaderboards=Tablas de clasificación
|
||||
leaderboardSettings=Opciones de tabla de clasificación
|
||||
settings=Opciones
|
||||
selectAtLeastOneLegDetail=Seleccione al menos un detalle de tramo
|
||||
currentSpeedOverGroundInKnots=SOG (speed over ground)
|
||||
currentSpeedOverGroundInKnotsTooltip=Velocidad actual sobre el fondo.
|
||||
@@ -158,7 +153,6 @@ overallDetailsToShow=Detalles generales
|
||||
legDetailsToShow=Detalles de tramo
|
||||
columnMoveUp=Arriba
|
||||
columnMoveDown=Abajo
|
||||
port=Babor
|
||||
raceStartTimeColumn=Carrera iniciada
|
||||
showOnlySelectedCompetitors=Mostrar solo competidores seleccionados
|
||||
showSelectedCompetitorsInfo=Mostrar cuadro de información para competidores seleccionados
|
||||
@@ -173,7 +167,6 @@ tacks=Bordadas
|
||||
jibes=Trasluchadas
|
||||
penaltyCircles=Círculos de penalización
|
||||
medalRaceIsNull=Valor de regata final no permitido
|
||||
configuration=Configuración
|
||||
maneuverTypes=Maniobras
|
||||
chooseChart=Seleccionar gráfico
|
||||
distanceTraveled=Distancia viajada
|
||||
@@ -191,13 +184,11 @@ secondsPerNauticalMileUnit=s/NM
|
||||
metersUnit=m
|
||||
millimetersUnit=mm
|
||||
degreesUnit=°
|
||||
close=Cerrar
|
||||
compareCompetitors=Comparar competidores
|
||||
description=Descripción
|
||||
sailNumber=Número de vela
|
||||
country=País
|
||||
no3LetterCodes=No es posible encontrar los códigos IOC de 3 letras.
|
||||
add=Añadir
|
||||
delete=Borrar
|
||||
showCharts=Mostrar gráficos
|
||||
raceWithThisNameAlreadyExists=Ya existe una carrera con este nombre.
|
||||
@@ -269,7 +260,6 @@ printHint=Imprime la versión aplicada
|
||||
blockedApplyButton=Los competidores registrados difieren de los competidores de la lista de emparejamientos
|
||||
multiplierInfo=Multiplica los flights y los crea uno junto a otro de modo que sea posible una competición con menos modificaciones de embarcación.
|
||||
noPairingListAvailable=La función de impresión solo está disponible si ya se han aplicado las listas de emparejamientos a los registros de carrera de las clasificaciones seleccionadas.
|
||||
settingsForComponent=Opciones para {0}
|
||||
noEventsFound=No se han encontrado eventos
|
||||
noEventSelected=Ningún evento seleccionado
|
||||
noLeaderboardsFound=No se han encontrado tablas de clasificación
|
||||
@@ -307,8 +297,6 @@ leaderboardGroup=Grupo de tablas de clasificación
|
||||
pleaseEnterNonEmptyDescription=Indique una descripción no vacía
|
||||
groupWithThisNameAlreadyExists=Ya existe un grupo de tablas de clasificación con este nombre.
|
||||
detailsOfLeaderboardGroup=Detalles del grupo de tablas de clasificación
|
||||
edit=Editar
|
||||
save=Grabar
|
||||
abort=Detener
|
||||
noLeaderboardGroupWithNameFound=No se ha encontrado ningún grupo de tablas de clasificación con el nombre {0}
|
||||
overview=Resumen
|
||||
@@ -343,7 +331,6 @@ degreesShort=grados
|
||||
untracked=No rastreado
|
||||
delayForLiveMode=Retraso para modo en directo
|
||||
notAvailable=No disponible
|
||||
details=Detalles
|
||||
noGroupSelected=Ningún grupo seleccionado
|
||||
combinedWindSourceTypeName=Combinado
|
||||
legMiddleWindSourceTypeName=Mitad de tramo
|
||||
@@ -425,7 +412,6 @@ simulateAsLiveRace=Simular como carrera en directo
|
||||
simulateWithOffset=Offset antes de inicio en minutos:
|
||||
boatClassDoesNotMatchSelectedRegatta=Las carreras seleccionadas contienen clases de embarcación distintas de la clase ''''{0}'''' de la regata seleccionada. No se cargará ninguna carrera.
|
||||
regattaExistForSelectedBoatClass=Hay al menos una regata para las clases de embarcación seleccionadas. ¿Realmente desea crear regata(s) por defecto para esta(s) carrera(s)?
|
||||
reload=Recargar
|
||||
addRegatta=Añadir regata...
|
||||
importRegattas=Importar regatas...
|
||||
exchangeName=Intercambiar nombre
|
||||
@@ -647,7 +633,6 @@ totalNetPointsColumnTooltip=El total de puntos netos de un competidor de la rega
|
||||
windData=Datos de vientos
|
||||
gpsData=Datos de GPS
|
||||
status=Status
|
||||
noDataFound=No se han encontrado datos
|
||||
displayName=Visualizar nombre
|
||||
histogram=Histograma
|
||||
numberOfDataPoints=Número de puntos de datos
|
||||
@@ -895,12 +880,9 @@ legType=Tipo de tramo
|
||||
sailID=Número de vela
|
||||
seriesLeaderboard=Tabla de clasificación de serie
|
||||
regattaLeaderboards=Clasificaciones de regata
|
||||
clearSelection=Borrar selección
|
||||
running=En ejecución
|
||||
runAsSubstantive=Ejecutar
|
||||
done=Hecho
|
||||
lastFinished=Último que ha terminado
|
||||
run=Ejecutar
|
||||
times=tiempos
|
||||
dataAmount=Cantidad de datos
|
||||
averageCleanedServerTime=∅ Tiempo de servidor limpio
|
||||
@@ -924,12 +906,8 @@ selectSheet=Seleccionar hoja
|
||||
cleanedServerTime=Tiempo de servidor limpio
|
||||
overallTime=Tiempo total
|
||||
cleanedOverallTime=Tiempo global limpio
|
||||
dataMiningResult=Resultado de minería de datos
|
||||
groupBy=Agrupar por
|
||||
statisticToCalculate=Calcular estadística
|
||||
queryResultsChartSubtitle=Se ha trabajado en {0} entradas de datos en {1} segundos
|
||||
noQuerySelected=Ninguna consulta seleccionada
|
||||
runAutomatically=Ejecutar automáticamente
|
||||
windImport_Upload=Cargar
|
||||
windImport_Title=Importar viento desde expedición
|
||||
windImport_BoatId=ID de embarcación:
|
||||
@@ -959,14 +937,9 @@ raceTimeTooltip=Tiempo total recorrido en esta carrera, se empieza a medir cuand
|
||||
raceTimeDownwindTooltip=Tiempo total de empopada recorrido en esta carrera
|
||||
raceTimeReachingTooltip=Tiempo total de alcance recorrido en esta carrera
|
||||
raceTimeUpwindTooltip=Tiempo total de ceñida recorrido en esta carrera
|
||||
noStatisticSelectedError=No se ha seleccionado ninguna estadística para calcular
|
||||
noCustomGrouperScriptTextError=El script de grupo está vacío
|
||||
noDimensionToGroupBySelectedError=No se ha seleccionado ninguna dimensión para agrupar
|
||||
noGrouperSelectedError=No se ha seleccionado ningún tipo de agrupador
|
||||
noDataRetrieverChainDefinitonSelectedError=No se ha seleccionado ningún recuperador de datos
|
||||
queryNotValidBecause=No se puede hacer ninguna consulta porque
|
||||
dataMining=Minería de datos
|
||||
errorRunningDataMiningQuery=Se ha producido un error al ejecutar la consulta
|
||||
hideToolbar=Ocultar barra de herramientas
|
||||
showSeriesLeaderboards=Mostrar tablas de clasificación de las series
|
||||
showOverallLeaderboard=Mostrar tabla de clasificación general
|
||||
@@ -989,7 +962,6 @@ id=ID
|
||||
allowReload=Permitir recarga
|
||||
compress=Comprimir
|
||||
compressTooltip=Utilizar solamente si la instancia del servidor de exportación funciona al menos con la confirmación 0fbf6071dea125bec4a56dee55d61c99def4a62e.
|
||||
queryRunner=Ejecutor de la consulta
|
||||
rerunQueryAfterRefresh=Volver a ejecutar la consulta después de actualizar
|
||||
refreshIntervalMustntBeEmpty=El intervalo de actualización no debe estar en blanco
|
||||
selectionTables=Tablas de selección
|
||||
@@ -1074,12 +1046,7 @@ TWATooltip=Ángulo entre la dirección de los competidores y el viento
|
||||
TWA=Ángulo real del viento
|
||||
showBoatClassChartsLabel=También puede visualizar el diagrama global para las clases de embarcación disponibles.
|
||||
showDiagram=Mostrar diagrama
|
||||
runAutomaticallyTooltip=Ejecute la consulta automáticamente después de modificar, por ejemplo, la estadística o la agrupación.
|
||||
rerunQueryAfterRefreshTooltip=Vuelve a ejecutar la consulta después de que se hayan refrescado las tablas.
|
||||
queryDefinitionProvider=Proveedor de definición de consultas
|
||||
statisticProvider=Proveedor de estadísticas
|
||||
calculateThe=Calcular
|
||||
groupingProvider=Proveedor de agrupación
|
||||
releaseNotes=Historial de noticias y lanzamientos
|
||||
hasSplitFleetContiguousScoring=Dividir flotas puntuadas de forma contigua
|
||||
addRaceLogTracker=Añadir rastreador de registro de carrera
|
||||
@@ -1237,7 +1204,6 @@ showAll=Mostrar todo
|
||||
raceVisibilityColumn=Visibilidad
|
||||
enterCarryValueFor=Introducir puntos acumulados para el competidor {0}
|
||||
advanced=Avanzado
|
||||
basedOn=Basado en
|
||||
retrieveWith=Recuperar con
|
||||
mappingDetails=Detalles de asignación
|
||||
deviceMappingQrCodeExplanation=Si utiliza la aplicación de rastreo, también puede añadir la asignación de dispositivos seleccionando un competidor/marca, definiendo las fechas de inicio y fin y escaneando este código QR
|
||||
@@ -1248,10 +1214,6 @@ enterImageURL=Indicar URL de imagen...
|
||||
enterVideoURL=Indicar URL de vídeo...
|
||||
enterSponsorImageURL=Indicar URL de imagen de patrocinador...
|
||||
enterRaceName=Indicar nombre de carrera...
|
||||
serverError=Se ha producido un error al intentar contactar con el servidor. Compruebe la conexión de red e inténtelo de nuevo.
|
||||
remoteProcedureCall=Llamada de procedimiento remoto
|
||||
serverReplies=Servidor responde
|
||||
errorCommunicatingWithServer=Error al comunicar con el servidor
|
||||
userManagement=Gestión de usuarios
|
||||
regattaStructureImport=Importación de la estructura de regata
|
||||
filteredBy=Filtrado por
|
||||
@@ -1279,7 +1241,6 @@ noFleetsDefined=Ninguna flota definida
|
||||
successfullyCreatedRegattas=Regatas creadas con éxito
|
||||
errorTryingToRegisterRacesForTracking=Error al intentar registrar carreras {0} para rastreo: {1}. Compruebe sintaxis URI en directo/grabada.
|
||||
errorDeterminingPolarAvailability=Error al determinar la disponibilidad de datos polares / VPP para la carrera {0}: {1}
|
||||
error=Error
|
||||
fileStorage=Almacenamiento de archivos
|
||||
active=Activo
|
||||
scoringSchemeHighPointEssOverallDescription=Calificación en puntos. El ganador de una prueba obtiene 10 puntos; el segundo, 9 puntos, etc. Si hay un empate en la serie de Extreme Sailing, el empate se resuelve a favor del competidor que ha ganado más pruebas. Si aún persiste el empate, se utiliza el resultado de la última prueba.
|
||||
@@ -1322,7 +1283,6 @@ showCompetitorFullNameColumn=Nombre completo del competidor
|
||||
alwaysShowCompetitorNationalityColumn=Siempre mostrar la nacionalidad del competidor
|
||||
alwaysShowCompetitorNationalityColumnTooltip=Muestra ambos, las banderas de país así como las imágenes de competidor, si están disponibles.
|
||||
loadingDimensionValues=Cargando valores de dimensión
|
||||
runningQuery=Ejecutando consulta
|
||||
inviteBuoyTenders=Invitar a balizadores
|
||||
orMultipleEmails=o varios correos electrónicos separados por una coma
|
||||
courseOverGroundTrueDegreesTooltip=Regata sobre el fondo (Course Over Ground) en grados
|
||||
@@ -1330,11 +1290,6 @@ courseOverGroundTrueDegrees=COG
|
||||
distanceIncludingGateStartInMeters=Distancia (con inicio de puerta de salida)
|
||||
distanceTraveledIncludingGateStartTooltip=Distancia recorrida desde el inicio hasta el final del tramo\o hasta la fecha y hora actual si no se ha acabado el tramo.\nSi el tramo incluye un inicio de puerta de salida, la distancia desde el extremo hasta la posición inicial se incluye\n para que puedan compararse los competidores aunque hayan empezado en tiempos diferentes.
|
||||
raceDistanceTraveledIncludingGateStartTooltip=Distancia recorrida desde el inicio hasta el final de la carrera\o hasta la fecha y hora actual si no se ha acabado la carrera. Para el inicio de la puerta de salida, la distancia desde el extremo hasta la posición inicial se incluye\n para que puedan compararse los competidores aunque hayan empezado en tiempos diferentes.
|
||||
results=Resultados
|
||||
groupName=Nombre de grupo
|
||||
valueAscending=Valor (ascendente)
|
||||
valueDescending=Valor (descendente)
|
||||
sortBy=Ordenado por
|
||||
dashboardHeader=Dashboard
|
||||
dashboardNoWindBotAvailableHeader=Wind Bot no está disponible
|
||||
dashboardNoWindBotAvailableMessage=Para obtener los datos de viento en directo a partir de unidades de medida de viento, asegúrese de que Wind Bot está activado y contectado con SAP Sailing Analytics.
|
||||
@@ -1370,16 +1325,12 @@ dashboardRCBoat=Barco RC
|
||||
fixedMarkPassing=(fijado)
|
||||
suppressedMarkPassing=(suprimido)
|
||||
windUp=Viento al norte (mostrar viento de la parte superior del mapa)
|
||||
filterBy=Filtrar por
|
||||
currentFilterSelection=Seleccion de filtro actual
|
||||
notCapableOfGeneratingACodeForIdentifier=No sé generar un código para este identificador.
|
||||
serverUrl=URL del servidor
|
||||
rotatedFromTrueNorth=Ha rotado {0} grados desde el norte real.
|
||||
clickToToggleWindUp=Golpe/clic para alternar entre visualización mapa viento al norte y norte arriba
|
||||
clickToToggleWindStreamlets=Golpe/clic para mostrar u ocultar corrientes de viento
|
||||
startLineToFirstMarkTriangle=Inicio a la primera marca ({0} m)
|
||||
dataMiningComponentsHaveBeenUpdated=Los componentes de minería de datos se han actualizado
|
||||
dataMiningComponentsNeedReloadDialogMessage=Pulse Recargar para volver a cargar los componentes ahora. De este modo, se descartarán los datos visualizados en ese momento y se ejecutará una consulta por defecto.\nHaga clic en «Cerrar» para no hacer nada. La minería de datos no funcionará correctamente hasta que se hayan vuelto a cargar los componentes.
|
||||
noDataForEvent=Todavía no hay datos para el evento.
|
||||
countriesCount={0,number} países
|
||||
countriesCount[one]={0,number} país
|
||||
@@ -1477,15 +1428,7 @@ noFinishedRaces=Todavía no ha finalizado ninguna carrera
|
||||
racesOverview=Resumen de carreras
|
||||
listFormatLabel=Formato de lista
|
||||
competitionFormatLabel=Formato de competición
|
||||
empty=Vacío
|
||||
runAQuery=Realice una consulta
|
||||
latestRegattaStandings=Últimas posiciones de la regata
|
||||
plainText=Texto sin formato
|
||||
columnChart=Gráfico de columnas
|
||||
columnChartWithErrorBars=Gráfico de columnas con barras de error
|
||||
choosePresentation=Seleccionar presentación
|
||||
cantDisplayDataOfType=No se pueden mostrar datos del tipo {0}
|
||||
shownDecimals=Decimales mostrados
|
||||
openFullscreenView=Abrir vista de pantalla completa
|
||||
closeFullscreenView=Cerrar vista de pantalla completa
|
||||
videosCount={0,number} vídeos
|
||||
@@ -1495,15 +1438,8 @@ photosCount[one]={0,number} foto
|
||||
eventsHaveTakenPlace=Han tenido lugar {0} eventos
|
||||
eventsHaveTakenPlace[one]=Ha tenido lugar un evento
|
||||
raceOffice=Oficina de carrera
|
||||
analyze=Analizar
|
||||
dataMiningSettings=Opciones de minería de datos
|
||||
multiResultsPresenter=Presentador de resultados múltiples
|
||||
plainResultsPresenter=Presentador de resultados simples
|
||||
resultsChart=Gráfico de resultados
|
||||
tabbedResultsPresenter=Presentador de resultados por pestañas
|
||||
polarResultsPresenter=Presentador de resultados de coordenadas polares
|
||||
maneuverSpeedDetailsResultsPresenter=Presentador de resultados detallados de velocidad de maniobra
|
||||
dataMiningRetrieval=Recuperación de datos
|
||||
actionWatch=Observar
|
||||
actionAnalyze=Analizar
|
||||
denoteAllRacesForRaceLogTrackingShorctut=Acceso directo para indicar todas las carreras para un rastreo de registro de carrera
|
||||
@@ -1519,24 +1455,8 @@ defaultName=Por defecto
|
||||
exampleTextForName=Su nombre aparece como:
|
||||
flightsCount={0,number} flights
|
||||
flightsCount[one]={0,number} flight
|
||||
viewQueryDefinition=Ver definición de consulta
|
||||
queryDefinitionViewer=Visor de definición de consulta
|
||||
groupAverageAscending=Promedio de grupo (ascendente)
|
||||
groupAverageDescending=Promedio de grupo (descendente)
|
||||
groupMedianAscending=Mediana de grupo (ascendente)
|
||||
groupMedianDescending=Mediana de grupo (descendente)
|
||||
resultsFoundForSearch={0,number} resultados encontrados para ''''{1}''''
|
||||
resultsFoundForSearch[one]={0,number} resultado encontrado para ''''{1}''''
|
||||
runPredefinedQuery=Ejecutar consulta predefinida
|
||||
selectPredefinedQuery=Seleccionar consulta predefinida
|
||||
predefinedQueryRunner=Programa de ejecución de consulta predefinida
|
||||
developerOptions=Opciones de desarrollador
|
||||
copyToClipboard=Copiar a portapapeles
|
||||
code=Código
|
||||
useClassGetName=Utilizar Class.getName() para nombres de tipo
|
||||
useClassGetNameTooltip=Más resistente frente a modificaciones en la base de código, pero el fragmento de código se puede utilizar solamente en el ámbito donde estén disponibles las clases.
|
||||
useStringLiterals=Utilizar literales de cadena para los nombres de tipo
|
||||
useStringLiteralsTooltip=El fragmento de código puede utilizarse en todas las ubicaciones, pero se romperá si se modifica la base de código.
|
||||
errorLoadingDataWithTryAgain=Error al cargar los datos. Inténtelo de nuevo más tarde.
|
||||
addGalleryPhoto=Añadir foto de galería
|
||||
addStageImage=Añadir imagen de etapa
|
||||
@@ -1556,7 +1476,6 @@ warningForDisabledCompetitors=Los siguientes competidores no se pueden registrar
|
||||
competitorToolTipMessage={0} ya ha sido asignado a la flota {2} en la carrera {3} y, por consiguiente, no se puede asignar a la flota {1} de la misma carrera
|
||||
addMarkToRegatta=Añadir marca a regata
|
||||
selectALeaderboardGroup=Seleccione un grupo de tablas de clasificación...
|
||||
pleaseSelect=Seleccione
|
||||
requiresValidRegatta=Esta página requiere una regata, una columna de carreras y un nombre de flota válidos para identificar la regata que se desea mostrar.
|
||||
couldNotObtainRace=No se ha podido obtener la regata con el nombre {1} para la flota {2} para una regata con el nombre {0}: {3}
|
||||
errorTryingToCreateEmbeddedMap=Error al intentar crear el mapa incrustado: {0}
|
||||
@@ -1836,30 +1755,9 @@ eventRegattaHeaderLegendGpsNo=No hay datos de rastreo
|
||||
eventRegattaHeaderLegendWindNo=No hay datos de viento
|
||||
eventRegattaHeaderLegendVideoNo=No hay transmisiones de vídeo
|
||||
eventRegattaHeaderLegendAudioNo=No hay transmisiones de audio
|
||||
angleInDegree=Ángulo en grado
|
||||
angleInRadian=Ángulo en radián
|
||||
centralAngleInRadian=Ángulo central en radián
|
||||
centralAngleInDegree=Ángulo central en grado
|
||||
kilometers=Kilómetros
|
||||
meters=Metros
|
||||
nauticalMiles=Millas náuticas
|
||||
seaMiles=Millas marinas
|
||||
geographicalMiles=Millas geográficas
|
||||
days=Días
|
||||
hours=Horas
|
||||
minutes=Minutos
|
||||
seconds=Segundos
|
||||
milliseconds=Milisegundos
|
||||
floatNumber=Flotador
|
||||
integer=Entero
|
||||
appendResult=Resultado de estructura append
|
||||
sampleColor=Muestra de color
|
||||
sharedSettingsLink=Enlace con opciones
|
||||
leaderboardPage=Página de clasificación
|
||||
makeDefault=Establecer como predeterminado
|
||||
makeDefaultInProgress=En curso...
|
||||
settingsSavedMessage=Sus opciones actuales se han definido correctamente como estándar
|
||||
settingsSaveErrorMessage=Se ha producido un error durante la definición de sus opciones como estándar
|
||||
showLiveNow=Mostrar "En directo ahora"
|
||||
useOnlyOneOfStartTimeInferenceAndControlTrackingFromStartAndFinishTimes=Utilice solo una "Inferencia de hora de inicio" y "Control de rastreo de horas de inicio y de fin"
|
||||
unknownLeaderboardType=Tipo de tabla de clasificación {0} desconocido
|
||||
@@ -1878,10 +1776,6 @@ settingsId=ID de opciones
|
||||
documentSettingsId=ID de opciones de documento
|
||||
settingsForId=Opciones para ID ''''{0}''''
|
||||
userProfileSettingsTabDescription=Las opciones del usuario se generan mediante los diálogos de opciones que se encuentran en varios puntos de la página. Esta vista muestra todas sus opciones agrupadas de una manera técnica para los usuarios expertos. Tenga en cuenta que las entradas eliminadas no se pueden restablecer, por lo que tenga precaución al utilizarlas.
|
||||
resetToDefault=Reinicializar a valores por defecto
|
||||
resetToDefaultInProgress=Reinicializando...
|
||||
settingsRemoved=Opciones por defecto restablecidas
|
||||
settingsRemovedError=No se han podido restablecer las opciones por defecto
|
||||
userSettingsFilter=Filtro de opciones
|
||||
requiresRegattaRaceAndLeaderboard=Esta página requiere un nombre de regata, un nombre de carrera y un nombre de tabla de clasificación válidos.
|
||||
couldNotFindRaceInRegatta=No se ha podido obtener la carrera con el nombre {0} para la regata con el nombre {1}
|
||||
@@ -1913,7 +1807,6 @@ errorFetchingDimensionData=Error al obtener los valores de dimensión de {0} : {
|
||||
errorFetchingStatistics=Error al obtener las estadísticas disponibles desde el servidor: {0}
|
||||
errorFetchingAggregators=Error al obtener los agregadores disponibles desde el servidor: {0}
|
||||
errorLoadingDataRetrieverChainDefinitions=Error al recuperar las DataRetrieverChainDefinitions disponibles: {0}
|
||||
errorFetchingComponentsChangedTimepoint=Error al obtener la fecha modificada de los componentes a partir del servidor: {0}
|
||||
errorRunningQuery=Error al ejecutar la consulta: {0}
|
||||
errorReadingWindFixes=Error al leer las correcciones de viento {0}
|
||||
errorAddingWindFixForRace=Error al añadir una corrección de viento para la carrera {0}: {1}
|
||||
@@ -1972,7 +1865,7 @@ anniversaryMajorCountdownTeaser[one]=Cuenta atrás. Solo falta {0,number,#,###}
|
||||
anniversaryMajorCountdownDescription=Estamos celebrando nuestra {0,number,#,###} carrera en www.sapsailing.com. ¿Qué carrera batirá la marca? El organizador de esta carrera de aniversario recibirá un total de 10 000 euros para fines benéficos. El ganador se anunciará en este sitio web. Estén atentos y cuenten con nosotros.
|
||||
anniversaryRepdigitCountdownTeaser=Cuenta atrás. Solo faltan {0,number,#,###} carreras hasta la {1,number,#,###} carrera.
|
||||
anniversaryRepdigitCountdownTeaser[one]=Cuenta atrás. Solo falta {0,number,#,###} carrera hasta la {1,number,#,###} carrera.
|
||||
anniversaryRepdigitCountdownDescription=Celebramos nuestra carrera del número afortunado. ¿Quién realizará la {0,number, #,###} carrera en www.sapsailing.com? Los participantes de esta carrera obtendrán un festival de verano gratuito de primera clase por parte de SAP. Los ganadores se anunciarán en este sitio web. Estén atentos y cuenten con nosotros.
|
||||
anniversaryRepdigitCountdownDescription=Celebramos nuestras carreras del número afortunado. ¿Quién realizará la {0,number, #,###} carrera en www.sapsailing.com? Los organizadores obtendrán una barbacoa gratuita con bebidas. Los ganadores se anunciarán en este sitio web. Estén atentos y cuenten con nosotros.
|
||||
anniversaryAnnouncementTeaser=Misión cumplida. {0,number,#,###} Carreras con SAP Sailing Analytics
|
||||
anniversaryAnnouncementDescription=3,2,1... Felicitamos a los participantes de la carrera {0}. ¡Lo conseguísteis! Os agradecemos vuestra confianza en SAP Sailing Analytics y esperamos continuar navegando más de 10 000 carreras con vosotros.
|
||||
anniversaryRaceLinkText=Mostrar carrera de aniversario
|
||||
@@ -1987,11 +1880,6 @@ minimumRideHeightInMetersTooltip=La altura de marcha mínima en metros requerida
|
||||
minimumDurationBetweenAdjacentFoilingSegmentsInSeconds=Duración mínima entre apéndice(s) de foil
|
||||
minimumDurationBetweenAdjacentFoilingSegmentsInSecondsTooltip=Si no está en blanco y el tiempo entre dos apéndices de foil adyacentes es inferior, aquellos apéndices de foil adyacentes se fusionarán en uno solo.
|
||||
needToProvideValidMinimumRideHeight=Requiere proporcionar un valor de altura de marcha mínimo válido en metros.
|
||||
dataMiningErrorMargins=Márgenes de error
|
||||
elements={0} elementos
|
||||
chooseDifferentDimensionTitle=Seleccione una dimensión diferente
|
||||
chooseDifferentDimensionMessage=Seleccione una dimensión diferente para agrupar resultados
|
||||
pleaseSelectADimension=Seleccione una dimensión
|
||||
currentPortDaggerboardRake=Inclinación de la orza de babor
|
||||
currentPortDaggerboardRakeTooltip=La inclinación actual de la orza de babor
|
||||
currentStbdDaggerboardRake=Inclinación de la orza de estribor
|
||||
@@ -2084,7 +1972,7 @@ windFinderWindSourceTypeName=Windfinder
|
||||
windFinderWindSourceTypeTooltip=Viento medido de uno o más puntos de www.windfinder.com
|
||||
windFinder=Windfinder
|
||||
enterTagsForTheVideo=Indique las etiquetas para el vídeo
|
||||
enterIdOfWindfinderReviewedSpotCollection=Indique el ID de una colección de puntos de Windfinder revisada, por ejemplo "schilksee"
|
||||
enterIdOfWindFinderReviewedSpotCollection=Indique el ID de una colección de puntos de Windfinder revisada, por ejemplo "schilksee"
|
||||
enterTagsForTheImage=Indique las etiquetas para la imagen
|
||||
unableToResolveWindFinderSpotId=Imposible resolver el punto Windfinder con ID {0}: {1}
|
||||
windFinderWeatherData=Datos meteorológicos
|
||||
@@ -2157,7 +2045,7 @@ multiVideoURLOfIndex=Inserte el URL de índice del servidor web
|
||||
multiVideoScan=Escanear índice
|
||||
multiVideoLinking=Añadir varios vídeos
|
||||
multiVideoNotAnalyzed=Vídeo aún por analizar
|
||||
multiVideoAlreadyKnown=Vídeo ya tiene pistas multimedia existentes
|
||||
multiVideoAlreadyKnown=Vídeo ya tiene rastreo de medios existente
|
||||
multiVideoClientIsUploading=Vídeo analizado mediante proxy de cliente
|
||||
multiVideoFinishedLinking=Vídeo enlazado
|
||||
multiVideoErrorInAnalyzingFile=Error al analizar el fichero
|
||||
@@ -2169,3 +2057,13 @@ multiVideoIdle=Cola de trabajo inactiva
|
||||
multiVideoDoNoAdd=No añadir
|
||||
multiVideoOffsetInput=Offset de vídeo global en milisegundos:
|
||||
multiVideoDescription=El servidor web debe proporcionar una lista de índices (subcarpetas compatibles si están indexadas) para permitir la detección de ficheros. Tras un análisis inicial de los metadatos contenidos en los ficheros mp4, los vídeos que deben añadirse, deben seleccionarse mediante la columna de la casilla de selección a la izquierda. El botón "Añadir audio/vídeo" creará pistas multimedia para todos los ficheros seleccionados y los añadirá a todas las carreras seleccionadas en la columna de la derecha.
|
||||
multiUrlChangeMediaTrack=Ajustar URLs de rastreo múltiple de medios
|
||||
multiUrlChangeReplace=Reemplazar por
|
||||
multiUrlChangeFind=Buscar
|
||||
multiUrlChangeCannotSave=Se ha producido un error al grabar
|
||||
multiUrlChangeSave=Grabar modificaciones de URL
|
||||
multiUrlChangeNewURL=URL nuevo
|
||||
multiUrlNoPrefixWarning=No existe ningún prefijo común, esto significa que normalmente no todos los vídeos seleccionados se alojan actualmente en la misma ubicación. Proceda bajo su propio riesgo.
|
||||
multiUrlChangeExplain=Este diálogo reemplazará en masa las partes comunes al inicio de los URLs del rastreo de medios. Asegúrate de que todas las URL empiezan con el mismo prefijo. Además, eche un vistazo a la nueva columna del URL, y pruebe los URLs resultantes antes de pulsar Grabar.
|
||||
lastEvent=Último evento: {0}
|
||||
teaserOverallLinkToolTip=Para visualizar las series generales, haga clic en la esquina amarilla
|
||||
|
||||
+15
-117
@@ -9,12 +9,10 @@ trackedBefore=Historique des manifestations suivies
|
||||
general=Généralités
|
||||
listRaces=Lister courses
|
||||
listRegattas=Lister régates
|
||||
numberPairResultsPresenter=Concentration points mesure
|
||||
wind=Vent
|
||||
maneuverType=Manœuvre
|
||||
windPanelLabel=Ceci est le panneau des mesures du vent, vide pour l''instant.
|
||||
refresh=Actualiser
|
||||
remove=Supprimer
|
||||
removeNumber=Supprimer ({0})
|
||||
windSource=Source du vent
|
||||
dampeningInterval=Intervalle d''atténuation
|
||||
@@ -53,7 +51,6 @@ trackedRaceConnectedToSelectedRaceName=Course suivie liée au nom de course sél
|
||||
linkToColumn=Lier à la colonne
|
||||
unlink=Annuler Lier
|
||||
leaderboardName=Nom du palmarès
|
||||
cancel=Annuler
|
||||
pleaseEnterAName=Saisissez un nom.
|
||||
pleaseEnterABoatClass=Saisissez une catégorie de bateau.
|
||||
discardRacesFromHowManyStartedRacesOn=Éliminez une course de plus en commençant au nombre de courses déjà lancées.
|
||||
@@ -65,7 +62,6 @@ startingFromNumberOfRaces=Début au nombre de courses
|
||||
renameLeaderboard=Renommer palmarès
|
||||
addColumnToLeaderboard=Ajouter colonne à palmarès
|
||||
pleaseEnterNameForNewRaceColumn=Saisissez un nom pour la nouvelle colonne de course.
|
||||
ok=OK
|
||||
medalRace=Course médaillée
|
||||
renameRace=Renommer course
|
||||
openSelectedLeaderboard=Ouvrir le palmarès sélectionné
|
||||
@@ -89,7 +85,6 @@ sapSailingAnalytics=Sailing Analytics
|
||||
leaderboard=Palmarès
|
||||
leaderboards=Palmarès
|
||||
leaderboardSettings=Options du palmarès
|
||||
settings=Options
|
||||
selectAtLeastOneLegDetail=Sélectionnez au moins un détail de portion de parcours.
|
||||
currentSpeedOverGroundInKnots=Vf
|
||||
currentSpeedOverGroundInKnotsTooltip=La vitesse fond actuelle.
|
||||
@@ -158,7 +153,6 @@ overallDetailsToShow=Détails généraux
|
||||
legDetailsToShow=Détails de la portion de parcours
|
||||
columnMoveUp=Dans le lit
|
||||
columnMoveDown=Arrière
|
||||
port=À bâbord
|
||||
raceStartTimeColumn=Course lancée
|
||||
showOnlySelectedCompetitors=Afficher uniquement concurrents sélectionnés
|
||||
showSelectedCompetitorsInfo=Afficher carré d''infos pour concurrents sélectionnés
|
||||
@@ -173,7 +167,6 @@ tacks=Virements
|
||||
jibes=Changements d''amure
|
||||
penaltyCircles=Tours de pénalité
|
||||
medalRaceIsNull=Valeur de course à la médaille non autorisée
|
||||
configuration=Configuration
|
||||
maneuverTypes=Manœuvres
|
||||
chooseChart=Sélectionner graphique
|
||||
distanceTraveled=Distance parcourue
|
||||
@@ -191,13 +184,11 @@ secondsPerNauticalMileUnit=s/NM
|
||||
metersUnit=m
|
||||
millimetersUnit=mm
|
||||
degreesUnit=°
|
||||
close=Fermer
|
||||
compareCompetitors=Comparer concurrents
|
||||
description=Description
|
||||
sailNumber=Numéro de voile
|
||||
country=Pays
|
||||
no3LetterCodes=Codes CIO à trois lettres introuvables
|
||||
add=Ajouter
|
||||
delete=Supprimer
|
||||
showCharts=Afficher graphiques
|
||||
raceWithThisNameAlreadyExists=Une course de ce nom existe déjà.
|
||||
@@ -269,7 +260,6 @@ printHint=Imprime la version appliquée
|
||||
blockedApplyButton=Le nombre de concurrents inscrits ne correspond pas au nombre de concurrents de la liste d''appariement.
|
||||
multiplierInfo=Multipliez les flights et créez-les les uns après les autres pour obtenir une compétition avec le moins de bateaux possible.
|
||||
noPairingListAvailable=La fonction d''impression est uniquement disponible lorsqu''une liste d''appariement a déjà été appliquée aux journaux de course des palmarès sélectionnés.
|
||||
settingsForComponent=Options pour {0}
|
||||
noEventsFound=Aucune manifestation trouvée
|
||||
noEventSelected=Aucune manifestation sélectionnée
|
||||
noLeaderboardsFound=Aucun palmarès trouvé
|
||||
@@ -307,8 +297,6 @@ leaderboardGroup=Groupe de palmarès
|
||||
pleaseEnterNonEmptyDescription=Saisissez une description (ne pas laisser vide).
|
||||
groupWithThisNameAlreadyExists=Un groupe de palmarès de ce nom existe déjà.
|
||||
detailsOfLeaderboardGroup=Détails du groupe de palmarès
|
||||
edit=Modifier
|
||||
save=Enregistrer
|
||||
abort=Abandonner
|
||||
noLeaderboardGroupWithNameFound=Aucun groupe de palmarès ayant pour nom {0} n''a été trouvé.
|
||||
overview=Synthèse
|
||||
@@ -343,7 +331,6 @@ degreesShort=deg
|
||||
untracked=Non suivi
|
||||
delayForLiveMode=Décalage pour mode en direct
|
||||
notAvailable=Non disponible
|
||||
details=Détails
|
||||
noGroupSelected=Aucun groupe sélectionné
|
||||
combinedWindSourceTypeName=Combiné
|
||||
legMiddleWindSourceTypeName=Milieu de la portion de parcours
|
||||
@@ -425,7 +412,6 @@ simulateAsLiveRace=Simulation de course en direct
|
||||
simulateWithOffset=Décalage avant le départ (en minutes) :
|
||||
boatClassDoesNotMatchSelectedRegatta=Les courses sélectionnées contiennent des catégories de bateaux qui ne sont pas identiques à la catégorie de bateau "{0}" de la régate sélectionnée. Aucune course ne sera chargée.
|
||||
regattaExistForSelectedBoatClass=Il existe au moins une régate pour les catégories de bateaux sélectionnées. Voulez-vous vraiment créer une (des) régate(s) pour cette (ces) course(s) ?
|
||||
reload=Recharger
|
||||
addRegatta=Ajouter régate...
|
||||
importRegattas=Importer régates...
|
||||
exchangeName=Nom d''échange
|
||||
@@ -647,7 +633,6 @@ totalNetPointsColumnTooltip=Total net des points d''un concurrent dans la régat
|
||||
windData=Données de vent
|
||||
gpsData=Données GPS
|
||||
status=Statut
|
||||
noDataFound=Aucune donnée trouvée
|
||||
displayName=Afficher nom
|
||||
histogram=Histogramme
|
||||
numberOfDataPoints=Nombre de points de données
|
||||
@@ -895,12 +880,9 @@ legType=Type de portion de parcours
|
||||
sailID=Numéro de voile
|
||||
seriesLeaderboard=Palmarès de la série
|
||||
regattaLeaderboards=Palmarès de la régate
|
||||
clearSelection=Réinitialiser sélection
|
||||
running=En cours d''exécution
|
||||
runAsSubstantive=Exécuter
|
||||
done=Terminé
|
||||
lastFinished=Fin de dernière exécution
|
||||
run=Exécuter
|
||||
times=durées
|
||||
dataAmount=Volume de données
|
||||
averageCleanedServerTime=∅ Durée de nettoyage du serveur
|
||||
@@ -924,12 +906,8 @@ selectSheet=Sélectionner fiche
|
||||
cleanedServerTime=Durée de nettoyage du serveur
|
||||
overallTime=Durée globale
|
||||
cleanedOverallTime=Durée globale de nettoyage
|
||||
dataMiningResult=Résultat d''exploration de données
|
||||
groupBy=Regrouper par
|
||||
statisticToCalculate=Calculer statistiques
|
||||
queryResultsChartSubtitle={0} entrées de données analysées en {1} secondes
|
||||
noQuerySelected=Aucune requête sélectionnée
|
||||
runAutomatically=Exécuter automatiquement
|
||||
windImport_Upload=Charger
|
||||
windImport_Title=Importer vent à partir de Expedition
|
||||
windImport_BoatId=N° du bateau :
|
||||
@@ -959,14 +937,9 @@ raceTimeTooltip=Durée de navigation totale dans cette course, chronométrée à
|
||||
raceTimeDownwindTooltip=Durée totale de navigation vent arrière dans cette course
|
||||
raceTimeReachingTooltip=Durée totale de navigation jusqu''à l''arrivée dans cette course
|
||||
raceTimeUpwindTooltip=Durée totale de navigation dans le lit du vent dans cette course
|
||||
noStatisticSelectedError=Aucune statistique à calculer sélectionnée
|
||||
noCustomGrouperScriptTextError=Script de regroupement vide
|
||||
noDimensionToGroupBySelectedError=Aucune dimension sélectionnée pour le regroupement
|
||||
noGrouperSelectedError=Aucun type de regroupement sélectionné
|
||||
noDataRetrieverChainDefinitonSelectedError=Aucun récupérateur de données sélectionné
|
||||
queryNotValidBecause=Aucune requête possible dû à
|
||||
dataMining=Exploration de données
|
||||
errorRunningDataMiningQuery=Une erreur s''est produite lors de l''exécution de la requête.
|
||||
hideToolbar=Masquer barre d''outils
|
||||
showSeriesLeaderboards=Afficher palmarès de la série
|
||||
showOverallLeaderboard=Afficher palmarès général
|
||||
@@ -989,7 +962,6 @@ id=ID
|
||||
allowReload=Autoriser rechargement
|
||||
compress=Comprimer
|
||||
compressTooltip=Utiliser seulement si l''instance de serveur d''exportation \ns''exécute au moins avec le commit 0fbf6071dea125bec4a56dee55d61c99def4a62e.
|
||||
queryRunner=Outil d''exécution de requêtes
|
||||
rerunQueryAfterRefresh=Exécuter la requête de nouveau après l''actualisation
|
||||
refreshIntervalMustntBeEmpty=L''intervalle d''actualisation ne doit pas être vide.
|
||||
selectionTables=Tables de sélection
|
||||
@@ -1074,12 +1046,7 @@ TWATooltip=L''angle entre la direction du concurrent et le vent.
|
||||
TWA=Angle du vent réel
|
||||
showBoatClassChartsLabel=Vous pouvez également afficher le diagramme global pour les catégories de bateau disponibles.
|
||||
showDiagram=Afficher diagramme
|
||||
runAutomaticallyTooltip=Exécutez la requête automatiquement, par exemple après avoir modifié les statistiques ou le regroupement.
|
||||
rerunQueryAfterRefreshTooltip=Réexécute la requête après l''actualisation des tables.
|
||||
queryDefinitionProvider=Fournisseur de définitions de requêtes
|
||||
statisticProvider=Fournisseur de statistiques
|
||||
calculateThe=Calculer le/la
|
||||
groupingProvider=Fournisseur de regroupements
|
||||
releaseNotes=Historique de versions et nouveautés
|
||||
hasSplitFleetContiguousScoring=Les flottes divisées ont obtenu des scores contigus.
|
||||
addRaceLogTracker=Ajouter tracker de journal de course
|
||||
@@ -1237,7 +1204,6 @@ showAll=Afficher tout
|
||||
raceVisibilityColumn=Visibilité
|
||||
enterCarryValueFor=Saisissez les points accumulés par le concurrent {0}.
|
||||
advanced=Avancé
|
||||
basedOn=basé sur
|
||||
retrieveWith=Récupérer avec
|
||||
mappingDetails=Détails du mappage
|
||||
deviceMappingQrCodeExplanation=Si vous utilisez l''application de suivi, vous pouvez également ajouter le mappage de l''appareil en sélectionnant un concurrent/une marque, en définissant des heures de départ et d''arrivée et en scannant ce code QR.
|
||||
@@ -1248,10 +1214,6 @@ enterImageURL=Saisissez l''URL de l''image...
|
||||
enterVideoURL=Saisissez l''URL de la vidéo...
|
||||
enterSponsorImageURL=Saisissez l''URL de l''image du sponsor...
|
||||
enterRaceName=Saisissez le nom du parcours...
|
||||
serverError=Une erreur s''est produite lors de la tentative de contact du serveur. Vérifiez la connexion réseau et réessayez.
|
||||
remoteProcedureCall=Procédure d''appel distante
|
||||
serverReplies=Réponses du serveur
|
||||
errorCommunicatingWithServer=Erreur lors de la communication avec le serveur
|
||||
userManagement=Gestion des utilisateurs
|
||||
regattaStructureImport=Importation de la structure de la régate
|
||||
filteredBy=filtré par
|
||||
@@ -1279,7 +1241,6 @@ noFleetsDefined=Aucune flotte définie
|
||||
successfullyCreatedRegattas=Création des régates réussie
|
||||
errorTryingToRegisterRacesForTracking=Erreur lors de la tentative d''enregistrement des courses {0} pour le suivi : {1}. Vérifiez la syntaxe de l''URL stockée/du direct.
|
||||
errorDeterminingPolarAvailability=Erreur lors de la détermination de la disponibilité des données polaires/VPP pour la course {0} : {1}
|
||||
error=Erreur
|
||||
fileStorage=Stockage du fichier
|
||||
active=Actif
|
||||
scoringSchemeHighPointEssOverallDescription=Score en points. Le vainqueur d''un Act totalise 10 points, le 2e - 9 points, etc. En cas d''égalité au classement général des Extreme Sailing Series, la victoire est accordée au concurrent ayant remporté le plus d''Act. Si l''égalité persiste, le résultat obtenu au dernier Act sera utilisé.
|
||||
@@ -1322,7 +1283,6 @@ showCompetitorFullNameColumn=Nom complet du concurrent
|
||||
alwaysShowCompetitorNationalityColumn=Afficher toujours la nationalité du concurrent
|
||||
alwaysShowCompetitorNationalityColumnTooltip=Si disponibles, afficher les deux : le drapeau national ainsi que l''image du concurrent
|
||||
loadingDimensionValues=Chargement des valeurs de dimension
|
||||
runningQuery=Requête en cours
|
||||
inviteBuoyTenders=Inviter navire baliseur
|
||||
orMultipleEmails=ou plusieurs adresses e-mails séparées par des virgules
|
||||
courseOverGroundTrueDegreesTooltip=Route fond vraie en degrés
|
||||
@@ -1330,11 +1290,6 @@ courseOverGroundTrueDegrees=Route fond
|
||||
distanceIncludingGateStartInMeters=Distance (si départ au lièvre)
|
||||
distanceTraveledIncludingGateStartTooltip=La distance naviguée du départ de la portion de parcours jusqu''à sa fin\nou jusqu''à l''heure actuelle (si la portion de parcours n''est pas terminée).\nSi la portion de parcours inclut un départ au lièvre, la distance de la bouée de ligne jusqu''à la position de départ est incluse \npour permettre de comparer les concurrents même si leur départ ne s''est pas fait à la même heure.
|
||||
raceDistanceTraveledIncludingGateStartTooltip=La distance naviguée du départ de la course jusqu''à sa fin\nou jusqu''à l''heure actuelle (si la course n''est pas terminée).\nEn cas de départ au lièvre, la distance de la bouée de ligne jusqu''à la position de départ est incluse \npour permettre de comparer les concurrents même si leur départ ne s''est pas fait à la même heure.
|
||||
results=Résultats
|
||||
groupName=Nom du groupe
|
||||
valueAscending=Valeur (ascendant)
|
||||
valueDescending=Valeur (descendant)
|
||||
sortBy=Trier par
|
||||
dashboardHeader=Tableau de bord
|
||||
dashboardNoWindBotAvailableHeader=Le bot du vent n''est pas disponible.
|
||||
dashboardNoWindBotAvailableMessage=Pour pouvoir recevoir des données de vent depuis des unités de mesure du vent, assurez-vous que le bot du vent est allumé et connecté à SAP Sailing Analytics.
|
||||
@@ -1370,16 +1325,12 @@ dashboardRCBoat=Bateau du comité de course
|
||||
fixedMarkPassing=(fixé)
|
||||
suppressedMarkPassing=(supprimé)
|
||||
windUp=Orienté par rapport au sens du vent (le vent vient du haut de la carte)
|
||||
filterBy=Filtrer par
|
||||
currentFilterSelection=Sélection du filtre actuel
|
||||
notCapableOfGeneratingACodeForIdentifier=Je ne suis pas capable de générer un code pour cet identifiant.
|
||||
serverUrl=URL du serveur
|
||||
rotatedFromTrueNorth=Pivoté de {0} degrés à partir du vrai Nord.
|
||||
clickToToggleWindUp=Touchez/Cliquez pour basculer entre l''affichage orienté nord et l''affichage par rapport au sens du vent
|
||||
clickToToggleWindStreamlets=Touchez/cliquez pour afficher ou masquer les couloirs de vent.
|
||||
startLineToFirstMarkTriangle=Distance entre le départ et la première marque ({0} m)
|
||||
dataMiningComponentsHaveBeenUpdated=Les composants d''exploration de données ont été mis à jour.
|
||||
dataMiningComponentsNeedReloadDialogMessage=Cliquez sur Recharger pour recharger les composants maintenant. Les données actuellement affichées seront ignorées et une requête par défaut sera exécutée.n\Cliquez sur Fermer pour ne rien faire. L''exploration de données ne fonctionnera pas correctement tant que les composants n''auront pas été rechargés.
|
||||
noDataForEvent=Il n''y a pas encore de données pour cette manifestation.
|
||||
countriesCount={0,number} pays
|
||||
countriesCount[one]={0,number} pays
|
||||
@@ -1477,15 +1428,7 @@ noFinishedRaces=Aucune course terminée pour l''instant.
|
||||
racesOverview=Synthèse des courses
|
||||
listFormatLabel=Format de liste
|
||||
competitionFormatLabel=Format de compétition
|
||||
empty=Vide
|
||||
runAQuery=Exécuter une requête
|
||||
latestRegattaStandings=Classement de régate le plus récent
|
||||
plainText=Texte brut
|
||||
columnChart=Diagramme à colonnes
|
||||
columnChartWithErrorBars=Diagramme à colonnes avec barres d''erreur
|
||||
choosePresentation=Sélectionner la présentation
|
||||
cantDisplayDataOfType=Affichage des données du type {0} impossible
|
||||
shownDecimals=Nombre de décimales
|
||||
openFullscreenView=Ouvrir le mode Plein écran
|
||||
closeFullscreenView=Fermer le mode Plein écran
|
||||
videosCount={0,number} vidéos
|
||||
@@ -1495,15 +1438,8 @@ photosCount[one]={0,number} photo
|
||||
eventsHaveTakenPlace={0} manifestations ont eu lieu.
|
||||
eventsHaveTakenPlace[one]=Une manifestation a eu lieu.
|
||||
raceOffice=Bureau de course
|
||||
analyze=Analyser
|
||||
dataMiningSettings=Options de l''exploration de données
|
||||
multiResultsPresenter=Visualiseur de résultats multiples
|
||||
plainResultsPresenter=Visualiseur de résultats bruts
|
||||
resultsChart=Graphique des résultats
|
||||
tabbedResultsPresenter=Visualiseur de résultats avec onglets
|
||||
polarResultsPresenter=Visualiseur de résultats polaires
|
||||
maneuverSpeedDetailsResultsPresenter=Visualiseur des résultats : détails sur la vitesse lors de la manœuvre
|
||||
dataMiningRetrieval=Récupération de données
|
||||
actionWatch=Regarder
|
||||
actionAnalyze=Analyser
|
||||
denoteAllRacesForRaceLogTrackingShorctut=Raccourci pour marquer toutes les courses pour le suivi par journaux de course
|
||||
@@ -1519,24 +1455,8 @@ defaultName=Par défaut
|
||||
exampleTextForName=Votre nom apparaît de la manière suivante :
|
||||
flightsCount={0,number} départs de deux équipages
|
||||
flightsCount[one]={0,number} départ de deux équipages
|
||||
viewQueryDefinition=Afficher la définition de la requête
|
||||
queryDefinitionViewer=Visualiseur de définitions de requête
|
||||
groupAverageAscending=Moyenne du groupe (ascendant)
|
||||
groupAverageDescending=Moyenne du groupe (descendant)
|
||||
groupMedianAscending=Médiane du groupe (ascendant)
|
||||
groupMedianDescending=Médiane du groupe (descendant)
|
||||
resultsFoundForSearch={0,number} résultats trouvés pour "{1}"
|
||||
resultsFoundForSearch[one]={0,number} résultat trouvé pour "{1}"
|
||||
runPredefinedQuery=Exécuter requête prédéfinie
|
||||
selectPredefinedQuery=Sélectionner requête prédéfinie
|
||||
predefinedQueryRunner=Outil d''exécution de requêtes prédéfinies
|
||||
developerOptions=Options développeur
|
||||
copyToClipboard=Copier dans le presse-papiers
|
||||
code=Code
|
||||
useClassGetName=Utilisez Class.getName() pour les noms des types.
|
||||
useClassGetNameTooltip=Plus stable face aux modifications dans la base de code, mais le fichier de script peut uniquement être utilisé dans l''étendue où les classes sont disponibles.
|
||||
useStringLiterals=Utiliser littéraux de chaîne pour les noms de types
|
||||
useStringLiteralsTooltip=Le fichier de script peut être utilisé partout, mais sera corrompu si la base de code change.
|
||||
errorLoadingDataWithTryAgain=Erreur lors du chargement des données. Nouvel essai dans quelques minutes.
|
||||
addGalleryPhoto=Ajouter photo de la galerie
|
||||
addStageImage=Ajouter image d''étape
|
||||
@@ -1556,7 +1476,6 @@ warningForDisabledCompetitors=Les concurrents suivants ne peuvent pas être insc
|
||||
competitorToolTipMessage={0} a déjà été affecté à la flotte {2} dans la course {3} et ne peut donc pas être affecté à la flotte {1} dans la même course.
|
||||
addMarkToRegatta=Ajouter marque à la régate
|
||||
selectALeaderboardGroup=Sélectionner groupe de palmarès…
|
||||
pleaseSelect=Sélectionnez
|
||||
requiresValidRegatta=Pour identifier la course à afficher, cette page nécessite une régate valide, une colonne de course et un nom de flotte.
|
||||
couldNotObtainRace=Impossible de trouver une course ayant le nom {1} pour la flotte {2} pour une régate ayant le nom {0} : {3}
|
||||
errorTryingToCreateEmbeddedMap=Erreur lors de la tentative de création de la carte intégrée : {0}
|
||||
@@ -1836,30 +1755,9 @@ eventRegattaHeaderLegendGpsNo=Aucune donnée de suivi
|
||||
eventRegattaHeaderLegendWindNo=Aucune donnée de vent
|
||||
eventRegattaHeaderLegendVideoNo=Aucun flux vidéo
|
||||
eventRegattaHeaderLegendAudioNo=Aucun flux audio
|
||||
angleInDegree=Angle en degrés
|
||||
angleInRadian=Angle en radians
|
||||
centralAngleInRadian=Angle au centre en radians
|
||||
centralAngleInDegree=Angle au centre en degrés
|
||||
kilometers=Kilomètres
|
||||
meters=Mètres
|
||||
nauticalMiles=Milles nautiques
|
||||
seaMiles=Milles marins
|
||||
geographicalMiles=Milles géographiques
|
||||
days=Jours
|
||||
hours=Heures
|
||||
minutes=Minutes
|
||||
seconds=Secondes
|
||||
milliseconds=Millisecondes
|
||||
floatNumber=Flottant
|
||||
integer=Entier
|
||||
appendResult=Ajouter résultat
|
||||
sampleColor=Échantillon de couleur
|
||||
sharedSettingsLink=Lier aux options
|
||||
leaderboardPage=Page du palmarès
|
||||
makeDefault=Définir par défaut
|
||||
makeDefaultInProgress=En cours...
|
||||
settingsSavedMessage=Vos options actuelles ont correctement été définies comme options par défaut.
|
||||
settingsSaveErrorMessage=Une erreur s''est produite lors de la définition de vos options comme options par défaut.
|
||||
showLiveNow=Afficher "En direct"
|
||||
useOnlyOneOfStartTimeInferenceAndControlTrackingFromStartAndFinishTimes=Utilisez "Interférence temporelle de départ" ou bien "Contrôler suivi à partir des heures de départ et d''arrivée", mais pas les deux.
|
||||
unknownLeaderboardType=Type de palmarès {0} inconnu
|
||||
@@ -1878,10 +1776,6 @@ settingsId=ID d''options
|
||||
documentSettingsId=ID d''options du document
|
||||
settingsForId=Options pour l''ID "{0}"
|
||||
userProfileSettingsTabDescription=Les options utilisateur sont générées dans des boîtes de dialogues pour options que vous trouverez à de nombreux endroits sur la page. Cette vue montre toutes vos options regroupées pour les mettre techniquement à disposition des utilisateurs de référence. Soyez prudent : les entrées supprimées ne peuvent pas être restaurées.
|
||||
resetToDefault=Réinitialiser sur les valeurs par défaut
|
||||
resetToDefaultInProgress=Réinitialisation en cours...
|
||||
settingsRemoved=Options par défaut restaurées
|
||||
settingsRemovedError=Impossible de restaurer les options par défaut
|
||||
userSettingsFilter=Filtre d''options
|
||||
requiresRegattaRaceAndLeaderboard=Cette page requiert un nom de régate, un nom de course et un nom de palmarès valides.
|
||||
couldNotFindRaceInRegatta=Impossible de trouver une course nommée {0} pour une régate nommée {1}.
|
||||
@@ -1913,7 +1807,6 @@ errorFetchingDimensionData=Erreur lors de l''accès aux valeurs de dimension de
|
||||
errorFetchingStatistics=Erreur lors de l''accès aux statistiques disponibles du serveur : {0}
|
||||
errorFetchingAggregators=Erreur lors de l''accès aux agrégateurs disponibles du serveur : {0}
|
||||
errorLoadingDataRetrieverChainDefinitions=Erreur lors de la récupération des définitions DataRetrieverChainDefinitions disponibles : {0}
|
||||
errorFetchingComponentsChangedTimepoint=Erreur lors de la récupération de l''heure modifiée du composant du serveur : {0}
|
||||
errorRunningQuery=Erreur lors de l''exécution de la requête : {0}
|
||||
errorReadingWindFixes=Erreur lors de la lecture des points vent {0}
|
||||
errorAddingWindFixForRace=Erreur lors de l''ajout d''un point vent pour la course {0} : {1}
|
||||
@@ -1972,7 +1865,7 @@ anniversaryMajorCountdownTeaser[one]=Attention ! Plus qu''{0,number,#,###} cour
|
||||
anniversaryMajorCountdownDescription=Nous célébrons notre {0,number,#,###}e course sur www.sapsailing.com ! Quelle sera cette course ? L''organisateur de cette course anniversaire recevra 10 000 euros au profit de l''association caritative de son choix. Le gagnant sera annoncé sur ce site Web. Restez connecté et ne ratez pas le compte à rebours !
|
||||
anniversaryRepdigitCountdownTeaser=Attention ! Plus que {0,number,#,###} courses avant la {1,number,#,###}e course.
|
||||
anniversaryRepdigitCountdownTeaser[one]=Attention ! Plus qu''{0,number,#,###} course avant la {1,number,#,###}e course.
|
||||
anniversaryRepdigitCountdownDescription=Nous célébrons notre numéro de course porte-bonheur ! Qui effectuera la {0,number, #,###}e course sur www.sapsailing.com ? Les participants de cette course seront invités à un festival d''été de haut niveau par SAP. Les gagnants seront annoncés sur ce site Web. Restez connecté et ne ratez pas le compte à rebours !
|
||||
anniversaryRepdigitCountdownDescription=Nous célébrons notre numéro de course porte-bonheur ! Qui effectuera la {0,number, #,###}e course sur www.sapsailing.com ? Les organisateurs se verront offrir un barbecue et des boissons. Les gagnants seront annoncés sur ce site Web. Restez connecté et ne ratez pas le compte à rebours !
|
||||
anniversaryAnnouncementTeaser=Mission accomplie ! {0,number,#,###} courses avec SAP Sailing Analytics !
|
||||
anniversaryAnnouncementDescription=3, 2, 1... Félicitation aux participants de la course {0}. Vous avez gagné ! Nous vous remercions pour la confiance que vous accordez à SAP Sailing Analytics et nous espérons partager encore de nombreuses courses avec vous !
|
||||
anniversaryRaceLinkText=Afficher la course anniversaire
|
||||
@@ -1987,11 +1880,6 @@ minimumRideHeightInMetersTooltip=Hauteur de planing minimale requise en mètres
|
||||
minimumDurationBetweenAdjacentFoilingSegmentsInSeconds=Durée minimum (s) entre deux tronçons en planing adjacents
|
||||
minimumDurationBetweenAdjacentFoilingSegmentsInSecondsTooltip=Si cette valeur est renseignée et que la durée entre deux tronçons de planing adjacents est inférieure à celle-ci, ces tronçons de planing adjacents seront combinés en un seul.
|
||||
needToProvideValidMinimumRideHeight=Vous devez fournir une valeur de hauteur de planing minimale valide en mètres.
|
||||
dataMiningErrorMargins=Marges d''erreur
|
||||
elements={0} éléments
|
||||
chooseDifferentDimensionTitle=Sélectionnez une dimension différente.
|
||||
chooseDifferentDimensionMessage=Sélectionnez une dimension différente pour regrouper les résultats.
|
||||
pleaseSelectADimension=Sélectionnez une dimension.
|
||||
currentPortDaggerboardRake=Inclinaison de la dérive bâbord
|
||||
currentPortDaggerboardRakeTooltip=Inclinaison actuelle de la dérive bâbord
|
||||
currentStbdDaggerboardRake=Inclinaison de la dérive tribord
|
||||
@@ -2084,7 +1972,7 @@ windFinderWindSourceTypeName=WindFinder
|
||||
windFinderWindSourceTypeTooltip=Vent mesuré d''un ou plusieurs endroits de www.windfinder.com
|
||||
windFinder=WindFinder
|
||||
enterTagsForTheVideo=Entrez des balises pour la vidéo.
|
||||
enterIdOfWindfinderReviewedSpotCollection=Entrez l''ID d''un ensemble d''endroits WindFinder révisé, par exemple, "Schilksee".
|
||||
enterIdOfWindFinderReviewedSpotCollection=Entrez l''ID d''un ensemble d''endroits WindFinder révisé, par exemple, "Schilksee".
|
||||
enterTagsForTheImage=Entrez des balises pour l''image.
|
||||
unableToResolveWindFinderSpotId=Impossible de traiter l''endroit WindFinder ayant l''ID {0} : {1}
|
||||
windFinderWeatherData=Données météo
|
||||
@@ -2099,9 +1987,9 @@ expeditionAws=eAWS
|
||||
expeditionTwa=eTWA
|
||||
expeditionTws=eTWS
|
||||
expeditionTwd=eTWD
|
||||
expeditionTargTwa=eTWA cible
|
||||
expeditionBoatSpeed=eVitesse du bateau
|
||||
expeditionTargBoatSpeed=eVitesse du bateau cible
|
||||
expeditionTargTwa=eTarget TWA
|
||||
expeditionBoatSpeed=eBoat speed
|
||||
expeditionTargBoatSpeed=eTarget boat speed
|
||||
expeditionBsSog=eVB Vf
|
||||
expeditionSOG=eVf
|
||||
expeditionCOG=eRf
|
||||
@@ -2169,3 +2057,13 @@ multiVideoIdle=La réserve de travail est inactive.
|
||||
multiVideoDoNoAdd=Ne pas ajouter
|
||||
multiVideoOffsetInput=Décalage vidéo global en millisecondes :
|
||||
multiVideoDescription=Le serveur Web doit fournir une liste des index (les sous-dossiers sont pris en charge s''ils sont répertoriés) pour permettre la détection de fichier. Après une analyse initiale des métadonnées contenues dans les fichiers MP4, les vidéos à ajouter doivent être sélectionnées en cochant les cases correspondantes dans la colonne de gauche. Le bouton "Ajouter audio/vidéo" va créer des pistes médias pour tous les fichiers sélectionnés, et ajouter ces fichiers à toutes les courses sélectionnées dans la colonne de droite.
|
||||
multiUrlChangeMediaTrack=Ajuster plusieurs URL de piste média
|
||||
multiUrlChangeReplace=Remplacer par
|
||||
multiUrlChangeFind=Rechercher
|
||||
multiUrlChangeCannotSave=Une erreur s''est produite lors de la sauvegarde.
|
||||
multiUrlChangeSave=Sauvegarder modifications apportées aux URL
|
||||
multiUrlChangeNewURL=Nouvelle URL
|
||||
multiUrlNoPrefixWarning=Aucun préfixe commun n''a été trouvé. Cela signifie généralement que les vidéos sélectionnées ne sont actuellement pas toutes hébergées au même endroit. Vous assumez les risques liés à leur utilisation.
|
||||
multiUrlChangeExplain=Cette boîte de dialogue remplacera en masse les éléments communs au début des URL de piste média. Assurez-vous que toutes les URL commencent par le même préfixe. Veuillez également prendre le temps d''observer la nouvelle colonne d''URL, et de tester les URL résultantes avant de sauvegarder.
|
||||
lastEvent=Dernier événement : {0}
|
||||
teaserOverallLinkToolTip=Pour afficher l''ensemble des séries, cliquez sur le coin jaune.
|
||||
|
||||
+12
-114
@@ -9,12 +9,10 @@ trackedBefore=追跡イベントの履歴
|
||||
general=一般
|
||||
listRaces=レース一覧
|
||||
listRegattas=レガッタ一覧
|
||||
numberPairResultsPresenter=散布図
|
||||
wind=風
|
||||
maneuverType=マニューバー
|
||||
windPanelLabel=これは風パネルであり、今のところ完全に空となっています。
|
||||
refresh=リフレッシュ
|
||||
remove=削除
|
||||
removeNumber=削除 ({0})
|
||||
windSource=風源
|
||||
dampeningInterval=制動間隔
|
||||
@@ -53,7 +51,6 @@ trackedRaceConnectedToSelectedRaceName=選択されたレース名に結び付
|
||||
linkToColumn=列にリンク
|
||||
unlink=リンク解除
|
||||
leaderboardName=リーダーボード名
|
||||
cancel=取消
|
||||
pleaseEnterAName=名称を入力してください
|
||||
pleaseEnterABoatClass=艇種を入力してください
|
||||
discardRacesFromHowManyStartedRacesOn=レース除外 (スタート済レースから)
|
||||
@@ -65,7 +62,6 @@ startingFromNumberOfRaces=レース数から開始
|
||||
renameLeaderboard=リーダーボード名称変更
|
||||
addColumnToLeaderboard=リーダーボードに列を追加
|
||||
pleaseEnterNameForNewRaceColumn=新規レース列の名称を入力してください
|
||||
ok=OK
|
||||
medalRace=メダルレース
|
||||
renameRace=レース名称変更
|
||||
openSelectedLeaderboard=選択したリーダーボードを開く
|
||||
@@ -89,7 +85,6 @@ sapSailingAnalytics=Sailing Analytics
|
||||
leaderboard=リーダーボード
|
||||
leaderboards=リーダーボード
|
||||
leaderboardSettings=リーダーボード設定
|
||||
settings=設定
|
||||
selectAtLeastOneLegDetail=レグ詳細を少なくとも 1 つ選択
|
||||
currentSpeedOverGroundInKnots=SOG
|
||||
currentSpeedOverGroundInKnotsTooltip=現在の対地速力です。
|
||||
@@ -158,7 +153,6 @@ overallDetailsToShow=全体詳細
|
||||
legDetailsToShow=レグ詳細
|
||||
columnMoveUp=上
|
||||
columnMoveDown=下
|
||||
port=ポート
|
||||
raceStartTimeColumn=レーススタート済
|
||||
showOnlySelectedCompetitors=選択した競技者のみ表示
|
||||
showSelectedCompetitorsInfo=選択した競技者の情報ボックスを表示
|
||||
@@ -173,7 +167,6 @@ tacks=タック
|
||||
jibes=ジャイブ
|
||||
penaltyCircles=ペナルティーサークル
|
||||
medalRaceIsNull=メダルレース値は不可
|
||||
configuration=設定
|
||||
maneuverTypes=マニューバー
|
||||
chooseChart=チャートの選択
|
||||
distanceTraveled=移動した距離
|
||||
@@ -191,13 +184,11 @@ secondsPerNauticalMileUnit=s/NM
|
||||
metersUnit=m
|
||||
millimetersUnit=mm
|
||||
degreesUnit=°
|
||||
close=閉じる
|
||||
compareCompetitors=競技者比較
|
||||
description=内容説明
|
||||
sailNumber=セールナンバー
|
||||
country=国
|
||||
no3LetterCodes=IOC 3 文字コードを検索できませんせした。
|
||||
add=追加
|
||||
delete=削除
|
||||
showCharts=チャート表示
|
||||
raceWithThisNameAlreadyExists=その名称のレースはすでに登録されています。
|
||||
@@ -269,7 +260,6 @@ printHint=適用バージョンを印刷
|
||||
blockedApplyButton=登録されている競技者数が対戦表にある競技者数と同じでありません
|
||||
multiplierInfo=複数のフライトを艇変更がより少なく競技が可能となるよう互いに隣り合わせに作成
|
||||
noPairingListAvailable=印刷機能は選択したリーダーボードレースログに対戦表がすでに適用されている場合にのみ利用が可能です。
|
||||
settingsForComponent={0} の設定
|
||||
noEventsFound=イベントが見つかりませんでした
|
||||
noEventSelected=イベントが選択されていません
|
||||
noLeaderboardsFound=リーダーボードが見つかりませんでした
|
||||
@@ -307,8 +297,6 @@ leaderboardGroup=リーダーボードグループ
|
||||
pleaseEnterNonEmptyDescription=空でない説明を入力してください
|
||||
groupWithThisNameAlreadyExists=この名称のリーダーボードグループはすでに登録されています。
|
||||
detailsOfLeaderboardGroup=リーダーボードグループの詳細
|
||||
edit=編集
|
||||
save=保存
|
||||
abort=中止
|
||||
noLeaderboardGroupWithNameFound={0} という名称のリーダーボードグループは見つかりませんでした
|
||||
overview=概要
|
||||
@@ -343,7 +331,6 @@ degreesShort=度
|
||||
untracked=未追跡
|
||||
delayForLiveMode=ライブモードの遅延:
|
||||
notAvailable=利用不可
|
||||
details=詳細
|
||||
noGroupSelected=グループ未選択
|
||||
combinedWindSourceTypeName=複合
|
||||
legMiddleWindSourceTypeName=レグの中間
|
||||
@@ -425,7 +412,6 @@ simulateAsLiveRace=ライブレースとしてシミュレート
|
||||
simulateWithOffset=スタート前のオフセット (分):
|
||||
boatClassDoesNotMatchSelectedRegatta=選択したレースに、選択したレガッタの艇種 ''{0}'' と同じでない艇種が含まれています。レースはロードされません。
|
||||
regattaExistForSelectedBoatClass=選択した艇種に対して少なくとも 1 つのレガッタがあります。このレースに対してレガッタを初期登録しますか。
|
||||
reload=リロード
|
||||
addRegatta=レガッタ追加...
|
||||
importRegattas=レガッタのインポート...
|
||||
exchangeName=エクスチェンジ名
|
||||
@@ -647,7 +633,6 @@ totalNetPointsColumnTooltip=レガッタにおける競技者の総合得点を
|
||||
windData=風データ
|
||||
gpsData=GPS データ
|
||||
status=ステータス
|
||||
noDataFound=データが見つかりませんでした
|
||||
displayName=名称表示
|
||||
histogram=ヒストグラム
|
||||
numberOfDataPoints=データポイント数
|
||||
@@ -895,12 +880,9 @@ legType=レグタイプ
|
||||
sailID=セールナンバー
|
||||
seriesLeaderboard=シリーズリーダーボード
|
||||
regattaLeaderboards=レガッタリーダーボード
|
||||
clearSelection=選択のクリア
|
||||
running=実行中
|
||||
runAsSubstantive=実行
|
||||
done=実行済
|
||||
lastFinished=最終フィニッシュ
|
||||
run=実行
|
||||
times=回
|
||||
dataAmount=データ量
|
||||
averageCleanedServerTime=∅ サーバ時間のクリア
|
||||
@@ -924,12 +906,8 @@ selectSheet=シート選択
|
||||
cleanedServerTime=サーバ時間のクリア
|
||||
overallTime=全体時間
|
||||
cleanedOverallTime=全体時間のクリア
|
||||
dataMiningResult=データマイニング結果
|
||||
groupBy=グループキー
|
||||
statisticToCalculate=統計の計算
|
||||
queryResultsChartSubtitle={0} データエントリを {1} 秒で処理済
|
||||
noQuerySelected=クエリ未選択
|
||||
runAutomatically=自動的に実行
|
||||
windImport_Upload=アップロード
|
||||
windImport_Title=Expedition から風をインポート
|
||||
windImport_BoatId=艇 ID:
|
||||
@@ -959,14 +937,9 @@ raceTimeTooltip=競技者がスタート変針点を通過したときに計測
|
||||
raceTimeDownwindTooltip=このレースでダウンウィンドで移動した合計時間
|
||||
raceTimeReachingTooltip=このレースでリーチングで移動した合計時間
|
||||
raceTimeUpwindTooltip=このレースでアップウィンドで移動した合計時間
|
||||
noStatisticSelectedError=計算する統計が選択されていません
|
||||
noCustomGrouperScriptTextError=分類スクリプトが空です
|
||||
noDimensionToGroupBySelectedError=グループ別にする次元が選択されていません
|
||||
noGrouperSelectedError=分類タイプが選択されていません
|
||||
noDataRetrieverChainDefinitonSelectedError=データリトリーバが選択されていません
|
||||
queryNotValidBecause=クエリが可能でありません。理由:
|
||||
dataMining=データマイニング
|
||||
errorRunningDataMiningQuery=クエリ実行でエラーが発生しました
|
||||
hideToolbar=ツールバー非表示
|
||||
showSeriesLeaderboards=シリーズリーダーボード表示
|
||||
showOverallLeaderboard=全体リーダーボード表示
|
||||
@@ -989,7 +962,6 @@ id=ID
|
||||
allowReload=リロード許可
|
||||
compress=圧縮
|
||||
compressTooltip=エクスポートサーバインスタンスが少なくとも\nコミット 0fbf6071dea125bec4a56dee55d61c99def4a62e で実行中の場合にのみ使用してください。
|
||||
queryRunner=クエリランナー
|
||||
rerunQueryAfterRefresh=リフレッシュ後にクエリを再実行
|
||||
refreshIntervalMustntBeEmpty=リフレッシュ間隔は空であってはなりません
|
||||
selectionTables=選択テーブル
|
||||
@@ -1074,12 +1046,7 @@ TWATooltip=競技者の方向と風の間のアングル
|
||||
TWA=真の風角度
|
||||
showBoatClassChartsLabel=利用可能な艇種の全体図も表示することができます。
|
||||
showDiagram=ダイアグラム表示
|
||||
runAutomaticallyTooltip=統計やグルーピングなどを変更した後にクエリを自動的に実行します。
|
||||
rerunQueryAfterRefreshTooltip=テーブルがリフレッシュされた後にクエリを再実行します。
|
||||
queryDefinitionProvider=クエリ定義プロバイダ
|
||||
statisticProvider=統計プロバイダ
|
||||
calculateThe=計算:
|
||||
groupingProvider=グルーピングプロバイダ
|
||||
releaseNotes=新規およびリリース履歴
|
||||
hasSplitFleetContiguousScoring=連続的に得点するフリートの分割
|
||||
addRaceLogTracker=RaceLog トラッカー追加
|
||||
@@ -1237,7 +1204,6 @@ showAll=全表示
|
||||
raceVisibilityColumn=可視性
|
||||
enterCarryValueFor=競技者 {0} の持ち越し得点を入力
|
||||
advanced=予選通過
|
||||
basedOn=基準
|
||||
retrieveWith=検索キー
|
||||
mappingDetails=マッピング詳細
|
||||
deviceMappingQrCodeExplanation=追跡アプリを使用している場合は、競技者/マークを選択し、開始時刻と終了時刻を設定してから、この QRCode をスキャンすることによってデバイスマッピングも追加することができます。
|
||||
@@ -1248,10 +1214,6 @@ enterImageURL=画像 URL を入力...
|
||||
enterVideoURL=動画 URL を入力...
|
||||
enterSponsorImageURL=スポンサー画像 URL を入力...
|
||||
enterRaceName=レース名を入力...
|
||||
serverError=サーバにアクセスしようとしてエラーが発生しました。ネットワーク接続を確認して再試行してください。
|
||||
remoteProcedureCall=リモートプロシージャコール
|
||||
serverReplies=サーバ応答
|
||||
errorCommunicatingWithServer=サーバとの通信でエラー発生
|
||||
userManagement=ユーザ管理
|
||||
regattaStructureImport=レガッタ構成のインポート
|
||||
filteredBy=フィルタキー
|
||||
@@ -1279,7 +1241,6 @@ noFleetsDefined=フリートが定義されていません。
|
||||
successfullyCreatedRegattas=レガッタが登録されました
|
||||
errorTryingToRegisterRacesForTracking=追跡するレース {0} を登録しようとしてエラーが発生: {1}。ライブ/格納 URI 構文をチェックしてください。
|
||||
errorDeterminingPolarAvailability=レース {0} のポーラー/VPP データの利用可能性の決定でエラー発生: {1}
|
||||
error=エラー
|
||||
fileStorage=ファイルストレージ
|
||||
active=有効
|
||||
scoringSchemeHighPointEssOverallDescription=得点数によるスコアです。アクトの勝者が 10 点、2 位が 9 点などと得点します。Extreme Sailing シリーズ全体で同点となった場合は、より多くのアクトで勝者となった競技者を上位とします。それでも同じ場合には、最終アクトの結果を用います。
|
||||
@@ -1322,7 +1283,6 @@ showCompetitorFullNameColumn=競技者氏名
|
||||
alwaysShowCompetitorNationalityColumn=競技者国籍を常に表示
|
||||
alwaysShowCompetitorNationalityColumnTooltip=国旗と競技者画像 (利用可能な場合) の両方を表示
|
||||
loadingDimensionValues=次元値ロード中
|
||||
runningQuery=クエリ実行中
|
||||
inviteBuoyTenders=ブイ入札の招待
|
||||
orMultipleEmails=または複数の電子メールをカンマで区切り
|
||||
courseOverGroundTrueDegreesTooltip=真の対地方位角度 (度)
|
||||
@@ -1330,11 +1290,6 @@ courseOverGroundTrueDegrees=COG
|
||||
distanceIncludingGateStartInMeters=距離 (ゲートスタートでの)
|
||||
distanceTraveledIncludingGateStartTooltip=レグ始点からレグ終点まで、またはレグをフィニッシュ\nしていない場合は現時点までに移動した距離です。\nレグにゲートスタートが含まれる場合は、異なる時刻にスタートしたときでも\n競技者を比較するためにピンエンドからスタート位置までの距離が含まれます。
|
||||
raceDistanceTraveledIncludingGateStartTooltip=レース開始からレース終了まで、またはレースがフィニッシュ\nしていない場合は現時点までに移動した距離です。ゲートスタートの場合は、異なる時刻にスタートしたときでも\n競技者を比較するためにピンエンドからスタート位置までの距離が含まれます。
|
||||
results=結果
|
||||
groupName=グループ名
|
||||
valueAscending=値 (昇順)
|
||||
valueDescending=値 (降順)
|
||||
sortBy=ソートキー
|
||||
dashboardHeader=ダッシュボード
|
||||
dashboardNoWindBotAvailableHeader=ウィンドボットは利用できません。
|
||||
dashboardNoWindBotAvailableMessage=風計測ユニットからライブ風データを受信するには、ウィンドボットがオンになっていて、SAP Sailing Analytics に接続されていることを確認してください。
|
||||
@@ -1370,16 +1325,12 @@ dashboardRCBoat=RC 艇
|
||||
fixedMarkPassing=(固定)
|
||||
suppressedMarkPassing=(非表示)
|
||||
windUp=ウィンドアップ (マップの最上部から風を表示)
|
||||
filterBy=フィルタキー
|
||||
currentFilterSelection=現在のフィルタ選択
|
||||
notCapableOfGeneratingACodeForIdentifier=この ID のコードを生成することができません。
|
||||
serverUrl=サーバ URL
|
||||
rotatedFromTrueNorth=真北から {0} 度回転しました。
|
||||
clickToToggleWindUp=タップ/クリックして、風上が上のマップと北が上のマップとで表示を切り替えます
|
||||
clickToToggleWindStreamlets=タップ/クリックして風細流を表示または非表示にします
|
||||
startLineToFirstMarkTriangle=スタートから第一マークまで ({0}m)
|
||||
dataMiningComponentsHaveBeenUpdated=データマイニングコンポーネントが更新されました
|
||||
dataMiningComponentsNeedReloadDialogMessage=リロードをクリックして、コンポーネントをリロードします。これにより、現在表示されているデータが破棄され、デフォルトクエリが実行されます。\n何も行わない場合は閉じるをクリックします。データマイニングは、コンポーネントがリロードされるまでは正しく機能しません。
|
||||
noDataForEvent=このイベントにまだデータが何もありません。
|
||||
countriesCount={0,number} カ国
|
||||
countriesCount[one]={0,number} カ国
|
||||
@@ -1477,15 +1428,7 @@ noFinishedRaces=フィニッシュしたレースがまだありません。
|
||||
racesOverview=レース概要
|
||||
listFormatLabel=レース一覧
|
||||
competitionFormatLabel=競技日
|
||||
empty=空
|
||||
runAQuery=クエリを実行
|
||||
latestRegattaStandings=レガッタ最新順位表
|
||||
plainText=プレーンテキスト
|
||||
columnChart=縦棒グラフ
|
||||
columnChartWithErrorBars=縦棒グラフ (エラーバーあり)
|
||||
choosePresentation=プレゼンテーション選択
|
||||
cantDisplayDataOfType={0} タイプのデータは表示できません。
|
||||
shownDecimals=小数点表示
|
||||
openFullscreenView=全画面ビューを開く
|
||||
closeFullscreenView=全画面ビューを閉じる
|
||||
videosCount={0,number} 動画
|
||||
@@ -1495,15 +1438,8 @@ photosCount[one]={0,number} 写真
|
||||
eventsHaveTakenPlace={0} イベントが行われました
|
||||
eventsHaveTakenPlace[one]=1 つのイベントが行われました
|
||||
raceOffice=レース事務所
|
||||
analyze=分析
|
||||
dataMiningSettings=データマイニング設定
|
||||
multiResultsPresenter=複数結果表示
|
||||
plainResultsPresenter=プレーン結果表示ツール
|
||||
resultsChart=結果チャート
|
||||
tabbedResultsPresenter=タブ結果表示ツール
|
||||
polarResultsPresenter=ポーラー結果表示ツール
|
||||
maneuverSpeedDetailsResultsPresenter=マニューバー速度詳細結果表示ツール
|
||||
dataMiningRetrieval=データ取得
|
||||
actionWatch=視聴
|
||||
actionAnalyze=分析
|
||||
denoteAllRacesForRaceLogTrackingShorctut=レースログ追跡の全レース表示へのショートカット
|
||||
@@ -1519,24 +1455,8 @@ defaultName=デフォルト
|
||||
exampleTextForName=名前は次のようになります:
|
||||
flightsCount={0,number} フライト
|
||||
flightsCount[one]={0,number} フライト
|
||||
viewQueryDefinition=クエリ定義の表示
|
||||
queryDefinitionViewer=クエリ定義ビューア
|
||||
groupAverageAscending=グループ平均 (昇順)
|
||||
groupAverageDescending=グループ平均 (降順)
|
||||
groupMedianAscending=グループ中央値 (昇順)
|
||||
groupMedianDescending=グループ中央値 (降順)
|
||||
resultsFoundForSearch={0,number} 結果が ''{1}’ に見つかりました
|
||||
resultsFoundForSearch[one]={0,number} 結果が ''{1}’ に見つかりました
|
||||
runPredefinedQuery=事前定義クエリを実行
|
||||
selectPredefinedQuery=事前定義クエリを選択
|
||||
predefinedQueryRunner=事前定義クエリランナー
|
||||
developerOptions=開発者オプション
|
||||
copyToClipboard=クリップボードにコピー
|
||||
code=コード
|
||||
useClassGetName=タイプ名に class.getName() を使用
|
||||
useClassGetNameTooltip=コードベースでの変更に対してより堅牢ですが、コードスニペットはクラスが利用できる範囲でのみ使用できます。
|
||||
useStringLiterals=タイプ名に文字列リテラルを使用
|
||||
useStringLiteralsTooltip=このコードスニペットはどこでも使用できますが、コードベースが変更される場合は中断します。
|
||||
errorLoadingDataWithTryAgain=データのロードでエラーが発生しました。しばらくしてから再試行してください。
|
||||
addGalleryPhoto=ギャラリーフォト追加
|
||||
addStageImage=ステージ画像追加
|
||||
@@ -1556,7 +1476,6 @@ warningForDisabledCompetitors=次の競技者はこのレースに登録する
|
||||
competitorToolTipMessage={0} はすでにレース {3} のフリート {2} に割り当てられており、そのため同一レース内でフリート {1} に割り当てることはできません
|
||||
addMarkToRegatta=マークをレガッタに追加
|
||||
selectALeaderboardGroup=リーダーボードグループを選択...
|
||||
pleaseSelect=選択:
|
||||
requiresValidRegatta=このページには、表示するレースを識別するために有効なレガッタ、レース列、およびフリート名が必要です。
|
||||
couldNotObtainRace=名称 {0} のレガッタに対してフリート {2} の名称 {1} でレースが取得できませんでした: {3}
|
||||
errorTryingToCreateEmbeddedMap=埋込マップを登録しようとしてエラーが発生: {0}
|
||||
@@ -1836,30 +1755,9 @@ eventRegattaHeaderLegendGpsNo=航跡データなし
|
||||
eventRegattaHeaderLegendWindNo=風向風速データなし
|
||||
eventRegattaHeaderLegendVideoNo=動画ストリームなし
|
||||
eventRegattaHeaderLegendAudioNo=音声ストリームなし
|
||||
angleInDegree=角度 (度)
|
||||
angleInRadian=角度 (ラジアン)
|
||||
centralAngleInRadian=中心角 (度)
|
||||
centralAngleInDegree=中心角 (ラジアン)
|
||||
kilometers=キロメートル
|
||||
meters=メートル
|
||||
nauticalMiles=海里
|
||||
seaMiles=海里
|
||||
geographicalMiles=地理マイル
|
||||
days=日
|
||||
hours=時間
|
||||
minutes=分
|
||||
seconds=秒
|
||||
milliseconds=ミリ秒
|
||||
floatNumber=浮動小数点型
|
||||
integer=整数
|
||||
appendResult=結果を追加
|
||||
sampleColor=色サンプル
|
||||
sharedSettingsLink=設定にリンク
|
||||
leaderboardPage=リーダーボードページ
|
||||
makeDefault=デフォルトを設定
|
||||
makeDefaultInProgress=実行中です...
|
||||
settingsSavedMessage=現在の設定がデフォルトとして設定されました
|
||||
settingsSaveErrorMessage=使用している設定をデフォルトとして設定する際にエラーが発生しました
|
||||
showLiveNow="実況中" を表示
|
||||
useOnlyOneOfStartTimeInferenceAndControlTrackingFromStartAndFinishTimes="推定スタート時刻" および "スタート/フィニッシュ時刻からのコントロール追跡" は 1 つだけ使用してください
|
||||
unknownLeaderboardType=未知のリーダーボードタイプ {0}
|
||||
@@ -1878,10 +1776,6 @@ settingsId=設定 ID
|
||||
documentSettingsId=文書設定 ID
|
||||
settingsForId=ID ''{0}'' の設定
|
||||
userProfileSettingsTabDescription=ユーザ設定は、このページの多くの場所にあるダイアログを設定することによって生成されます。このビューには、収集したすべての設定がパワーユーザ向けの技術的な方法で表示されます。削除したエントリは復元できないため、注意して使用してください。
|
||||
resetToDefault=デフォルトに戻す
|
||||
resetToDefaultInProgress=リセット中です...
|
||||
settingsRemoved=デフォルト設定が復元されました
|
||||
settingsRemovedError=デフォルト設定を復元できませんでした
|
||||
userSettingsFilter=設定フィルタ
|
||||
requiresRegattaRaceAndLeaderboard=このページには有効なレガッタ名、レース名、およびリーダーボード名が必要です。
|
||||
couldNotFindRaceInRegatta=名前 {1} のレガッタに対して名前 {0} のレースが取得できませんでした
|
||||
@@ -1913,7 +1807,6 @@ errorFetchingDimensionData={0} の次元値のフェッチでエラーが発生:
|
||||
errorFetchingStatistics=サーバからの利用可能な統計のフェッチでエラーが発生: {0}
|
||||
errorFetchingAggregators=サーバからの利用可能な集計のフェッチでエラーが発生: {0}
|
||||
errorLoadingDataRetrieverChainDefinitions=利用可能な DataRetrieverChainDefinitions の取得でエラーが発生: {0}
|
||||
errorFetchingComponentsChangedTimepoint=サーバからのコンポーネント変更済タイムポイントのフェッチでエラーが発生: {0}
|
||||
errorRunningQuery=クエリの実行でエラーが発生: {0}
|
||||
errorReadingWindFixes=風フィックス {0} の読込でエラーが発生しました
|
||||
errorAddingWindFixForRace=レース {0} に対する風フィックスの追加でエラーが発生: {1}
|
||||
@@ -1972,7 +1865,7 @@ anniversaryMajorCountdownTeaser[one]=カウントダウン情報です。{1,numb
|
||||
anniversaryMajorCountdownDescription=SAP では、www.sapsailing.com における {0,number,#,###} 番目のレースを祝うことにしています。どのレースがゴールテープを切るでしょうか。その記念レースの主催者には、義援目的で合計で 10,000 ユーロが贈呈されます。当選者はこの Web サイトで発表されます。引き続きご注目いただき、一緒にカウントダウンしていきましょう。
|
||||
anniversaryRepdigitCountdownTeaser=カウントダウン情報です。{1,number,#,###} 番目のレースまでもうわずか {0,number,#,###} レースです。
|
||||
anniversaryRepdigitCountdownTeaser[one]=カウントダウン情報です。{1,number,#,###} 番目のレースまでもうわずか {0,number,#,###} レースです。
|
||||
anniversaryRepdigitCountdownDescription=SAP では、縁起のいい番号のレースを祝うことにしています。www.sapsailing.com における {0,number, #,###} 番目のレースは誰が行うことになるでしょうか。そのレースの参加者には、SAP から最上級のサマーフェスティバルが無料で提供されます。当選者はこの Web サイトで発表されます。引き続きご注目いただき、一緒にカウントダウンしていきましょう。
|
||||
anniversaryRepdigitCountdownDescription=SAP では、縁起のいい番号のレースを祝うことにしています。www.sapsailing.com における {0,number, #,###} 番目のレースは誰が行うことになるでしょうか。その主催者にはドリンク込みのバーベキューが無料で提供されます。当選者はこの Web サイトで発表されます。引き続きご注目いただき、一緒にカウントダウンしていきましょう。
|
||||
anniversaryAnnouncementTeaser=SAP Sailing Analytics で {0,number,#,###} レースというミッションが達成されました。
|
||||
anniversaryAnnouncementDescription=3、2、1...。レース {0} の参加者のみなさん、おめでとうございます。当選をお知らせします。SAP Sailing Analytics をご信頼いただきありがとうございます。さらに 10,000 レースをご一緒に帆走できることを願っております。
|
||||
anniversaryRaceLinkText=記念レース表示
|
||||
@@ -1987,11 +1880,6 @@ minimumRideHeightInMetersTooltip=艇がフォイリング状態にあるとみ
|
||||
minimumDurationBetweenAdjacentFoilingSegmentsInSeconds=隣り合ったフォイリングセグメント間の最小時間 (秒)
|
||||
minimumDurationBetweenAdjacentFoilingSegmentsInSecondsTooltip=空白でなく、隣り合った 2 つのフォイリングセグメント間の時間がこの値より小さい場合、隣り合ったこれらのフォイリングセグメントは 1 つにマージされます。
|
||||
needToProvideValidMinimumRideHeight=浮上高さの有効な値をメートル単位で指定する必要があります。
|
||||
dataMiningErrorMargins=エラーマージン
|
||||
elements={0} 要素
|
||||
chooseDifferentDimensionTitle=異なる次元の選択
|
||||
chooseDifferentDimensionMessage=結果をグルーピングするための異なる次元を選択してください
|
||||
pleaseSelectADimension=次元を選択してください
|
||||
currentPortDaggerboardRake=左舷ダガーボード傾斜角
|
||||
currentPortDaggerboardRakeTooltip=現在の左舷ダガーボード傾斜角
|
||||
currentStbdDaggerboardRake=右舷ダガーボード傾斜角
|
||||
@@ -2084,7 +1972,7 @@ windFinderWindSourceTypeName=Windfinder
|
||||
windFinderWindSourceTypeTooltip=www.windfinder.com の 1 つ以上のスポットから風を測定しました
|
||||
windFinder=Windfinder
|
||||
enterTagsForTheVideo=動画のタグを入力
|
||||
enterIdOfWindfinderReviewedSpotCollection=レビューする Windfinder スポットコレクションの ID を入力 (例: "schilksee")
|
||||
enterIdOfWindFinderReviewedSpotCollection=レビューする Windfinder スポットコレクションの ID を入力 (例: "schilksee")
|
||||
enterTagsForTheImage=画像のタグを入力
|
||||
unableToResolveWindFinderSpotId=ID {0} で Windfinder スポットを決定できませんでした: {1}
|
||||
windFinderWeatherData=気象データ
|
||||
@@ -2169,3 +2057,13 @@ multiVideoIdle=ワークキューが無効です
|
||||
multiVideoDoNoAdd=追加しないでください
|
||||
multiVideoOffsetInput=グローバル動画オフセット (msec):
|
||||
multiVideoDescription=Web サーバは、ファイルディスカバリを可能にするため、インデックスリスト (インデックスが作成されている場合はサブフォルダがサポート済) を用意していることが必要です。MP4 ファイルに含まれているメタデータの初期解析後に、追加する動画を左側のチェックボックス列で選択する必要があります。"音声/動画追加" ボタンにより、選択されているすべてのファイルに対してメディアトラックが作成され、右側の列で選択されているすべてのレースに対してそれらのファイルが追加されます。
|
||||
multiUrlChangeMediaTrack=複数メディアトラック URL の調整
|
||||
multiUrlChangeReplace=置換後の文字列
|
||||
multiUrlChangeFind=検索
|
||||
multiUrlChangeCannotSave=保存中にエラーが発生
|
||||
multiUrlChangeSave=URL 変更の保存
|
||||
multiUrlChangeNewURL=新規 URL
|
||||
multiUrlNoPrefixWarning=共通の接頭辞が見つかりませんでした。これは通常、選択した動画のうち現在同じ場所に置かれていないものがあることを意味します。自身の責任で進めてください。
|
||||
multiUrlChangeExplain=このダイアログはメディアトラック URL 先頭の共通部分をまとめて置換します。すべての URL が同一の接頭辞で始まっていることを確認してください。また、新規 URL 列を参照し、保存を選択する前に結果として得られる URL を吟味してください。
|
||||
lastEvent=最終イベント: {0}
|
||||
teaserOverallLinkToolTip=シリーズ全体を参照するには、黄色になっている隅の部分をクリックしてください
|
||||
|
||||
+12
-114
@@ -9,12 +9,10 @@ trackedBefore=Histórico de eventos rastreados
|
||||
general=Geral
|
||||
listRaces=Listar corridas
|
||||
listRegattas=Listar regatas
|
||||
numberPairResultsPresenter=Diagrama de dispersão
|
||||
wind=Vento
|
||||
maneuverType=Manobra
|
||||
windPanelLabel=Este é o painel eólico que até o momento está completamente vazio.
|
||||
refresh=Atualizar
|
||||
remove=Remover
|
||||
removeNumber=Remover ({0})
|
||||
windSource=Origem do vento
|
||||
dampeningInterval=Intervalo de atenuação
|
||||
@@ -53,7 +51,6 @@ trackedRaceConnectedToSelectedRaceName=Corrida rastreada conectada ao nome da co
|
||||
linkToColumn=Link a coluna
|
||||
unlink=Eliminar link
|
||||
leaderboardName=Nome do painel de classificação
|
||||
cancel=Cancelar
|
||||
pleaseEnterAName=Insira um nome
|
||||
pleaseEnterABoatClass=Insira uma classe de barcos
|
||||
discardRacesFromHowManyStartedRacesOn=Descartar mais um corrida a partir de quantas corridas iniciadas
|
||||
@@ -65,7 +62,6 @@ startingFromNumberOfRaces=A partir de quantas corridas
|
||||
renameLeaderboard=Renomear painel de classificação
|
||||
addColumnToLeaderboard=Adicionar coluna ao painel de classificação
|
||||
pleaseEnterNameForNewRaceColumn=Insira um nome para a coluna da corrida nova
|
||||
ok=OK
|
||||
medalRace=Corrida para medalha
|
||||
renameRace=Renomear corrida
|
||||
openSelectedLeaderboard=Abrir painel de classificação selecionado
|
||||
@@ -89,7 +85,6 @@ sapSailingAnalytics=Sailing Analytics
|
||||
leaderboard=Painel de classificação
|
||||
leaderboards=Painéis de classificação
|
||||
leaderboardSettings=Configurações do painel de classificação
|
||||
settings=Configurações
|
||||
selectAtLeastOneLegDetail=Selecionar pelo menos um detalhe da perna
|
||||
currentSpeedOverGroundInKnots=SOG
|
||||
currentSpeedOverGroundInKnotsTooltip=A velocidade no fundo atual.
|
||||
@@ -158,7 +153,6 @@ overallDetailsToShow=Detalhes gerais
|
||||
legDetailsToShow=Detalhes da perna
|
||||
columnMoveUp=Para cima
|
||||
columnMoveDown=Para baixo
|
||||
port=Bombordo
|
||||
raceStartTimeColumn=Corrida iniciada
|
||||
showOnlySelectedCompetitors=Visualizar somente os competidores selecionados
|
||||
showSelectedCompetitorsInfo=Visualizar caixa de informações para competidores selecionados
|
||||
@@ -173,7 +167,6 @@ tacks=Cambadas
|
||||
jibes=Jaibes
|
||||
penaltyCircles=Voltas de punição
|
||||
medalRaceIsNull=Valor de corrida para medalha não permitido
|
||||
configuration=Configuração
|
||||
maneuverTypes=Manobras
|
||||
chooseChart=Selecionar diagrama
|
||||
distanceTraveled=Distância navegada
|
||||
@@ -191,13 +184,11 @@ secondsPerNauticalMileUnit=s/NM
|
||||
metersUnit=m
|
||||
millimetersUnit=mm
|
||||
degreesUnit=°
|
||||
close=Encerrar
|
||||
compareCompetitors=Comparar competidores
|
||||
description=Descrição
|
||||
sailNumber=Número da vela
|
||||
country=País
|
||||
no3LetterCodes=Impossível encontrar os códigos de 3 letras do COI.
|
||||
add=Adicionar
|
||||
delete=Eliminar
|
||||
showCharts=Visualizar diagramas
|
||||
raceWithThisNameAlreadyExists=Já existe uma corrida com esse nome.
|
||||
@@ -269,7 +260,6 @@ printHint=Imprime a versão aplicada
|
||||
blockedApplyButton=Os competidores registrados são diferentes dos competidores da lista de pares!
|
||||
multiplierInfo=Multiplica os voos e cria-os uns junto aos outros de modo que seja possível uma competição com menos mudanças de barco.
|
||||
noPairingListAvailable=A função de impressão só está disponível caso já tenha sido aplicada uma lista de pares aos registros de corridas dos painéis de classificação selecionados.
|
||||
settingsForComponent=Configurações para {0}
|
||||
noEventsFound=Nenhum evento encontrado
|
||||
noEventSelected=Nenhum evento selecionado
|
||||
noLeaderboardsFound=Nenhum painel de classificação encontrado
|
||||
@@ -307,8 +297,6 @@ leaderboardGroup=Grupo de painéis de classificação
|
||||
pleaseEnterNonEmptyDescription=Insira uma descrição não vazia
|
||||
groupWithThisNameAlreadyExists=Já existe um grupo de painéis de classificação com este nome.
|
||||
detailsOfLeaderboardGroup=Detalhes do grupo de painéis de classificação
|
||||
edit=Processar
|
||||
save=Gravar
|
||||
abort=Cancelar
|
||||
noLeaderboardGroupWithNameFound=Não foi encontrado um grupo de painéis de classificação com o nome {0}
|
||||
overview=Síntese
|
||||
@@ -343,7 +331,6 @@ degreesShort=graus
|
||||
untracked=Não rastreado
|
||||
delayForLiveMode=Atraso para modo ao vivo:
|
||||
notAvailable=Não disponível
|
||||
details=Detalhes
|
||||
noGroupSelected=Nenhum grupo selecionado
|
||||
combinedWindSourceTypeName=Combinado
|
||||
legMiddleWindSourceTypeName=Meio da perna
|
||||
@@ -425,7 +412,6 @@ simulateAsLiveRace=Simular como corrida ao vivo
|
||||
simulateWithOffset=Deslocamento antes da partida em minutos:
|
||||
boatClassDoesNotMatchSelectedRegatta=As corridas selecionadas contêm classes de barcos que não são as mesmas da classe de barcos ''{0}'' da regata selecionada. Não será carregada nenhuma corrida.
|
||||
regattaExistForSelectedBoatClass=Existe pelo menos uma regata para as classes de barcos selecionadas. Criar regatas padrão para essas corridas?
|
||||
reload=Recarregar
|
||||
addRegatta=Adicionar regata...
|
||||
importRegattas=Importar regatas...
|
||||
exchangeName=Trocar nome
|
||||
@@ -647,7 +633,6 @@ totalNetPointsColumnTooltip=O total de pontos líquidos de um competidor na rega
|
||||
windData=Dados do vento
|
||||
gpsData=Dados do GPS
|
||||
status=Status
|
||||
noDataFound=Não foram encontrados dados
|
||||
displayName=Nome de exibição
|
||||
histogram=Histograma
|
||||
numberOfDataPoints=Número de pontos de dados
|
||||
@@ -895,12 +880,9 @@ legType=Tipo de perna
|
||||
sailID=Número da vela
|
||||
seriesLeaderboard=Painel de classificação da série
|
||||
regattaLeaderboards=Painéis de classificação de regata
|
||||
clearSelection=Anular seleção
|
||||
running=Em competição
|
||||
runAsSubstantive=Competir
|
||||
done=Concluído
|
||||
lastFinished=Último a concluir
|
||||
run=Competir
|
||||
times=vezes
|
||||
dataAmount=Quantidade de dados
|
||||
averageCleanedServerTime=∅ tempo de servidor anulado
|
||||
@@ -924,12 +906,8 @@ selectSheet=Selecionar folha
|
||||
cleanedServerTime=Tempo de servidor anulado
|
||||
overallTime=Tempo geral
|
||||
cleanedOverallTime=Tempo geral anulado
|
||||
dataMiningResult=Resultado de data mining
|
||||
groupBy=Agrupar por
|
||||
statisticToCalculate=Calcular estatística
|
||||
queryResultsChartSubtitle=Percorreu {0} entradas de dados em {1} segundos
|
||||
noQuerySelected=Nenhuma consulta selecionada
|
||||
runAutomatically=Executar automaticamente
|
||||
windImport_Upload=Carregar
|
||||
windImport_Title=Importar vento de Expedition
|
||||
windImport_BoatId=ID do barco:
|
||||
@@ -959,14 +937,9 @@ raceTimeTooltip=Tempo total navegado nesta corrida, começando a contagem quando
|
||||
raceTimeDownwindTooltip=Tempo total navegado a sotavento nesta corrida
|
||||
raceTimeReachingTooltip=Tempo total navegado a través nesta corrida
|
||||
raceTimeUpwindTooltip=Tempo total navegado a barlavento nesta corrida
|
||||
noStatisticSelectedError=Não foi selecionada uma estatística para calcular
|
||||
noCustomGrouperScriptTextError=O script do sistema de agrupamento está vazio
|
||||
noDimensionToGroupBySelectedError=Nenhuma dimensão pela qual efetuar o agrupamento selecionada
|
||||
noGrouperSelectedError=Nenhum tipo de sistema de agrupamento selecionado
|
||||
noDataRetrieverChainDefinitonSelectedError=Nenhum recuperador de dados selecionado
|
||||
queryNotValidBecause=Nenhuma consulta possível, pois
|
||||
dataMining=Data mining
|
||||
errorRunningDataMiningQuery=Ocorreu um erro ao executar a consulta
|
||||
hideToolbar=Ocultar barra de ferramentas
|
||||
showSeriesLeaderboards=Visualizar painéis de classificação da série
|
||||
showOverallLeaderboard=Visualizar painel de classificação geral
|
||||
@@ -989,7 +962,6 @@ id=ID
|
||||
allowReload=Permitir recarregamento
|
||||
compress=Comprimir
|
||||
compressTooltip=Utilizar somente se a instância do servidor de exportação estiver sendo executada pelo menos\ncom o commit 0fbf6071dea125bec4a56dee55d61c99def4a62e.
|
||||
queryRunner=Executor de consulta
|
||||
rerunQueryAfterRefresh=Executar consulta novamente após atualização
|
||||
refreshIntervalMustntBeEmpty=O intervalo de atualização não deve estar em branco
|
||||
selectionTables=Tabelas de seleção
|
||||
@@ -1074,12 +1046,7 @@ TWATooltip=O ângulo entre a direção dos competidores e o vento
|
||||
TWA=Ângulo do vento verdadeiro
|
||||
showBoatClassChartsLabel=Você também pode ver o diagrama geral para as classes de barcos disponíveis.
|
||||
showDiagram=Visualizar diagrama
|
||||
runAutomaticallyTooltip=Execute a consulta automaticamente, após modificar, por exemplo, a estatística ou o agrupamento.
|
||||
rerunQueryAfterRefreshTooltip=Executa novamente a consulta após a atualização das tabelas.
|
||||
queryDefinitionProvider=Fornecedor da definição da consulta
|
||||
statisticProvider=Fornecedor da estatística
|
||||
calculateThe=Calcular
|
||||
groupingProvider=Fornecedor do agrupamento
|
||||
releaseNotes=Novidades e histórico de releases
|
||||
hasSplitFleetContiguousScoring=Flotilhas divididas com pontuação contínua
|
||||
addRaceLogTracker=Adicionar rastreador RaceLog
|
||||
@@ -1237,7 +1204,6 @@ showAll=Visualizar tudo
|
||||
raceVisibilityColumn=Visibilidade
|
||||
enterCarryValueFor=Inserir pontos transferidos para competidor {0}
|
||||
advanced=Avançado
|
||||
basedOn=com base em
|
||||
retrieveWith=Recuperar com
|
||||
mappingDetails=Detalhes de mapeamento
|
||||
deviceMappingQrCodeExplanation=Se você estiver utilizando o app de rastreamento, você também pode adicionar o mapeamento do dispositivo selecionando um competidor/marca, definindo as horas de partida e chegada e digitalizando depois este código QR.
|
||||
@@ -1248,10 +1214,6 @@ enterImageURL=Inserir URL de imagem...
|
||||
enterVideoURL=Inserir URL de vídeo...
|
||||
enterSponsorImageURL=Inserir URL de imagem de patrocinador...
|
||||
enterRaceName=Inserir nome da corrida...
|
||||
serverError=Ocorreu um erro ao tentar contatar o servidor. Verifique sua conexão à rede e tente novamente.
|
||||
remoteProcedureCall=Chamada de procedimento remoto
|
||||
serverReplies=Respostas do servidor
|
||||
errorCommunicatingWithServer=Erro ao comunicar com o servidor
|
||||
userManagement=Administração de usuários
|
||||
regattaStructureImport=Importação de estrutura da regata
|
||||
filteredBy=filtrado por
|
||||
@@ -1279,7 +1241,6 @@ noFleetsDefined=Nenhuma flotilha definida.
|
||||
successfullyCreatedRegattas=Regatas criadas com êxito
|
||||
errorTryingToRegisterRacesForTracking=Erro ao tentar registrar corridas {0} para rastreamento: {1}. Verificar sintaxe de URI ao vivo/armazenado.
|
||||
errorDeterminingPolarAvailability=Erro ao determinar disponibilidade de dados de carta polar/VPP para corrida {0}: {1}
|
||||
error=Erro
|
||||
fileStorage=Armazenamento de arquivos
|
||||
active=Ativo
|
||||
scoringSchemeHighPointEssOverallDescription=Pontuação em pontos. O vencedor de uma etapa pontua 10 pontos, o 2º - 9 pontos, .... O desempate na pontuação geral da Extreme Sailing Series é efetuado a favor do competidor com o maior número de vitórias em etapas. Se isso não efetuar o desempate, será utilizado o resultado na última etapa.
|
||||
@@ -1322,7 +1283,6 @@ showCompetitorFullNameColumn=Nome completo do competidor
|
||||
alwaysShowCompetitorNationalityColumn=Exibir sempre a nacionalidade do competidor
|
||||
alwaysShowCompetitorNationalityColumnTooltip=Exibir ambos, bandeiras de nacionalidade e imagens do competidor, se disponíveis
|
||||
loadingDimensionValues=Carregando valores de dimensão
|
||||
runningQuery=Executando consulta
|
||||
inviteBuoyTenders=Convidar navios-balizadores
|
||||
orMultipleEmails=ou vários e-mails separados por vírgula
|
||||
courseOverGroundTrueDegreesTooltip=Percurso verdadeiro no fundo em graus
|
||||
@@ -1330,11 +1290,6 @@ courseOverGroundTrueDegrees=COG
|
||||
distanceIncludingGateStartInMeters=Distância (com início do portão)
|
||||
distanceTraveledIncludingGateStartTooltip=A distância navegada desde o início até o fim da perna\nou até a data/hora atual, se a perna não estiver concluída.\nSe a perna incluir o início de um portão, a distância desde o fim da marcação até a posição inicial é incluída\npara ser possível comparar os competidores mesmo quando começam em horas diferentes.
|
||||
raceDistanceTraveledIncludingGateStartTooltip=A distância navegada desde o início até o fim da corrida\nou até a data/hora atual, se a corrida não estiver concluída.\nPara o início de um portão, a distância desde o fim da marcação até a posição inicial é incluída\npara ser possível comparar os competidores mesmo quando começam em horas diferentes.
|
||||
results=Resultados
|
||||
groupName=Nome do grupo
|
||||
valueAscending=Valor (crescente)
|
||||
valueDescending=Valor (decrescente)
|
||||
sortBy=Ordenar por
|
||||
dashboardHeader=Painel
|
||||
dashboardNoWindBotAvailableHeader=O Wind Bot não está disponível.
|
||||
dashboardNoWindBotAvailableMessage=Para receber dados do vento em tempo real de unidades de medida do vento, certifique-se de que o Wind Bot está ativado e conectado ao SAP Sailing Analytics.
|
||||
@@ -1370,16 +1325,12 @@ dashboardRCBoat=Barco de rádio-controle
|
||||
fixedMarkPassing=(fixo)
|
||||
suppressedMarkPassing=(suprimido)
|
||||
windUp=Orientação pelo vento (visualizar vento no topo do mapa)
|
||||
filterBy=Filtrar por
|
||||
currentFilterSelection=Seleção de filtro atual
|
||||
notCapableOfGeneratingACodeForIdentifier=Não é possível gerar um código para este identificador.
|
||||
serverUrl=URL de servidor
|
||||
rotatedFromTrueNorth=Efetuou a rotação de {0} graus desde o norte verdadeiro.
|
||||
clickToToggleWindUp=Tocar/clicar para comutar entre exibição do mapa com orientação pelo vento e pelo norte
|
||||
clickToToggleWindStreamlets=Tocar/clicar para visualizar ou ocultar os cursos do vento
|
||||
startLineToFirstMarkTriangle=Partida para primeira marca ({0}m)
|
||||
dataMiningComponentsHaveBeenUpdated=Os componentes de data mining foram atualizados
|
||||
dataMiningComponentsNeedReloadDialogMessage=Clicar em Recarregar para recarregar os componentes agora. Isto irá descartar os dados exibidos atualmente e executar uma consulta padrão.\nClicar em Fechar para não efetuar nada. O data mining não irá funcionar corretamente até ser efetuado o recarregamento dos componentes.
|
||||
noDataForEvent=Ainda não existem dados para o evento.
|
||||
countriesCount={0,number} países
|
||||
countriesCount[one]={0,number} país
|
||||
@@ -1477,15 +1428,7 @@ noFinishedRaces=Ainda não existem corridas concluídas.
|
||||
racesOverview=Síntese de corridas
|
||||
listFormatLabel=Formato de lista
|
||||
competitionFormatLabel=Formato da competição
|
||||
empty=Vazio
|
||||
runAQuery=Executar uma consulta
|
||||
latestRegattaStandings=Posições da última regata
|
||||
plainText=Texto simples
|
||||
columnChart=Diagrama de colunas
|
||||
columnChartWithErrorBars=Diagrama de colunas com barras de erros
|
||||
choosePresentation=Selecionar apresentação
|
||||
cantDisplayDataOfType=Não é possível exibir dados do tipo {0}
|
||||
shownDecimals=Decimais exibidos
|
||||
openFullscreenView=Abrir visão de tela inteira
|
||||
closeFullscreenView=Fechar visão de tela inteira
|
||||
videosCount={0,number} vídeos
|
||||
@@ -1495,15 +1438,8 @@ photosCount[one]={0,number} foto
|
||||
eventsHaveTakenPlace=Foram realizados {0} eventos
|
||||
eventsHaveTakenPlace[one]=Foi realizado um evento
|
||||
raceOffice=Secretaria do evento
|
||||
analyze=Analisar
|
||||
dataMiningSettings=Configurações de data mining
|
||||
multiResultsPresenter=Apresentador de vários resultados
|
||||
plainResultsPresenter=Apresentador de resultados simples
|
||||
resultsChart=Diagrama de resultados
|
||||
tabbedResultsPresenter=Apresentador de resultados por fichas
|
||||
polarResultsPresenter=Apresentador de resultados da carta polar
|
||||
maneuverSpeedDetailsResultsPresenter=Apresentador de resultados detalhados da velocidade da manobra
|
||||
dataMiningRetrieval=Obtenção de dados
|
||||
actionWatch=Ver
|
||||
actionAnalyze=Analisar
|
||||
denoteAllRacesForRaceLogTrackingShorctut=Atalho para denotar todas as corridas para rastreamento do registro de corridas
|
||||
@@ -1519,24 +1455,8 @@ defaultName=Padrão
|
||||
exampleTextForName=Seu nome é parecido com:
|
||||
flightsCount={0,number} voos
|
||||
flightsCount[one]={0,number} voo
|
||||
viewQueryDefinition=Ver definição da consulta
|
||||
queryDefinitionViewer=Visualizador de definição da consulta
|
||||
groupAverageAscending=Média do grupo (crescente)
|
||||
groupAverageDescending=Média do grupo (decrescente)
|
||||
groupMedianAscending=Mediana do grupo (crescente)
|
||||
groupMedianDescending=Mediana do grupo (decrescente)
|
||||
resultsFoundForSearch={0,number} resultados encontrados para ''{1}''
|
||||
resultsFoundForSearch[one]={0,number} resultado encontrado para ''{1}''
|
||||
runPredefinedQuery=Executar consulta predefinida
|
||||
selectPredefinedQuery=Selecionar consulta predefinida
|
||||
predefinedQueryRunner=Executor de consulta predefinida
|
||||
developerOptions=Opções do desenvolvedor
|
||||
copyToClipboard=Copiar para o clipboard
|
||||
code=Código
|
||||
useClassGetName=Utilizar Class.getName() para nomes de tipo
|
||||
useClassGetNameTooltip=Mais robusto em relação às modificações na base do código, mas o trecho do código só pode ser utilizado no âmbito em que as classes estão disponíveis.
|
||||
useStringLiterals=Utilizar literais de cadeia para nomes de tipo
|
||||
useStringLiteralsTooltip=O trecho do código pode ser utilizado em qualquer local, mas será quebrado se a base do código for modificada.
|
||||
errorLoadingDataWithTryAgain=Erro ao carregar dados. Tentar novamente dentro de momentos.
|
||||
addGalleryPhoto=Adicionar foto da galeria
|
||||
addStageImage=Adicionar imagem da etapa
|
||||
@@ -1556,7 +1476,6 @@ warningForDisabledCompetitors=Os competidores seguintes não podem ser registrad
|
||||
competitorToolTipMessage={0} já foi atribuído à flotilha {2} na corrida {3} e por isso não pode ser atribuído à flotilha {1} na mesma corrida
|
||||
addMarkToRegatta=Adicionar marca à regata
|
||||
selectALeaderboardGroup=Selecionar um grupo de painéis de classificação...
|
||||
pleaseSelect=Selecione
|
||||
requiresValidRegatta=Esta página requer uma regata válida, a coluna da corrida e o nome da flotilha para identificar a corrida a ser exibida.
|
||||
couldNotObtainRace=Não foi possível obter uma corrida com o nome {1} para a flotilha {2} para uma regata com o nome {0}: {3}
|
||||
errorTryingToCreateEmbeddedMap=Erro ao tentar criar o mapa integrado: {0}
|
||||
@@ -1836,30 +1755,9 @@ eventRegattaHeaderLegendGpsNo=Sem dados de rastreamento
|
||||
eventRegattaHeaderLegendWindNo=Sem dados de vento
|
||||
eventRegattaHeaderLegendVideoNo=Sem fluxos de vídeo
|
||||
eventRegattaHeaderLegendAudioNo=Sem fluxos de áudio
|
||||
angleInDegree=Ângulo em graus
|
||||
angleInRadian=Ângulo em radianos
|
||||
centralAngleInRadian=Ângulo central em radianos
|
||||
centralAngleInDegree=Ângulo central em graus
|
||||
kilometers=Quilômetros
|
||||
meters=Metros
|
||||
nauticalMiles=Milhas náuticas
|
||||
seaMiles=Milhas marítimas
|
||||
geographicalMiles=Milhas geográficas
|
||||
days=Dias
|
||||
hours=Horas
|
||||
minutes=Minutos
|
||||
seconds=Segundos
|
||||
milliseconds=Milissegundos
|
||||
floatNumber=Margem
|
||||
integer=Número inteiro
|
||||
appendResult=Anexar resultado
|
||||
sampleColor=Amostra de cor
|
||||
sharedSettingsLink=Link com configurações
|
||||
leaderboardPage=Página do painel de classificação
|
||||
makeDefault=Definir como padrão
|
||||
makeDefaultInProgress=Em andamento...
|
||||
settingsSavedMessage=Suas configurações atuais foram definidas com êxito como padrão
|
||||
settingsSaveErrorMessage=Ocorreu um erro ao definir suas configurações como padrão
|
||||
showLiveNow=Exibir "Ao vivo agora"
|
||||
useOnlyOneOfStartTimeInferenceAndControlTrackingFromStartAndFinishTimes=Utilizar só um de "Inferência da hora de partida" e "Rastreamento de controle das horas de partida e chegada"
|
||||
unknownLeaderboardType=Tipo de painel de classificação desconhecido {0}
|
||||
@@ -1878,10 +1776,6 @@ settingsId=ID de configurações
|
||||
documentSettingsId=ID de configuração do documento
|
||||
settingsForId=Configurações para ID ''{0}''
|
||||
userProfileSettingsTabDescription=As configurações dos usuários são geradas por diálogos de configurações que podem ser encontrados em vários locais da página. Esta visão exibe todas as suas configurações coletadas de uma forma técnica para usuários avançados. Tenha em atenção que as entradas removidas não podem ser restauradas, por isso utilize-as com cuidado.
|
||||
resetToDefault=Reinicializar para padrão
|
||||
resetToDefaultInProgress=Na reinicialização...
|
||||
settingsRemoved=Configurações padrão restauradas
|
||||
settingsRemovedError=Não foi possível restaurar configurações padrão
|
||||
userSettingsFilter=Filtro de configurações
|
||||
requiresRegattaRaceAndLeaderboard=Esta página necessita de um nome de regata, um nome de corrida e um nome de painel de classificação válidos.
|
||||
couldNotFindRaceInRegatta=Não foi possível obter uma corrida com o nome {0} para uma regata com o nome {1}
|
||||
@@ -1913,7 +1807,6 @@ errorFetchingDimensionData=Erro ao chamar os valores de dimensão de {0}: {1}
|
||||
errorFetchingStatistics=Erro ao chamar as estatísticas disponíveis do servidor: {0}
|
||||
errorFetchingAggregators=Erro ao chamar os agregadores disponíveis do servidor: {0}
|
||||
errorLoadingDataRetrieverChainDefinitions=Erro ao recuperar as definições da cadeia do recuperador de dados disponíveis: {0}
|
||||
errorFetchingComponentsChangedTimepoint=Erro ao chamar data/hora modificada de componentes do servidor: {0}
|
||||
errorRunningQuery=Erro ao executar consulta: {0}
|
||||
errorReadingWindFixes=Erro ao ler pontos fixos de vento {0}
|
||||
errorAddingWindFixForRace=Erro ao adicionar um ponto fixo de vento para corrida {0}: {1}
|
||||
@@ -1972,7 +1865,7 @@ anniversaryMajorCountdownTeaser[one]=Contagem regressiva! Só resta {0,number,#,
|
||||
anniversaryMajorCountdownDescription=Estamos celebrando nossa {0,number,#,###}ª corrida em www.sapsailing.com! Que corrida ultrapassará a marca? O organizador desta corrida de comemoração receberá um total de 10.000 Euros para fins beneficentes. O vencedor será anunciado neste site. Fique atento e conte conosco.
|
||||
anniversaryRepdigitCountdownTeaser=Contagem regressiva! Só restam {0,number,#,###} corridas até a {1,number,#,###}ª corrida.
|
||||
anniversaryRepdigitCountdownTeaser[one]=Contagem regressiva! Só resta {0,number,#,###} corrida até a {1,number,#,###}ª corrida.
|
||||
anniversaryRepdigitCountdownDescription=Estamos celebrando a corrida do nosso número da sorte! Quem fará parte da {0,number, #,###}ª corrida em www.sapsailing.com? Os participantes desta corrida receberão entradas gratuitas no festival de verão de topo da SAP. Os vencedores serão anunciados neste site. Fique atento e conte conosco.
|
||||
anniversaryRepdigitCountdownDescription=Estamos celebrando a corrida do nosso número da sorte! Quem fará parte da {0,number, #,###}ª corrida em www.sapsailing.com? Os organizadores receberão um churrasco grátis com bebidas. Os vencedores serão anunciados neste site. Fique atento e conte conosco.
|
||||
anniversaryAnnouncementTeaser=Missão cumprida! {0,number,#,###} corridas com o SAP Sailing Analytics!
|
||||
anniversaryAnnouncementDescription=3,2,1... Felicitamos os participantes da corrida {0}. Você conseguiu! Agradecemos sua confiança no SAP Sailing Analytics e esperamos continuar velejando mais 10.000 corridas com você!
|
||||
anniversaryRaceLinkText=Exibir corrida de comemoração
|
||||
@@ -1987,11 +1880,6 @@ minimumRideHeightInMetersTooltip=A altura mínima de flutuação em metros neces
|
||||
minimumDurationBetweenAdjacentFoilingSegmentsInSeconds=Duração mínima entre segmentos adjacentes de navegação com hidrofólio (s)
|
||||
minimumDurationBetweenAdjacentFoilingSegmentsInSecondsTooltip=Se não estiver em branco e o tempo entre dois segmentos adjacentes de navegação com hidrofólio for inferior a este, esses segmentos adjacentes de navegação com hidrofólio serão consolidados em um.
|
||||
needToProvideValidMinimumRideHeight=Você precisa fornecer um valor válido da altura mínima de flutuação em metros.
|
||||
dataMiningErrorMargins=Margens de erro
|
||||
elements={0} elementos
|
||||
chooseDifferentDimensionTitle=Selecionar dimensão diferente
|
||||
chooseDifferentDimensionMessage=Selecionar uma dimensão diferente para os resultados do agrupamento
|
||||
pleaseSelectADimension=Selecione uma dimensão
|
||||
currentPortDaggerboardRake=Inclinação bolina bombordo
|
||||
currentPortDaggerboardRakeTooltip=A inclinação atual da bolina para bombordo
|
||||
currentStbdDaggerboardRake=Inclinação bolina boreste
|
||||
@@ -2084,7 +1972,7 @@ windFinderWindSourceTypeName=WindFinder
|
||||
windFinderWindSourceTypeTooltip=Vento medido de um ou mais locais de www.windfinder.com
|
||||
windFinder=WindFinder
|
||||
enterTagsForTheVideo=Inserir etiquetas para o vídeo
|
||||
enterIdOfWindfinderReviewedSpotCollection=Inserir ID de uma coleção revisada de locais do WindFinder, por exemplo, "schilksee"
|
||||
enterIdOfWindFinderReviewedSpotCollection=Inserir ID de uma coleção revisada de locais do WindFinder, por exemplo, "schilksee"
|
||||
enterTagsForTheImage=Inserir etiquetas para a imagem
|
||||
unableToResolveWindFinderSpotId=Impossível resolver local do WindFinder com ID {0}: {1}
|
||||
windFinderWeatherData=Dados metereológicos
|
||||
@@ -2169,3 +2057,13 @@ multiVideoIdle=A lista de trabalho está inativa
|
||||
multiVideoDoNoAdd=Não adicionar
|
||||
multiVideoOffsetInput=Deslocamento de vídeo global em milissegundos:
|
||||
multiVideoDescription=O servidor da web é necessário para fornecer a lista de índices (subpastas suportadas se indexadas) de modo a permitir a descoberta de arquivos. Após uma análise inicial dos metadados contidos nos arquivos mp4, os vídeos que deviam ser adicionados precisam ser selecionados mediante a coluna do campo de seleção à esquerda. O botão "Adicionar áudio/vídeo" irá criar faixas de mídia para todos os arquivos selecionados e adicionar esses arquivos a todas as corridas selecionadas na coluna à direita.
|
||||
multiUrlChangeMediaTrack=Ajustar vários URLs de faixa de mídia
|
||||
multiUrlChangeReplace=Substituir por
|
||||
multiUrlChangeFind=Procurar
|
||||
multiUrlChangeCannotSave=Ocorreu um erro ao gravar
|
||||
multiUrlChangeSave=Gravar modificações do URL
|
||||
multiUrlChangeNewURL=novo URL
|
||||
multiUrlNoPrefixWarning=Não foi encontrado um prefixo comum. Isso normalmente significa que nem todos os vídeos selecionados estão hospedados atualmente no mesmo local. Continue por sua própria conta e risco!
|
||||
multiUrlChangeExplain=Este diálogo irá substituir em massa as partes comuns no início dos URLs da faixa de mídia. Assegure que todos os URLs começam com o mesmo prefixo! Veja também a nova coluna do URL e teste os URLs resultantes antes de pressionar Gravar!
|
||||
lastEvent=Último evento: {0}
|
||||
teaserOverallLinkToolTip=Para ver a série completa, clique no canto amarelo
|
||||
|
||||
+12
-114
@@ -9,12 +9,10 @@ trackedBefore=История отслеживаемых событий
|
||||
general=Общее
|
||||
listRaces=Список гонок
|
||||
listRegattas=Список регат
|
||||
numberPairResultsPresenter=Множество точек
|
||||
wind=Ветер
|
||||
maneuverType=Маневр
|
||||
windPanelLabel=Это панель ветров, которая пока что совсем пуста.
|
||||
refresh=Обновить
|
||||
remove=Удалить
|
||||
removeNumber=Удалить ({0})
|
||||
windSource=Источник ветра
|
||||
dampeningInterval=Интервал смягчения
|
||||
@@ -53,7 +51,6 @@ trackedRaceConnectedToSelectedRaceName=Отслеживаемая гонка с
|
||||
linkToColumn=Связать со столбцом
|
||||
unlink=Отменить связывание
|
||||
leaderboardName=Название таблицы лидеров
|
||||
cancel=Отмена
|
||||
pleaseEnterAName=Введите название
|
||||
pleaseEnterABoatClass=Введите класс лодки
|
||||
discardRacesFromHowManyStartedRacesOn=Исключить еще одну гонку, начиная с числа стартов в гонках
|
||||
@@ -65,7 +62,6 @@ startingFromNumberOfRaces=Начиная с числа гонок
|
||||
renameLeaderboard=Переименовать таблицу лидеров
|
||||
addColumnToLeaderboard=Добавить столбец в таблицу лидеров
|
||||
pleaseEnterNameForNewRaceColumn=Введите название для столбца новой гонки
|
||||
ok=ОК
|
||||
medalRace=Гонка на медали
|
||||
renameRace=Переименовать гонку
|
||||
openSelectedLeaderboard=Открыть выбранную таблицу лидеров
|
||||
@@ -89,7 +85,6 @@ sapSailingAnalytics=Sailing Analytics
|
||||
leaderboard=Таблица лидеров
|
||||
leaderboards=Таблицы лидеров
|
||||
leaderboardSettings=Параметры таблицы лидеров
|
||||
settings=Параметры
|
||||
selectAtLeastOneLegDetail=Выберите подробности минимум одного отрезка
|
||||
currentSpeedOverGroundInKnots=SOG
|
||||
currentSpeedOverGroundInKnotsTooltip=Текущая скорость относительно грунта.
|
||||
@@ -158,7 +153,6 @@ overallDetailsToShow=Общие подробности
|
||||
legDetailsToShow=Подробности
|
||||
columnMoveUp=Вверх
|
||||
columnMoveDown=Вниз
|
||||
port=Порт
|
||||
raceStartTimeColumn=Гонка начата
|
||||
showOnlySelectedCompetitors=Показать только выбранных участников
|
||||
showSelectedCompetitorsInfo=Показать окно сведений для выбранных участников
|
||||
@@ -173,7 +167,6 @@ tacks=Поворотов оверштаг
|
||||
jibes=Поворотов фордевинд
|
||||
penaltyCircles=Штрафных кругов
|
||||
medalRaceIsNull=Значение гонки на медали не разрешено
|
||||
configuration=Конфигурация
|
||||
maneuverTypes=Маневры
|
||||
chooseChart=Выберите диаграмму
|
||||
distanceTraveled=Пройденная дистанция
|
||||
@@ -191,13 +184,11 @@ secondsPerNauticalMileUnit=с/NM
|
||||
metersUnit=м
|
||||
millimetersUnit=мм
|
||||
degreesUnit=°
|
||||
close=Закрыть
|
||||
compareCompetitors=Сравнить участников
|
||||
description=Описание
|
||||
sailNumber=Номер на парусе
|
||||
country=Страна
|
||||
no3LetterCodes=Не удалось найти 3-буквенные коды IOC.
|
||||
add=Добавить
|
||||
delete=Удалить
|
||||
showCharts=Показать диаграммы
|
||||
raceWithThisNameAlreadyExists=Гонка с таким названием уже существует.
|
||||
@@ -269,7 +260,6 @@ printHint=Печать примененной версии
|
||||
blockedApplyButton=Число зарегистрированных участников не равно числу участников из списка пар!
|
||||
multiplierInfo=Планируйте флайты таким образом, чтобы обеспечить соревнования с минимально возможной переменой лодок
|
||||
noPairingListAvailable=Функция печати доступна только после применения списка пар к журналам гонок выбранных таблиц лидеров.
|
||||
settingsForComponent={0} — параметры
|
||||
noEventsFound=События не найдены
|
||||
noEventSelected=Не выбрано событие
|
||||
noLeaderboardsFound=Таблицы лидеров не найдены
|
||||
@@ -307,8 +297,6 @@ leaderboardGroup=Группа таблиц лидеров
|
||||
pleaseEnterNonEmptyDescription=Введите непустое описание
|
||||
groupWithThisNameAlreadyExists=Группа таблиц лидеров с таким названием уже существует.
|
||||
detailsOfLeaderboardGroup=Подробности группы таблиц лидеров
|
||||
edit=Изменить
|
||||
save=Сохранить
|
||||
abort=Прервать
|
||||
noLeaderboardGroupWithNameFound=Группа таблиц лидеров с названием {0} не найдена
|
||||
overview=Обзор
|
||||
@@ -343,7 +331,6 @@ degreesShort=град.
|
||||
untracked=Не отслеживается
|
||||
delayForLiveMode=Задержка для оперативного режима:
|
||||
notAvailable=Недоступно
|
||||
details=Подробности
|
||||
noGroupSelected=Не выбрана группа
|
||||
combinedWindSourceTypeName=Объединено
|
||||
legMiddleWindSourceTypeName=Середина отрезка
|
||||
@@ -425,7 +412,6 @@ simulateAsLiveRace=Симулировать как текущую гонку
|
||||
simulateWithOffset=Сдвиг перед стартом в минутах:
|
||||
boatClassDoesNotMatchSelectedRegatta=Выбранные гонки содержат классы лодок, не совпадающие с классом лодок ''{0}'' выбранной регаты. Гонки не будут загружены.
|
||||
regattaExistForSelectedBoatClass=Для выбранных классов лодок имеется минимум одна регата. Создать регаты по умолчанию для соответствующих гонок?
|
||||
reload=Перезагрузить
|
||||
addRegatta=Добавить регату...
|
||||
importRegattas=Импортировать регаты...
|
||||
exchangeName=Имя Exchange
|
||||
@@ -647,7 +633,6 @@ totalNetPointsColumnTooltip=Общая чистая сумма баллов уч
|
||||
windData=Данные о ветре
|
||||
gpsData=Данные GPS
|
||||
status=Статус
|
||||
noDataFound=Данные не найдены
|
||||
displayName=Отображаемое имя
|
||||
histogram=Гистограмма
|
||||
numberOfDataPoints=Число точек данных
|
||||
@@ -895,12 +880,9 @@ legType=Тип отрезка
|
||||
sailID=Номер на парусе
|
||||
seriesLeaderboard=Таблица лидеров серии
|
||||
regattaLeaderboards=Таблицы лидеров регаты
|
||||
clearSelection=Очистить выбор
|
||||
running=Выполнение
|
||||
runAsSubstantive=Выполнить
|
||||
done=Готово
|
||||
lastFinished=Последний финиш
|
||||
run=Выполнить
|
||||
times=раз
|
||||
dataAmount=Объем данных
|
||||
averageCleanedServerTime=∅ Очищенное время сервера
|
||||
@@ -924,12 +906,8 @@ selectSheet=Выбрать лист
|
||||
cleanedServerTime=Очищенное время сервера
|
||||
overallTime=Общее время
|
||||
cleanedOverallTime=Очищенное общее время
|
||||
dataMiningResult=Результат добычи данных
|
||||
groupBy=Группировать по
|
||||
statisticToCalculate=Рассчитать статистику
|
||||
queryResultsChartSubtitle=Обработано {0} зап. данных за {1} с
|
||||
noQuerySelected=Не выбран запрос
|
||||
runAutomatically=Выполнить автоматически
|
||||
windImport_Upload=Отправить
|
||||
windImport_Title=Импортировать ветер из Expedition
|
||||
windImport_BoatId=Ид. лодки:
|
||||
@@ -959,14 +937,9 @@ raceTimeTooltip=Общее время хода в данной гонке, от
|
||||
raceTimeDownwindTooltip=Общее время хода по ветру в данной гонке
|
||||
raceTimeReachingTooltip=Общее время хода полным ветром в данной гонке
|
||||
raceTimeUpwindTooltip=Общее время хода против ветра в данной гонке
|
||||
noStatisticSelectedError=Не выбрана статистика для расчета
|
||||
noCustomGrouperScriptTextError=Скрипт группирования пуст
|
||||
noDimensionToGroupBySelectedError=Не выбрано базовое измерение для группировки
|
||||
noGrouperSelectedError=Не выбран тип группирования
|
||||
noDataRetrieverChainDefinitonSelectedError=Не выбрано средство извлечения данных
|
||||
queryNotValidBecause=Запрос невозможен, так как
|
||||
dataMining=Добыча данных
|
||||
errorRunningDataMiningQuery=При выполнении запроса возникла ошибка
|
||||
hideToolbar=Скрыть панель инструментов
|
||||
showSeriesLeaderboards=Показать таблицы лидеров серии
|
||||
showOverallLeaderboard=Показать итоговую таблицу лидеров
|
||||
@@ -989,7 +962,6 @@ id=Ид.
|
||||
allowReload=Разрешить перезагрузку
|
||||
compress=Сжать
|
||||
compressTooltip=Используйте только при выполнении экспортирующего экземпляра\nсервера с commit 0fbf6071dea125bec4a56dee55d61c99def4a62e.
|
||||
queryRunner=Выполнение запроса
|
||||
rerunQueryAfterRefresh=Повторить запрос после обновления
|
||||
refreshIntervalMustntBeEmpty=Интервал обновления не должен быть пустым
|
||||
selectionTables=Таблица выбора
|
||||
@@ -1074,12 +1046,7 @@ TWATooltip=Угол между направлением участников и
|
||||
TWA=Угол истинного ветра
|
||||
showBoatClassChartsLabel=Также можно просмотреть общую диаграмму для доступных классов лодок.
|
||||
showDiagram=Показать диаграмму
|
||||
runAutomaticallyTooltip=Автоматически выполнять запрос после изменения, например, статистики или группирования.
|
||||
rerunQueryAfterRefreshTooltip=Повторно выполняет запрос после обновления таблиц.
|
||||
queryDefinitionProvider=Поставщик определения запроса
|
||||
statisticProvider=Поставщик статистики
|
||||
calculateThe=Рассчитать
|
||||
groupingProvider=Поставщик группирования
|
||||
releaseNotes=Новости и история выпусков
|
||||
hasSplitFleetContiguousScoring=Разделенные флоты получили сходные оценки
|
||||
addRaceLogTracker=Добавить средство отслеживания журнала гонки
|
||||
@@ -1237,7 +1204,6 @@ showAll=Показать все
|
||||
raceVisibilityColumn=Видимость
|
||||
enterCarryValueFor=Введите перенесенные баллы для участника {0}
|
||||
advanced=Дополнительно
|
||||
basedOn=на базе
|
||||
retrieveWith=Получить с помощью
|
||||
mappingDetails=Подробности сопоставления
|
||||
deviceMappingQrCodeExplanation=Если используется приложение отслеживания, можно добавить также сопоставление устройства, выбрав участника/отметку, установив интервал времени, а затем отсканировав этот QR-код.
|
||||
@@ -1248,10 +1214,6 @@ enterImageURL=Ввести URL-адрес изображения...
|
||||
enterVideoURL=Ввести URL-адрес видео...
|
||||
enterSponsorImageURL=Ввести URL-адрес изображения спонсора...
|
||||
enterRaceName=Ввести название гонки...
|
||||
serverError=При попытке обратиться к серверу возникла ошибка. Проверьте подключение к сети и повторите попытку.
|
||||
remoteProcedureCall=Удаленный вызов процедуры
|
||||
serverReplies=Ответы сервера
|
||||
errorCommunicatingWithServer=Ошибка связи с сервером
|
||||
userManagement=Управление пользователями
|
||||
regattaStructureImport=Импорт структуры регаты
|
||||
filteredBy=отфильтровано по
|
||||
@@ -1279,7 +1241,6 @@ noFleetsDefined=Не определены флоты.
|
||||
successfullyCreatedRegattas=Регаты успешно созданы
|
||||
errorTryingToRegisterRacesForTracking=Ошибка при попытке зарегистрировать гонки {0} для отслеживания: {1}. проверьте синтаксис URI оперативных/хранимых данных.
|
||||
errorDeterminingPolarAvailability=Ошибка при определении доступности полярных/VPP данных для гонки {0}: {1}
|
||||
error=Ошибка
|
||||
fileStorage=Хранилище файлов
|
||||
active=Активно
|
||||
scoringSchemeHighPointEssOverallDescription=Оценка в баллах. Победитель в акте получает 10 баллов, 2-й — 9 баллов, .... Любое равенство в общей оценке Extreme Sailing Series разрешается в пользу участника с наибольшим числом побед в актах. В случае равенства числа побед для разрешения используется последний акт.
|
||||
@@ -1322,7 +1283,6 @@ showCompetitorFullNameColumn=Полное имя участника
|
||||
alwaysShowCompetitorNationalityColumn=Всегда показывать государственную принадлежность участника
|
||||
alwaysShowCompetitorNationalityColumnTooltip=Показывать и флаги государств, и изображения участников (при наличии)
|
||||
loadingDimensionValues=Загрузка значений измерения
|
||||
runningQuery=Выполнение запроса
|
||||
inviteBuoyTenders=Пригласить лоцманов
|
||||
orMultipleEmails=или несколько адресов эл. почты через запятую
|
||||
courseOverGroundTrueDegreesTooltip=Истинный курс относительно грунта в градусах
|
||||
@@ -1330,11 +1290,6 @@ courseOverGroundTrueDegrees=COG
|
||||
distanceIncludingGateStartInMeters=Дистанция (при старте через ворота)
|
||||
distanceTraveledIncludingGateStartTooltip=Дистанция, пройденная от начала отрезка до конца\nили до текущего момента времени, если отрезок не завершен.\nЕсли отрезок включает старт через ворота, дистанция от отметки до стартовой позиции включается,\nчтобы обеспечить сравнение участников даже при старте в разное время.
|
||||
raceDistanceTraveledIncludingGateStartTooltip=Дистанция, пройденная от начала гонки до конца\nили до текущего момента времени, если гонка не завершена.\nДля старта через ворота включается дистанция от привязки до стартовой позиции,\nчтобы обеспечить сравнение участников даже при старте в разное время.
|
||||
results=Результаты
|
||||
groupName=Название группы
|
||||
valueAscending=Значение (по возрастанию)
|
||||
valueDescending=Значение (по убыванию)
|
||||
sortBy=Сортировать по
|
||||
dashboardHeader=Панель
|
||||
dashboardNoWindBotAvailableHeader=WindBot недоступен.
|
||||
dashboardNoWindBotAvailableMessage=Чтобы получать оперативные данные от устройств измерения ветра, убедитесь, что WindBot включен и подключен к SAP Sailing Analytics.
|
||||
@@ -1370,16 +1325,12 @@ dashboardRCBoat=Лодка ГК
|
||||
fixedMarkPassing=(фиксировано)
|
||||
suppressedMarkPassing=(подавляется)
|
||||
windUp=На ветер (показать ветер сверху карты)
|
||||
filterBy=Фильтровать по
|
||||
currentFilterSelection=Текущий выбор фильтра
|
||||
notCapableOfGeneratingACodeForIdentifier=Я не могу сгенерировать код для этого идентификатора.
|
||||
serverUrl=URL-адрес сервера
|
||||
rotatedFromTrueNorth=Повернуто на {0} град. от истинного севера.
|
||||
clickToToggleWindUp=Коснитесь/щелкните для переключения между отображением карты с ветром или севером вверху
|
||||
clickToToggleWindStreamlets=Коснитесь/щелкните для отображения или скрытия потоков ветра
|
||||
startLineToFirstMarkTriangle=От старта до первой отметки ({0} м)
|
||||
dataMiningComponentsHaveBeenUpdated=Компоненты добычи данных обновлены.
|
||||
dataMiningComponentsNeedReloadDialogMessage=Щелкните ''Перезагрузить'', чтобы перезагрузить компоненты сейчас. Текущее отображение данных будет сброшено с выполнением запроса по умолчанию. Добыча данных не будет работать правильно до перезагрузки компонентов.
|
||||
noDataForEvent=Данных для данного события еще нет.
|
||||
countriesCount={0,number} стран
|
||||
countriesCount[one]={0,number} страна
|
||||
@@ -1477,15 +1428,7 @@ noFinishedRaces=Завершенных гонок еще нет
|
||||
racesOverview=Обзор гонок
|
||||
listFormatLabel=Формат списка
|
||||
competitionFormatLabel=Формат соревнования
|
||||
empty=Пусто
|
||||
runAQuery=Выполнить запрос
|
||||
latestRegattaStandings=Позиции в последней регате
|
||||
plainText=Простой текст
|
||||
columnChart=Столбчатая диаграмма
|
||||
columnChartWithErrorBars=Столбчатая диаграмма с планками погрешностей
|
||||
choosePresentation=Выберите представление
|
||||
cantDisplayDataOfType=Невозможно отобразить данные типа {0}
|
||||
shownDecimals=Отображаемые десятичные разряды
|
||||
openFullscreenView=Открыть полноэкранный режим
|
||||
closeFullscreenView=Закрыть полноэкранный режим
|
||||
videosCount={0,number} видео
|
||||
@@ -1495,15 +1438,8 @@ photosCount[one]={0,number} фото
|
||||
eventsHaveTakenPlace=Имело место {0} событий
|
||||
eventsHaveTakenPlace[one]=Имело место одно событие
|
||||
raceOffice=Служба гонки
|
||||
analyze=Анализировать
|
||||
dataMiningSettings=Параметры добычи данных
|
||||
multiResultsPresenter=Презентатор множества результатов
|
||||
plainResultsPresenter=Презентатор простых результатов
|
||||
resultsChart=Диаграмма результатов
|
||||
tabbedResultsPresenter=Презентатор результатов со вкладками
|
||||
polarResultsPresenter=Полярный презентатор результатов
|
||||
maneuverSpeedDetailsResultsPresenter=Демонстратор результатов детализации скорости маневра
|
||||
dataMiningRetrieval=Извлечение данных
|
||||
actionWatch=Смотреть
|
||||
actionAnalyze=Анализировать
|
||||
denoteAllRacesForRaceLogTrackingShorctut=Ярлык для отмены отслеживания журналов всех гонок
|
||||
@@ -1519,24 +1455,8 @@ defaultName=По умолчанию
|
||||
exampleTextForName=Выше имя выглядит так:
|
||||
flightsCount={0,number} полетов
|
||||
flightsCount[one]={0,number} полет
|
||||
viewQueryDefinition=Просмотреть определение запроса
|
||||
queryDefinitionViewer=Средство просмотра определения запроса
|
||||
groupAverageAscending=Среднее группы (по возрастанию)
|
||||
groupAverageDescending=Среднее группы (по убыванию)
|
||||
groupMedianAscending=Медиана группы (по возрастанию)
|
||||
groupMedianDescending=Медиана группы (по убыванию)
|
||||
resultsFoundForSearch=Найдено {0,number} результатов для ''{1}''
|
||||
resultsFoundForSearch[one]=Найден {0,number} результат для ''{1}''
|
||||
runPredefinedQuery=Выполнить готовый запрос
|
||||
selectPredefinedQuery=Выбрать готовый запрос
|
||||
predefinedQueryRunner=Средство выполнения готовых запросов
|
||||
developerOptions=Параметры разработчика
|
||||
copyToClipboard=Копировать в буфер обмена
|
||||
code=Код
|
||||
useClassGetName=Использовать Class.getName() для имен типов
|
||||
useClassGetNameTooltip=Более надежно по сравнению с изменениями в основании кода, но фрагмент кода может быть использован только в области с доступными классами.
|
||||
useStringLiterals=Использовать строковые литералы для имен типов
|
||||
useStringLiteralsTooltip=Фрагмент кода может использоваться где угодно, но будет нарушен в случае изменения основания кода.
|
||||
errorLoadingDataWithTryAgain=Ошибка при загрузке данных. Повтор попытки через несколько секунд.
|
||||
addGalleryPhoto=Добавить фото галереи
|
||||
addStageImage=Добавить изображение этапа
|
||||
@@ -1556,7 +1476,6 @@ warningForDisabledCompetitors=Регистрация следующих учас
|
||||
competitorToolTipMessage={0} уже присвоен флоту {2} в гонке {3} и поэтому не может быть присвоен флоту {1} в той же самой гонке
|
||||
addMarkToRegatta=Добавить отметку к регате
|
||||
selectALeaderboardGroup=Выбрать группу таблиц лидеров...
|
||||
pleaseSelect=Выберите
|
||||
requiresValidRegatta=Эта страница определяет гонку для отображения по допустимым значениям регаты, столбца гонки и названия флота.
|
||||
couldNotObtainRace=Не удалось получить гонку с названием {1} для флота {2} для регаты с названием {0}: {3}
|
||||
errorTryingToCreateEmbeddedMap=Ошибка при попытке создать внедренную карту: {0}
|
||||
@@ -1836,30 +1755,9 @@ eventRegattaHeaderLegendGpsNo=Нет данных отслеживания
|
||||
eventRegattaHeaderLegendWindNo=Нет данных о ветре
|
||||
eventRegattaHeaderLegendVideoNo=Нет видеопотоков
|
||||
eventRegattaHeaderLegendAudioNo=Нет аудиопотоков
|
||||
angleInDegree=Угол в градусах
|
||||
angleInRadian=Угол в радианах
|
||||
centralAngleInRadian=Центральный угол в градусах
|
||||
centralAngleInDegree=Центральный угол в радианах
|
||||
kilometers=Километры
|
||||
meters=Метры
|
||||
nauticalMiles=Морские мили
|
||||
seaMiles=Морские мили
|
||||
geographicalMiles=Географические мили
|
||||
days=Дни
|
||||
hours=Часы
|
||||
minutes=Минуты
|
||||
seconds=Секунды
|
||||
milliseconds=Миллисекунды
|
||||
floatNumber=Плавающее
|
||||
integer=Целое
|
||||
appendResult=Добавить результат
|
||||
sampleColor=Образец цвета
|
||||
sharedSettingsLink=Связать с настройками
|
||||
leaderboardPage=Страница таблицы лидеров
|
||||
makeDefault=Использовать по умолчанию
|
||||
makeDefaultInProgress=Выполняется...
|
||||
settingsSavedMessage=Текущие настройки успешно заданы для использования по умолчанию
|
||||
settingsSaveErrorMessage=При задании настроек для использования по умолчанию возникла ошибка
|
||||
showLiveNow=Показать "Оперативные данные"
|
||||
useOnlyOneOfStartTimeInferenceAndControlTrackingFromStartAndFinishTimes=Использовать только один из параметров "Вывод о времени старта" и "Контролировать отслеживание от времени старта и финиша"
|
||||
unknownLeaderboardType=Неизвестный тип таблицы лидеров {0}
|
||||
@@ -1878,10 +1776,6 @@ settingsId=Ид. настроек
|
||||
documentSettingsId=Ид. настроек документа
|
||||
settingsForId=Настройки для ид. ''{0}''
|
||||
userProfileSettingsTabDescription=Пользовательские настройки генерируются диалогами настроек, расположенными во многих местах страницы. Здесь отображено техническое представление всех собранных настроек для ключевых пользователей. Соблюдайте осторожность, так как восстановить удаленные записи невозможно.
|
||||
resetToDefault=Восстановить настройки по умолчанию
|
||||
resetToDefaultInProgress=Выполняется сброс...
|
||||
settingsRemoved=Восстановлены настройки по умолчанию
|
||||
settingsRemovedError=Не удалось восстановить настройки по умолчанию
|
||||
userSettingsFilter=Фильтр настроек
|
||||
requiresRegattaRaceAndLeaderboard=Для этой страницы требуются действительные названия регаты, гонки и таблицы лидеров.
|
||||
couldNotFindRaceInRegatta=Не удалось получить гонку с названием {0} для регаты с названием {1}
|
||||
@@ -1913,7 +1807,6 @@ errorFetchingDimensionData=Ошибка при вызове значений и
|
||||
errorFetchingStatistics=Ошибка при вызове доступной статистики с сервера: {0}
|
||||
errorFetchingAggregators=Ошибка при вызове доступных агрегаторов с сервера: {0}
|
||||
errorLoadingDataRetrieverChainDefinitions=Ошибка при вызове доступных определений цепочек извлечения данных: {0}
|
||||
errorFetchingComponentsChangedTimepoint=Ошибка при вызове отметки времени изменения компонентов с сервера: {0}
|
||||
errorRunningQuery=Ошибка при выполнении запроса: {0}
|
||||
errorReadingWindFixes=Ошибка при считывании замеров ветра {0}
|
||||
errorAddingWindFixForRace=Ошибка при добавлении замера ветра для гонки {0}: {1}
|
||||
@@ -1972,7 +1865,7 @@ anniversaryMajorCountdownTeaser[one]=Обратный отсчет! До {1,numb
|
||||
anniversaryMajorCountdownDescription={0,number,#,###}-ая гонка на сайте www.sapsailing.com! Какая из них станет юбилейной? Организатор юбилейной гонки получит 10 000 евро, которые сможет потратить в благотворительных целях. Победитель будет объявлен на нашем сайте, оставайтесь с нами.
|
||||
anniversaryRepdigitCountdownTeaser=Обратный отсчет! До {1,number,#,###} гонки осталось всего {0,number,#,###} гонки(ок).
|
||||
anniversaryRepdigitCountdownTeaser[one]=Обратный отсчет! До {1,number,#,###} гонки осталась всего {0,number,#,###} гонка.
|
||||
anniversaryRepdigitCountdownDescription=Ждем гонку под счастливым номером! Чья гонка станет {0,number, #,###}-ой на сайте www.sapsailing.com? Участники этой гонки получат право на участие в бесплатном высококлассном летнем фестивале от SAP Победитель будет объявлен на нашем сайте, оставайтесь с нами.
|
||||
anniversaryRepdigitCountdownDescription=Ждем гонку под счастливым номером! Чья гонка станет {0,number, #,###}-ой на сайте www.sapsailing.com? Организаторы получат бесплатный барбекю-обед с напитками. Победители будут объявлен на нашем сайте. Оставайтесь с нами.
|
||||
anniversaryAnnouncementTeaser=Миссия выполнена! Проведено {0,number,#,###} гонок с SAP Sailing Analytics!
|
||||
anniversaryAnnouncementDescription=3, 2, 1... Поздравляем участников гонк {0}. Вы победили! Спасибо, что пользуетесь SAP Sailing Analytics. Надеюсь, нас с вами ждет еще множество гонок!
|
||||
anniversaryRaceLinkText=Показать юбилейную гонку
|
||||
@@ -1987,11 +1880,6 @@ minimumRideHeightInMetersTooltip=Минимальная высота просв
|
||||
minimumDurationBetweenAdjacentFoilingSegmentsInSeconds=Минимальная продолжительность между двумя сегментами в крыльевом режиме (сек)
|
||||
minimumDurationBetweenAdjacentFoilingSegmentsInSecondsTooltip=Если поле не является пустым, и время между двумя смежными сегментами в крыльевом режиме не превышает указанное значение, смежные сегменты в крыльевом режиме считаются одним сегментом.
|
||||
needToProvideValidMinimumRideHeight=Укажите действительное значение минимальной высоты просвета в метрах.
|
||||
dataMiningErrorMargins=Пределы погрешности
|
||||
elements={0} элем.
|
||||
chooseDifferentDimensionTitle=Выберите другое измерение
|
||||
chooseDifferentDimensionMessage=Выберите другое измерение для группирования результатов
|
||||
pleaseSelectADimension=Выберите измерение
|
||||
currentPortDaggerboardRake=Скос киля на левый борт
|
||||
currentPortDaggerboardRakeTooltip=Текущий скос выдвижного киля на левый борт
|
||||
currentStbdDaggerboardRake=Скос киля на правый борт
|
||||
@@ -2084,7 +1972,7 @@ windFinderWindSourceTypeName=WindFinder
|
||||
windFinderWindSourceTypeTooltip=Измерения ветра из одного или нескольких мест www.windfinder.com
|
||||
windFinder=WindFinder
|
||||
enterTagsForTheVideo=Введите теги для видео
|
||||
enterIdOfWindfinderReviewedSpotCollection=Введите ид. просмотренной коллекции мест WindFinder (например, "schilksee")
|
||||
enterIdOfWindFinderReviewedSpotCollection=Введите ид. просмотренной коллекции мест WindFinder (например, "schilksee")
|
||||
enterTagsForTheImage=Введите теги для изображения
|
||||
unableToResolveWindFinderSpotId=Не удалось разрешить место WindFinder по ид. {0}: {1}
|
||||
windFinderWeatherData=Данные погоды
|
||||
@@ -2169,3 +2057,13 @@ multiVideoIdle=Рабочий список неактивен
|
||||
multiVideoDoNoAdd=Не добавлять
|
||||
multiVideoOffsetInput=Смещение глобального видео в мс:
|
||||
multiVideoDescription=Веб-сервер необходим для предоставления списка индекса (подпапки поддерживаются, если проиндексированы) для поиска файлов. После первичного анализа метаданных, содержащихся в файлах mp4, видео, которые должны быть добавлены, необходимо выбрать в столбце с флажками слева. При нажатии кнопки "Добавить аудио/видео" будут созданы медиаканалы для всех выбранных файлов, а файлы будут добавлены ко всем гонкам, выбранным в правом столбце.
|
||||
multiUrlChangeMediaTrack=Адаптировать URL объектов мультимедиа
|
||||
multiUrlChangeReplace=Заменить на
|
||||
multiUrlChangeFind=Найти
|
||||
multiUrlChangeCannotSave=Ошибка при сохранении
|
||||
multiUrlChangeSave=Сохранить изменения URL
|
||||
multiUrlChangeNewURL=Новый URL
|
||||
multiUrlNoPrefixWarning=Общий префикс не найден, это означает, что не все выбранные видео хранятся в одном и том же месте. Можете продолжить на свой риск!
|
||||
multiUrlChangeExplain=В этом диалоговом окне можно выполнить массовую замену общих частей в начале URL объектов мультимедиа. Убедитесь, что все URL имеют одинаковый префикс! Проверьте также столбец с новым URL и протестируйте новые URL перед сохранением!
|
||||
lastEvent=Последнее событие: {0}
|
||||
teaserOverallLinkToolTip=Чтобы увидеть все серии, нажмите желтый треугольник
|
||||
|
||||
+12
-114
@@ -9,12 +9,10 @@ trackedBefore=已跟踪活动的历史记录
|
||||
general=常规
|
||||
listRaces=比赛轮次清单
|
||||
listRegattas=比赛清单
|
||||
numberPairResultsPresenter=散点图
|
||||
wind=风力
|
||||
maneuverType=操作
|
||||
windPanelLabel=这是风力面板,目前完全空白。
|
||||
refresh=刷新
|
||||
remove=移除
|
||||
removeNumber=移除 ({0})
|
||||
windSource=风源
|
||||
dampeningInterval=阻尼间隔
|
||||
@@ -53,7 +51,6 @@ trackedRaceConnectedToSelectedRaceName=跟踪的比赛轮次已连接到所选
|
||||
linkToColumn=链接到列
|
||||
unlink=取消链接
|
||||
leaderboardName=积分榜名称
|
||||
cancel=取消
|
||||
pleaseEnterAName=请输入名称
|
||||
pleaseEnterABoatClass=请输入船只级别
|
||||
discardRacesFromHowManyStartedRacesOn=从已开始的比赛轮次数开始再放弃一个比赛轮次
|
||||
@@ -65,7 +62,6 @@ startingFromNumberOfRaces=从比赛轮次数开始
|
||||
renameLeaderboard=重命名积分榜
|
||||
addColumnToLeaderboard=向积分榜添加列
|
||||
pleaseEnterNameForNewRaceColumn=请输入新比赛轮次列的名称
|
||||
ok=确定
|
||||
medalRace=奖牌轮
|
||||
renameRace=重命名比赛轮次
|
||||
openSelectedLeaderboard=打开选择的积分榜
|
||||
@@ -89,7 +85,6 @@ sapSailingAnalytics=Sailing Analytics
|
||||
leaderboard=积分榜
|
||||
leaderboards=积分榜
|
||||
leaderboardSettings=积分榜设置
|
||||
settings=设置
|
||||
selectAtLeastOneLegDetail=至少选择一个航段详情
|
||||
currentSpeedOverGroundInKnots=SOG
|
||||
currentSpeedOverGroundInKnotsTooltip=当前实际速度。
|
||||
@@ -158,7 +153,6 @@ overallDetailsToShow=总体详情
|
||||
legDetailsToShow=航段详情
|
||||
columnMoveUp=向上
|
||||
columnMoveDown=向下
|
||||
port=端口
|
||||
raceStartTimeColumn=比赛轮次开始时间
|
||||
showOnlySelectedCompetitors=仅显示选择的参赛队
|
||||
showSelectedCompetitorsInfo=显示所选参赛队的信息框
|
||||
@@ -173,7 +167,6 @@ tacks=迎风转向
|
||||
jibes=顺风转向
|
||||
penaltyCircles=惩罚转圈
|
||||
medalRaceIsNull=不允许奖牌轮值
|
||||
configuration=配置
|
||||
maneuverTypes=操作
|
||||
chooseChart=选择记录
|
||||
distanceTraveled=航行距离
|
||||
@@ -191,13 +184,11 @@ secondsPerNauticalMileUnit=s/NM
|
||||
metersUnit=m
|
||||
millimetersUnit=mm
|
||||
degreesUnit=°
|
||||
close=关闭
|
||||
compareCompetitors=比较参赛队
|
||||
description=描述
|
||||
sailNumber=帆号
|
||||
country=国家/地区
|
||||
no3LetterCodes=无法找到 IOC 3 个字母的代码。
|
||||
add=添加
|
||||
delete=删除
|
||||
showCharts=显示记录
|
||||
raceWithThisNameAlreadyExists=具有此名称的比赛轮次已存在。
|
||||
@@ -269,7 +260,6 @@ printHint=打印应用的版本
|
||||
blockedApplyButton=注册的参赛队不等于配对清单中的参赛队!
|
||||
multiplierInfo=多个航程,彼此相邻,这样就可以减少船只更换情况进行比赛
|
||||
noPairingListAvailable=打印功能仅在配对清单可应用于选择的积分榜比赛轮次日志时可用。
|
||||
settingsForComponent={0}设置
|
||||
noEventsFound=未找到活动
|
||||
noEventSelected=未选择活动
|
||||
noLeaderboardsFound=未找到积分榜
|
||||
@@ -307,8 +297,6 @@ leaderboardGroup=积分榜组
|
||||
pleaseEnterNonEmptyDescription=请输入非空描述
|
||||
groupWithThisNameAlreadyExists=具有此名称的积分榜组已存在。
|
||||
detailsOfLeaderboardGroup=积分榜组详情
|
||||
edit=编辑
|
||||
save=保存
|
||||
abort=中止
|
||||
noLeaderboardGroupWithNameFound=未找到名称为{0}的积分榜组
|
||||
overview=总览
|
||||
@@ -343,7 +331,6 @@ degreesShort=deg
|
||||
untracked=取消跟踪
|
||||
delayForLiveMode=实况模式延迟:
|
||||
notAvailable=不可用
|
||||
details=详情
|
||||
noGroupSelected=未选择分组
|
||||
combinedWindSourceTypeName=组合
|
||||
legMiddleWindSourceTypeName=航段中部
|
||||
@@ -425,7 +412,6 @@ simulateAsLiveRace=模拟实况比赛轮次
|
||||
simulateWithOffset=起航前的偏移(分钟):
|
||||
boatClassDoesNotMatchSelectedRegatta=所选的比赛轮次包含不同于所选比赛船只级别 "{0}" 的船只级别。将不加载比赛轮次。
|
||||
regattaExistForSelectedBoatClass=所选船只级别至少有一场比赛。是否确定为这些比赛轮次创建默认比赛?
|
||||
reload=重新加载
|
||||
addRegatta=添加比赛...
|
||||
importRegattas=导入比赛...
|
||||
exchangeName=交换机名称
|
||||
@@ -647,7 +633,6 @@ totalNetPointsColumnTooltip=参赛队在比赛中的总净分。\n按名次(
|
||||
windData=风力数据
|
||||
gpsData=GPS 数据
|
||||
status=状态
|
||||
noDataFound=未找到数据
|
||||
displayName=显示名称
|
||||
histogram=直方图
|
||||
numberOfDataPoints=数据点数
|
||||
@@ -895,12 +880,9 @@ legType=航段形式
|
||||
sailID=帆号
|
||||
seriesLeaderboard=系列积分榜
|
||||
regattaLeaderboards=比赛积分榜
|
||||
clearSelection=清除选择
|
||||
running=运行
|
||||
runAsSubstantive=运行
|
||||
done=完成
|
||||
lastFinished=最后完成
|
||||
run=运行
|
||||
times=次数
|
||||
dataAmount=数据量
|
||||
averageCleanedServerTime=∅ 清理服务器时间
|
||||
@@ -924,12 +906,8 @@ selectSheet=选择工作表
|
||||
cleanedServerTime=清理服务器时间
|
||||
overallTime=总时间
|
||||
cleanedOverallTime=清理总时间
|
||||
dataMiningResult=数据挖掘结果
|
||||
groupBy=分组方式
|
||||
statisticToCalculate=计算统计
|
||||
queryResultsChartSubtitle={1}秒内处理{0}个数据条目
|
||||
noQuerySelected=没有查询选择
|
||||
runAutomatically=自动运行
|
||||
windImport_Upload=上传
|
||||
windImport_Title=从导航图导入风力
|
||||
windImport_BoatId=船只编号:
|
||||
@@ -959,14 +937,9 @@ raceTimeTooltip=参赛队在本比赛轮次中越过开始航路点\n(不是
|
||||
raceTimeDownwindTooltip=本轮次比赛顺风航行的总时间
|
||||
raceTimeReachingTooltip=本轮次比赛横风航行的总时间
|
||||
raceTimeUpwindTooltip=本轮次比赛迎风航行的总时间
|
||||
noStatisticSelectedError=未选择待计算的统计
|
||||
noCustomGrouperScriptTextError=群集器脚本为空
|
||||
noDimensionToGroupBySelectedError=未选择分组方式尺寸
|
||||
noGrouperSelectedError=未选择群集器类型
|
||||
noDataRetrieverChainDefinitonSelectedError=未选择数据检索器
|
||||
queryNotValidBecause=无法查询,原因:
|
||||
dataMining=数据挖掘
|
||||
errorRunningDataMiningQuery=运行查询期间出错
|
||||
hideToolbar=隐藏工具栏
|
||||
showSeriesLeaderboards=显示系列积分榜
|
||||
showOverallLeaderboard=显示总积分榜
|
||||
@@ -989,7 +962,6 @@ id=编号
|
||||
allowReload=允许重新加载
|
||||
compress=压缩
|
||||
compressTooltip=仅在导出服务器实例至少通过\n任务 0fbf6071dea125bec4a56dee55d61c99def4a62e 运行时使用。
|
||||
queryRunner=查询执行器
|
||||
rerunQueryAfterRefresh=在刷新后重新运行查询
|
||||
refreshIntervalMustntBeEmpty=刷新间隔不得为空
|
||||
selectionTables=选择表
|
||||
@@ -1074,12 +1046,7 @@ TWATooltip=赛队航行的方向和风的夹角
|
||||
TWA=实际风向角
|
||||
showBoatClassChartsLabel=还可以查看可用船只级别的总直直方图。
|
||||
showDiagram=显示直方图
|
||||
runAutomaticallyTooltip=在更改统计或分组后,自动运行查询。
|
||||
rerunQueryAfterRefreshTooltip=在刷新表后,重新运行查询。
|
||||
queryDefinitionProvider=查询定义提供方
|
||||
statisticProvider=统计提供方
|
||||
calculateThe=计算
|
||||
groupingProvider=分组提供方
|
||||
releaseNotes=消息和版本历史
|
||||
hasSplitFleetContiguousScoring=区分连续得分的船队
|
||||
addRaceLogTracker=添加比赛轮次日志跟踪器
|
||||
@@ -1237,7 +1204,6 @@ showAll=显示全部
|
||||
raceVisibilityColumn=可见性
|
||||
enterCarryValueFor=输入参赛队{0}的得分
|
||||
advanced=高级
|
||||
basedOn=基于
|
||||
retrieveWith=检索依据
|
||||
mappingDetails=映射详情
|
||||
deviceMappingQrCodeExplanation=如果使用跟踪应用,可以通过选择参赛队/标志、设置开始和结束时间,然后扫描此二维码添加设备映射。
|
||||
@@ -1248,10 +1214,6 @@ enterImageURL=输入图像 URL...
|
||||
enterVideoURL=输入视频 URL...
|
||||
enterSponsorImageURL=输入赞助商图像 URL...
|
||||
enterRaceName=输入比赛轮次名称...
|
||||
serverError=尝试联系服务器时出错。请检查网络连接,并重试。
|
||||
remoteProcedureCall=远程过程调用
|
||||
serverReplies=服务器应答
|
||||
errorCommunicatingWithServer=与服务器通信时发生错误
|
||||
userManagement=用户管理
|
||||
regattaStructureImport=比赛结构导入
|
||||
filteredBy=筛选方式
|
||||
@@ -1279,7 +1241,6 @@ noFleetsDefined=未定义船队。
|
||||
successfullyCreatedRegattas=已成功创建比赛
|
||||
errorTryingToRegisterRacesForTracking=尝试注册比赛轮次{0}用于跟踪出错: {1}。检查实况/存储的 URI 语法。
|
||||
errorDeterminingPolarAvailability=确定比赛轮次{0}的极坐标/VPP 数据的可用性出错: {1}
|
||||
error=错误
|
||||
fileStorage=文件存储
|
||||
active=已激活
|
||||
scoringSchemeHighPointEssOverallDescription=得分。 分站赛获胜者得 10 分、第 2 名 9 分、.... 在总体极限帆船系列赛中如有平局,以获得最多分站赛名次的参赛队取得胜利。 如果仍有平局,就以最后一个分站赛的名次打破平局。
|
||||
@@ -1322,7 +1283,6 @@ showCompetitorFullNameColumn=参赛队全名
|
||||
alwaysShowCompetitorNationalityColumn=始终显示参赛队国籍
|
||||
alwaysShowCompetitorNationalityColumnTooltip=如果可用,显示两者:国籍旗帜和参赛队图像
|
||||
loadingDimensionValues=正在加载尺寸值
|
||||
runningQuery=运行查询
|
||||
inviteBuoyTenders=邀请浮标供应船
|
||||
orMultipleEmails=或使用逗号隔开的多个电子邮件
|
||||
courseOverGroundTrueDegreesTooltip=实在对地航向角度
|
||||
@@ -1330,11 +1290,6 @@ courseOverGroundTrueDegrees=对地航向
|
||||
distanceIncludingGateStartInMeters=距离(起航门标)
|
||||
distanceTraveledIncludingGateStartTooltip=如果航段没有结束,从航段起点到终点\n或当前时间点航行的距离。\n如果航段包括起航门标,则包括尾销到起航位置的距离,\n这样,即便在不同时间起航,也可以比较参赛队。
|
||||
raceDistanceTraveledIncludingGateStartTooltip=如果比赛轮次没有结束,从比赛轮次开始到结束\n或当前时间点航行的距离。对于起航门标,包括尾销到起航位置的距离,\n这样,即便在不同时间起航,也可以比较参赛队。
|
||||
results=结果
|
||||
groupName=分组名称
|
||||
valueAscending=值(递增)
|
||||
valueDescending=值(递减)
|
||||
sortBy=排序方式
|
||||
dashboardHeader=仪表盘
|
||||
dashboardNoWindBotAvailableHeader=风力机器人程序不可用。
|
||||
dashboardNoWindBotAvailableMessage=要从风力测量装置接收实况风力数据,请确保开启风力机器人程序,并与 SAP Sailing Analytics 相连。
|
||||
@@ -1370,16 +1325,12 @@ dashboardRCBoat=RC 船只
|
||||
fixedMarkPassing=(固定)
|
||||
suppressedMarkPassing=(禁止)
|
||||
windUp=风向向上(从地图的顶部显示风力)
|
||||
filterBy=筛选方式
|
||||
currentFilterSelection=当前筛选器选择
|
||||
notCapableOfGeneratingACodeForIdentifier=无法为此标识符生成代码。
|
||||
serverUrl=服务器 URL
|
||||
rotatedFromTrueNorth=自真北方向旋转{0}度。
|
||||
clickToToggleWindUp=点按/点击在风向向上和北向上地图显示之间切换
|
||||
clickToToggleWindStreamlets=点按/点击显示或隐藏风流
|
||||
startLineToFirstMarkTriangle=开始经过第一个标志 ({0}m)
|
||||
dataMiningComponentsHaveBeenUpdated=数据挖掘组件已更新
|
||||
dataMiningComponentsNeedReloadDialogMessage=点击“重新加载”,立即重新加载组件。这将放弃当前显示的数据,并运行默认查询。\n点击“关闭”不执行任何操作。在重新加载组件前,数据挖掘不能正常工作。
|
||||
noDataForEvent=活动暂无任何数据。
|
||||
countriesCount={0,number} 个国家/地区
|
||||
countriesCount[one]={0,number} 个国家/地区
|
||||
@@ -1477,15 +1428,7 @@ noFinishedRaces=没有结束的比赛轮次
|
||||
racesOverview=比赛轮次总览
|
||||
listFormatLabel=清单格式
|
||||
competitionFormatLabel=赛制
|
||||
empty=空
|
||||
runAQuery=运行查询
|
||||
latestRegattaStandings=最新比赛排名
|
||||
plainText=纯文本
|
||||
columnChart=柱状图
|
||||
columnChartWithErrorBars=含误差线的柱状图
|
||||
choosePresentation=选择介绍
|
||||
cantDisplayDataOfType=无法显示类型为{0}的数据
|
||||
shownDecimals=显示小数
|
||||
openFullscreenView=打开全屏视图
|
||||
closeFullscreenView=关闭全屏视图
|
||||
videosCount={0,number} 个视频
|
||||
@@ -1495,15 +1438,8 @@ photosCount[one]={0,number} 张照片
|
||||
eventsHaveTakenPlace={0}个活动已举行
|
||||
eventsHaveTakenPlace[one]=已举行一场活动
|
||||
raceOffice=竞赛办公室
|
||||
analyze=分析
|
||||
dataMiningSettings=数据挖掘设置
|
||||
multiResultsPresenter=多结果展示区
|
||||
plainResultsPresenter=纯结果展示区
|
||||
resultsChart=结果记录
|
||||
tabbedResultsPresenter=选项卡式结果展示区
|
||||
polarResultsPresenter=极坐标结果展示区
|
||||
maneuverSpeedDetailsResultsPresenter=操作速度详细结果展示区
|
||||
dataMiningRetrieval=数据检索
|
||||
actionWatch=观看
|
||||
actionAnalyze=分析
|
||||
denoteAllRacesForRaceLogTrackingShorctut=为比赛轮次日志跟踪描述所有比赛轮次的快捷方式
|
||||
@@ -1519,24 +1455,8 @@ defaultName=默认值
|
||||
exampleTextForName=名称如下所示:
|
||||
flightsCount={0,number} 个航程
|
||||
flightsCount[one]={0,number} 个航程
|
||||
viewQueryDefinition=查看查询定义
|
||||
queryDefinitionViewer=查询定义查看器
|
||||
groupAverageAscending=分组平均值(递增)
|
||||
groupAverageDescending=分组平均值(递减)
|
||||
groupMedianAscending=分组中值(递增)
|
||||
groupMedianDescending=分组中值(递减)
|
||||
resultsFoundForSearch=找到 "{1}" 的 {0,number} 个结果
|
||||
resultsFoundForSearch[one]=找到 "{1}" 的 {0,number} 个结果
|
||||
runPredefinedQuery=运行预定义的查询
|
||||
selectPredefinedQuery=选择预定义的查询
|
||||
predefinedQueryRunner=预定义的查询执行器
|
||||
developerOptions=开发人员选项
|
||||
copyToClipboard=复制到剪贴板
|
||||
code=代码
|
||||
useClassGetName=将 Class.getName() 用于类型名称
|
||||
useClassGetNameTooltip=更灵活应对代码库的更改,但是代码片段只能在级别可用的范围内使用。
|
||||
useStringLiterals=将字符串文本用于类型名称
|
||||
useStringLiteralsTooltip=代码片段可在任意位置使用,当如果基本代码更改,其将中断。
|
||||
errorLoadingDataWithTryAgain=加载数据出错。请稍后重试。
|
||||
addGalleryPhoto=添加图库照片
|
||||
addStageImage=添加阶段图像
|
||||
@@ -1556,7 +1476,6 @@ warningForDisabledCompetitors=以下参赛队无法注册此比赛轮次:{0}
|
||||
competitorToolTipMessage={0}已分配到比赛轮次{3}中的船队{2},因此,无法分配到同一比赛轮次中的船队{1}
|
||||
addMarkToRegatta=向比赛添加标志
|
||||
selectALeaderboardGroup=选择积分榜组...
|
||||
pleaseSelect=请选择
|
||||
requiresValidRegatta=此页面需要有效的比赛、比赛轮次列和船队名称,才能识别显示的比赛轮次。
|
||||
couldNotObtainRace=无法为名称为{0}的比赛获取船队{2}名称为{1}的比赛轮次: {3}
|
||||
errorTryingToCreateEmbeddedMap=尝试创建嵌入式地图出错: {0}
|
||||
@@ -1836,30 +1755,9 @@ eventRegattaHeaderLegendGpsNo=无跟踪数据
|
||||
eventRegattaHeaderLegendWindNo=无风力数据
|
||||
eventRegattaHeaderLegendVideoNo=无视频流
|
||||
eventRegattaHeaderLegendAudioNo=无音频流
|
||||
angleInDegree=角度(度)
|
||||
angleInRadian=角度(弧度)
|
||||
centralAngleInRadian=圆心角(弧度)
|
||||
centralAngleInDegree=圆心角(度)
|
||||
kilometers=千米
|
||||
meters=米
|
||||
nauticalMiles=海里
|
||||
seaMiles=海里
|
||||
geographicalMiles=地理英里
|
||||
days=天
|
||||
hours=小时
|
||||
minutes=分钟
|
||||
seconds=秒
|
||||
milliseconds=毫秒
|
||||
floatNumber=浮点数
|
||||
integer=整数
|
||||
appendResult=附加结果
|
||||
sampleColor=颜色样本
|
||||
sharedSettingsLink=链接与设置
|
||||
leaderboardPage=积分榜页面
|
||||
makeDefault=设为默认值
|
||||
makeDefaultInProgress=正在进行...
|
||||
settingsSavedMessage=当前设置已成功设为默认设置
|
||||
settingsSaveErrorMessage=将设置设为默认设置时发生错误
|
||||
showLiveNow=显示“现在直播”
|
||||
useOnlyOneOfStartTimeInferenceAndControlTrackingFromStartAndFinishTimes=仅使用“开始时间推断”和“从开始和结束时间控制跟踪”中的其中一个
|
||||
unknownLeaderboardType=积分榜类型{0}未知
|
||||
@@ -1878,10 +1776,6 @@ settingsId=设置编号
|
||||
documentSettingsId=文档设置编号
|
||||
settingsForId=编号 ''''{0}'''' 的设置
|
||||
userProfileSettingsTabDescription=用户设置由可在页面多个位置找到的设置对话生成。此视图以技术方式为高级用户显示收集的所有设置。请注意,移除的条目无法恢复,请谨慎使用。
|
||||
resetToDefault=重置为默认值
|
||||
resetToDefaultInProgress=正在重置...
|
||||
settingsRemoved=默认设置已恢复
|
||||
settingsRemovedError=无法恢复默认设置
|
||||
userSettingsFilter=设置筛选器
|
||||
requiresRegattaRaceAndLeaderboard=此页面需要有效的比赛名称、比赛轮次名称和积分榜名称。
|
||||
couldNotFindRaceInRegatta=无法为名称为{1}的比赛获取名称为{0}的比赛轮次
|
||||
@@ -1913,7 +1807,6 @@ errorFetchingDimensionData=获取{0}的维度值出错:{1}
|
||||
errorFetchingStatistics=从服务器获取可用统计出错:{0}
|
||||
errorFetchingAggregators=从服务器获取可用聚合器出错:{0}
|
||||
errorLoadingDataRetrieverChainDefinitions=检索可用 DataRetrieverChainDefinitions 出错:{0}
|
||||
errorFetchingComponentsChangedTimepoint=从服务器获取组件更改的时间点出错:{0}
|
||||
errorRunningQuery=运行查询出错:{0}
|
||||
errorReadingWindFixes=读取风力修复{0}出错
|
||||
errorAddingWindFixForRace=添加比赛轮次{0}的风力修复出错:{1}
|
||||
@@ -1972,7 +1865,7 @@ anniversaryMajorCountdownTeaser[one]=倒计时!距离第{1,number,#,###}轮比
|
||||
anniversaryMajorCountdownDescription=我们正在 www.sapsailing.com 上庆祝第{0,number,#,###}轮比赛!哪轮比赛会打破记录?本周年纪念赛的主办机构将获得共计 10,000 欧元作为慈善用途。获胜者将在本网站上公布。敬请关注,让我们拭目以待。
|
||||
anniversaryRepdigitCountdownTeaser=倒计时!距离第{1,number,#,###}轮比赛仅剩{0,number,#,###}轮比赛。
|
||||
anniversaryRepdigitCountdownTeaser[one]=倒计时!距离第{1,number,#,###}轮比赛仅剩{0,number,#,###}轮比赛。
|
||||
anniversaryRepdigitCountdownDescription=我们正在庆祝幸运号码赛!谁将在 www.sapsailing.com 上进行的第{0,number, #,###}轮比赛中获胜?这次比赛的参赛者将获得 SAP 提供的免费一流夏日嘉年华活动。获胜者将在本网站上公布。敬请关注,让我们拭目以待。
|
||||
anniversaryRepdigitCountdownDescription=我们正在庆祝幸运号码赛!谁将在 www.sapsailing.com 上进行的第{0,number, #,###}轮比赛中获胜?主办机构将获得免费烧烤和饮料。获胜者将在本网站上公布。敬请关注,让我们拭目以待。
|
||||
anniversaryAnnouncementTeaser=任务圆满完成!使用 SAP Sailing Analytics 完成{0,number,#,###}轮比赛!
|
||||
anniversaryAnnouncementDescription=3,2,1...我们祝贺{0}比赛的参赛者。你们做到了!我们感谢您对 SAP Sailing Analytics 的信任,并希望与你们在以后的 10,000 多场比赛中继续并肩作战!
|
||||
anniversaryRaceLinkText=显示周年纪念赛
|
||||
@@ -1987,11 +1880,6 @@ minimumRideHeightInMetersTooltip=将船只视为处于水翼腾空状态所需
|
||||
minimumDurationBetweenAdjacentFoilingSegmentsInSeconds=相邻水翼腾空段之间的最短持续时间 (s)
|
||||
minimumDurationBetweenAdjacentFoilingSegmentsInSecondsTooltip=如果不为空,并且两个相邻水翼腾空段之间的时间短于此值,会将这些相邻的水翼腾空段合并成一个。
|
||||
needToProvideValidMinimumRideHeight=您需要提供有效的最小行驶高度值(米)。
|
||||
dataMiningErrorMargins=误差容限
|
||||
elements={0}元素
|
||||
chooseDifferentDimensionTitle=选择不同的维度
|
||||
chooseDifferentDimensionMessage=为分组结果选择不同的维度
|
||||
pleaseSelectADimension=请选择维度
|
||||
currentPortDaggerboardRake=左舷活动披水板倾斜度
|
||||
currentPortDaggerboardRakeTooltip=当前左舷活动披水板倾斜度
|
||||
currentStbdDaggerboardRake=右舷活动披水板倾斜度
|
||||
@@ -2084,7 +1972,7 @@ windFinderWindSourceTypeName=WindFinder
|
||||
windFinderWindSourceTypeTooltip=Www.windfinder.com 中一个或多个地点的测量风力
|
||||
windFinder=WindFinder
|
||||
enterTagsForTheVideo=为视频输入标记
|
||||
enterIdOfWindfinderReviewedSpotCollection=输入经检查的 WindFinder 地点集合的编号,例如 "schilksee"
|
||||
enterIdOfWindFinderReviewedSpotCollection=输入经检查的 WindFinder 地点集合的编号,例如 "schilksee"
|
||||
enterTagsForTheImage=为图像输入标记
|
||||
unableToResolveWindFinderSpotId=无法解析编号为{0}的 WindFinder 地点:{1}
|
||||
windFinderWeatherData=天气数据
|
||||
@@ -2169,3 +2057,13 @@ multiVideoIdle=工作队列空闲
|
||||
multiVideoDoNoAdd=请勿添加
|
||||
multiVideoOffsetInput=全球视频偏移量(毫秒):
|
||||
multiVideoDescription=需要 Web 服务器提供索引清单(如果已编入索引,则支持的子文件夹),以允许发现文件。初始分析 mp4 文件中包含的元数据后,需要通过左侧的复选框列选择应添加的视频。“添加音频/视频”按钮将为所有选择的文件创建媒体轨道,并将这些文件添加到右列中选择的所有比赛轮次中。
|
||||
multiUrlChangeMediaTrack=调整多个媒体轨道 URL
|
||||
multiUrlChangeReplace=替换为
|
||||
multiUrlChangeFind=查找
|
||||
multiUrlChangeCannotSave=保存时出错
|
||||
multiUrlChangeSave=保存 URL 更改
|
||||
multiUrlChangeNewURL=新 URL
|
||||
multiUrlNoPrefixWarning=未找到通用前缀,这通常意味着并非所有选定的视频都被托管在当前的相同位置。如继续操作,后果自负!
|
||||
multiUrlChangeExplain=此对话框将在媒体轨道 URL 的起始处批量替换通用部分。确保所有 URL 都以相同的前缀开头!也请注意新的 URL 列,然后在保存之前测试生成的 URL!
|
||||
lastEvent=上次活动:{0}
|
||||
teaserOverallLinkToolTip=请查看整体系列赛,请点击黄色角落
|
||||
|
||||
+13
@@ -78,6 +78,7 @@ public class TimePanel<T extends TimePanelSettings> extends AbstractCompositeCom
|
||||
private final Button slowDownButton;
|
||||
private final Button speedUpButton;
|
||||
private final Button toggleAdvancedModeButton;
|
||||
private final Button resetZoomButton;
|
||||
|
||||
private final FlowPanel controlsPanel;
|
||||
private final SimplePanel timePanelSlider;
|
||||
@@ -309,6 +310,16 @@ public class TimePanel<T extends TimePanelSettings> extends AbstractCompositeCom
|
||||
controlsPanel.add(timeControlPanel);
|
||||
controlsPanel.add(timeToStartControlPanel);
|
||||
|
||||
resetZoomButton = new Button(stringMessages.resetZoom());
|
||||
resetZoomButton.setEnabled(false);
|
||||
resetZoomButton.addClickHandler(new ClickHandler() {
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
timeRangeProvider.resetTimeZoom();
|
||||
}
|
||||
});
|
||||
controlsPanel.add(resetZoomButton);
|
||||
|
||||
hideControlsPanel();
|
||||
}
|
||||
|
||||
@@ -536,10 +547,12 @@ public class TimePanel<T extends TimePanelSettings> extends AbstractCompositeCom
|
||||
|
||||
@Override
|
||||
public void onTimeZoomChanged(Date zoomStartTimepoint, Date zoomEndTimepoint) {
|
||||
resetZoomButton.setEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeZoomReset() {
|
||||
resetZoomButton.setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+26
-58
@@ -98,7 +98,6 @@ public class NewMediaDialog extends DataEntryDialog<MediaTrack> {
|
||||
this.stringMessages = stringMessages;
|
||||
this.raceIdentifier = raceIdentifier;
|
||||
this.mediaService = mediaService;
|
||||
registerNativeMethods();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -114,6 +113,7 @@ public class NewMediaDialog extends DataEntryDialog<MediaTrack> {
|
||||
mediaTrack.assignedRaces = assignedRaces;
|
||||
}
|
||||
|
||||
//used for audio only tracks, using native mediaelement to determine time
|
||||
private void loadMediaDuration() {
|
||||
MediaBase mediaBase = Audio.createIfSupported();
|
||||
if (mediaBase != null) {
|
||||
@@ -223,7 +223,7 @@ public class NewMediaDialog extends DataEntryDialog<MediaTrack> {
|
||||
if (youtubeId != null) {
|
||||
mediaTrack.url = youtubeId;
|
||||
mediaTrack.mimeType = MimeType.youtube;
|
||||
loadYoutubeMetadata(youtubeId);
|
||||
loadYoutubeMetadata();
|
||||
} else {
|
||||
mediaTrack.url = url;
|
||||
loadMediaDuration();
|
||||
@@ -251,6 +251,29 @@ public class NewMediaDialog extends DataEntryDialog<MediaTrack> {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadYoutubeMetadata() {
|
||||
if(mediaTrack.url != null && !mediaTrack.url.isEmpty()) {
|
||||
mediaService.checkYoutubeMetadata(mediaTrack.url, new AsyncCallback<VideoMetadataDTO>() {
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
infoLabel.setWidget(new Label(caught.getMessage()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(VideoMetadataDTO result) {
|
||||
if (result.isDownloadable()) {
|
||||
mediaTrack.duration = result.getDuration();
|
||||
mediaTrack.title = result.getMessage();
|
||||
refreshUI();
|
||||
} else {
|
||||
infoLabel.setWidget(new Label(result.getMessage()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private String sliceBefore(String lastPathSegment, String slicer) {
|
||||
int paramSegment = lastPathSegment.indexOf(slicer);
|
||||
if (paramSegment > 0) {
|
||||
@@ -259,64 +282,10 @@ public class NewMediaDialog extends DataEntryDialog<MediaTrack> {
|
||||
return lastPathSegment;
|
||||
}
|
||||
|
||||
private native void registerNativeMethods() /*-{
|
||||
var that = this;
|
||||
window.youtubeMetadataCallback = function(metadata) {
|
||||
var title = metadata.entry.media$group.media$title.$t;
|
||||
var duration = metadata.entry.media$group.yt$duration.seconds;
|
||||
var description = metadata.entry.media$group.media$description.$t;
|
||||
that.@com.sap.sailing.gwt.ui.client.media.NewMediaDialog::youtubeMetadataCallback(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)(title, duration, description);
|
||||
}
|
||||
}-*/;
|
||||
|
||||
/**
|
||||
* Inspired by https://developers.google.com/web-toolkit/doc/latest/tutorial/Xsite
|
||||
*
|
||||
* @param youtubeId
|
||||
*/
|
||||
public native void loadYoutubeMetadata(String youtubeId) /*-{
|
||||
var that = this;
|
||||
//Create temporary script element.
|
||||
window.youtubeMetadataCallbackScript = document.createElement("script");
|
||||
window.youtubeMetadataCallbackScript.src = "http://gdata.youtube.com/feeds/api/videos/"
|
||||
+ youtubeId
|
||||
+ "?alt=json&orderby=published&format=6&callback=youtubeMetadataCallback";
|
||||
document.body.appendChild(window.youtubeMetadataCallbackScript);
|
||||
|
||||
// Cancel meta data capturing after has 2-seconds timeout.
|
||||
setTimeout(
|
||||
function() {
|
||||
//Remove temporary script element.
|
||||
if (window != null && window.youtubeMetadataCallbackScript != null) {
|
||||
document.body.removeChild(window.youtubeMetadataCallbackScript);
|
||||
delete window.youtubeMetadataCallbackScript;
|
||||
}
|
||||
that.@com.sap.sailing.gwt.ui.client.media.NewMediaDialog::setBusy(Z)(false);
|
||||
}, 2000);
|
||||
}-*/;
|
||||
|
||||
public void setBusy(boolean busy) {
|
||||
busyIndicator.setBusy(busy);
|
||||
}
|
||||
|
||||
public void youtubeMetadataCallback(String title, String durationInSeconds, String description) {
|
||||
busyIndicator.setBusy(false);
|
||||
mediaTrack.title = title;
|
||||
try {
|
||||
long duration = (long) Math.round(1000 * Double
|
||||
.valueOf(durationInSeconds));
|
||||
if (duration > 0) {
|
||||
mediaTrack.duration = new MillisecondsDurationImpl(duration);
|
||||
} else {
|
||||
mediaTrack.duration = null;
|
||||
}
|
||||
} catch (NumberFormatException ex) {
|
||||
mediaTrack.duration = null;
|
||||
}
|
||||
mediaTrack.startTime = this.defaultStartTime;
|
||||
refreshUI();
|
||||
}
|
||||
|
||||
protected void refreshUI() {
|
||||
titleBox.setValue(mediaTrack.title, DONT_FIRE_EVENTS);
|
||||
if (mediaTrack.isYoutube()) {
|
||||
@@ -371,11 +340,10 @@ public class NewMediaDialog extends DataEntryDialog<MediaTrack> {
|
||||
|
||||
/**
|
||||
* For a given url that points to an mp4 video, attempts are made to parse the header, to determine the actual
|
||||
* starttime of the video and to check for a 360° flag. The video will be analyzed by the backendserver, either via
|
||||
* starttime of the video and to check for a 360° flag. The video will be analyzed by the backendserver, either via
|
||||
* direct download, or proxied by the client, if a video is only available locally. If the video header cannot be
|
||||
* read, default values are used instead.
|
||||
*/
|
||||
// TODO Eclipse doesn't find any calls to this private method; can it be removed?
|
||||
private void checkMetadata(String url, Label lbl, AsyncCallback<VideoMetadataDTO> asyncCallback) {
|
||||
// check on server first
|
||||
mediaService.checkMetadata(mediaTrack.url, new AsyncCallback<VideoMetadataDTO>() {
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ public class BoatClassVectorGraphicsResolver {
|
||||
BoatClassMasterdata.TOM_28_MAX, BoatClassMasterdata.DELPHIA_24,
|
||||
BoatClassMasterdata.RS200, BoatClassMasterdata.RS400, BoatClassMasterdata.RS500, BoatClassMasterdata.RS800,
|
||||
BoatClassMasterdata.STREAMLINE, BoatClassMasterdata.SWAN_45, BoatClassMasterdata.TEENY, BoatClassMasterdata.X_99,
|
||||
BoatClassMasterdata.TRIAS, BoatClassMasterdata.VENT_D_OUEST, BoatClassMasterdata.FLYING_JUNIOR, BoatClassMasterdata.VAURIEN);
|
||||
BoatClassMasterdata.TRIAS, BoatClassMasterdata.VENT_D_OUEST, BoatClassMasterdata.FLYING_JUNIOR, BoatClassMasterdata.VAURIEN, BoatClassMasterdata.VARIANTA);
|
||||
BoatClassVectorGraphics circle = new CircleVectorGraphics(BoatClassMasterdata.RUNNING);
|
||||
|
||||
defaultBoatVectorGraphics = dinghyWithSpinnaker; // TODO see bug 2571; this should be a slup-rigged icon working for 470, 505, J/70 etc.
|
||||
|
||||
+14
-78
@@ -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;
|
||||
|
||||
+94
@@ -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());
|
||||
}
|
||||
}
|
||||
+12
-3
@@ -1,6 +1,7 @@
|
||||
package com.sap.sailing.gwt.ui.leaderboard;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -26,6 +27,8 @@ import com.sap.sailing.gwt.settings.client.leaderboard.MultiCompetitorLeaderboar
|
||||
import com.sap.sailing.gwt.settings.client.leaderboard.MultiCompetitorLeaderboardChartSettings;
|
||||
import com.sap.sailing.gwt.settings.client.utils.StoredSettingsLocationFactory;
|
||||
import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceHelper;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.shared.EventDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
|
||||
@@ -133,6 +136,13 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint implements
|
||||
|
||||
@Override
|
||||
public void onSuccess(Iterable<DetailType> result) {
|
||||
final Function<String, SailingServiceAsync> sailingServiceFactory = leaderboardName -> SailingServiceHelper
|
||||
.createSailingServiceInstance(new ProvidesLeaderboardRouting() {
|
||||
@Override
|
||||
public String getLeaderboardName() {
|
||||
return leaderboardName;
|
||||
}
|
||||
});
|
||||
if (leaderboardDTO.type.isMetaLeaderboard()) {
|
||||
// overall
|
||||
MetaLeaderboardPerspectiveLifecycle rootComponentLifeCycle = new MetaLeaderboardPerspectiveLifecycle(
|
||||
@@ -145,10 +155,9 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint implements
|
||||
public void onSuccess(
|
||||
PerspectiveCompositeSettings<LeaderboardPerspectiveOwnSettings> defaultSettings) {
|
||||
configureWithSettings(defaultSettings, timer);
|
||||
|
||||
final MetaLeaderboardViewer leaderboardViewer = new MetaLeaderboardViewer(
|
||||
null, context, rootComponentLifeCycle, defaultSettings,
|
||||
getSailingService(), new AsyncActionsExecutor(), timer, null,
|
||||
sailingServiceFactory, new AsyncActionsExecutor(), timer, null,
|
||||
leaderboardGroupName, leaderboardName, LeaderboardEntryPoint.this,
|
||||
getStringMessages(), getActualChartDetailType(defaultSettings),
|
||||
result);
|
||||
@@ -190,7 +199,7 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint implements
|
||||
configureWithSettings(defaultSettings, timer);
|
||||
final MultiRaceLeaderboardViewer leaderboardViewer = new MultiRaceLeaderboardViewer(
|
||||
null, context, rootComponentLifeCycle,
|
||||
defaultSettings, getSailingService(),
|
||||
defaultSettings, sailingServiceFactory,
|
||||
new AsyncActionsExecutor(), timer,
|
||||
leaderboardGroupName, leaderboardName,
|
||||
LeaderboardEntryPoint.this, getStringMessages(),
|
||||
|
||||
+9
-6
@@ -1,5 +1,7 @@
|
||||
package com.sap.sailing.gwt.ui.leaderboard;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.google.gwt.dom.client.Style.Unit;
|
||||
import com.google.gwt.user.client.ui.FlowPanel;
|
||||
import com.google.gwt.user.client.ui.Label;
|
||||
@@ -37,12 +39,12 @@ public class MetaLeaderboardViewer extends AbstractLeaderboardViewer<MetaLeaderb
|
||||
ComponentContext<PerspectiveCompositeSettings<LeaderboardPerspectiveOwnSettings>> componentContext,
|
||||
MetaLeaderboardPerspectiveLifecycle lifecycle,
|
||||
PerspectiveCompositeSettings<LeaderboardPerspectiveOwnSettings> settings,
|
||||
SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor,
|
||||
Function<String, SailingServiceAsync> sailingServiceFactory, AsyncActionsExecutor asyncActionsExecutor,
|
||||
Timer timer, String preselectedLeaderboardName,
|
||||
String leaderboardGroupName, String metaLeaderboardName, ErrorReporter errorReporter,
|
||||
StringMessages stringMessages, DetailType chartDetailType, Iterable<DetailType> availableDetailTypes) {
|
||||
this(parent, componentContext, lifecycle, settings, new CompetitorSelectionModel(/* hasMultiSelection */true),
|
||||
sailingService, asyncActionsExecutor, timer,
|
||||
sailingServiceFactory, asyncActionsExecutor, timer,
|
||||
preselectedLeaderboardName, leaderboardGroupName, metaLeaderboardName,
|
||||
errorReporter, stringMessages, chartDetailType, availableDetailTypes);
|
||||
}
|
||||
@@ -51,7 +53,7 @@ public class MetaLeaderboardViewer extends AbstractLeaderboardViewer<MetaLeaderb
|
||||
ComponentContext<PerspectiveCompositeSettings<LeaderboardPerspectiveOwnSettings>> componentContext,
|
||||
MetaLeaderboardPerspectiveLifecycle lifecycle,
|
||||
PerspectiveCompositeSettings<LeaderboardPerspectiveOwnSettings> settings,
|
||||
CompetitorSelectionModel competitorSelectionModel, SailingServiceAsync sailingService,
|
||||
CompetitorSelectionModel competitorSelectionModel, Function<String, SailingServiceAsync> sailingServiceFactory,
|
||||
AsyncActionsExecutor asyncActionsExecutor, Timer timer,
|
||||
String preselectedLeaderboardName, String leaderboardGroupName,
|
||||
String metaLeaderboardName, ErrorReporter errorReporter, StringMessages stringMessages,
|
||||
@@ -59,7 +61,8 @@ public class MetaLeaderboardViewer extends AbstractLeaderboardViewer<MetaLeaderb
|
||||
super(parent, componentContext, lifecycle, settings, competitorSelectionModel, asyncActionsExecutor, timer,
|
||||
stringMessages);
|
||||
|
||||
init(new MultiRaceLeaderboardPanel(this, componentContext, sailingService, asyncActionsExecutor,
|
||||
final SailingServiceAsync sailingServiceForMetaLeaderboard = sailingServiceFactory.apply(metaLeaderboardName);
|
||||
init(new MultiRaceLeaderboardPanel(this, componentContext, sailingServiceForMetaLeaderboard, asyncActionsExecutor,
|
||||
settings.findSettingsByComponentId(LeaderboardPanelLifecycle.ID), /* isEmbedded */ false,
|
||||
competitorSelectionModel, timer,
|
||||
leaderboardGroupName, metaLeaderboardName, errorReporter, stringMessages,
|
||||
@@ -77,7 +80,7 @@ public class MetaLeaderboardViewer extends AbstractLeaderboardViewer<MetaLeaderb
|
||||
initWidget(mainPanel);
|
||||
final Label overallStandingsLabel = new Label(stringMessages.overallStandings());
|
||||
overallStandingsLabel.setStyleName("leaderboardHeading");
|
||||
multiCompetitorChart = new MultiCompetitorLeaderboardChart(this, componentContext, sailingService,
|
||||
multiCompetitorChart = new MultiCompetitorLeaderboardChart(this, componentContext, sailingServiceForMetaLeaderboard,
|
||||
asyncActionsExecutor,
|
||||
metaLeaderboardName,
|
||||
chartDetailType, competitorSelectionProvider, timer, stringMessages, true, errorReporter);
|
||||
@@ -90,7 +93,7 @@ public class MetaLeaderboardViewer extends AbstractLeaderboardViewer<MetaLeaderb
|
||||
leaderboardSettings = lifecycle.getMultiLeaderboardPanelLifecycle().createDefaultSettings();
|
||||
}
|
||||
|
||||
multiLeaderboardPanel = new MultiLeaderboardProxyPanel(this, componentContext, sailingService,
|
||||
multiLeaderboardPanel = new MultiLeaderboardProxyPanel(this, componentContext, sailingServiceFactory,
|
||||
metaLeaderboardName,
|
||||
asyncActionsExecutor, timer, false /* isEmbedded */,
|
||||
preselectedLeaderboardName, errorReporter, stringMessages,
|
||||
|
||||
+13
-12
@@ -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,6 +120,7 @@ public class MinMaxRenderer<T> {
|
||||
* @param row
|
||||
* The row to get the percentage for.
|
||||
*/
|
||||
|
||||
protected int getPercentage(T row) {
|
||||
int percentage = 0;
|
||||
Double value = valueProvider.getDoubleValue(row);
|
||||
@@ -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 (minimumRow != null) {
|
||||
minimumValue = valueProvider.getDoubleValue(minimumRow);
|
||||
if (minimumOrderRow != null) {
|
||||
minimumValue = valueProvider.getDoubleValue(minimumOrderRow);
|
||||
|
||||
}
|
||||
if (maximumRow != null) {
|
||||
maximumValue = valueProvider.getDoubleValue(maximumRow);
|
||||
if (maximumOrderRow != null) {
|
||||
maximumValue = valueProvider.getDoubleValue(maximumOrderRow);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-3
@@ -6,6 +6,7 @@ import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.google.gwt.core.shared.GWT;
|
||||
import com.google.gwt.dom.client.Style.FontWeight;
|
||||
@@ -70,22 +71,24 @@ public class MultiLeaderboardProxyPanel extends AbstractLazyComponent<MultiRaceL
|
||||
private MultiRaceLeaderboardSettings loadedSettings;
|
||||
private final FlagImageResolver flagImageResolver;
|
||||
private final Iterable<DetailType> availableDetailTypes;
|
||||
private Function<String, SailingServiceAsync> sailingServiceFactory;
|
||||
|
||||
public MultiLeaderboardProxyPanel(Component<?> parent, ComponentContext<?> context,
|
||||
SailingServiceAsync sailingService, String metaLeaderboardName,
|
||||
Function<String, SailingServiceAsync> sailingServiceFactory, String metaLeaderboardName,
|
||||
AsyncActionsExecutor asyncActionsExecutor,
|
||||
Timer timer, boolean isEmbedded, String preselectedLeaderboardName,
|
||||
ErrorReporter errorReporter, StringMessages stringMessages,
|
||||
boolean showRaceDetails, boolean autoExpandLastRaceColumn,
|
||||
MultiRaceLeaderboardSettings settings, FlagImageResolver flagImageResolver, Iterable<DetailType> availableDetailTypes) {
|
||||
super(parent, context);
|
||||
this.sailingServiceFactory = sailingServiceFactory;
|
||||
|
||||
loadedSettings = settings;
|
||||
|
||||
this.availableDetailTypes = availableDetailTypes;
|
||||
this.stringMessages = stringMessages;
|
||||
this.errorReporter = errorReporter;
|
||||
this.sailingService = sailingService;
|
||||
this.sailingService = sailingServiceFactory.apply(metaLeaderboardName);
|
||||
this.metaLeaderboardName = metaLeaderboardName;
|
||||
this.asyncActionsExecutor = asyncActionsExecutor;
|
||||
this.showRaceDetails = showRaceDetails;
|
||||
@@ -237,8 +240,9 @@ public class MultiLeaderboardProxyPanel extends AbstractLazyComponent<MultiRaceL
|
||||
toMerge = loadedSettings;
|
||||
}
|
||||
|
||||
|
||||
MultiRaceLeaderboardPanel newSelectedLeaderboardPanel = new MultiRaceLeaderboardPanel(this, getComponentContext(),
|
||||
sailingService,
|
||||
sailingServiceFactory.apply(newSelectedLeaderboardName),
|
||||
asyncActionsExecutor, toMerge, isEmbedded,
|
||||
new CompetitorSelectionModel(true), timer,
|
||||
null, newSelectedLeaderboardName, errorReporter, stringMessages,
|
||||
|
||||
+15
-11
@@ -1,6 +1,7 @@
|
||||
package com.sap.sailing.gwt.ui.leaderboard;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.google.gwt.dom.client.Style.Unit;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
@@ -36,12 +37,12 @@ public class MultiRaceLeaderboardViewer extends AbstractLeaderboardViewer<Leader
|
||||
ComponentContext<PerspectiveCompositeSettings<LeaderboardPerspectiveOwnSettings>> componentContext,
|
||||
LeaderboardPerspectiveLifecycle lifecycle,
|
||||
PerspectiveCompositeSettings<LeaderboardPerspectiveOwnSettings> settings,
|
||||
final SailingServiceAsync sailingService, final AsyncActionsExecutor asyncActionsExecutor,
|
||||
final Function<String, SailingServiceAsync> sailingServiceFactory, final AsyncActionsExecutor asyncActionsExecutor,
|
||||
final Timer timer,
|
||||
final String leaderboardGroupName, String leaderboardName, final ErrorReporter errorReporter,
|
||||
final StringMessages stringMessages, DetailType chartDetailType, Iterable<DetailType> availableDetailTypes) {
|
||||
this(parent, componentContext, lifecycle, settings, new CompetitorSelectionModel(/* hasMultiSelection */true),
|
||||
sailingService, asyncActionsExecutor, timer,
|
||||
sailingServiceFactory, asyncActionsExecutor, timer,
|
||||
leaderboardGroupName, leaderboardName, errorReporter,
|
||||
stringMessages, chartDetailType, availableDetailTypes);
|
||||
}
|
||||
@@ -51,13 +52,16 @@ public class MultiRaceLeaderboardViewer extends AbstractLeaderboardViewer<Leader
|
||||
LeaderboardPerspectiveLifecycle lifecycle,
|
||||
PerspectiveCompositeSettings<LeaderboardPerspectiveOwnSettings> settings,
|
||||
CompetitorSelectionModel competitorSelectionModel,
|
||||
final SailingServiceAsync sailingService, final AsyncActionsExecutor asyncActionsExecutor,
|
||||
final Function<String, SailingServiceAsync> sailingServiceFactory, final AsyncActionsExecutor asyncActionsExecutor,
|
||||
final Timer timer,
|
||||
final String leaderboardGroupName, String leaderboardName, final ErrorReporter errorReporter,
|
||||
final StringMessages stringMessages, DetailType chartDetailType, Iterable<DetailType> availableDetailTypes) {
|
||||
super(parent, componentContext, lifecycle, settings, competitorSelectionModel, asyncActionsExecutor, timer,
|
||||
stringMessages);
|
||||
init(new MultiRaceLeaderboardPanel(this, getComponentContext(), sailingService, asyncActionsExecutor,
|
||||
|
||||
final SailingServiceAsync sailingServiceForMainLeaderboard = sailingServiceFactory.apply(leaderboardName);
|
||||
|
||||
init(new MultiRaceLeaderboardPanel(this, getComponentContext(), sailingServiceForMainLeaderboard, asyncActionsExecutor,
|
||||
settings.findSettingsByComponentId(LeaderboardPanelLifecycle.ID), false,
|
||||
competitorSelectionModel, timer, leaderboardGroupName, leaderboardName, errorReporter,
|
||||
stringMessages, settings.getPerspectiveOwnSettings().isShowRaceDetails(),
|
||||
@@ -71,9 +75,8 @@ public class MultiRaceLeaderboardViewer extends AbstractLeaderboardViewer<Leader
|
||||
|
||||
final FlowPanel mainPanel = createViewerPanel();
|
||||
initWidget(mainPanel);
|
||||
multiCompetitorChart = new MultiCompetitorLeaderboardChart(this, getComponentContext(), sailingService,
|
||||
asyncActionsExecutor,
|
||||
leaderboardName, chartDetailType,
|
||||
multiCompetitorChart = new MultiCompetitorLeaderboardChart(this, getComponentContext(),
|
||||
sailingServiceForMainLeaderboard, asyncActionsExecutor, leaderboardName, chartDetailType,
|
||||
competitorSelectionProvider, timer, stringMessages, false, errorReporter);
|
||||
multiCompetitorChart.setVisible(showCharts);
|
||||
multiCompetitorChart.getElement().getStyle().setMarginTop(10, Unit.PX);
|
||||
@@ -91,15 +94,16 @@ public class MultiRaceLeaderboardViewer extends AbstractLeaderboardViewer<Leader
|
||||
multiCompetitorChart.timeChanged(timer.getTime(), null);
|
||||
}
|
||||
overallLeaderboardPanel = null;
|
||||
if(perspectiveSettings.isShowOverallLeaderboard()) {
|
||||
sailingService.getOverallLeaderboardNamesContaining(leaderboardName, new MarkedAsyncCallback<List<String>>(
|
||||
if (perspectiveSettings.isShowOverallLeaderboard()) {
|
||||
sailingServiceForMainLeaderboard.getOverallLeaderboardNamesContaining(leaderboardName, new MarkedAsyncCallback<List<String>>(
|
||||
new AsyncCallback<List<String>>() {
|
||||
@Override
|
||||
public void onSuccess(List<String> result) {
|
||||
if(result.size() == 1) {
|
||||
if (result.size() == 1) {
|
||||
String overallLeaderboardName = result.get(0);
|
||||
final SailingServiceAsync sailingServiceForOverallLeaderboard = sailingServiceFactory.apply(overallLeaderboardName);
|
||||
overallLeaderboardPanel = new OverallLeaderboardPanel(MultiRaceLeaderboardViewer.this,
|
||||
getComponentContext(), sailingService,
|
||||
getComponentContext(), sailingServiceForOverallLeaderboard,
|
||||
asyncActionsExecutor,
|
||||
settings.findSettingsByComponentId(OverallLeaderboardPanelLifecycle.ID),
|
||||
false, competitorSelectionProvider, timer,
|
||||
|
||||
+2
@@ -397,7 +397,9 @@ public class RaceBoardPanel
|
||||
selectedRaceIdentifier, stringMessages, competitorSelectionProvider, errorReporter, timer,
|
||||
maneuverTableSettings, timeRangeWithZoomModel, new ClassicLeaderboardStyle(), userService);
|
||||
maneuverTablePanel.getEntryWidget().setTitle(stringMessages.maneuverTable());
|
||||
if (showChartMarkEditMediaButtonsAndVideo) {
|
||||
componentsForSideBySideViewer.add(maneuverTablePanel);
|
||||
}
|
||||
editMarkPassingPanel = new EditMarkPassingsPanel(this, getComponentContext(), sailingService,
|
||||
selectedRaceIdentifier,
|
||||
stringMessages,
|
||||
|
||||
+61
-1
@@ -1,5 +1,6 @@
|
||||
package com.sap.sailing.gwt.ui.server;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
@@ -7,17 +8,24 @@ import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
@@ -37,6 +45,8 @@ import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import com.google.gwt.thirdparty.json.JSONException;
|
||||
import com.google.gwt.thirdparty.json.JSONObject;
|
||||
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.dto.VideoMetadataDTO;
|
||||
@@ -49,6 +59,7 @@ import com.sap.sse.common.Duration;
|
||||
import com.sap.sse.common.impl.MillisecondsDurationImpl;
|
||||
|
||||
public class MediaServiceImpl extends RemoteServiceServlet implements MediaService {
|
||||
private String YOUTUBE_V3_API_KEY = "AIzaSyBzCJ9cxb9_PPzuYfrHIEdSRtR631b64Xs";
|
||||
|
||||
private static final Logger logger = Logger.getLogger(MediaServiceImpl.class.getName());
|
||||
|
||||
@@ -57,6 +68,7 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi
|
||||
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
|
||||
private ServiceTracker<RacingEventService, RacingEventService> racingEventServiceTracker;
|
||||
|
||||
|
||||
private static final int REQUIRED_SIZE_IN_BYTES = 10000000;
|
||||
private static final long serialVersionUID = -8917349579281305977L;
|
||||
|
||||
@@ -230,17 +242,24 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi
|
||||
try {
|
||||
tmp = createFileFromData(start, end, skipped);
|
||||
try (IsoFile isof = new IsoFile(tmp)) {
|
||||
try {
|
||||
recordStartedTimer = determineRecordingStart(isof);
|
||||
spherical = determine360(isof);
|
||||
duration = determineDuration(isof);
|
||||
} finally {
|
||||
removeTempFiles(isof);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.WARNING, "Error in video analysis ", e);
|
||||
message = e.getMessage();
|
||||
} finally {
|
||||
if (tmp != null) {
|
||||
tmp.delete();
|
||||
try {
|
||||
Files.delete(tmp.toPath());
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.SEVERE, "Could not delete tmp mp4 file", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new VideoMetadataDTO(true, duration, spherical, recordStartedTimer, message);
|
||||
@@ -344,4 +363,45 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoMetadataDTO checkYoutubeMetadata(String videoId) throws UnsupportedEncodingException {
|
||||
ensureUserCanManageMedia();
|
||||
boolean canDownload = false;
|
||||
String message = "";
|
||||
Duration duration = null;
|
||||
if (videoId.isEmpty()) {
|
||||
message = "Empty id";
|
||||
} else {
|
||||
videoId = URLEncoder.encode(videoId, StandardCharsets.UTF_8.name());
|
||||
try {
|
||||
URL apiURL = new URL(
|
||||
"https://www.googleapis.com/youtube/v3/videos?id=" + videoId + "&key=" + YOUTUBE_V3_API_KEY
|
||||
+ "&part=snippet,contentDetails&fields=items(snippet/title,contentDetails/duration)");
|
||||
URLConnection connection = apiURL.openConnection();
|
||||
connection.setRequestProperty("Referer", "http://mediaservice.sapsailing.com/");
|
||||
connection.setConnectTimeout(METADATA_CONNECTION_TIMEOUT);
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
|
||||
String pageText = reader.lines().collect(Collectors.joining("\n"));
|
||||
JSONObject jsonAnswer = new JSONObject(pageText);
|
||||
final JSONObject item = jsonAnswer.getJSONArray("items").getJSONObject(0);
|
||||
message = item.getJSONObject("snippet").getString("title");
|
||||
String rawDuration = item.getJSONObject("contentDetails").getString("duration");
|
||||
duration = new MillisecondsDurationImpl(java.time.Duration.parse(rawDuration).toMillis());
|
||||
canDownload = true;
|
||||
} catch (JSONException e) {
|
||||
message = e.getMessage();
|
||||
logger.log(Level.WARNING, "Error in youtube metadata call", e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
message = e.getMessage();
|
||||
logger.log(Level.WARNING, "Error in youtube metadata call", e);
|
||||
}
|
||||
}
|
||||
//sanitize, as we inject it into an url with our api key!
|
||||
|
||||
|
||||
return new VideoMetadataDTO(canDownload, duration, false, null, message);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-24
@@ -2546,20 +2546,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
return results;
|
||||
}
|
||||
|
||||
private void checkLeaderboardRouting(String leaderboardName) {
|
||||
final String currentRequestUrl = getThreadLocalRequest().getRequestURL().toString();
|
||||
if (!currentRequestUrl.contains("/leaderboard/")) {
|
||||
logger.log(Level.WARNING, "Leaderboard routing stacktrace", new RuntimeException("Request without leaderboard routing information"));
|
||||
} else {
|
||||
if (currentRequestUrl.contains(leaderboardName)) {
|
||||
logger.info("leaderboard access matches leaderboard url");
|
||||
} else {
|
||||
logger.info("leaderboard access to " + leaderboardName + " does not match request url " + currentRequestUrl);
|
||||
logger.log(Level.SEVERE, "Leaderboard routing stacktrace", new RuntimeException("Request without leaderboard routing information"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link LeaderboardDTO} for <code>leaderboard</code> and fills in the name, race master data
|
||||
* in the form of {@link RaceColumnDTO}s, whether or not there are {@link LeaderboardDTO#hasCarriedPoints carried points}
|
||||
@@ -2569,8 +2555,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
* If <code>withGeoLocationData</code> is <code>true</code> the geographical location of all races will be determined.
|
||||
*/
|
||||
private StrippedLeaderboardDTO createStrippedLeaderboardDTO(Leaderboard leaderboard, boolean withGeoLocationData, boolean withStatisticalData) {
|
||||
checkLeaderboardRouting(leaderboard.getName());
|
||||
|
||||
StrippedLeaderboardDTO leaderboardDTO = new StrippedLeaderboardDTO(convertToBoatClassDTO(leaderboard.getBoatClass()));
|
||||
TimePoint startOfLatestRace = null;
|
||||
Long delayToLiveInMillisForLatestRace = null;
|
||||
@@ -5078,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()
|
||||
@@ -6460,16 +6444,13 @@ 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());
|
||||
RegattaNameAndRaceName regattaAndRaceIdentifier = new RegattaNameAndRaceName(raceIdentifier.getRegattaName(),
|
||||
raceIdentifier.getRaceName());
|
||||
DynamicTrackedRace trackedRace = getService().getTrackedRace(regattaAndRaceIdentifier);
|
||||
trackedRace.setStartTimeReceived(new MillisecondsTimePoint(newStartTimeReceived));
|
||||
|
||||
trackedRace.setStartTimeReceived(
|
||||
newStartTimeReceived == null ? null : new MillisecondsTimePoint(newStartTimeReceived));
|
||||
return baseDomainFactory.createRaceDTO(getService(), false, regattaAndRaceIdentifier, trackedRace);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<EventDTO> getEventsForLeaderboard(String leaderboardName) {
|
||||
|
||||
+1
-1
@@ -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));
|
||||
|
||||
+18
-5
@@ -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,12 +237,22 @@ 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) {
|
||||
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -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;
|
||||
|
||||
+29
-14
@@ -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());
|
||||
|
||||
+22
-4
@@ -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;
|
||||
|
||||
+29
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -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);
|
||||
|
||||
}
|
||||
+144
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+11
-2
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+40
-60
@@ -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()));
|
||||
public JSONArray serialize(TrackedRace trackedRace, Competitor competitor, TimePoint from, TimePoint to,
|
||||
TrackTimeInfo trackTimeInfo) {
|
||||
final JSONArray completeManeuverCurvesWithEstimationData = new JSONArray();
|
||||
for (CompleteManeuverCurveWithEstimationData maneuver : getCompleteManeuverCurvesWithEstimationData(
|
||||
trackedRace, competitor)) {
|
||||
completeManeuverCurvesWithEstimationData
|
||||
.add(maneuverWithEstimationDataJsonSerializer.serialize(maneuver));
|
||||
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));
|
||||
}
|
||||
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();
|
||||
return completeManeuverCurvesWithEstimationData;
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
+90
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+9
-2
@@ -3,6 +3,7 @@ package com.sap.sailing.server.gateway.impl;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
@@ -49,24 +50,30 @@ public class FileUploadServlet extends AbstractFileUploadServlet {
|
||||
for (FileItem fileItem : fileItems) {
|
||||
final JSONObject result = new JSONObject();
|
||||
final String fileExtension;
|
||||
final String fileName = Paths.get(fileItem.getName()).getFileName().toString();
|
||||
final String fileType = fileItem.getContentType();
|
||||
if (fileType.equals("image/jpeg")) {
|
||||
fileExtension = ".jpg";
|
||||
} else if (fileType.equals("image/png")) {
|
||||
fileExtension = ".png";
|
||||
} else {
|
||||
int lastDot = fileName.lastIndexOf(".");
|
||||
if (lastDot > 0) {
|
||||
fileExtension = fileName.substring(lastDot);
|
||||
} else {
|
||||
fileExtension = "";
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (fileItem.getSize() > 1024 * 1024 * MAX_SIZE_IN_MB) {
|
||||
final String errorMessage = "Image is larger than " + MAX_SIZE_IN_MB + "MB";
|
||||
logger.warning("Ignoring file storage request because file "+fileItem.getName()+" is larger than "+MAX_SIZE_IN_MB+"MB");
|
||||
logger.warning("Ignoring file storage request because file "+fileName+" is larger than "+MAX_SIZE_IN_MB+"MB");
|
||||
result.put("status", Status.INTERNAL_SERVER_ERROR.name());
|
||||
result.put("message", errorMessage);
|
||||
} else {
|
||||
final URI fileUri = getService().getFileStorageManagementService().getActiveFileStorageService()
|
||||
.storeFile(fileItem.getInputStream(), fileExtension, fileItem.getSize());
|
||||
result.put(JSON_FILE_NAME, fileItem.getName());
|
||||
result.put(JSON_FILE_NAME, fileName);
|
||||
result.put(JSON_FILE_URI, fileUri.toString());
|
||||
}
|
||||
} catch (IOException | OperationFailedException | InvalidPropertiesException | NoCorrespondingServiceRegisteredException e) {
|
||||
|
||||
+9
@@ -86,6 +86,7 @@ import com.sap.sailing.domain.common.racelog.tracking.NotDenotedForRaceLogTracki
|
||||
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
|
||||
import com.sap.sailing.domain.common.security.Permission;
|
||||
import com.sap.sailing.domain.common.security.Permission.Mode;
|
||||
import com.sap.sailing.domain.common.sharding.ShardingType;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFix;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
|
||||
@@ -96,6 +97,7 @@ import com.sap.sailing.domain.racelogtracking.impl.SmartphoneUUIDIdentifierImpl;
|
||||
import com.sap.sailing.domain.regattalike.HasRegattaLike;
|
||||
import com.sap.sailing.domain.regattalike.IsRegattaLike;
|
||||
import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
|
||||
import com.sap.sailing.domain.sharding.ShardingContext;
|
||||
import com.sap.sailing.domain.tracking.GPSFixTrack;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.RaceHandle;
|
||||
@@ -148,6 +150,9 @@ public class LeaderboardsResource extends AbstractLeaderboardsResource {
|
||||
public Response getLeaderboard(@PathParam("name") String leaderboardName,
|
||||
@DefaultValue("Live") @QueryParam("resultState") ResultStates resultState,
|
||||
@QueryParam("maxCompetitorsCount") Integer maxCompetitorsCount) {
|
||||
ShardingContext.setShardingConstraint(ShardingType.LEADERBOARDNAME, leaderboardName);
|
||||
|
||||
try {
|
||||
Response response;
|
||||
TimePoint requestTimePoint = MillisecondsTimePoint.now();
|
||||
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
|
||||
@@ -171,6 +176,9 @@ public class LeaderboardsResource extends AbstractLeaderboardsResource {
|
||||
}
|
||||
}
|
||||
return response;
|
||||
} finally {
|
||||
ShardingContext.clearShardingConstraint(ShardingType.LEADERBOARDNAME);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -186,6 +194,7 @@ public class LeaderboardsResource extends AbstractLeaderboardsResource {
|
||||
writeCommonLeaderboardData(jsonLeaderboard, leaderboard, resultState, leaderboardDTO.getTimePoint(), maxCompetitorsCount);
|
||||
JSONArray jsonCompetitorEntries = new JSONArray();
|
||||
jsonLeaderboard.put("competitors", jsonCompetitorEntries);
|
||||
jsonLeaderboard.put("ShardingLeaderboardName", ShardingType.LEADERBOARDNAME.encodeIfNeeded(leaderboard.getName()));
|
||||
int counter = 1;
|
||||
for (CompetitorDTO competitor : leaderboardDTO.competitors) {
|
||||
LeaderboardRowDTO leaderboardRowDTO = leaderboardDTO.rows.get(competitor);
|
||||
|
||||
+9
@@ -37,7 +37,9 @@ import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardRowDTO;
|
||||
import com.sap.sailing.domain.common.dto.LegEntryDTO;
|
||||
import com.sap.sailing.domain.common.sharding.ShardingType;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
import com.sap.sailing.domain.sharding.ShardingContext;
|
||||
import com.sap.sse.InvalidDateException;
|
||||
import com.sap.sse.common.Distance;
|
||||
import com.sap.sse.common.Duration;
|
||||
@@ -55,6 +57,9 @@ public class LeaderboardsResourceV2 extends AbstractLeaderboardsResource {
|
||||
@QueryParam("raceDetails") final List<String> raceDetails,
|
||||
@QueryParam("time") String time, @QueryParam("timeasmillis") Long timeasmillis,
|
||||
@QueryParam("maxCompetitorsCount") Integer maxCompetitorsCount) {
|
||||
ShardingContext.setShardingConstraint(ShardingType.LEADERBOARDNAME, leaderboardName);
|
||||
|
||||
try {
|
||||
Response response;
|
||||
Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
|
||||
if (leaderboard == null) {
|
||||
@@ -86,6 +91,9 @@ public class LeaderboardsResourceV2 extends AbstractLeaderboardsResource {
|
||||
}
|
||||
}
|
||||
return response;
|
||||
} finally {
|
||||
ShardingContext.clearShardingConstraint(ShardingType.LEADERBOARDNAME);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -122,6 +130,7 @@ public class LeaderboardsResourceV2 extends AbstractLeaderboardsResource {
|
||||
}
|
||||
JSONArray jsonCompetitorEntries = new JSONArray();
|
||||
jsonLeaderboard.put("competitors", jsonCompetitorEntries);
|
||||
jsonLeaderboard.put("ShardingLeaderboardName", ShardingType.LEADERBOARDNAME.encodeIfNeeded(leaderboard.getName()));
|
||||
int competitorCounter = 1;
|
||||
// Remark: leaderboardDTO.competitors are ordered by total rank
|
||||
for (CompetitorDTO competitor : leaderboardDTO.competitors) {
|
||||
|
||||
+70
-4
@@ -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(),
|
||||
CompetitorTrackWithEstimationDataJsonSerializer serializer = new CompetitorTrackWithEstimationDataJsonSerializer(
|
||||
getService().getPolarDataService(), new DetailedBoatClassJsonSerializer(),
|
||||
new CompleteManeuverCurvesWithEstimationDataJsonSerializer(getService().getPolarDataService(),
|
||||
new CompleteManeuverCurveWithEstimationDataJsonSerializer(
|
||||
new ManeuverMainCurveWithEstimationDataJsonSerializer(),
|
||||
new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer(),
|
||||
new WindJsonSerializer(new PositionJsonSerializer()), new PositionJsonSerializer()));
|
||||
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")
|
||||
|
||||
+1
@@ -28,6 +28,7 @@ public class RestApiApplication extends Application {
|
||||
classes.add(PingResource.class);
|
||||
classes.add(TrackedRaceListResource.class);
|
||||
classes.add(StatisticsResource.class);
|
||||
classes.add(WindResource.class);
|
||||
|
||||
// Exception Mappers
|
||||
classes.add(ShiroAuthorizationExceptionTo401ResponseMapper.class);
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package com.sap.sailing.server.gateway.jaxrs.api;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.JSONValue;
|
||||
import org.json.simple.parser.ParseException;
|
||||
|
||||
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
|
||||
import com.sap.sailing.domain.common.Wind;
|
||||
import com.sap.sailing.domain.common.WindSource;
|
||||
import com.sap.sailing.domain.common.WindSourceType;
|
||||
import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID;
|
||||
import com.sap.sailing.domain.common.security.Permission;
|
||||
import com.sap.sailing.domain.common.security.Permission.Mode;
|
||||
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
|
||||
import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException;
|
||||
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
|
||||
import com.sap.sailing.server.gateway.deserialization.impl.Helpers;
|
||||
import com.sap.sailing.server.gateway.deserialization.impl.PositionJsonDeserializer;
|
||||
import com.sap.sailing.server.gateway.deserialization.impl.WindJsonDeserializer;
|
||||
import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource;
|
||||
|
||||
@Path("/v1/wind")
|
||||
public class WindResource extends AbstractSailingServerResource {
|
||||
private final JsonDeserializer<Wind> deserializer = new WindJsonDeserializer(new PositionJsonDeserializer());
|
||||
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Path("putWind")
|
||||
public Response putWind(String json) throws ParseException, JsonDeserializationException {
|
||||
SecurityUtils.getSubject().checkPermission(Permission.TRACKED_RACE.getStringPermission(Mode.UPDATE));
|
||||
|
||||
Object requestBody = JSONValue.parseWithException(json);
|
||||
JSONObject requestObject = Helpers.toJSONObjectSafe(requestBody);
|
||||
JSONArray windDatas = (JSONArray) requestObject.get("windData");
|
||||
|
||||
JSONArray regattaNamesAndRaceNames = (JSONArray) requestObject.get("regattaNamesAndRaceNames");
|
||||
|
||||
WindSourceType windSourceType = WindSourceType.valueOf((String) requestObject.get("windSourceType"));
|
||||
JSONArray answer = new JSONArray();
|
||||
String windSourceId = (String) requestObject.get("windSourceId");
|
||||
for (Object regattaNameAndRaceName : regattaNamesAndRaceNames) {
|
||||
final JSONObject regattaNameAndRaceNameObject = Helpers.toJSONObjectSafe(regattaNameAndRaceName);
|
||||
String regattaName = (String) regattaNameAndRaceNameObject.get("regattaName");
|
||||
String raceName = (String) regattaNameAndRaceNameObject.get("raceName");
|
||||
RegattaNameAndRaceName identifier = new RegattaNameAndRaceName(regattaName, raceName);
|
||||
JSONObject answerForRace = new JSONObject();
|
||||
answerForRace.put("regattaNameAndRaceName", regattaNameAndRaceName);
|
||||
if (windSourceType == WindSourceType.EXPEDITION || windSourceType == WindSourceType.WEB) {
|
||||
DynamicTrackedRace trackedRace = getService().getTrackedRace(identifier);
|
||||
WindSource windsource = new WindSourceWithAdditionalID(windSourceType, windSourceId);
|
||||
|
||||
if (trackedRace != null) {
|
||||
JSONArray subAnswer = new JSONArray();
|
||||
for (int i = 0; i < windDatas.size(); i++) {
|
||||
JSONObject windData = Helpers.toJSONObjectSafe(windDatas.get(i));
|
||||
Wind data = deserializer.deserialize(windData);
|
||||
boolean success = trackedRace.recordWind(data, windsource);
|
||||
subAnswer.add(i, success);
|
||||
}
|
||||
answerForRace.put("answer", subAnswer);
|
||||
} else {
|
||||
answerForRace.put("answer", "Could not resolve traced race");
|
||||
}
|
||||
} else {
|
||||
answerForRace.put("answer", "Only Windsourcetypes expedition or web are allowed");
|
||||
}
|
||||
answer.add(answerForRace);
|
||||
}
|
||||
return Response.ok(answer.toJSONString()).build();
|
||||
}
|
||||
}
|
||||
@@ -249,6 +249,11 @@ name to make sure you get to the event you're interested in.
|
||||
<td><a href="boats.html">/api/v1/boats</a></td>
|
||||
<td>Obtains information about a single boat</td>
|
||||
</tr>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="putWind.html">/api/v1/wind/putWind</a></td>
|
||||
<td>Allows to add wind values to 1-x races, similar to AdminConsole</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div style="height: 1em;"></div>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<link href='../../../../sailing-fontface-1.0.cache.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
|
||||
<link href='../../../main.css' media='screen' rel='stylesheet' title='Default stylesheet' type='text/css' />
|
||||
<link rel='icon' href='../../../../sap.ico' type='image/x-icon'>
|
||||
<title>SAP Sailing Analytics Webservices API Version 1.0</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>SAP Sailing Analytics Webservices API Version 1.0</h1>
|
||||
<h2>URL: /api/v1/wind/putWind</h2>
|
||||
|
||||
<b>Description:</b>
|
||||
<p>Gets the details of a competitor</p>
|
||||
<br/>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Webservice Type:</td>
|
||||
<td>REST</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Output format:</td>
|
||||
<td>Json</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mandatory parameters:</td>
|
||||
<td>windData, regattaName, raceName, windSourceType, windSourceId</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Examples:</td>
|
||||
<td>
|
||||
Request: <br> {"windData":[{"position":{"latitude_deg":1,"longitude_deg":1},"timepoint":1234,"direction":120,"speedinknots":60}],"regattaNamesAndRaceNames":[{"regattaName":"ESS 2016 Cardiff","raceName":"Race 1"}],"windSourceType":"WEB","windSourceId":"tracker02"}
|
||||
<br>
|
||||
Answer: <br> [{"regattaNameAndRaceName":{"regattaName":"ESS 2016 Cardiff","raceName":"Race 1"},"answer":[true]}]
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="height: 1em;"></div>
|
||||
<a href="index.html">Back to Web Service Overview</a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -22,8 +22,21 @@
|
||||
<h2 class="releaseHeadline">Release Notes - Administration Console</h2>
|
||||
<div class="innerContent">
|
||||
|
||||
<h2 class="articleSubheadline">July 2018</h2>
|
||||
<ul class="bulletList">
|
||||
<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>
|
||||
<li>Up to 15 discards can now be configured for series and leaderboards. This enables, e.g., "Wednesday Night" scenarios
|
||||
where over the season, say, 20 races are run but due to changing participation only, say, the five best ones shall be scored.</li>
|
||||
</ul>
|
||||
|
||||
<h2 class="articleSubheadline">June 2018</h2>
|
||||
<ul class="bulletList">
|
||||
<li>Added the available WindFinder Spot Collections. The desired ones can be selected from the WindFinder Tab in the Event Dialog.</li>
|
||||
<li>Added the ability to change the url of multiple Mediatracks with a common prefix at once.
|
||||
This is useful e.g. for video migration scenarios when movin videaos from one server to another.
|
||||
This can be done from the adminconsole->tracked races->audio & videos tab</li>
|
||||
|
||||
+28
-1
@@ -1,5 +1,6 @@
|
||||
package com.sap.sse.datamining.ui.client.presentation;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -25,11 +26,15 @@ import org.moxieapps.gwt.highcharts.client.events.SeriesClickEvent;
|
||||
import org.moxieapps.gwt.highcharts.client.events.SeriesClickEventHandler;
|
||||
import org.moxieapps.gwt.highcharts.client.labels.AxisLabelsData;
|
||||
import org.moxieapps.gwt.highcharts.client.labels.AxisLabelsFormatter;
|
||||
import org.moxieapps.gwt.highcharts.client.labels.DataLabels;
|
||||
import org.moxieapps.gwt.highcharts.client.labels.DataLabelsData;
|
||||
import org.moxieapps.gwt.highcharts.client.labels.DataLabelsFormatter;
|
||||
import org.moxieapps.gwt.highcharts.client.labels.YAxisLabels;
|
||||
import org.moxieapps.gwt.highcharts.client.plotOptions.SeriesPlotOptions;
|
||||
|
||||
import com.google.gwt.text.shared.AbstractRenderer;
|
||||
import com.google.gwt.user.client.ui.Button;
|
||||
import com.google.gwt.user.client.ui.CheckBox;
|
||||
import com.google.gwt.user.client.ui.HorizontalPanel;
|
||||
import com.google.gwt.user.client.ui.Label;
|
||||
import com.google.gwt.user.client.ui.SimpleLayoutPanel;
|
||||
@@ -136,6 +141,7 @@ public class ResultsChart extends AbstractNumericResultsPresenter<Settings> {
|
||||
private final HorizontalPanel sortByPanel;
|
||||
private final ValueListBox<Comparator<GroupKey>> keyComparatorListBox;
|
||||
private final ValueListBox<Integer> decimalsListBox;
|
||||
private final CheckBox showDataLabelsCheckBox;
|
||||
|
||||
private final SimpleLayoutPanel chartPanel;
|
||||
private final Chart chart;
|
||||
@@ -208,6 +214,19 @@ public class ResultsChart extends AbstractNumericResultsPresenter<Settings> {
|
||||
});
|
||||
addControl(decimalsPanel);
|
||||
|
||||
HorizontalPanel showDataLabelsPanel = new HorizontalPanel();
|
||||
showDataLabelsPanel.setSpacing(5);
|
||||
showDataLabelsPanel.add(new Label(getDataMiningStringMessages().showDataLabels() + ":"));
|
||||
showDataLabelsCheckBox = new CheckBox();
|
||||
showDataLabelsPanel.add(showDataLabelsCheckBox);
|
||||
showDataLabelsCheckBox.setValue(true);
|
||||
showDataLabelsCheckBox.addValueChangeHandler(e -> {
|
||||
resetChartSeries();
|
||||
showResultData();
|
||||
});
|
||||
addControl(showDataLabelsPanel);
|
||||
|
||||
|
||||
StringMessages stringMessages = getDataMiningStringMessages();
|
||||
ChartToCsvExporter csvExporter = new ChartToCsvExporter(stringMessages.csvCopiedToClipboard());
|
||||
Button exportButton = new Button(stringMessages.csvExport());
|
||||
@@ -430,7 +449,15 @@ public class ResultsChart extends AbstractNumericResultsPresenter<Settings> {
|
||||
}
|
||||
}
|
||||
}));
|
||||
chart.setSeriesPlotOptions(new SeriesPlotOptions().setSeriesClickEventHandler(new SeriesClickHandler()));
|
||||
chart.setSeriesPlotOptions(new SeriesPlotOptions()
|
||||
.setDataLabels(new DataLabels().setEnabled(true).setFormatter(new DataLabelsFormatter() {
|
||||
@Override
|
||||
public String format(DataLabelsData dataLabelsData) {
|
||||
String dataLabel = String.valueOf(BigDecimal.valueOf(dataLabelsData.getYAsDouble())
|
||||
.setScale(decimalsListBox.getValue(), BigDecimal.ROUND_HALF_UP).doubleValue());
|
||||
return showDataLabelsCheckBox.getValue() ? dataLabel : null;
|
||||
}
|
||||
})).setSeriesClickEventHandler(new SeriesClickHandler()));
|
||||
return chart;
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ public interface StringMessages extends Messages {
|
||||
String groupMedianDescending();
|
||||
String choosePresentation();
|
||||
String shownDecimals();
|
||||
String showDataLabels();
|
||||
String elements(long count);
|
||||
String resultsChart();
|
||||
String cantDisplayDataOfType(String resultType);
|
||||
|
||||
@@ -56,6 +56,7 @@ groupMedianAscending=Group Median (Ascending)
|
||||
groupMedianDescending=Group Median (Descending)
|
||||
choosePresentation=Choose Presentation
|
||||
shownDecimals=Shown Decimals
|
||||
showDataLabels=Show labels
|
||||
elements={0} elements
|
||||
resultsChart=Results Chart
|
||||
cantDisplayDataOfType=Can''t display data of type {0}
|
||||
|
||||
@@ -55,6 +55,7 @@ groupMedianAscending=Gruppe Median (Aufsteigend)
|
||||
groupMedianDescending=Gruppe Median (Absteigend)
|
||||
choosePresentation=Wähle Präsentation
|
||||
shownDecimals=Angezeigte Nachkommastellen
|
||||
showDataLabels=Zahlen anzeigen
|
||||
elements={0} Elemente
|
||||
resultsChart=Ergebnis-Diagramm
|
||||
cantDisplayDataOfType=Daten des Typs {0} können nicht dargestellt werden
|
||||
|
||||
+17
-5
@@ -3,6 +3,7 @@ package com.sap.sse.gwt.client.controls.listedit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gwt.dom.client.Style.Unit;
|
||||
import com.google.gwt.event.dom.client.ClickEvent;
|
||||
import com.google.gwt.event.dom.client.ClickHandler;
|
||||
import com.google.gwt.event.dom.client.KeyUpEvent;
|
||||
@@ -85,11 +86,9 @@ public abstract class GenericStringListEditorComposite<ValueType> extends ListEd
|
||||
}
|
||||
|
||||
public static class ExpandedUi<ValueType> extends ExpandedListEditorUi<ValueType> {
|
||||
|
||||
protected final MultiWordSuggestOracle inputOracle;
|
||||
protected final String placeholderTextForAddTextbox;
|
||||
|
||||
|
||||
protected final Integer inputBoxSize;
|
||||
public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, Iterable<String> suggestValues) {
|
||||
this(stringMessages, removeImage, suggestValues, /* placeholderTextForAddTextbox */ null);
|
||||
}
|
||||
@@ -98,8 +97,17 @@ public abstract class GenericStringListEditorComposite<ValueType> extends ListEd
|
||||
* @param suggestValues must not be null but may be empty
|
||||
* @param placeholderTextForAddTextbox may be null
|
||||
*/
|
||||
public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, Iterable<String> suggestValues, String placeholderTextForAddTextbox) {
|
||||
super(stringMessages, removeImage, /*canRemoveItems*/true);
|
||||
public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, Iterable<String> suggestValues,
|
||||
String placeholderTextForAddTextbox) {
|
||||
this(stringMessages, removeImage, suggestValues, placeholderTextForAddTextbox, /* inputBoxSize */ null);
|
||||
|
||||
}
|
||||
/**
|
||||
* @param inputBoxSize The size of the input box in EM Unit.
|
||||
*/
|
||||
public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, Iterable<String> suggestValues,
|
||||
String placeholderTextForAddTextbox, Integer inputBoxSize) {
|
||||
super(stringMessages, removeImage, /* canRemoveItems */true);
|
||||
this.placeholderTextForAddTextbox = placeholderTextForAddTextbox;
|
||||
this.inputOracle = new MultiWordSuggestOracle();
|
||||
for (String suggestValue : suggestValues) {
|
||||
@@ -108,6 +116,7 @@ public abstract class GenericStringListEditorComposite<ValueType> extends ListEd
|
||||
List<String> defaultSuggestions = new ArrayList<>();
|
||||
Util.addAll(suggestValues, defaultSuggestions);
|
||||
this.inputOracle.setDefaultSuggestionsFromText(defaultSuggestions);
|
||||
this.inputBoxSize = inputBoxSize;
|
||||
}
|
||||
|
||||
protected GenericStringListEditorComposite<ValueType> getContext() {
|
||||
@@ -134,6 +143,9 @@ public abstract class GenericStringListEditorComposite<ValueType> extends ListEd
|
||||
protected Widget createAddWidget() {
|
||||
final SuggestBox inputBox = createSuggestBox();
|
||||
inputBox.ensureDebugId("InputSuggestBox");
|
||||
if (inputBoxSize != null) {
|
||||
inputBox.setWidth(Integer.toString(inputBoxSize) + Unit.EM);
|
||||
}
|
||||
final Button addButton = new Button(getStringMessages().add());
|
||||
addButton.ensureDebugId("AddButton");
|
||||
addButton.setEnabled(false);
|
||||
|
||||
+2
-1
@@ -36,7 +36,8 @@ public abstract class GenericStringListInlineEditorComposite<ValueType> extends
|
||||
this(stringMessages, removeImage, suggestValues, /* placeholderTextForAddTextbox */ null, textBoxSize);
|
||||
}
|
||||
|
||||
public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, List<String> suggestValues, String placeholderTextForAddTextbox, int textBoxSize) {
|
||||
public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, List<String> suggestValues,
|
||||
String placeholderTextForAddTextbox, int textBoxSize) {
|
||||
super(stringMessages, removeImage, suggestValues, placeholderTextForAddTextbox);
|
||||
this.textBoxSize = textBoxSize;
|
||||
}
|
||||
|
||||
+2
-1
@@ -83,7 +83,8 @@ public class UserDetailsView extends FlowPanel {
|
||||
for (Permission permission : additionalPermissions) {
|
||||
defaultPermissionNames.add(permission.getStringPermission());
|
||||
}
|
||||
rolesEditor = new StringListEditorComposite(user==null?Collections.<String>emptySet():user.getRoles(), stringMessages, com.sap.sse.gwt.client.IconResources.INSTANCE.removeIcon(), defaultRoleNames,
|
||||
rolesEditor = new StringListEditorComposite(user == null ? Collections.<String> emptySet() : user.getRoles(),
|
||||
stringMessages, com.sap.sse.gwt.client.IconResources.INSTANCE.removeIcon(), defaultRoleNames,
|
||||
stringMessages.enterRoleName());
|
||||
rolesEditor.addValueChangeHandler(new ValueChangeHandler<Iterable<String>>() {
|
||||
@Override
|
||||
|
||||
@@ -88,7 +88,6 @@ ADDITIONAL_JAVA_ARGS="-Dpersistentcompetitors.clear=false -XX:ThreadPriorityPoli
|
||||
# Uncomment for use with SAP JVM only:
|
||||
#ADDITIONAL_JAVA_ARGS="$ADDITIONAL_JAVA_ARGS -XX:+GCHistory -XX:GCHistoryFilename=logs/sapjvm_gc@PID.prf"
|
||||
|
||||
JAVA_HOME=/opt/jdk1.8.0_20
|
||||
if [[ ! -d $JAVA_HOME ]] && [[ -f "/usr/libexec/java_home" ]]; then
|
||||
JAVA_HOME=`/usr/libexec/java_home`
|
||||
fi
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<string name="error_invalid_qr_code">Ungültiger QR-Code</string>
|
||||
|
||||
<!-- Regatta -->
|
||||
<string name="your_regattas">Ihre Regattas:</string>
|
||||
<string name="your_regattas">Ihre Regatten:</string>
|
||||
<string name="options_refresh">Aktualisieren</string>
|
||||
|
||||
<!-- Eula -->
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ To ensure that all components of the Analysis Suite are working, you should also
|
||||
- In Eclipse click Help -> Install New Software -> Add and enter [https://dl-ssl.google.com/android/eclipse/](https://dl-ssl.google.com/android/eclipse/)
|
||||
- Select the Developer Tools and install
|
||||
- After restarting Eclipse the "Welcome to Android Development"-window should help you with installing the Android SDK
|
||||
- It is also possible to download the Android SDK separately from the official Google download website. However, as of Revision 25.0.0 of the Android SDK Tools, the SDK Manager became an integrated part of Android Studio. Therefore, Revisions newer than 24.4.1 will not come with a standalone SDK Manager. Since it is absolutely essential if you want to use Eclipse, please download the Android SDK from the following link: [https://dl.google.com/android/installer_r24.4.1-windows.exe](https://dl.google.com/android/installer_r24.4.1-windows.exe)
|
||||
- It is also possible to download the Android SDK separately from the official Google download website. However, as of Revision 25.0.0 of the Android SDK Tools, the SDK Manager became an integrated part of Android Studio. Therefore, Revisions newer than 24.4.1 will not come with a standalone SDK Manager. Since it is absolutely essential if you want to use Eclipse, please download the Android SDK from the following link: Windows: [https://dl.google.com/android/installer_r24.4.1-windows.exe](https://dl.google.com/android/installer_r24.4.1-windows.exe) MacOS: [https://dl.google.com/android/android-sdk_r24.4.1-macosx.zip](https://dl.google.com/android/android-sdk_r24.4.1-macosx.zip) Linux: [https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz](https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz)
|
||||
2. Setup the Android SDK
|
||||
* In Eclipse press Window -> Android SDK Manager
|
||||
* Install everything of "Tools" (hint: watchout not to update Android SDK Tools, see note below)
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# Amazon EC2 for SAP Sailing Analytics
|
||||
|
||||
[[_TOC_]]
|
||||
|
||||
## Sharding
|
||||
|
||||
#### Servers
|
||||
|
||||
- For a minimum Setup at least 2 Servers are required.
|
||||
- One server is required to be the master server as in usual replication situations
|
||||
- All other servers are required to be replicas
|
||||
|
||||
|
||||
e.g. a simple example:
|
||||
|
||||
|
||||

|
||||
|
||||
#### Routing Setup
|
||||
|
||||
Multiple target groups must be defined per routing target.
|
||||
|
||||
Interesting here are the groups named after leaderboards (or other discriminators).
|
||||
|
||||
The names of the target groups can be defined however fitting, e.g. multiple leaderboards can be routed to each, in this example the leaderboard name is used for simplicity.
|
||||
|
||||

|
||||
|
||||
The interesting part is the configuration of the load balancer:
|
||||
|
||||
It is recommended to have a route with a dedicated hostname going specifically to the master!, as else it might not be possible to reach the non replicated AdminConsole!
|
||||
|
||||
It is required to have a default route, that can go to any server, because there are some requests that are not specific to leaderboards, and still need to be processed.
|
||||
|
||||
For each Leaderboard that should not go to the default group, a entry is required. To discriminate between leaderboards, they are encoded into (mostly) all requests.
|
||||
|
||||
This allows to specify a path based routing. The path are "/gwt/service/sailing/" and "/gwt/service/dispatch/" and the routing relevant suffix is leaderboard/underscore\_escaped\_leaderboardname.
|
||||
|
||||
|
||||
All non a-z,A-Z,0-9 characters are replaced with \_.
|
||||
|
||||
This is because limitations in the ALB do not allow the unmodified URL encoded leaderboard name.
|
||||
|
||||
|
||||
In case it is unclear how the name of a leaderboard is, the REST api of the master server can be asked for this. (after creating the leaderboards)
|
||||
|
||||
NAME\_OF\_LEADERBOARD\_YOU\_NEED\_TO\_KNOW\_THE\_PATH\_OF is the same name as in the admin console, the browser will do all required url encoding for this
|
||||
|
||||
https://www.sharding-master.sapsailing.com/sailingserver/api/v1/leaderboards/NAME\_OF\_LEADERBOARD\_YOU\_NEED\_TO\_KNOW\_THE\_PATH\_OF/
|
||||
|
||||
eg:
|
||||
https://www.sharding-master.sapsailing.com/sailingserver/api/v1/leaderboards/ESS 2016 Cardiff
|
||||
|
||||
This will return a JSON document with the leaderboard name encoded near at the end:
|
||||
|
||||
"ShardingLeaderboardName":"\/leaderboard\/ESS\_2016\_Cardiff", please note that the response is encoded, to get the proper name this needs to be decoded first e.g using some json parser.
|
||||
|
||||

|
||||
|
||||
Please note that the example image is missing the dispatch related rules!
|
||||
|
||||
If you need to use the REST api for other applications, we suggest to use the master server directly, as else the requests would need to be replicated to the master server anyway.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user