Merge branch 'master' into bug4210

Conflicts:
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SetStartTimeDialog.java

fixed small issue with parsing in chrome
This commit is contained in:
Kai Boernert committed 2017-11-24 16:31:43 +01:00
commit 027022e40a
273 files changed
+4449 -2766

No files matched your search

@@ -56,7 +56,7 @@ public class BravoFixTrackWithContext implements HasBravoFixTrackContext {
end = endOfRace;
}
return bravoFixTrack.getDistanceSpentFoiling(
getRaceOfCompetitorContext().getTrackedRaceContext().getTrackedRace().getTrack(getRaceOfCompetitorContext().getCompetitor()),
startOfRace, end);
startOfRace,
end);
}
}
@@ -96,7 +96,7 @@ public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompeti
public Distance getDistanceSpentFoiling() {
return getSomethingForLegTrackingInterval((start, end) -> {
BravoFixTrack<Competitor> bravoFixTrack = getTrackedLegContext().getTrackedLeg().getTrackedRace().getSensorTrack(getCompetitor(), BravoFixTrack.TRACK_NAME);
return bravoFixTrack == null ? Distance.NULL : bravoFixTrack.getDistanceSpentFoiling(getTrackedLegContext().getTrackedLeg().getTrackedRace().getTrack(getCompetitor()), start, end);
return bravoFixTrack == null ? Distance.NULL : bravoFixTrack.getDistanceSpentFoiling(start, end);
});
}
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>com.sap.sailing.domain.bravoadapter</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -0,0 +1,7 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8
@@ -0,0 +1,3 @@
eclipse.preferences.version=1
pluginProject.extensions=false
resolve.requirebundle=false
@@ -0,0 +1,14 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Bravoadapter
Bundle-SymbolicName: com.sap.sailing.domain.bravoadapter
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: org.osgi.framework;version="1.8.0"
Require-Bundle: com.sap.sailing.domain,
com.sap.sailing.server.trackfiles,
com.sap.sailing.domain.common,
com.sap.sse.common,
com.sap.sailing.server.gateway
Bundle-Activator: com.sap.sailing.domain.bravoadapter.impl.Activator
@@ -0,0 +1,4 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.sap.sailing.domain.bravoadapter</artifactId>
<packaging>eclipse-plugin</packaging>
</project>
@@ -0,0 +1,17 @@
package com.sap.sailing.domain.bravoadapter.impl;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import com.sap.sailing.server.trackfiles.common.GPSFixImporterRegistration;
public class Activator implements BundleActivator {
@Override
public void start(BundleContext context) throws Exception {
GPSFixImporterRegistration.register(new BravoGPSFixImporter(), context);
}
@Override
public void stop(BundleContext context) throws Exception {
}
}
@@ -0,0 +1,50 @@
package com.sap.sailing.domain.bravoadapter.impl;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Date;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.sensordata.BravoExtendedSensorDataMetadata;
import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier;
import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifierImpl;
import com.sap.sailing.domain.trackimport.FormatNotSupportedException;
import com.sap.sailing.domain.trackimport.GPSFixImporter;
import com.sap.sailing.server.gateway.windimport.bravo.FunnyDegreeConverter;
import com.sap.sailing.server.trackfiles.impl.BravoExtendedDataImporterImpl;
public class BravoGPSFixImporter implements GPSFixImporter {
@Override
public void importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing, final String filename)
throws FormatNotSupportedException, IOException {
TrackFileImportDeviceIdentifier device = new TrackFileImportDeviceIdentifierImpl(filename, getType() + "@" + new Date());
new BravoExtendedDataImporterImpl().importFixes(inputStream,
(Iterable<DoubleVectorFix> fixes, TrackFileImportDeviceIdentifier deviceIdentifier)->{
for (final DoubleVectorFix fix : fixes) {
GPSFixMoving gpsFix = new GPSFixMovingImpl(
new DegreePosition(FunnyDegreeConverter.funnyLatLng(fix.get(BravoExtendedSensorDataMetadata.LAT.getColumnIndex())),
FunnyDegreeConverter.funnyLatLng(fix.get(BravoExtendedSensorDataMetadata.LON.getColumnIndex()))),
fix.getTimePoint(),
new KnotSpeedWithBearingImpl(fix.get(BravoExtendedSensorDataMetadata.SOG.getColumnIndex()),
new DegreeBearingImpl(fix.get(BravoExtendedSensorDataMetadata.COG.getColumnIndex()))));
callback.addFix(gpsFix, device);
}
}, filename, /* sourceName */ getType(), /* downsample */ false);
}
@Override
public Iterable<String> getSupportedFileExtensions() {
return Arrays.asList(new String[] { "csv", "log", "txt" });
}
@Override
public String getType() {
return "Bravo";
}
}
@@ -25,6 +25,8 @@ public enum DetailType implements Serializable {
CURRENT_HEEL_IN_DEGREES(2,DESCENDING),
CURRENT_PITCH_IN_DEGREES(2,DESCENDING),
CURRENT_RIDE_HEIGHT_IN_METERS(2,DESCENDING),
CURRENT_DISTANCE_FOILED_IN_METERS(0, DESCENDING),
CURRENT_DURATION_FOILED_IN_SECONDS(0, DESCENDING),
CURRENT_PORT_DAGGERBOARD_RAKE(2,DESCENDING),
CURRENT_STBD_DAGGERBOARD_RAKE(2,DESCENDING),
CURRENT_PORT_RUDDER_RAKE(2,DESCENDING),
@@ -64,8 +66,10 @@ public enum DetailType implements Serializable {
MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS(1, DESCENDING),
TIME_ON_TIME_FACTOR(4, DESCENDING), TIME_ON_DISTANCE_ALLOWANCE_IN_SECONDS_PER_NAUTICAL_MILE(0, ASCENDING),
TOTAL_DISTANCE_TRAVELED(0, ASCENDING), TOTAL_AVERAGE_SPEED_OVER_GROUND(2, DESCENDING),
TOTAL_TIME_SAILED_IN_SECONDS(1, ASCENDING), RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS(2,
DESCENDING), RACE_CURRENT_RIDE_HEIGHT_IN_METERS(2, DESCENDING),
TOTAL_TIME_SAILED_IN_SECONDS(1, ASCENDING),
TOTAL_DURATION_FOILED_IN_SECONDS(0, DESCENDING), TOTAL_DISTANCE_FOILED_IN_METERS(0, DESCENDING),
RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS(2, DESCENDING), RACE_CURRENT_RIDE_HEIGHT_IN_METERS(2, DESCENDING),
RACE_CURRENT_DISTANCE_FOILED_IN_METERS(0, DESCENDING), RACE_CURRENT_DURATION_FOILED_IN_SECONDS(0, DESCENDING),
RACE_NET_POINTS(2, ASCENDING), REGATTA_NET_POINTS(2, ASCENDING), REGATTA_NET_POINTS_SUM(2, ASCENDING),
RACE_RATIO_BETWEEN_TIME_SINCE_LAST_POSITION_FIX_AND_AVERAGE_SAMPLING_INTERVAL(1, ASCENDING), RACE_DISTANCE_TO_START_FIVE_SECONDS_BEFORE_RACE_START(1, ASCENDING),
RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START(2, DESCENDING),
@@ -138,7 +138,7 @@ public class LeaderboardEntryDTO implements Serializable {
return totalPointsCorrected || (reasonForMaxPoints != null && reasonForMaxPoints != MaxPointsReason.NONE);
}
public Duration getTimeSailedInMillis() {
public Duration getTimeSailed() {
final Duration result;
if (legDetails != null) {
long timeInMilliseconds = 0;
@@ -159,7 +159,7 @@ public class LeaderboardEntryDTO implements Serializable {
return result;
}
public Distance getDistanceTraveledInMeters() {
public Distance getDistanceTraveled() {
Distance result = null;
if (legDetails != null) {
for (LegEntryDTO legDetail : legDetails) {
@@ -176,6 +176,47 @@ public class LeaderboardEntryDTO implements Serializable {
return result;
}
public Duration getDurationFoiled() {
final Duration result;
if (legDetails != null) {
Double acc = null;
for (LegEntryDTO legDetail : legDetails) {
if (legDetail != null) {
if (legDetail.currentDurationFoiledInSeconds != null) {
if (acc == null) {
acc = 0.0;
}
acc += legDetail.currentDurationFoiledInSeconds;
} else {
acc = null;
break;
}
}
}
result = acc == null ? null : new MillisecondsDurationImpl((long) (acc*1000.));
} else {
result = null;
}
return result;
}
public Distance getDistanceFoiled() {
Distance result = null;
if (legDetails != null) {
for (LegEntryDTO legDetail : legDetails) {
if (legDetail != null) {
if (legDetail.currentDistanceFoiledInMeters != null) {
if (result == null) {
result = Distance.NULL;
}
result = result.add(new MeterDistance(legDetail.currentDistanceFoiledInMeters));
}
}
}
}
return result;
}
public int getOneBasedCurrentLegNumber() {
int result = 0;
if (legDetails != null && !legDetails.isEmpty()) {
@@ -26,6 +26,8 @@ public class LeaderboardRowDTO implements Serializable {
public Double totalTimeSailedUpwindInSeconds;
public Double totalTimeSailedReachingInSeconds;
public Double totalDistanceTraveledInMeters;
public Double totalDurationFoiledInSeconds;
public Double totalDistanceFoiledInMeters;
@Override
public int hashCode() {
@@ -38,6 +40,10 @@ public class LeaderboardRowDTO implements Serializable {
+ ((maximumSpeedOverGroundInKnots == null) ? 0 : maximumSpeedOverGroundInKnots.hashCode());
result = prime * result
+ ((totalDistanceTraveledInMeters == null) ? 0 : totalDistanceTraveledInMeters.hashCode());
result = prime * result
+ ((totalDurationFoiledInSeconds == null) ? 0 : totalDurationFoiledInSeconds.hashCode());
result = prime * result
+ ((totalDistanceFoiledInMeters == null) ? 0 : totalDistanceFoiledInMeters.hashCode());
result = prime * result + ((netPoints == null) ? 0 : netPoints.hashCode());
result = prime * result
+ ((totalTimeSailedDownwindInSeconds == null) ? 0 : totalTimeSailedDownwindInSeconds.hashCode());
@@ -87,6 +93,16 @@ public class LeaderboardRowDTO implements Serializable {
return false;
} else if (!totalDistanceTraveledInMeters.equals(other.totalDistanceTraveledInMeters))
return false;
if (totalDurationFoiledInSeconds == null) {
if (other.totalDurationFoiledInSeconds != null)
return false;
} else if (!totalDurationFoiledInSeconds.equals(other.totalDurationFoiledInSeconds))
return false;
if (totalDistanceFoiledInMeters == null) {
if (other.totalDistanceFoiledInMeters != null)
return false;
} else if (!totalDistanceFoiledInMeters.equals(other.totalDistanceFoiledInMeters))
return false;
if (netPoints == null) {
if (other.netPoints != null)
return false;
@@ -120,11 +136,11 @@ public class LeaderboardRowDTO implements Serializable {
return true;
}
public Distance getDistanceTraveledInMeters(String raceColumnName) {
public Distance getDistanceTraveled(String raceColumnName) {
final Distance result;
LeaderboardEntryDTO fieldsForRace = fieldsByRaceColumnName.get(raceColumnName);
if (fieldsForRace != null) {
result = fieldsForRace.getDistanceTraveledInMeters();
result = fieldsForRace.getDistanceTraveled();
} else {
result = null;
}
@@ -135,7 +151,29 @@ public class LeaderboardRowDTO implements Serializable {
final Duration result;
LeaderboardEntryDTO fieldsForRace = fieldsByRaceColumnName.get(raceColumnName);
if (fieldsForRace != null) {
result = fieldsForRace.getTimeSailedInMillis();
result = fieldsForRace.getTimeSailed();
} else {
result = null;
}
return result;
}
public Distance getDistanceFoiled(String raceColumnName) {
final Distance result;
LeaderboardEntryDTO fieldsForRace = fieldsByRaceColumnName.get(raceColumnName);
if (fieldsForRace != null) {
result = fieldsForRace.getDistanceFoiled();
} else {
result = null;
}
return result;
}
public Duration getDurationFoiled(String raceColumnName) {
final Duration result;
LeaderboardEntryDTO fieldsForRace = fieldsByRaceColumnName.get(raceColumnName);
if (fieldsForRace != null) {
result = fieldsForRace.getDurationFoiled();
} else {
result = null;
}
@@ -44,6 +44,8 @@ public class LegEntryDTO implements Serializable {
public Double currentHeelInDegrees;
public Double currentPitchInDegrees;
public Double currentDistanceFoiledInMeters;
public Double currentDurationFoiledInSeconds;
/**
* If the competitor has already finished the leg at the time point for which this entry was created, contains the
@@ -81,6 +83,8 @@ public class LegEntryDTO implements Serializable {
result = prime * result + ((currentHeelInDegrees == null) ? 0 : currentHeelInDegrees.hashCode());
result = prime * result + ((currentPitchInDegrees == null) ? 0 : currentPitchInDegrees.hashCode());
result = prime * result + ((currentRideHeightInMeters == null) ? 0 : currentRideHeightInMeters.hashCode());
result = prime * result + ((currentDurationFoiledInSeconds == null) ? 0 : currentDurationFoiledInSeconds.hashCode());
result = prime * result + ((currentDistanceFoiledInMeters == null) ? 0 : currentDistanceFoiledInMeters.hashCode());
result = prime * result
+ ((currentSpeedOverGroundInKnots == null) ? 0 : currentSpeedOverGroundInKnots.hashCode());
result = prime * result + ((distanceTraveledInMeters == null) ? 0 : distanceTraveledInMeters.hashCode());
@@ -150,6 +154,16 @@ public class LegEntryDTO implements Serializable {
return false;
} else if (!currentRideHeightInMeters.equals(other.currentRideHeightInMeters))
return false;
if (currentDurationFoiledInSeconds == null) {
if (other.currentDurationFoiledInSeconds != null)
return false;
} else if (!currentDurationFoiledInSeconds.equals(other.currentDurationFoiledInSeconds))
return false;
if (currentDistanceFoiledInMeters == null) {
if (other.currentDistanceFoiledInMeters != null)
return false;
} else if (!currentDistanceFoiledInMeters.equals(other.currentDistanceFoiledInMeters))
return false;
if (currentSpeedOverGroundInKnots == null) {
if (other.currentSpeedOverGroundInKnots != null)
return false;
@@ -9,7 +9,7 @@ public class DegreeBearingImpl extends AbstractBearing implements Bearing {
private final double bearingDeg;
/**
*
* @param bearingDeg if a mount degrees over 360, then it will be mod 360 degrees
* @param bearingDeg if amount degrees over 360, then it will be mod 360 degrees
*/
public DegreeBearingImpl(double bearingDeg) {
super();
@@ -42,6 +42,10 @@ public enum BravoExtendedSensorDataMetadata implements ColumnMetadata {
TARGET_HEEL(null, /* expectedInLogfile */ false), //
DEFLECTOR_MILLIMETERS(null, /* expectedInLogfile */ false), //
TARGET_BOATSPEED_P(null, /* expectedInLogfile */ false), //
LAT("Lat", /* expectedInLogfile */ true), //
LON("Lon", /* expectedInLogfile */ true), //
COG("COG", /* expectedInLogfile */ true), //
SOG("SOG", /* expectedInLogfile */ true), //
;
private final String columnName;
@@ -39,6 +39,7 @@ import com.mongodb.WriteResult;
import com.mongodb.util.JSON;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.race.CompetitorResult.MergeState;
import com.sap.sailing.domain.abstractlog.race.CompetitorResults;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent;
@@ -169,8 +170,8 @@ import com.sap.sailing.domain.common.SpeedWithBearing;
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.dto.EventType;
import com.sap.sailing.domain.common.dto.AnniversaryType;
import com.sap.sailing.domain.common.dto.EventType;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
@@ -1601,8 +1602,15 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
final Long finishingTimePointAsMillis = (Long) dbObject.get(FieldNames.RACE_LOG_FINISHING_TIME_AS_MILLIS.name());
final TimePoint finishingTime = finishingTimePointAsMillis == null ? null : new MillisecondsTimePoint(finishingTimePointAsMillis);
final String comment = (String) dbObject.get(FieldNames.LEADERBOARD_SCORE_CORRECTION_COMMENT.name());
final String mergeStateAsString = (String) dbObject.get(FieldNames.LEADERBOARD_SCORE_CORRECTION_MERGE_STATE.name());
final MergeState mergeState;
if (mergeStateAsString == null) {
mergeState = MergeState.OK;
} else {
mergeState = MergeState.valueOf(mergeStateAsString);
}
CompetitorResultImpl positionedCompetitor = new CompetitorResultImpl(
competitorId, competitorDisplayName, rank == null ? rankCounter : rank, maxPointsReason, score, finishingTime, comment);
competitorId, competitorDisplayName, rank == null ? rankCounter : rank, maxPointsReason, score, finishingTime, comment, mergeState);
positionedCompetitors.add(positionedCompetitor);
rankCounter++;
}
@@ -80,7 +80,8 @@ public enum FieldNames {
LEADERBOARD_IS_MEDAL_RACE_COLUMN, LEADERBOARD_CARRIED_POINTS, LEADERBOARD_CARRIED_POINTS_BY_ID,
LEADERBOARD_SCORE_CORRECTIONS, LEADERBOARD_DISCARDING_THRESHOLDS,
LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON, LEADERBOARD_CORRECTED_SCORE, LEADERBOARD_SCORE_CORRECTION_TIMESTAMP,
LEADERBOARD_RANK, LEADERBOARD_SCORE_CORRECTION_COMMENT, LEADERBOARD_COLUMN_FACTORS, WRAPPED_REGATTA_LEADERBOARD_NAME,
LEADERBOARD_RANK, LEADERBOARD_SCORE_CORRECTION_COMMENT, LEADERBOARD_SCORE_CORRECTION_MERGE_STATE,
LEADERBOARD_COLUMN_FACTORS, WRAPPED_REGATTA_LEADERBOARD_NAME,
ELMINATED_COMPETITORS,
// leaderboard group
@@ -1239,6 +1239,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
result.put(FieldNames.RACE_LOG_FINISHING_TIME_AS_MILLIS.name(), competitorResult.getFinishingTime() == null ? null : competitorResult.getFinishingTime().asMillis());
result.put(FieldNames.LEADERBOARD_RANK.name(), competitorResult.getOneBasedRank());
result.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_COMMENT.name(), competitorResult.getComment());
result.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_MERGE_STATE.name(), competitorResult.getMergeState().name());
return result;
}
@@ -56,6 +56,8 @@ import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.DeviceIdentifier;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
@@ -81,6 +83,7 @@ import com.sap.sailing.domain.racelogtracking.impl.fixtracker.FixLoaderAndTracke
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
import com.sap.sailing.domain.tracking.BravoFixTrack;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
import com.sap.sailing.domain.tracking.DynamicTrack;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
@@ -92,6 +95,7 @@ import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
import com.sap.sailing.domain.tracking.impl.SensorFixTrackImpl;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Timed;
import com.sap.sse.common.Util.Pair;
@@ -178,18 +182,12 @@ public class SensorFixStoreAndLoadTest {
public void testLoadAlreadyAddedFixes() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2);
fixLoaderAndTracker.stop(true, /* willBeRemoved */ false);
}
@@ -197,18 +195,12 @@ public class SensorFixStoreAndLoadTest {
public void testAddFixesWhileTracking() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
addBravoFixes();
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2);
fixLoaderAndTracker.stop(true, /* willBeRemoved */ false);
}
@@ -216,18 +208,12 @@ public class SensorFixStoreAndLoadTest {
public void testNoFixesAreLoadedIfNoStoredFixIsInTimeRange() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(AFTER_LAST_FIX), new MillisecondsTimePoint(END_OF_TRACKING)));
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 0);
fixLoaderAndTracker.stop(true, /* willBeRemoved */ false);
}
@@ -235,18 +221,12 @@ public class SensorFixStoreAndLoadTest {
public void testFixesNotInMappedTimeRangeAreIgnoredWhileTracking() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(AFTER_LAST_FIX), new MillisecondsTimePoint(END_OF_TRACKING)));
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
addBravoFixes();
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 0);
fixLoaderAndTracker.stop(true, /* willBeRemoved */ false);
}
@@ -254,18 +234,12 @@ public class SensorFixStoreAndLoadTest {
public void testCompetitorWithoutMappingHasNoTrack() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish();
assertNull(trackedRace.getSensorTrack(comp2, BravoFixTrack.TRACK_NAME));
fixLoaderAndTracker.stop(true, /* willBeRemoved */ false);
}
@@ -275,19 +249,13 @@ public class SensorFixStoreAndLoadTest {
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp2,
device, new MillisecondsTimePoint(MID_OF_TRACKING + 1), new MillisecondsTimePoint(END_OF_TRACKING)));
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2);
testNumberOfRawFixes(trackedRace.getSensorTrack(comp2, BravoFixTrack.TRACK_NAME), 1);
fixLoaderAndTracker.stop(true, /* willBeRemoved */ false);
}
@@ -297,18 +265,12 @@ public class SensorFixStoreAndLoadTest {
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(MID_OF_TRACKING + 1), new MillisecondsTimePoint(END_OF_TRACKING)));
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 3);
fixLoaderAndTracker.stop(true, /* willBeRemoved */ false);
}
@@ -319,18 +281,12 @@ public class SensorFixStoreAndLoadTest {
new MillisecondsTimePoint(MID_OF_TRACKING));
regattaLog.add(mappingEvent);
regattaLog.add(new RegattaLogRevokeEventImpl(author, mappingEvent, "Test purposes"));
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
addBravoFixes();
trackedRace.waitForLoadingToFinish();
assertNull(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME));
fixLoaderAndTracker.stop(true, /* willBeRemoved */ false);
}
@@ -342,23 +298,16 @@ public class SensorFixStoreAndLoadTest {
new MillisecondsTimePoint(END_OF_TRACKING));
regattaLog.add(mappingEvent);
regattaLog.add(new RegattaLogRevokeEventImpl(author, mappingEvent, "Test purposes"));
// non-revoked mapping timestamp 100-200
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
addBravoFixes();
trackedRace.waitForLoadingToFinish();
// Only Fixes from 100 to 200 may be included
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2);
fixLoaderAndTracker.stop(true, /* willBeRemoved */ false);
}
@@ -371,25 +320,16 @@ public class SensorFixStoreAndLoadTest {
RegattaLogCloseOpenEndedDeviceMappingEventImpl closeEvent = new RegattaLogCloseOpenEndedDeviceMappingEventImpl(
new MillisecondsTimePoint(4), author, mappingEventId, new MillisecondsTimePoint(MID_OF_TRACKING));
regattaLog.add(closeEvent);
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish();
BravoFixTrack<Competitor> bravoFixTrack = trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME);
testNumberOfRawFixes(bravoFixTrack, 2);
regattaLog.add(new RegattaLogRevokeEventImpl(author, closeEvent, "Test purposes"));
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(bravoFixTrack, 3);
fixLoaderAndTracker.stop(true, /* willBeRemoved */ false);
}
@@ -414,20 +354,14 @@ public class SensorFixStoreAndLoadTest {
deviceTest, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
addTestFixes();
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2);
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, TestFixTrackImpl.TRACK_NAME), 1);
fixLoaderAndTracker.stop(true, /* willBeRemoved */ false);
}
@@ -437,21 +371,14 @@ public class SensorFixStoreAndLoadTest {
deviceTest, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
addTestFixes();
addBravoFixes();
trackedRace.waitForLoadingToFinish();
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, TestFixTrackImpl.TRACK_NAME), 1);
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2);
fixLoaderAndTracker.stop(true, /* willBeRemoved */ false);
}
@@ -461,18 +388,12 @@ public class SensorFixStoreAndLoadTest {
deviceTest, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(END_OF_TRACKING)));
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(END_OF_TRACKING)));
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
addTestFixes();
addBravoFixes();
trackedRace.waitForLoadingToFinish();
BravoFixTrack<Competitor> bravoFixTrack = trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME);
assertEquals(FIX_RIDE_HEIGHT,
bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP)).getRideHeight());
@@ -480,11 +401,9 @@ public class SensorFixStoreAndLoadTest {
bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP2)).getRideHeight());
assertEquals(FIX_RIDE_HEIGHT3,
bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP3)).getRideHeight());
TestFixTrackImpl<Competitor> testFixTrack = trackedRace.getSensorTrack(comp, TestFixTrackImpl.TRACK_NAME);
assertEquals(FIX_TEST_VALUE,
testFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP)).getTestValue());
fixLoaderAndTracker.stop(true, /* willBeRemoved */ false);
}
@@ -501,7 +420,6 @@ public class SensorFixStoreAndLoadTest {
}
private FixLoaderAndTracker createFixLoaderAndTracker() {
return new FixLoaderAndTracker(trackedRace, store, new SensorFixMapperFactory() {
private TestDataFixMapper testDataFixMapper = new TestDataFixMapper();
private BravoDataFixMapper bravoDataFixMapper = new BravoDataFixMapper();
@@ -747,6 +665,50 @@ public class SensorFixStoreAndLoadTest {
// only the initial 3 fixes are available
testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 3);
fixLoaderAndTracker.stop(true, /* willBeRemoved */ false);
final DynamicGPSFixTrack<Competitor, GPSFixMoving> gpsFixTrack = trackedRace.getTrack(comp);
final DegreePosition pos1 = new DegreePosition(0, 0);
final DegreeBearingImpl course = new DegreeBearingImpl(0);
final KnotSpeedWithBearingImpl speed = new KnotSpeedWithBearingImpl(10, course);
final MillisecondsTimePoint timePoint = new MillisecondsTimePoint(FIX_TIMESTAMP);
final MillisecondsTimePoint timePoint2 = new MillisecondsTimePoint(FIX_TIMESTAMP2);
final MillisecondsTimePoint timePoint3 = new MillisecondsTimePoint(FIX_TIMESTAMP3);
final TimePoint timePointBetween2And3 = new MillisecondsTimePoint((FIX_TIMESTAMP2+FIX_TIMESTAMP3)/2);
final GPSFixMoving fix1 = new GPSFixMovingImpl(pos1, timePoint, speed);
final Position pos2 = pos1.translateGreatCircle(course, speed.travel(timePoint.until(timePoint2)));
final GPSFixMoving fix2 = new GPSFixMovingImpl(pos2, timePoint2, speed);
final Position pos3 = pos2.translateGreatCircle(course, speed.travel(timePoint2.until(timePoint3)));
final GPSFixMoving fix3 = new GPSFixMovingImpl(pos3, timePoint3, speed);
gpsFixTrack.add(fix1);
gpsFixTrack.add(fix2);
gpsFixTrack.add(fix3);
final BravoFixTrack<Competitor> bravoFixTrack = trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME);
final Distance foilingDistance = bravoFixTrack.getDistanceSpentFoiling(timePoint, timePoint3);
final Distance distanceTraveled = gpsFixTrack.getDistanceTraveled(timePoint, timePoint3);
assertEquals(distanceTraveled.getMeters(), foilingDistance.getMeters(), 0.001);
final Duration foilingDuration = bravoFixTrack.getTimeSpentFoiling(timePoint, timePoint3);
assertEquals(timePoint.until(timePoint3).asMillis(), foilingDuration.asMillis());
// now insert a GPS fix which leads to an extended distance traveled as it is not exactly on the previous track
final DegreeBearingImpl temporaryCourse = new DegreeBearingImpl(90);
final GPSFixMoving fixBetween2And3 = new GPSFixMovingImpl(
pos2.translateGreatCircle(temporaryCourse, speed.add(new KnotSpeedWithBearingImpl(5, temporaryCourse)).travel(timePoint2.until(timePointBetween2And3))),
timePointBetween2And3, speed);
gpsFixTrack.add(fixBetween2And3);
final Distance distanceTraveledDifferently = gpsFixTrack.getDistanceTraveled(timePoint, timePoint3);
assertTrue(distanceTraveledDifferently.compareTo(distanceTraveled) > 0);
final Distance foilingDistanceDifferent = bravoFixTrack.getDistanceSpentFoiling(timePoint, timePoint3);
assertEquals(distanceTraveledDifferently.getMeters(), foilingDistanceDifferent.getMeters(), 0.001);
// now overwrite fix3 by a new one with increased speed, therefore extended distance:
final SpeedWithBearing doubledSpeed = speed.add(speed);
final GPSFixMoving fix3Faster = new GPSFixMovingImpl(
pos2.translateGreatCircle(course, doubledSpeed.travel(timePoint2.until(timePoint3))),
timePoint3, doubledSpeed);
gpsFixTrack.add(fix3Faster, /* replace */ true);
final Distance distanceTraveledFaster = gpsFixTrack.getDistanceTraveled(timePoint, timePoint3);
assertTrue(distanceTraveledFaster.compareTo(distanceTraveled) > 0);
final Distance foilingDistanceFaster = bravoFixTrack.getDistanceSpentFoiling(timePoint, timePoint3);
assertEquals(distanceTraveledFaster.getMeters(), foilingDistanceFaster.getMeters(), 0.001);
}
@Test
@@ -20,7 +20,8 @@ public class BravoDataFixMapper implements SensorFixMapper<BravoFix, DynamicSens
@Override
public DynamicSensorFixTrack<Competitor, BravoFix> getTrack(DynamicTrackedRace race, Competitor key) {
return race.getOrCreateSensorTrack(key, BravoFixTrack.TRACK_NAME,
() -> new BravoFixTrackImpl<Competitor>(key, BravoFixTrack.TRACK_NAME, /* hasExtendedFixes */ false));
() -> new BravoFixTrackImpl<Competitor>(key, BravoFixTrack.TRACK_NAME, /* hasExtendedFixes */ false,
race.getTrack(key)));
}
@Override
@@ -21,7 +21,8 @@ public class BravoExtendedDataFixMapper implements SensorFixMapper<BravoFix, Dyn
@Override
public DynamicSensorFixTrack<Competitor, BravoFix> getTrack(DynamicTrackedRace race, Competitor key) {
return race.getOrCreateSensorTrack(key, BravoFixTrack.TRACK_NAME,
() -> new BravoFixTrackImpl<Competitor>(key, BravoFixTrack.TRACK_NAME, /* hasExtendedFixes */ true));
() -> new BravoFixTrackImpl<Competitor>(key, BravoFixTrack.TRACK_NAME, /* hasExtendedFixes */ true,
race.getTrack(key)));
}
@Override
@@ -25,7 +25,8 @@ public class ExpeditionExtendedDataFixMapper implements SensorFixMapper<BravoFix
@Override
public DynamicSensorFixTrack<Competitor, BravoFix> getTrack(DynamicTrackedRace race, Competitor key) {
return race.getOrCreateSensorTrack(key, BravoFixTrack.TRACK_NAME,
() -> new BravoFixTrackImpl<Competitor>(key, BravoFixTrack.TRACK_NAME, /* hasExtendedFixes */ true));
() -> new BravoFixTrackImpl<Competitor>(key, BravoFixTrack.TRACK_NAME, /* hasExtendedFixes */ true,
race.getTrack(key)));
}
@Override
@@ -12,6 +12,7 @@ import org.junit.Test;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.race.CompetitorResult;
import com.sap.sailing.domain.abstractlog.race.CompetitorResult.MergeState;
import com.sap.sailing.domain.abstractlog.race.CompetitorResults;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
@@ -56,10 +57,10 @@ public class ConfirmedFinishPositioningListFinderTest extends
public void testMostRecent() {
RaceLogFinishPositioningConfirmedEvent event1 = createEvent(RaceLogFinishPositioningConfirmedEvent.class, 1);
RaceLogFinishPositioningConfirmedEvent event2 = createEvent(RaceLogFinishPositioningConfirmedEvent.class, 2);
final CompetitorResultImpl oldResultC1 = new CompetitorResultImpl("Comp1", "Comp 1", 1, MaxPointsReason.NONE, 1.0, /* finishing time */ null, /* comment */ null);
final CompetitorResultImpl oldResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.NONE, 2.0, /* finishing time */ null, /* comment */ null);
final CompetitorResultImpl oldResultC1 = new CompetitorResultImpl("Comp1", "Comp 1", 1, MaxPointsReason.NONE, 1.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResultImpl oldResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.NONE, 2.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResults olderResults = mockCompetitorResults(oldResultC1, oldResultC2);
final CompetitorResultImpl newResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null);
final CompetitorResultImpl newResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResults newerResults = mockCompetitorResults(newResultC2);
when(event1.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(olderResults);
when(event2.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(newerResults);
@@ -74,16 +75,16 @@ public class ConfirmedFinishPositioningListFinderTest extends
RaceLogFinishPositioningConfirmedEvent event2 = createEvent(/* priority */ 1, RaceLogFinishPositioningConfirmedEvent.class, 2);
RaceLogFinishPositioningConfirmedEvent event3 = createEvent(/* priority */ 2, RaceLogFinishPositioningConfirmedEvent.class, 3);
RaceLogFinishPositioningConfirmedEvent event4 = createEvent(/* priority */ 1, RaceLogFinishPositioningConfirmedEvent.class, 4);
final CompetitorResultImpl e1ResultC1 = new CompetitorResultImpl("Comp1", "Comp 1", 1, MaxPointsReason.DNC, 6.0, /* finishing time */ null, /* comment */ null);
final CompetitorResultImpl e1ResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.NONE, 2.0, /* finishing time */ null, /* comment */ null);
final CompetitorResultImpl e1ResultC1 = new CompetitorResultImpl("Comp1", "Comp 1", 1, MaxPointsReason.DNC, 6.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResultImpl e1ResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.NONE, 2.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResults e1Results = mockCompetitorResults(e1ResultC1, e1ResultC2);
final CompetitorResultImpl e2ResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null);
final CompetitorResultImpl e2ResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResults e2Results = mockCompetitorResults(e2ResultC2);
final CompetitorResultImpl e3ResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.DSQ, 6.0, /* finishing time */ null, /* comment */ null);
final CompetitorResultImpl e3ResultC3 = new CompetitorResultImpl("Comp3", "Comp 3", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null);
final CompetitorResultImpl e3ResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.DSQ, 6.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResultImpl e3ResultC3 = new CompetitorResultImpl("Comp3", "Comp 3", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResults e3Results = mockCompetitorResults(e3ResultC2, e3ResultC3);
final CompetitorResultImpl e4ResultC1 = new CompetitorResultImpl("Comp1", "Comp 1", 1, /* maxPointsReason */ null, /* score */ null, /* finishing time */ null, /* comment */ null);
final CompetitorResultImpl e4ResultC4 = new CompetitorResultImpl("Comp4", "Comp 4", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null);
final CompetitorResultImpl e4ResultC1 = new CompetitorResultImpl("Comp1", "Comp 1", 1, /* maxPointsReason */ null, /* score */ null, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResultImpl e4ResultC4 = new CompetitorResultImpl("Comp4", "Comp 4", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null, MergeState.OK);
final CompetitorResults e4Results = mockCompetitorResults(e4ResultC1, e4ResultC4);
when(event1.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(e1Results);
when(event2.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(e2Results);
@@ -14,6 +14,30 @@ import com.sap.sse.common.TimePoint;
*
*/
public interface CompetitorResult extends Serializable {
public static enum MergeState {
/**
* No merge was necessary at competitor level.
*/
OK,
/**
* A field-wise merge was necessary because an update was received that affected this competitor result
* while locally this competitor result was in a "dirty" (modified) state. The merge was carried out
* automatically because the fields changed in the dirty state did not overlap with the fields changed
* in the version of the object received.
*/
WARNING,
/**
* A field-wise merge was necessary because an update was received that affected this competitor result while
* locally this competitor result was in a "dirty" (modified) state. The fields changed in the dirty state
* overlap with the fields changed in the version of the object received. Field values from the local dirty state
* have been preferred or have been appended in case of {@link String}-typed fields and need manual
* intervention.
*/
ERROR
};
Serializable getCompetitorId();
String getCompetitorDisplayName();
@@ -27,4 +51,9 @@ public interface CompetitorResult extends Serializable {
TimePoint getFinishingTime();
String getComment();
/**
* Always returns a valid, non-{@code null} result. See {@link MergeState} for details.
*/
MergeState getMergeState();
}
@@ -23,8 +23,11 @@ public class CompetitorResultImpl implements CompetitorResult {
private final String comment;
private final MergeState mergeState;
public CompetitorResultImpl(Serializable competitorId, String competitorDisplayName, int oneBasedRank,
MaxPointsReason maxPointsReason, Double score, TimePoint finishingTime, String comment) {
MaxPointsReason maxPointsReason, Double score, TimePoint finishingTime, String comment,
MergeState mergeState) {
super();
this.competitorId = competitorId;
this.competitorDisplayName = competitorDisplayName;
@@ -33,6 +36,7 @@ public class CompetitorResultImpl implements CompetitorResult {
this.score = score;
this.finishingTime = finishingTime;
this.comment = comment;
this.mergeState = mergeState;
}
@Override
@@ -70,6 +74,11 @@ public class CompetitorResultImpl implements CompetitorResult {
return comment;
}
@Override
public MergeState getMergeState() {
return mergeState == null ? MergeState.OK : mergeState; // default in case of having de-serialized an old version
}
@Override
public int hashCode() {
final int prime = 31;
@@ -81,6 +90,7 @@ public class CompetitorResultImpl implements CompetitorResult {
result = prime * result + ((maxPointsReason == null) ? 0 : maxPointsReason.hashCode());
result = prime * result + oneBasedRank;
result = prime * result + ((score == null) ? 0 : score.hashCode());
result = prime * result + ((mergeState == null) ? 0 : mergeState.hashCode());
return result;
}
@@ -122,6 +132,11 @@ public class CompetitorResultImpl implements CompetitorResult {
return false;
} else if (!score.equals(other.score))
return false;
if (mergeState == null) {
if (other.mergeState != null)
return false;
} else if (!mergeState.equals(other.mergeState))
return false;
return true;
}
@@ -129,7 +144,7 @@ public class CompetitorResultImpl implements CompetitorResult {
public String toString() {
return "CompetitorResultImpl [competitorId=" + competitorId + ", competitorName=" + competitorDisplayName + ", rank="
+ oneBasedRank + ", maxPointsReason=" + maxPointsReason + ", score=" + score + ", finishingTime="
+ finishingTime + ", comment=" + comment + "]";
+ finishingTime + ", comment=" + comment + ", mergeState=" + mergeState + "]";
}
}
@@ -15,6 +15,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor;
import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
import com.sap.sailing.domain.tracking.impl.TimeRangeCache;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Function;
@@ -263,4 +264,11 @@ public class NoAddingRaceLogWrapper implements RaceLog {
public int size() {
return innerRaceLog.size();
}
@Override
public <T> T getValueSum(TimePoint from, TimePoint to, T nullElement,
com.sap.sailing.domain.tracking.Track.Adder<T> adder, TimeRangeCache<T> cache,
com.sap.sailing.domain.tracking.Track.TimeRangeValueCalculator<T> valueCalculator) {
return innerRaceLog.getValueSum(from, to, nullElement, adder, cache, valueCalculator);
}
}
@@ -158,6 +158,7 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState {
public void forceNewDependentStartTime(TimePoint now, final Duration startTimeDifference, final SimpleRaceLogIdentifier dependentOnRace) {
raceLog.add(new RaceLogDependentStartTimeEventImpl(now, author, raceLog.getCurrentPassId(), dependentOnRace, startTimeDifference));
}
@Override
public void setFinishingTime(TimePoint timePoint) {
@@ -166,7 +167,10 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState {
@Override
public void setFinishedTime(TimePoint timePoint) {
raceLog.add(new RaceLogRaceStatusEventImpl(timePoint, author, raceLog.getCurrentPassId(), RaceLogRaceStatus.FINISHED));
raceLog.add(new RaceLogRaceStatusEventImpl(timePoint, author, raceLog.getCurrentPassId(),
RaceLogRaceStatus.FINISHED));
// ensure caches are synched
forceUpdate();
}
@Override
@@ -5,6 +5,7 @@ import java.util.Iterator;
import java.util.concurrent.locks.ReadWriteLock;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.impl.TimeRangeCache;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Timed;
@@ -31,6 +32,29 @@ import com.sap.sse.common.scalablevalue.ScalableValue;
* @author Axel Uhl (d043530)
*/
public interface Track<FixType extends Timed> extends Serializable {
/**
* An adding function to be used together with {@link Track#getValueSum(TimePoint, TimePoint, Object, Adder, TimeRangeCache, TimeRangeValueCalculator)}.
*
* @author Axel Uhl (D043530)
*
* @param <T>
*/
static interface Adder<T> {
/**
* Adds two elements of type {@code T}. Neither argument must be {@code null}.
*/
T add(T t1, T t2);
}
static interface TimeRangeValueCalculator<T> {
/**
* Calculates a value for fixes across a time range. When the method is called,
* a read lock will previously have been {@link Track#lockForRead obtained} before,
* so an implementing class does not need to worry about acquiring the lock.
*/
T calculate(TimePoint from, TimePoint to);
}
/**
* Locks this track for reading by the calling thread. If the thread already holds the lock for this track,
* the hold count will be incremented. Make sure to call {@link #unlockAfterRead()} in a <code>finally</code>
@@ -209,6 +233,8 @@ public interface Track<FixType extends Timed> extends Serializable {
*/
Duration getAverageIntervalBetweenRawFixes();
<T> T getValueSum(TimePoint from, TimePoint to, T nullElement, Adder<T> adder, TimeRangeCache<T> cache, TimeRangeValueCalculator<T> valueCalculator);
/**
* @return the number of raw fixes contained in the Track.
*/
@@ -218,4 +244,5 @@ public interface Track<FixType extends Timed> extends Serializable {
* Tells whether the collection of {@link #getRawFixes() raw fixes} (no outliers removed) is empty
*/
boolean isEmpty();
}
@@ -0,0 +1,221 @@
package com.sap.sailing.domain.tracking.impl;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.NavigableSet;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
import com.sap.sse.util.impl.ArrayListNavigableSet;
/**
* This cache looks "backwards." It contains pairs whose first component represents a <code>to</code> parameter used in
* a calculation for a time range. It is ordered by this component. The second component is a navigable, ordered set of
* pairs where the first pair component represents a <code>from</code> parameter used in the calculation's time range
* and the second pair component represents the result of the calculation for this parameter combination.
* <p>
*
* For implementation efficiency in combination with using an {@link ArrayListNavigableSet} for the values and in order
* to be able to efficiently extend a cache entry for a single <code>to</code> fix, the navigable sets containing the
* <code>from</code> fixes and results are ordered such that earlier fixes come later in the set. This way, extending
* the cache entry for a <code>to</code> fix to an earlier <code>from</code> fix only requires appending to the set.
* <p>
*
* <b>Invalidation</b>: When a new fix is added to the track, all cache entries for fixes at or later than the new fix's
* time point are removed from this cache. Additionally, the fix insertion may have an impact on the
* {@link #getEarliestFromAndResultAtOrAfterFrom(TimePoint, TimePoint) previous fix's} validity (track smoothing) and
* therefore on its selection for result aggregation. Therefore, if fix addition turned the previous fix invalid, the
* cache entries for the time points at or after the previous fix also need to be removed.
* <p>
*
* <b>Cache use</b>: When a result across a time range is to be computed the calculating method should first look for a
* cache entry for the <code>to</code> parameter. If one is found, the earliest entry in the navigable set for the
* navigable set of <code>from</code> and result values that is at or after the requested <code>from</code> time point
* is determined. If such an entry exists, the result is remembered and the algorithm is repeated recursively, using the
* <code>from</code> value found in the cache as the new <code>to</code> value, and the <code>from</code> value
* originally passed to the calculating method as <code>from</code> again. If no entry is found in the cache entry for
* <code>to</code> that is at or after the requested <code>from</code> time, the result has to be computed "from
* scratch."
* <p>
*
* If a cache entry for <code>to</code> is not found, the latest cache entry before it is looked up. If one is found,
* the result for the time range between the <code>to</code> time point requested and the <code>to</code> time point
* found in the cache is computed by iterating the smoothened fixes for this interval. If none is found, the result is
* computed by iterating backwards all the way to <code>from</code>.
* <p>
*
* Once the calculating method has computed its value, it should {@link #cache(TimePoint, TimePoint, Object) add} the
* result to the cache.
*
* @author Axel Uhl (D043530)
*/
public class TimeRangeCache<T> {
public static final int MAX_SIZE = 100;
private final NavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>> timeRangeCache;
/**
* The cache is to have limited size. Eviction shall happen based on a least-recently-used strategy. Usage is
* defined as having been returned by {@link #getEarliestFromAndResultAtOrAfterFrom(TimePoint, TimePoint)} or
* having been added by {@link #cache(TimePoint, TimePoint, Object)}.
* <p>
*
* When an eldest entry is asked to be expunged from this map and the map has more than {@link #MAX_SIZE} elements,
* the expunging will be admitted, and the entry is removed from the {@link #timeRangeCache} core structure. Reading
* and writing this structure must happen under the {@link #lock write lock} because also reading the linked hash
* map that counts access as "use" has a modifying effect on its internal structures.
* <p>
*
* The key pairs are from/to pairs. Note that this is in some sense "the opposite direction" compared to the
* alignment of the {@link #timeRangeCache} structure which has as its outer keys the "to" time point.<p>
*
* Read access is to be <code>synchronized<code> using this field's mutex; write access only happens under the
* {@link #lock write lock} and therefore will have no contenders.
*/
private final LinkedHashMap<Util.Pair<TimePoint, TimePoint>, Void> lruCache;
private final NamedReentrantReadWriteLock lock;
private static final Comparator<Util.Pair<TimePoint, ?>> timePointInPairComparator = new Comparator<Util.Pair<TimePoint, ?>>() {
@Override
public int compare(Util.Pair<TimePoint, ?> o1, Util.Pair<TimePoint, ?> o2) {
return o1.getA().compareTo(o2.getA());
}
};
public TimeRangeCache(String nameForLockLogging) {
lock = new NamedReentrantReadWriteLock("lock for TimeRangeCache for "+nameForLockLogging, /* fair */ true);
this.timeRangeCache = new ArrayListNavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>>(timePointInPairComparator);
this.lruCache = new LinkedHashMap<Util.Pair<TimePoint, TimePoint>, Void>(/* initial capacity */ 10, /* load factor */ 0.75f,
/* access-based ordering */ true) {
private static final long serialVersionUID = -6568235517111733193L;
@Override
protected boolean removeEldestEntry(Entry<Pair<TimePoint, TimePoint>, Void> eldest) {
final boolean expunge = size() > MAX_SIZE;
if (expunge) {
removeCacheEntry(eldest.getKey().getA(), eldest.getKey().getB());
}
return expunge;
}
};
}
public int size() {
return lruCache.size();
}
private void removeCacheEntry(TimePoint from, TimePoint to) {
assert lock.getWriteHoldCount() == 1; // we can be sure we are alone here; this only happens when adding a new entry, holding the write lock
Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryForTo = timeRangeCache.floor(createDummy(to));
if (entryForTo.getA().equals(to)) {
Pair<TimePoint, T> entryForFrom = entryForTo.getB().ceiling(new Util.Pair<TimePoint, T>(from, null));
if (entryForFrom.getA().equals(from)) {
entryForTo.getB().remove(entryForFrom);
if (entryForTo.getB().isEmpty()) {
timeRangeCache.remove(entryForTo);
}
}
}
}
/**
* Looks up the entry closest to but no later than <code>to</code>. If not found, <code>null</code> is returned. If
* found, the earliest pair of from/result that is at or after <code>from</code> will be returned, together with
* the <code>to</code> value of the entry. If there is no entry that is at or after <code>from</code>,
* <code>null</code> is returned.
*/
public Util.Pair<TimePoint, Util.Pair<TimePoint, T>> getEarliestFromAndResultAtOrAfterFrom(TimePoint from, TimePoint to) {
LockUtil.lockForRead(lock);
try {
Util.Pair<TimePoint, Util.Pair<TimePoint, T>> result = null;
Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryForTo = timeRangeCache.floor(createDummy(to));
if (entryForTo != null) {
final Util.Pair<TimePoint, T> fromCeiling = entryForTo.getB().ceiling(new Util.Pair<TimePoint, T>(from, null));
if (fromCeiling != null) {
result = new Util.Pair<TimePoint, Util.Pair<TimePoint, T>>(entryForTo.getA(), fromCeiling);
}
}
// 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) {
lruCache.get(new Util.Pair<TimePoint, TimePoint>(from, to));
}
return result;
} finally {
LockUtil.unlockAfterRead(lock);
}
}
/**
* Removes all cache entries that have a <code>to</code> time point that is at or after <code>timePoint</code>.
*/
public void invalidateAllAtOrLaterThan(TimePoint timePoint) {
LockUtil.lockForWrite(lock);
try {
Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> dummy = createDummy(timePoint);
NavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>> toRemove = timeRangeCache.tailSet(dummy, /* inclusive */ true);
for (Iterator<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>> i=toRemove.iterator(); i.hasNext(); ) {
Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryToRemove = i.next();
assert entryToRemove.getA().compareTo(timePoint) >= 0;
for (Pair<TimePoint, T> fromAndResult : entryToRemove.getB()) {
lruCache.remove(new Util.Pair<>(fromAndResult.getA(), entryToRemove.getA()));
}
i.remove();
}
} finally {
LockUtil.unlockAfterWrite(lock);
}
}
private NavigableSet<Util.Pair<TimePoint, T>> getEntryForTo(TimePoint to) {
NavigableSet<Util.Pair<TimePoint, T>> result = null;
Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> dummyForTo = createDummy(to);
Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> entryForTo = timeRangeCache.floor(dummyForTo);
if (entryForTo != null && entryForTo.getA().equals(to)) {
result = entryForTo.getB();
}
return result;
}
public void cache(TimePoint from, TimePoint to, T result) {
LockUtil.lockForWrite(lock);
try {
NavigableSet<Util.Pair<TimePoint, T>> entryForTo = getEntryForTo(to);
if (entryForTo == null) {
entryForTo = new ArrayListNavigableSet<Util.Pair<TimePoint, T>>(timePointInPairComparator);
Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> pairForTo = new Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>(
to, entryForTo);
timeRangeCache.add(pairForTo);
}
entryForTo.add(new Util.Pair<TimePoint, T>(from, result));
lruCache.put(new Util.Pair<TimePoint, TimePoint>(from, to), null);
} finally {
LockUtil.unlockAfterWrite(lock);
}
}
private Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>> createDummy(TimePoint to) {
return new Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, T>>>(to, null);
}
/**
* Removes all contents from this cache
*/
public void clear() {
LockUtil.lockForWrite(lock);
try {
timeRangeCache.clear();
lruCache.clear();
} finally {
LockUtil.unlockAfterWrite(lock);
}
}
}
@@ -11,6 +11,7 @@ import com.sap.sailing.domain.tracking.Track;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Timed;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Function;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.scalablevalue.ScalableValue;
@@ -459,6 +460,64 @@ public class TrackImpl<FixType extends Timed> implements Track<FixType> {
}
}
@Override
public <T> T getValueSum(TimePoint from, TimePoint to, T nullElement, Adder<T> adder, TimeRangeCache<T> cache, TimeRangeValueCalculator<T> valueCalculator) {
return getValueSumRecursively(from, to, /* recursionLevel */ 0, nullElement, adder, cache, valueCalculator);
}
private <T> T getValueSumRecursively(TimePoint from, TimePoint to, int recursionDepth, T nullElement,
Adder<T> adder, TimeRangeCache<T> cache, TimeRangeValueCalculator<T> valueCalculator) {
T result;
if (!from.before(to)) {
result = nullElement;
} else {
boolean perfectCacheHit = false;
lockForRead();
try {
Util.Pair<TimePoint, Util.Pair<TimePoint, T>> bestCacheEntry = cache.getEarliestFromAndResultAtOrAfterFrom(from, to);
if (bestCacheEntry != null) {
perfectCacheHit = true; // potentially a cache hit; but if it doesn't span the full interval, it's not perfect; see below
// compute the missing stretches between best cache entry's "from" and our "from" and the cache
// entry's "to" and our "to"
T valueFromFromToBeginningOfCacheEntry = nullElement;
T valueFromEndOfCacheEntryToTo = nullElement;
if (!bestCacheEntry.getB().getA().equals(from)) {
assert bestCacheEntry.getB().getA().after(from);
perfectCacheHit = false;
valueFromFromToBeginningOfCacheEntry = getValueSumRecursively(from, bestCacheEntry
.getB().getA(), recursionDepth + 1, nullElement, adder, cache, valueCalculator);
}
if (!bestCacheEntry.getA().equals(to)) {
assert bestCacheEntry.getA().before(to);
perfectCacheHit = false;
valueFromEndOfCacheEntryToTo = getValueSumRecursively(bestCacheEntry.getA(), to,
recursionDepth + 1, nullElement, adder, cache, valueCalculator);
}
if (valueFromEndOfCacheEntryToTo == null || bestCacheEntry.getB().getB() == null) {
result = null;
} else {
result = adder.add(adder.add(valueFromFromToBeginningOfCacheEntry, bestCacheEntry.getB().getB()),
valueFromEndOfCacheEntryToTo);
}
} else {
if (from.compareTo(to) < 0) {
result = valueCalculator.calculate(from, to);
} else {
result = nullElement;
}
}
} finally {
unlockAfterRead();
}
if (!perfectCacheHit && recursionDepth == 0) {
cache.cache(from, to, result);
}
}
return result;
}
@Override
public int size() {
return fixes.size();
@@ -46,6 +46,16 @@ public class BearingTest {
assertNull(new BearingWithConfidenceCluster<Void>(null).getAverage(null));
}
@Test
public void testNegativeAngle() {
assertEquals(-10., new DegreeBearingImpl(-10.).getDegrees(), 0.000001);
}
@Test
public void testNegativeAngleOverflow() {
assertEquals(-10., new DegreeBearingImpl(-370.).getDegrees(), 0.000001);
}
@Test
public void testZeroConfidenceLeadsToNullBearingAverage() {
BearingWithConfidenceCluster<Void> cluster = new BearingWithConfidenceCluster<Void>(null);
@@ -207,27 +207,29 @@ public class CompetitorProviderCacheInvalidationTest extends AbstractLeaderboard
for (Competitor c : compLists[1]) {
raceLog.add(new RaceLogRegisterCompetitorEventImpl(MillisecondsTimePoint.now(), new LogEventAuthorImpl("Me", 0), 1, c));
}
Set<Competitor> allCompetitors = new HashSet<>();
allCompetitors.addAll(compLists[0]);
allCompetitors.addAll(compLists[1]);
// expected are only the competitors in the RaceLog, because only one RaceColumn is
// registered, which has the competitors registered in the RaceLog.
assertRegattaAndRaceCompetitors(new HashSet<>(compLists[1]));
assertRegattaAndRaceCompetitors(allCompetitors, new HashSet<>(compLists[1]));
// Now we revoke that the race log provides the competitors for R1; the competitors should then
// snap back to those competitors taken from the regatta log for both, the rentire leaderboard
// as well as for the race column
raceLog.revokeEvent(author, usesCompetitorsFromRaceLogEvent);
assertRegattaAndRaceCompetitors(new HashSet<>(compLists[0]));
assertRegattaAndRaceCompetitors(new HashSet<>(compLists[0]), new HashSet<>(compLists[0]));
// And now re-introduce per-race competitors and validate again that the cache adjusts properly:
raceLog.add(new RaceLogUseCompetitorsFromRaceLogEventImpl(MillisecondsTimePoint.now(), author, MillisecondsTimePoint.now(), UUID.randomUUID(), passId));
assertRegattaAndRaceCompetitors(new HashSet<>(compLists[1]));
assertRegattaAndRaceCompetitors(allCompetitors, new HashSet<>(compLists[1]));
}
private void assertRegattaAndRaceCompetitors(Set<Competitor> expected) {
private void assertRegattaAndRaceCompetitors(Set<Competitor> expectedAllCompetitors, Set<Competitor> expectedForRace) {
Set<Competitor> actual = new HashSet<>();
Util.addAll(competitorProviderFlexibleLeaderboard.getAllCompetitors(), actual);
assertEquals(expected, actual);
assertEquals(expectedAllCompetitors, actual);
Set<Competitor> actualForRace = new HashSet<>();
//For the race only the competitors registered on RaceLog as RaceLogUseCompetitorsFromRaceLogEvent is present
Set<Competitor> expectedForRace = new HashSet<>(expected);
// For the race only the competitors registered on RaceLog as RaceLogUseCompetitorsFromRaceLogEvent is present
Util.addAll(competitorProviderFlexibleLeaderboard.getAllCompetitors(
flexibleLeaderboard.getRaceColumnByName("R1"),
flexibleLeaderboard.getFleet(LeaderboardNameConstants.DEFAULT_FLEET_NAME)), actualForRace);
@@ -24,6 +24,7 @@ import org.junit.Test;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.race.CompetitorResult;
import com.sap.sailing.domain.abstractlog.race.CompetitorResult.MergeState;
import com.sap.sailing.domain.abstractlog.race.CompetitorResults;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningConfirmedEvent;
@@ -153,7 +154,7 @@ public class MarkPassingUpdateFromRaceLogFinishPositioingListTest extends Abstra
int rank = 1;
for (Pair<Competitor, TimePoint> finishingTime : finishingTimes) {
results.add(new CompetitorResultImpl(finishingTime.getA().getId(), finishingTime.getA().getName(), rank++, /* maxPointsReason */
null, /* score */null, finishingTime.getB(), /* comment */null));
null, /* score */null, finishingTime.getB(), /* comment */null, MergeState.OK));
}
return results;
}
@@ -51,7 +51,7 @@ import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.common.tracking.impl.VeryCompactGPSFixMovingImpl;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.impl.DistanceCache;
import com.sap.sailing.domain.tracking.impl.TimeRangeCache;
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl;
import com.sap.sailing.domain.tracking.impl.GPSFixTrackImpl;
@@ -304,7 +304,7 @@ public class TrackTest {
// The following getMaximumSpeedOverGround call will trigger a computeMaxSpeed(...) and a cache(...) call
new Thread(()->
assertEquals(1., track.getMaximumSpeedOverGround(new MillisecondsTimePoint(0), new MillisecondsTimePoint(7200000)).
getB().getKnots(), 0.001)).start(); // produces a cache entry that ends
getB().getKnots(), 0.01)).start(); // produces a cache entry that ends
// now don't release the cacheBarrier as yet but add more fixes
new Thread(() -> {
GPSFixMoving fix2 = new GPSFixMovingImpl(new DegreePosition(0, 0), new MillisecondsTimePoint(3600000),
@@ -442,7 +442,7 @@ public class TrackTest {
@Test
public void testDistanceTraveledBackwardsQuery() {
final Set<com.sap.sse.common.Util.Triple<TimePoint, TimePoint, Distance>> cacheEntries = new HashSet<>();
final DistanceCache distanceCache = new DistanceCache("test-DistanceCache") {
final TimeRangeCache<Distance> distanceCache = new TimeRangeCache<Distance>("test-DistanceCache") {
@Override
public void cache(TimePoint from, TimePoint to, Distance distance) {
super.cache(from, to, distance);
@@ -453,7 +453,7 @@ public class TrackTest {
DynamicGPSFixTrack<Object, GPSFix> track = new DynamicGPSFixTrackImpl<Object>(new Object(), /* millisecondsOverWhichToAverage */ 30000l) {
private static final long serialVersionUID = -7277196393160609503L;
@Override
protected DistanceCache getDistanceCache() {
protected TimeRangeCache<Distance> getDistanceCache() {
return distanceCache;
}
};
@@ -779,7 +779,7 @@ public class TrackTest {
@Test
public void testDistanceTraveledOnSmoothenedTrackThenAddingOutlier() {
final Set<TimePoint> invalidationCalls = new HashSet<TimePoint>();
final DistanceCache distanceCache = new DistanceCache("test-DistanceCache") {
final TimeRangeCache<Distance> distanceCache = new TimeRangeCache<Distance>("test-DistanceCache") {
@Override
public void invalidateAllAtOrLaterThan(TimePoint timePoint) {
super.invalidateAllAtOrLaterThan(timePoint);
@@ -789,7 +789,7 @@ public class TrackTest {
DynamicGPSFixTrack<Object, GPSFix> track = new DynamicGPSFixTrackImpl<Object>(new Object(), /* millisecondsOverWhichToAverage */ 30000l) {
private static final long serialVersionUID = -7277196393160609503L;
@Override
protected DistanceCache getDistanceCache() {
protected TimeRangeCache<Distance> getDistanceCache() {
return distanceCache;
}
};
@@ -811,7 +811,7 @@ public class TrackTest {
}
invalidationCalls.clear();
assertEquals(speed.getMetersPerSecond()*(steps-1), track.getDistanceTraveled(now, start).getMeters(), 0.02);
final com.sap.sse.common.Util.Pair<TimePoint, com.sap.sse.common.Util.Pair<TimePoint, Distance>> fullIntervalCacheEntry = distanceCache.getEarliestFromAndDistanceAtOrAfterFrom(now, start);
final com.sap.sse.common.Util.Pair<TimePoint, com.sap.sse.common.Util.Pair<TimePoint, Distance>> fullIntervalCacheEntry = distanceCache.getEarliestFromAndResultAtOrAfterFrom(now, start);
assertNotNull(fullIntervalCacheEntry); // no more entry for "to"-value start in cache
assertEquals(start, fullIntervalCacheEntry.getA());
assertEquals(now, fullIntervalCacheEntry.getB().getA());
@@ -822,13 +822,13 @@ public class TrackTest {
assertEquals(1, invalidationCalls.size());
TimePoint timePointOfLastFixBeforeOutlier = track.getLastFixBefore(timePointForOutlier).getTimePoint();
assertTrue(invalidationCalls.iterator().next().after(timePointOfLastFixBeforeOutlier)); // outlier doesn't turn its preceding element into an outlier
assertNull(distanceCache.getEarliestFromAndDistanceAtOrAfterFrom(now, start)); // no more entry for "to"-value start in cache
assertNull(distanceCache.getEarliestFromAndResultAtOrAfterFrom(now, start)); // no more entry for "to"-value start in cache
invalidationCalls.clear();
final TimePoint timePointOfLastOriginalFix = track.getLastRawFix().getTimePoint();
assertEquals(speed.getMetersPerSecond() * (steps - 1),
track.getDistanceTraveled(now, timePointOfLastOriginalFix).getMeters(), 0.02);
final com.sap.sse.common.Util.Pair<TimePoint, com.sap.sse.common.Util.Pair<TimePoint, Distance>> newFullIntervalCacheEntry = distanceCache
.getEarliestFromAndDistanceAtOrAfterFrom(now, timePointOfLastOriginalFix);
.getEarliestFromAndResultAtOrAfterFrom(now, timePointOfLastOriginalFix);
assertNotNull(newFullIntervalCacheEntry); // no more entry for "to"-value start in cache
assertEquals(timePointOfLastOriginalFix, newFullIntervalCacheEntry.getA());
assertEquals(now, newFullIntervalCacheEntry.getB().getA());
@@ -845,7 +845,7 @@ public class TrackTest {
invalidationCalls.clear();
// expect the invalidation to have started after the single cache entry, so the cache entry still has to be there:
final com.sap.sse.common.Util.Pair<TimePoint, com.sap.sse.common.Util.Pair<TimePoint, Distance>> stillPresentFullIntervalCacheEntry = distanceCache
.getEarliestFromAndDistanceAtOrAfterFrom(now, timePointOfLastOriginalFix);
.getEarliestFromAndResultAtOrAfterFrom(now, timePointOfLastOriginalFix);
assertNull(stillPresentFullIntervalCacheEntry); // no more entry for "to"-value start in cache because new temporary outlier lies in previously cached interval
GPSFix polishedLastFix = track.getLastFixBefore(new MillisecondsTimePoint(Long.MAX_VALUE)); // get the last smoothened fix...
// ...which now is expected to be two fixes before lateOutlier because lateOutlier has previous fixes within the time range
@@ -870,7 +870,7 @@ public class TrackTest {
assertTrue(timePointForLateOutlier.compareTo(fix.getTimePoint()) < 0);
// expect the invalidation to have started at the fix before the outlier, leaving the previous result ending at the fix right before the outlier intact
final com.sap.sse.common.Util.Pair<TimePoint, com.sap.sse.common.Util.Pair<TimePoint, Distance>> stillStillPresentFullIntervalCacheEntry = distanceCache
.getEarliestFromAndDistanceAtOrAfterFrom(now, timePointOfLastOriginalFix);
.getEarliestFromAndResultAtOrAfterFrom(now, timePointOfLastOriginalFix);
assertNull(stillStillPresentFullIntervalCacheEntry); // still nothing in the cache
track.lockForRead();
try {
@@ -955,11 +955,11 @@ public class TrackTest {
final Duration raceDuration = Duration.ONE_HOUR.times(48);
final Speed speed = new KnotSpeedImpl(12);
final Distance distancePerSample = speed.travel(fixTime, fixTime.plus(samplingInterval));
final DistanceCache distanceCache = new DistanceCache("test-DistanceCache");
final TimeRangeCache<Distance> distanceCache = new TimeRangeCache<>("test-DistanceCache");
DynamicGPSFixTrack<Object, GPSFix> track = new DynamicGPSFixTrackImpl<Object>(new Object(), /* millisecondsOverWhichToAverage */ 30000l) {
private static final long serialVersionUID = -7277196393160609503L;
@Override
protected DistanceCache getDistanceCache() {
protected TimeRangeCache<Distance> getDistanceCache() {
return distanceCache;
}
};
@@ -980,13 +980,13 @@ public class TrackTest {
queryTime = queryTime.plus(queryInterval);
}
}
assertTrue(distanceCache.size() <= DistanceCache.MAX_SIZE);
assertTrue(distanceCache.size() <= TimeRangeCache.MAX_SIZE);
}
@Test
public void testDistanceCacheAccessForPartialStrip() {
final Set<TimePoint> invalidationCalls = new HashSet<TimePoint>();
final DistanceCache distanceCache = new DistanceCache("test-DistanceCache") {
final TimeRangeCache<Distance> distanceCache = new TimeRangeCache<Distance>("test-DistanceCache") {
@Override
public void invalidateAllAtOrLaterThan(TimePoint timePoint) {
super.invalidateAllAtOrLaterThan(timePoint);
@@ -996,7 +996,7 @@ public class TrackTest {
DynamicGPSFixTrack<Object, GPSFix> track = new DynamicGPSFixTrackImpl<Object>(new Object(), /* millisecondsOverWhichToAverage */ 30000l) {
private static final long serialVersionUID = -7277196393160609503L;
@Override
protected DistanceCache getDistanceCache() {
protected TimeRangeCache<Distance> getDistanceCache() {
return distanceCache;
}
};
@@ -1024,11 +1024,11 @@ public class TrackTest {
assertEquals(speed.getMetersPerSecond()*(steps-1), track.getDistanceTraveled(now, start).getMeters(), 0.02);
assertTrue(invalidationCalls.isEmpty());
// expect a cache entry exactly for the strip's boundaries
com.sap.sse.common.Util.Pair<TimePoint, com.sap.sse.common.Util.Pair<TimePoint, Distance>> stripCacheEntry = distanceCache.getEarliestFromAndDistanceAtOrAfterFrom(stripFrom, stripTo);
com.sap.sse.common.Util.Pair<TimePoint, com.sap.sse.common.Util.Pair<TimePoint, Distance>> stripCacheEntry = distanceCache.getEarliestFromAndResultAtOrAfterFrom(stripFrom, stripTo);
assertEquals(stripTo, stripCacheEntry.getA());
assertEquals(stripFrom, stripCacheEntry.getB().getA());
// expect a cache entry exactly for the full boundaries
com.sap.sse.common.Util.Pair<TimePoint, com.sap.sse.common.Util.Pair<TimePoint, Distance>> fullCacheEntry = distanceCache.getEarliestFromAndDistanceAtOrAfterFrom(now, start);
com.sap.sse.common.Util.Pair<TimePoint, com.sap.sse.common.Util.Pair<TimePoint, Distance>> fullCacheEntry = distanceCache.getEarliestFromAndResultAtOrAfterFrom(now, start);
assertEquals(start, fullCacheEntry.getA());
assertEquals(now, fullCacheEntry.getB().getA());
}
@@ -674,6 +674,16 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public Distance getDistanceFoiled(Competitor competitor, TimePoint timePoint) {
return null;
}
@Override
public Duration getDurationFoiled(Competitor competitor, TimePoint timePoint) {
return null;
}
@Override
public Distance getWindwardDistanceToCompetitorFarthestAhead(Competitor competitor, TimePoint timePoint,
WindPositionMode windPositionMode) {
@@ -1155,4 +1165,10 @@ public class MockedTrackedRace implements DynamicTrackedRace {
public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) {
return null;
}
@Override
public SpeedWithBearing getVelocityMadeGood(Competitor competitor, TimePoint timePoint,
WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) {
return null;
}
}
@@ -60,6 +60,7 @@ import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
import com.sap.sse.common.Duration;
import com.sap.sse.common.IsManagedByCache;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
@@ -360,6 +361,16 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
return null;
}
@Override
public Distance getDistanceFoiled(Competitor competitor, TimePoint timePoint) {
return null;
}
@Override
public Duration getDurationFoiled(Competitor competitor, TimePoint timePoint) {
return null;
}
@Override
public Distance getWindwardDistanceToCompetitorFarthestAhead(Competitor competitor, TimePoint timePoint, WindPositionMode windPositionMode) {
return null;
@@ -707,4 +718,10 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) {
return null;
}
@Override
public SpeedWithBearing getVelocityMadeGood(Competitor competitor, TimePoint timePoint,
WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) {
return null;
}
}
@@ -269,7 +269,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns {
* competitor doesn't appear in any of the race column's attached tracked races. A 0.0 score is returned
* if the competitor's result for <code>race</code> is discarded.
*/
Double getNetPoints(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException;
Double getNetPoints(Competitor competitor, RaceColumn race, TimePoint timePoint);
/**
* Tells whether the contribution of <code>raceColumn</code> is discarded in the current leaderboard's standings for
@@ -481,6 +481,26 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns {
* leaderboard
*/
Distance getTotalDistanceTraveled(Competitor competitor, TimePoint timePoint);
/**
* Computes the distance the <code>competitor</code> has been foiling in the tracked races in this leaderboard, starting
* to count in each race when the competitor passes the start line, aggregating up to <code>timePoint</code> or the
* end of the last race, whichever is first.
*
* @return <code>null</code> if the <code>competitor</code> hasn't foiled any distance in any tracked race in this
* leaderboard
*/
Distance getTotalDistanceFoiled(Competitor competitor, TimePoint timePoint);
/**
* Computes the duration the <code>competitor</code> has foiled in the tracked races in this leaderboard, starting
* to count in each race when the competitor passes the start line, aggregating up to <code>timePoint</code> or the
* end of the last race, whichever is first.
*
* @return <code>null</code> if the <code>competitor</code> hasn't foiled at any time in any tracked race in this
* leaderboard
*/
Duration getTotalDurationFoiled(Competitor competitor, TimePoint timePoint);
/**
* Same as {@link #getNetPoints(Competitor, RaceColumn, TimePoint)}, only that for determining the discarded
@@ -568,4 +588,10 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns {
BoatClass getBoatClass();
LeaderboardType getLeaderboardType();
/**
* Tells if there is at least one non-{@code null} score for the given {@code competitor} in the
* leaderboard.
*/
boolean hasScores(Competitor competitor, TimePoint timePoint);
}
@@ -19,6 +19,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RunnableFuture;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -642,8 +643,12 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
row.totalTimeSailedReachingInSeconds = totalTimeSailedReaching==null?null:totalTimeSailedReaching.asSeconds();
final Duration totalTimeSailed = this.getTotalTimeSailed(competitor, timePoint);
row.totalTimeSailedInSeconds = totalTimeSailed==null?null:totalTimeSailed.asSeconds();
final Distance totalDistanceTraveledInMeters = this.getTotalDistanceTraveled(competitor, timePoint);
row.totalDistanceTraveledInMeters = totalDistanceTraveledInMeters==null?null:totalDistanceTraveledInMeters.getMeters();
final Distance totalDistanceTraveled = this.getTotalDistanceTraveled(competitor, timePoint);
row.totalDistanceTraveledInMeters = totalDistanceTraveled==null?null:totalDistanceTraveled.getMeters();
final Distance totalDistanceFoiled = this.getTotalDistanceFoiled(competitor, timePoint);
row.totalDistanceFoiledInMeters = totalDistanceFoiled==null?null:totalDistanceFoiled.getMeters();
final Duration totalDurationFoiled = this.getTotalDurationFoiled(competitor, timePoint);
row.totalDurationFoiledInSeconds = totalDurationFoiled==null?null:totalDurationFoiled.asSeconds();
}
private LeaderboardDTOCache getLeaderboardDTOCache() {
@@ -821,6 +826,12 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
result.currentHeelInDegrees = heel == null ? null : heel.getDegrees();
Bearing pitch = trackedLeg.getPitch(timePoint);
result.currentPitchInDegrees = pitch == null ? null : pitch.getDegrees();
Distance distanceFoiled = trackedLeg.getDistanceFoiled(timePoint);
result.currentDistanceFoiledInMeters = distanceFoiled == null ? null : distanceFoiled.getMeters();
Duration durationFoiled = trackedLeg.getDurationFoiled(timePoint);
result.currentDurationFoiledInSeconds = durationFoiled == null ? null : durationFoiled.asSeconds();
result.currentSpeedOverGroundInKnots = speedOverGroundInKnots == null ? null : speedOverGroundInKnots;
Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint);
result.distanceTraveledInMeters = distanceTraveled == null ? null : distanceTraveled.getMeters();
@@ -858,7 +869,7 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
if (trackedLeg.hasFinishedLeg(timePoint)) {
velocityMadeGood = trackedLeg.getAverageVelocityMadeGood(timePoint);
} else {
velocityMadeGood = trackedLeg.getVelocityMadeGood(timePoint, WindPositionMode.EXACT);
velocityMadeGood = trackedLeg.getVelocityMadeGood(timePoint, WindPositionMode.EXACT, cache);
}
result.velocityMadeGoodInKnots = velocityMadeGood == null ? null : velocityMadeGood.getKnots();
Distance windwardDistanceToGo = trackedLeg.getWindwardDistanceToGo(timePoint, WindPositionMode.LEG_MIDDLE);
@@ -1082,18 +1093,37 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
@Override
public Distance getTotalDistanceTraveled(Competitor competitor, TimePoint timePoint) {
Distance result = null;
return getTotals(competitor, timePoint, Distance::add, TrackedRace::getDistanceTraveled);
}
@Override
public Distance getTotalDistanceFoiled(Competitor competitor, TimePoint timePoint) {
return getTotals(competitor, timePoint, Distance::add, TrackedRace::getDistanceFoiled);
}
@Override
public Duration getTotalDurationFoiled(Competitor competitor, TimePoint timePoint) {
return getTotals(competitor, timePoint, Duration::plus, TrackedRace::getDurationFoiled);
}
@FunctionalInterface
private static interface ValueFromRaceGetter<T> {
T get(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint);
}
private <T> T getTotals(Competitor competitor, TimePoint timePoint, BiFunction<T, T, T> adder, ValueFromRaceGetter<T> valueGetter) {
T result = null;
for (TrackedRace trackedRace : getTrackedRaces()) {
TimePoint startOfRace;
if (Util.contains(trackedRace.getRace().getCompetitors(), competitor) &&
(startOfRace=trackedRace.getStartOfRace()) != null &&
!startOfRace.after(timePoint)) {
Distance distanceSailedInRace = trackedRace.getDistanceTraveled(competitor, timePoint);
T distanceSailedInRace = valueGetter.get(trackedRace, competitor, timePoint);
if (distanceSailedInRace != null) {
if (result == null) {
result = distanceSailedInRace;
} else {
result = result.add(distanceSailedInRace);
result = adder.apply(result, distanceSailedInRace);
}
} else {
// if competitor has not finished one single race in the whole
@@ -1118,4 +1148,13 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
}
}
@Override
public boolean hasScores(Competitor competitor, TimePoint timePoint) {
for (final RaceColumn raceColumn : getRaceColumns()) {
if (getTotalPoints(competitor, raceColumn, timePoint) != null) {
return true;
}
}
return false;
}
}
@@ -425,13 +425,13 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
}
@Override
public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException {
public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
return getNetPoints(competitor, raceColumn, getRaceColumns(), timePoint);
}
@Override
public Double getNetPoints(Competitor competitor, RaceColumn raceColumn,
Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) throws NoWindException {
Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) {
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule()
.getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint);
return getNetPoints(competitor, raceColumn, timePoint, discardedRaceColumns);
@@ -181,9 +181,7 @@ public class CompetitorProviderFromRaceColumnsAndRegattaLike {
if (allCompetitorsCache == null) {
final Set<Competitor> result = new HashSet<>();
final Set<RaceDefinition> raceDefinitions = new HashSet<>();
boolean hasRaceColumns = false;
for (RaceColumn rc : provider.getRaceColumns()) {
hasRaceColumns = true;
final Pair<Iterable<RaceDefinition>, Iterable<Competitor>> allCompetitorsInRaceColumnWithRaceDefinitionsConsidered = rc.getAllCompetitorsWithRaceDefinitionsConsidered();
Util.addAll(allCompetitorsInRaceColumnWithRaceDefinitionsConsidered.getB(), result);
Util.addAll(allCompetitorsInRaceColumnWithRaceDefinitionsConsidered.getA(), raceDefinitions);
@@ -192,12 +190,10 @@ public class CompetitorProviderFromRaceColumnsAndRegattaLike {
}
}
final RegattaLog regattaLog = provider.getRegattaLike().getRegattaLog();
if (!hasRaceColumns) {
// If no race exists, the regatta log-provided competitor registrations will not have
// been considered yet; add them:
final Set<Competitor> regattaLogProvidedCompetitors = new CompetitorsInLogAnalyzer<>(regattaLog).analyze();
result.addAll(regattaLogProvidedCompetitors);
}
// If no race exists, the regatta log-provided competitor registrations will not have
// been considered yet; add them:
final Set<Competitor> regattaLogProvidedCompetitors = new CompetitorsInLogAnalyzer<>(regattaLog).analyze();
result.addAll(regattaLogProvidedCompetitors);
// else, don't add regatta log competitors because they have been added in each column already.
// The competitors are collected from the races. Those, however, will be the regatta log
// competitors if the race does not define its own.
@@ -280,7 +280,7 @@ public class DelegatingRegattaLeaderboardWithCompetitorElimination extends Abstr
return getFullLeaderboard().getMaxPointsReason(competitor, race, timePoint);
}
public Double getNetPoints(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException {
public Double getNetPoints(Competitor competitor, RaceColumn race, TimePoint timePoint) {
return getFullLeaderboard().getNetPoints(competitor, race, timePoint);
}
@@ -171,9 +171,16 @@ public abstract class AbstractMetaLeaderboard extends AbstractSimpleLeaderboardI
@Override
public int getTrackedRank(Competitor competitor, RaceColumn race, TimePoint timePoint) {
final List<Competitor> competitorsFromBestToWorst = ((MetaLeaderboardColumn) race).getLeaderboard().getCompetitorsFromBestToWorst(timePoint);
Util.removeAll(getSuppressedCompetitors(), competitorsFromBestToWorst);
return competitorsFromBestToWorst.indexOf(competitor)+1;
final Leaderboard leaderboard = ((MetaLeaderboardColumn) race).getLeaderboard();
final int result;
if (leaderboard.hasScores(competitor, timePoint)) {
final List<Competitor> competitorsFromBestToWorst = leaderboard.getCompetitorsFromBestToWorst(timePoint);
Util.removeAll(getSuppressedCompetitors(), competitorsFromBestToWorst);
result = competitorsFromBestToWorst.indexOf(competitor)+1;
} else {
result = 0;
}
return result;
}
@Override
@@ -2,12 +2,10 @@ package com.sap.sailing.domain.tracking;
import java.io.Serializable;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.tracking.BravoExtendedFix;
import com.sap.sailing.domain.common.tracking.BravoFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.WithID;
@@ -73,7 +71,7 @@ public interface BravoFixTrack<ItemType extends WithID & Serializable> extends S
/**
* The distance sailed foiling during the time range provided
*/
Distance getDistanceSpentFoiling(GPSFixTrack<Competitor, GPSFixMoving> gpsFixTrack, TimePoint from, TimePoint to);
Distance getDistanceSpentFoiling(TimePoint from, TimePoint to);
/**
* Returns {@code true}, if the track contains {@link BravoExtendedFix} instances instead of simple {@link BravoFix BravoFixes}.
@@ -211,7 +211,7 @@ public interface TrackedLegOfCompetitor extends Serializable {
* Same as {@link #getVelocityMadeGood(TimePoint, WindPositionMode)}, only that a cache for wind data and leg type and bearing
* is passed.
*/
Speed getVelocityMadeGood(TimePoint at, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache);
SpeedWithBearing getVelocityMadeGood(TimePoint at, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache);
/**
@@ -241,6 +241,11 @@ public interface TrackedLegOfCompetitor extends Serializable {
Bearing getHeel(TimePoint at);
Bearing getPitch(TimePoint at);
Distance getDistanceFoiled(TimePoint at);
Duration getDurationFoiled(TimePoint at);
/**
* Computes the distance along the wind track to the wind-projected position of the race's overall leader. If leader
* and competitor are in the same leg, this is simply the windward distance. If the leader is already one or more
@@ -261,7 +266,6 @@ public interface TrackedLegOfCompetitor extends Serializable {
/**
* Same as {@link #getWindwardDistanceToCompetitorFarthestAhead(TimePoint, WindPositionMode, RankingInfo)}, only that a cache for leg type
* calculation is passed.
* @param rankingInfo TODO
*/
Distance getWindwardDistanceToCompetitorFarthestAhead(TimePoint timePoint, WindPositionMode windPositionMode, RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache);
@@ -46,6 +46,7 @@ import com.sap.sailing.domain.common.racelog.RacingProcedureType;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.SensorFix;
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.domain.polars.PolarDataService;
@@ -611,7 +612,7 @@ public interface TrackedRace extends Serializable, IsManagedByCache<SharedDomain
boolean notifyAboutGPSFixesAlreadyLoaded);
void removeListener(RaceChangeListener listener);
/**
* @return <code>null</code> if there are no mark passings for the <code>competitor</code> in this race
* or if the competitor has not finished one of the legs in the race.
@@ -623,6 +624,18 @@ public interface TrackedRace extends Serializable, IsManagedByCache<SharedDomain
*/
Distance getDistanceTraveledIncludingGateStart(Competitor competitor, TimePoint timePoint);
/**
* @return <code>null</code> if there are no mark passings for the <code>competitor</code> in this race
* or if the competitor has not finished one of the legs in the race.
*/
Distance getDistanceFoiled(Competitor competitor, TimePoint timePoint);
/**
* @return <code>null</code> if there are no mark passings for the <code>competitor</code> in this race
* or if the competitor has not finished one of the legs in the race.
*/
Duration getDurationFoiled(Competitor competitor, TimePoint timePoint);
/**
* See {@link TrackedLegOfCompetitor#getWindwardDistanceToCompetitorFarthestAhead(TimePoint, WindPositionMode, RankingInfo)}
*/
@@ -1021,4 +1034,42 @@ public interface TrackedRace extends Serializable, IsManagedByCache<SharedDomain
* time point up and until to compute the speed
*/
Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint);
/**
* Computes the competitor's speed projected onto the wind (if wind data is available and the competitor is not
* between start and finish (not racing) or not on a {@link LegType#REACHING reaching} leg; if outside a race and no
* wind information is available, {@code null} is returned. Otherwise, the speed at {@code timePoint} is projected
* onto the course. The wind direction at the {@link WindPositionMode#EXACT exact} competitor position at
* {@code timePoint} is used for the calculation.
*/
default SpeedWithBearing getVelocityMadeGood(Competitor competitor, TimePoint timePoint) {
return getVelocityMadeGood(competitor, timePoint, new LeaderboardDTOCalculationReuseCache(timePoint));
}
/**
* Like {@link #getVelocityMadeGood(Competitor, TimePoint)}, but allowing callers to specify a {@link WindPositionMode}
* other than the default {@link WindPositionMode#EXACT}. If {@link WindPositionMode#LEG_MIDDLE} is used and the
* competitor is not currently sailing on a leg (hasn't started or has already finished), {@code null} is returned.
*/
default SpeedWithBearing getVelocityMadeGood(Competitor competitor, TimePoint timePoint, WindPositionMode windPositionMode) {
return getVelocityMadeGood(competitor, timePoint, windPositionMode, new LeaderboardDTOCalculationReuseCache(timePoint));
}
/**
* Like {@link #getVelocityMadeGood(Competitor, TimePoint)}, but allowing callers to specify a cache that can
* accelerate requests for wind directions, the leg type and the competitor's current leg's bearing.
*/
default SpeedWithBearing getVelocityMadeGood(Competitor competitor, TimePoint timePoint, WindLegTypeAndLegBearingCache cache) {
return getVelocityMadeGood(competitor, timePoint, WindPositionMode.EXACT, cache);
}
/**
* Like {@link #getVelocityMadeGood(Competitor, TimePoint)}, but allowing callers to specify a
* {@link WindPositionMode} other than the default {@link WindPositionMode#EXACT} as well as a cache that can
* accelerate requests for wind directions, the leg type and the competitor's current leg's bearing. If
* {@link WindPositionMode#LEG_MIDDLE} is used and the competitor is not currently sailing on a leg (hasn't started
* or has already finished), {@code null} is returned.
*/
SpeedWithBearing getVelocityMadeGood(Competitor competitor, TimePoint timePoint, WindPositionMode windPositionMode,
WindLegTypeAndLegBearingCache cache);
}
@@ -1,14 +1,14 @@
package com.sap.sailing.domain.tracking.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.function.Function;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.confidence.impl.ScalableDouble;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableDistance;
import com.sap.sailing.domain.common.tracking.BravoExtendedFix;
import com.sap.sailing.domain.common.tracking.BravoFix;
@@ -16,6 +16,8 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.BravoFixTrack;
import com.sap.sailing.domain.tracking.DynamicBravoFixTrack;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.GPSTrackListener;
import com.sap.sailing.domain.tracking.Track;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
@@ -32,6 +34,37 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
private static final long serialVersionUID = 460944392510182976L;
private final boolean hasExtendedFixes;
private transient TimeRangeCache<Duration> foilingTimeCache;
private transient TimeRangeCache<Distance> foilingDistanceCache;
private transient TimeRangeCache<Distance> averageRideHeightCache;
/**
* If a GPS track was provided at construction time, remember it non-transiently. It is needed when restoring
* the object after de-serialization, so the cache invalidation listener can be re-established.
*/
private GPSFixTrack<ItemType, GPSFixMoving> gpsTrack;
private class CacheInvalidationGpsTrackListener implements GPSTrackListener<ItemType, GPSFixMoving> {
private static final long serialVersionUID = 6395529765232404414L;
@Override
public boolean isTransient() {
return true;
}
@Override
public void gpsFixReceived(GPSFixMoving fix, ItemType item, boolean firstFixInTrack) {
assert item == getTrackedItem();
foilingDistanceCache.invalidateAllAtOrLaterThan(fix.getTimePoint());
}
@Override
public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage,
long newMillisecondsOverWhichToAverage) {
}
}
/**
* @param trackedItem
@@ -40,8 +73,55 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
* the name of the track by which it can be obtained from the {@link TrackedRace}.
*/
public BravoFixTrackImpl(ItemType trackedItem, String trackName, boolean hasExtendedFixes) {
this(trackedItem, trackName, hasExtendedFixes, /* listen to GPS track for distance cache invalidation */ null);
}
/**
* @param gpsTrack
* if not {@code null}, this track will listen on that track for changes in order to invalidate this
* track's caches; for example, if a GPS fix is added, this track may need to invalidate its
* {@link #foilingDistanceCache} accordingly because the distance traveled between the fixes may have
* changed.
*/
public BravoFixTrackImpl(ItemType trackedItem, String trackName, boolean hasExtendedFixes, GPSFixTrack<ItemType, GPSFixMoving> gpsTrack) {
super(trackedItem, trackName, BravoFixTrack.TRACK_NAME + " for " + trackedItem);
this.hasExtendedFixes = hasExtendedFixes;
this.foilingTimeCache = createFoilingTimeCache(trackedItem);
this.foilingDistanceCache = createFoilingDistanceCache(trackedItem);
this.averageRideHeightCache = createAverageRideHeightCache(trackedItem);
this.gpsTrack = gpsTrack;
if (gpsTrack != null) {
gpsTrack.addListener(new CacheInvalidationGpsTrackListener());
}
}
private TimeRangeCache<Distance> createAverageRideHeightCache(ItemType trackedItem) {
return createTimeRangeCache(trackedItem, "averageRideHeightCache");
}
private <T> TimeRangeCache<T> createTimeRangeCache(ItemType trackedItem, final String cacheName) {
return new TimeRangeCache<>(cacheName+" for "+trackedItem);
}
private TimeRangeCache<Distance> createFoilingDistanceCache(ItemType trackedItem) {
return createTimeRangeCache(trackedItem, "foilingDistanceCache");
}
private TimeRangeCache<Duration> createFoilingTimeCache(ItemType trackedItem) {
return createTimeRangeCache(trackedItem, "foilingTimeCache");
}
/**
* After reading this object from an {@link ObjectInputStream}, initialize the caches properly.
*/
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
this.foilingTimeCache = createFoilingTimeCache(getTrackedItem());
this.foilingDistanceCache = createFoilingDistanceCache(getTrackedItem());
this.averageRideHeightCache = createAverageRideHeightCache(getTrackedItem());
if (gpsTrack != null) {
gpsTrack.addListener(new CacheInvalidationGpsTrackListener());
}
}
@Override
@@ -53,13 +133,13 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
@Override
public Bearing getHeel(TimePoint timePoint) {
return getValueFromExtendedFixSkippingNullValues(timePoint, BravoFix::getHeel,
ScalableBearing::new);
NaivelyScalableBearing::new);
}
@Override
public Bearing getPitch(TimePoint timePoint) {
return getValueFromExtendedFixSkippingNullValues(timePoint, BravoFix::getPitch,
ScalableBearing::new);
NaivelyScalableBearing::new);
}
@Override
@@ -68,29 +148,43 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
return rideHeight != null && rideHeight.compareTo(BravoFix.MIN_FOILING_HEIGHT_THRESHOLD) >= 0;
}
@Override
public boolean add(BravoFix fix, boolean replace) {
final boolean added = super.add(fix, replace);
if (added) {
final TimePoint fixTimePoint = fix.getTimePoint();
averageRideHeightCache.invalidateAllAtOrLaterThan(fixTimePoint);
foilingDistanceCache.invalidateAllAtOrLaterThan(fixTimePoint);
foilingTimeCache.invalidateAllAtOrLaterThan(fixTimePoint);
}
return added;
}
@Override
public Distance getAverageRideHeight(TimePoint from, TimePoint to) {
final Distance result;
lockForRead();
try {
Distance sum = Distance.NULL;
int count = 0;
for (final BravoFix fix : getFixes(from, true, to, true)) {
final Distance rideHeight = fix.getRideHeight();
if (rideHeight != null) {
sum = sum.add(rideHeight);
count++;
return getValueSum(from, to, /* nullElement */ Distance.NULL, Distance::add, averageRideHeightCache,
/* valueCalculator */ new Track.TimeRangeValueCalculator<Distance>() {
@Override
public Distance calculate(TimePoint from, TimePoint to) {
Distance result;
Distance sum = Distance.NULL;
int count = 0;
for (final BravoFix fix : getFixes(from, true, to, true)) {
final Distance rideHeight = fix.getRideHeight();
if (rideHeight != null) {
sum = sum.add(rideHeight);
count++;
}
}
if (count > 0) {
result = sum.scale(1./count);
} else {
result = null;
}
return result;
}
if (count > 0) {
result = sum.scale(1./count);
} else {
result = null;
}
} finally {
unlockAfterRead();
}
return result;
});
}
private boolean isFoiling(BravoFix fix) {
@@ -99,44 +193,47 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
@Override
public Duration getTimeSpentFoiling(TimePoint from, TimePoint to) {
Duration result = Duration.NULL;
lockForRead();
try {
TimePoint last = from;
boolean isFoiling = false;
for (final BravoFix fix : getFixes(from, true, to, true)) {
final boolean fixFoils = isFoiling(fix);
if (isFoiling && fixFoils) {
result = result.plus(last.until(fix.getTimePoint()));
return getValueSum(from, to, /* nullElement */ Duration.NULL, Duration::plus, foilingTimeCache,
/* valueCalculator */ new Track.TimeRangeValueCalculator<Duration>() {
@Override
public Duration calculate(TimePoint from, TimePoint to) {
Duration result = Duration.NULL;
TimePoint last = from;
boolean isFoiling = false;
for (final BravoFix fix : getFixes(from, true, to, true)) {
final boolean fixFoils = isFoiling(fix);
if (isFoiling && fixFoils) {
result = result.plus(last.until(fix.getTimePoint()));
}
last = fix.getTimePoint();
isFoiling = fixFoils;
}
last = fix.getTimePoint();
isFoiling = fixFoils;
return result;
}
} finally {
unlockAfterRead();
}
return result;
});
}
@Override
public Distance getDistanceSpentFoiling(GPSFixTrack<Competitor, GPSFixMoving> gpsFixTrack, TimePoint from, TimePoint to) {
Distance result = Distance.NULL;
lockForRead();
try {
TimePoint last = from;
boolean isFoiling = false;
for (final BravoFix fix : getFixes(from, true, to, true)) {
final boolean fixFoils = isFoiling(fix);
if (isFoiling && fixFoils) {
result = result.add(gpsFixTrack.getDistanceTraveled(last, fix.getTimePoint()));
public Distance getDistanceSpentFoiling(TimePoint from, TimePoint to) {
assert gpsTrack != null;
return getValueSum(from, to, /* nullElement */ Distance.NULL, Distance::add, foilingDistanceCache,
/* valueCalculator */ new Track.TimeRangeValueCalculator<Distance>() {
@Override
public Distance calculate(TimePoint from, TimePoint to) {
Distance result = Distance.NULL;
TimePoint last = from;
boolean isFoiling = false;
for (final BravoFix fix : getFixes(from, true, to, true)) {
final boolean fixFoils = isFoiling(fix);
if (isFoiling && fixFoils) {
result = result.add(gpsTrack.getDistanceTraveled(last, fix.getTimePoint()));
}
last = fix.getTimePoint();
isFoiling = fixFoils;
}
last = fix.getTimePoint();
isFoiling = fixFoils;
return result;
}
} finally {
unlockAfterRead();
}
return result;
});
}
@Override
@@ -1,237 +0,0 @@
package com.sap.sailing.domain.tracking.impl;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.NavigableSet;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
import com.sap.sse.util.impl.ArrayListNavigableSet;
/**
* Computing {@link #getDistanceTraveled(TimePoint, TimePoint)} is more expensive the longer the track is and the
* higher the tracking frequency. It is called regularly, usually for the same start time. Keeping to re-evaluate
* all distances between all GPS fixes, even if they remain constant, is a waste of resources, particularly when we
* assume that usually GPS fixes are appended to a track and not usually randomly inserted, even though this can
* happen.
* <p>
*
* This cache looks "backwards." It contains pairs whose first component represents a <code>to</code> parameter used
* in {@link #getDistanceTraveled(TimePoint, TimePoint)}. It is ordered by this component. The second component is a
* navigable, ordered set of pairs where the first pair component represents a <code>from</code> parameter used in
* {@link #getDistanceTraveled(TimePoint, TimePoint)} and the second pair component represents the result of
* {@link #getDistanceTraveled(TimePoint, TimePoint)} for this parameter combination. Note that the cache does only
* store distances for <code>from</code> time points actually passed to
* {@link #getDistanceTraveled(TimePoint, TimePoint)} and does not cache all interim combinations collected while
* computing a result. As such, it differs from a typical "dynamic programming" algorithm which would store all
* in-between values too. Assuming that <code>from</code> values are sparse (leg start, race start, ...), there
* doesn't seem to be much use in storing all the other in-between values too.
* <p>
*
* For implementation efficiency in combination with using a {@link ArrayListNavigableSet} for the values and in
* order to be able to efficiently extend a cache entry for a single <code>to</code> fix, the navigable sets
* containing the <code>from</code> fixes and distances are ordered such that earlier fixes come later in the set.
* This way, extending the cache entry for a <code>to</code> fix to an earlier <code>from</code> fix only requires
* appending to the set.
* <p>
*
* <b>Invalidation</b>: When a new fix is added to this track, all distance cache entries for fixes at or later than
* the new fix's time point are removed from this cache. Additionally, the fix insertion may have an impact on the
* {@link #getEarlierFixesWhoseValidityMayBeAffected(GPSFix) previous fix's} validity (track smoothing) and
* therefore on its selection for distance aggregation. Therefore, if fix addition turned the previous fix invalid,
* the cache entries for the time points at or after the previous fix also need to be removed.
* <p>
*
* <b>Cache use</b>: When {@link #getDistanceTraveled(TimePoint, TimePoint)} is called, it first looks for a cache
* entry for the <code>to</code> parameter. If one is found, the earliest entry in the navigable set for the
* navigable set of <code>from</code> and distance values that is at or after the requested <code>from</code> time
* point is determined. If such an entry exists, the distance is remembered and the algorithm is repeated
* recursively, using the <code>from</code> value found in the cache as the new <code>to</code> value, and the
* <code>from</code> value originally passed to {@link #getDistanceTraveled(TimePoint, TimePoint)} as
* <code>from</code> again. If no entry is found in the cache entry for <code>to</code> that is at or after the
* requested <code>from</code> time, the distance is computed by iterating the smoothened fixes.
* <p>
*
* If a cache entry for <code>to</code> is not found, the latest cache entry before it is looked up. If one is
* found, the distance between the <code>to</code> time point requested and the <code>to</code> time point found in
* the cache is computed by iterating the smoothened fixes for this interval. If none is found, the distance is
* computed by iterating backwards all the way to <code>from</code>.
* <p>
*
* Once the {@link #getDistanceTraveled(TimePoint, TimePoint)} has computed its value, it adds the result to the
* cache.
*
* @author Axel Uhl (D043530)
*/
public class DistanceCache {
public static final int MAX_SIZE = 100;
private final NavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>>> distanceCache;
/**
* The cache is to have limited size. Eviction shall happen based on a least-recently-used strategy. Usage is
* defined as having been returned by {@link #getEarliestFromAndDistanceAtOrAfterFrom(TimePoint, TimePoint)} or
* having been added by {@link #cache(TimePoint, TimePoint, Distance)}.
* <p>
*
* When an eldest entry is asked to be expunged from this map and the map has more than {@link #MAX_SIZE} elements,
* the expunging will be admitted, and the entry is removed from the {@link #distanceCache} core structure. Reading
* and writing this structure must happen under the {@link #lock write lock} because also reading the linked hash
* map that counts access as "use" has a modifying effect on its internal structures.
* <p>
*
* The key pairs are from/to pairs. Note that this is in some sense "the opposite direction" compared to the
* alignment of the {@link #distanceCache} structure which has as its outer keys the "to" time point.<p>
*
* Read access is to be <code>synchronized<code> using this field's mutex; write access only happens under the
* {@link #lock write lock} and therefore will have no contenders.
*/
private final LinkedHashMap<Util.Pair<TimePoint, TimePoint>, Void> lruCache;
private final NamedReentrantReadWriteLock lock;
private static final Comparator<Util.Pair<TimePoint, ?>> timePointInPairComparator = new Comparator<Util.Pair<TimePoint, ?>>() {
@Override
public int compare(Util.Pair<TimePoint, ?> o1, Util.Pair<TimePoint, ?> o2) {
return o1.getA().compareTo(o2.getA());
}
};
public DistanceCache(String nameForLockLogging) {
lock = new NamedReentrantReadWriteLock("lock for DistanceCache for "+nameForLockLogging, /* fair */ true);
this.distanceCache = new ArrayListNavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>>>(timePointInPairComparator);
this.lruCache = new LinkedHashMap<Util.Pair<TimePoint, TimePoint>, Void>(/* initial capacity */ 10, /* load factor */ 0.75f,
/* access-based ordering */ true) {
private static final long serialVersionUID = -6568235517111733193L;
@Override
protected boolean removeEldestEntry(Entry<Pair<TimePoint, TimePoint>, Void> eldest) {
final boolean expunge = size() > MAX_SIZE;
if (expunge) {
removeCacheEntry(eldest.getKey().getA(), eldest.getKey().getB());
}
return expunge;
}
};
}
public int size() {
return lruCache.size();
}
private void removeCacheEntry(TimePoint from, TimePoint to) {
assert lock.getWriteHoldCount() == 1; // we can be sure we are alone here; this only happens when adding a new entry, holding the write lock
Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>> entryForTo = distanceCache.floor(createDummy(to));
if (entryForTo.getA().equals(to)) {
Pair<TimePoint, Distance> entryForFrom = entryForTo.getB().ceiling(new Util.Pair<TimePoint, Distance>(from, null));
if (entryForFrom.getA().equals(from)) {
entryForTo.getB().remove(entryForFrom);
if (entryForTo.getB().isEmpty()) {
distanceCache.remove(entryForTo);
}
}
}
}
/**
* Looks up the entry closest to but no later than <code>to</code>. If not found, <code>null</code> is returned. If
* found, the earliest pair of from/distance that is at or after <code>from</code> will be returned, together with
* the <code>to</code> value of the entry. If there is no entry that is at or after <code>from</code>,
* <code>null</code> is returned.
*/
public Util.Pair<TimePoint, Util.Pair<TimePoint, Distance>> getEarliestFromAndDistanceAtOrAfterFrom(TimePoint from, TimePoint to) {
LockUtil.lockForRead(lock);
try {
Util.Pair<TimePoint, Util.Pair<TimePoint, Distance>> result = null;
Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>> entryForTo = distanceCache.floor(createDummy(to));
if (entryForTo != null) {
final Util.Pair<TimePoint, Distance> fromCeiling = entryForTo.getB().ceiling(new Util.Pair<TimePoint, Distance>(from, null));
if (fromCeiling != null) {
result = new Util.Pair<TimePoint, Util.Pair<TimePoint, Distance>>(entryForTo.getA(), fromCeiling);
}
}
// 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) {
lruCache.get(new Util.Pair<TimePoint, TimePoint>(from, to));
}
return result;
} finally {
LockUtil.unlockAfterRead(lock);
}
}
/**
* Removes all cache entries that have a <code>to</code> time point that is at or after <code>timePoint</code>.
*/
public void invalidateAllAtOrLaterThan(TimePoint timePoint) {
LockUtil.lockForWrite(lock);
try {
Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>> dummy = createDummy(timePoint);
NavigableSet<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>>> toRemove = distanceCache.tailSet(dummy, /* inclusive */ true);
for (Iterator<Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>>> i=toRemove.iterator(); i.hasNext(); ) {
Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>> entryToRemove = i.next();
assert entryToRemove.getA().compareTo(timePoint) >= 0;
for (Pair<TimePoint, Distance> fromAndDistance : entryToRemove.getB()) {
lruCache.remove(new Util.Pair<>(fromAndDistance.getA(), entryToRemove.getA()));
}
i.remove();
}
} finally {
LockUtil.unlockAfterWrite(lock);
}
}
private NavigableSet<Util.Pair<TimePoint, Distance>> getEntryForTo(TimePoint to) {
NavigableSet<Util.Pair<TimePoint, Distance>> result = null;
Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>> dummyForTo = createDummy(to);
Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>> entryForTo = distanceCache.floor(dummyForTo);
if (entryForTo != null && entryForTo.getA().equals(to)) {
result = entryForTo.getB();
}
return result;
}
public void cache(TimePoint from, TimePoint to, Distance distance) {
LockUtil.lockForWrite(lock);
try {
NavigableSet<Util.Pair<TimePoint, Distance>> entryForTo = getEntryForTo(to);
if (entryForTo == null) {
entryForTo = new ArrayListNavigableSet<Util.Pair<TimePoint, Distance>>(timePointInPairComparator);
Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>> pairForTo = new Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>>(
to, entryForTo);
distanceCache.add(pairForTo);
}
entryForTo.add(new Util.Pair<TimePoint, Distance>(from, distance));
lruCache.put(new Util.Pair<TimePoint, TimePoint>(from, to), null);
} finally {
LockUtil.unlockAfterWrite(lock);
}
}
private Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>> createDummy(TimePoint to) {
return new Util.Pair<TimePoint, NavigableSet<Util.Pair<TimePoint, Distance>>>(to, null);
}
/**
* Removes all contents from this cache
*/
public void clear() {
LockUtil.lockForWrite(lock);
try {
distanceCache.clear();
lruCache.clear();
} finally {
LockUtil.unlockAfterWrite(lock);
}
}
}
@@ -38,6 +38,7 @@ import com.sap.sailing.domain.common.tracking.WithValidityCache;
import com.sap.sailing.domain.common.tracking.impl.CompactPositionHelper;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.GPSTrackListener;
import com.sap.sailing.domain.tracking.Track;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.TimeRange;
@@ -116,7 +117,7 @@ public abstract class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends
* Once the {@link #getDistanceTraveled(TimePoint, TimePoint)} has computed its value, it adds the result to the
* cache.
*/
private transient DistanceCache distanceCache;
private transient TimeRangeCache<Distance> distanceCache;
private transient MaxSpeedCache<ItemType, FixType> maxSpeedCache;
@@ -155,7 +156,7 @@ public abstract class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends
this.millisecondsOverWhichToAverage = millisecondsOverWhichToAverage;
this.maxSpeedForSmoothing = maxSpeedForSmoothening;
this.listeners = new TrackListenerCollection<ItemType, FixType, GPSTrackListener<ItemType,FixType>>();
this.distanceCache = new DistanceCache(trackedItem==null?"null":trackedItem.toString());
this.distanceCache = new TimeRangeCache<>(trackedItem==null?"null":trackedItem.toString());
this.maxSpeedCache = createMaxSpeedCache();
this.validityCachingSuspended = false;
this.losslessCompaction = losslessCompaction;
@@ -190,7 +191,7 @@ public abstract class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
distanceCache = new DistanceCache(getTrackedItem().toString());
distanceCache = new TimeRangeCache<>(getTrackedItem().toString());
maxSpeedCache = createMaxSpeedCache();
}
@@ -506,76 +507,33 @@ public abstract class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends
@Override
public Distance getDistanceTraveled(TimePoint from, TimePoint to) {
return getDistanceTraveledRecursively(from, to, 0);
}
private Distance getDistanceTraveledRecursively(TimePoint from, TimePoint to, int recursionDepth) {
Distance result;
if (!from.before(to)) {
result = Distance.NULL;
} else {
boolean perfectCacheHit = false;
lockForRead();
try {
Util.Pair<TimePoint, Util.Pair<TimePoint, Distance>> bestCacheEntry = getDistanceCache()
.getEarliestFromAndDistanceAtOrAfterFrom(from, to);
if (bestCacheEntry != null) {
perfectCacheHit = true; // potentially a cache hit; but if it doesn't span the full interval, it's not perfect; see below
// compute the missing stretches between best cache entry's "from" and our "from" and the cache
// entry's "to" and our "to"
Distance distanceFromFromToBeginningOfCacheEntry = Distance.NULL;
Distance distanceFromEndOfCacheEntryToTo = Distance.NULL;
if (!bestCacheEntry.getB().getA().equals(from)) {
assert bestCacheEntry.getB().getA().after(from);
perfectCacheHit = false;
distanceFromFromToBeginningOfCacheEntry = getDistanceTraveledRecursively(from, bestCacheEntry
.getB().getA(), recursionDepth + 1);
}
if (!bestCacheEntry.getA().equals(to)) {
assert bestCacheEntry.getA().before(to);
perfectCacheHit = false;
distanceFromEndOfCacheEntryToTo = getDistanceTraveledRecursively(bestCacheEntry.getA(), to,
recursionDepth + 1);
}
result = distanceFromFromToBeginningOfCacheEntry.add(bestCacheEntry.getB().getB()).add(
distanceFromEndOfCacheEntryToTo);
} else {
double distanceInNauticalMiles = 0;
if (from.compareTo(to) < 0) {
return getValueSum(from, to, /* nullElement */ Distance.NULL, Distance::add, getDistanceCache(),
/* valueCalculator */ new Track.TimeRangeValueCalculator<Distance>() {
@Override
public Distance calculate(TimePoint from, TimePoint to) {
// getEstimatedPosition's current implementation returns a position equal to that of a fix at
// "from" if there is one with exactly that time stamp
Distance distance = Distance.NULL;
Position fromPos = getEstimatedPosition(from, /* extrapolate */false);
if (fromPos == null) {
result = Distance.NULL;
} else {
if (fromPos != null) {
// TODO bug 4122: idea: use the subset to determine fromPos and toPos in one go, eliminating two expensive getEstimatedPosition calls
NavigableSet<GPSFix> subset = getGPSFixes().subSet(new DummyGPSFix(from),
/* fromInclusive */false, new DummyGPSFix(to),
/* toInclusive */false);
for (GPSFix fix : subset) {
double distanceBetweenAdjacentFixesInNauticalMiles = fromPos.getDistance(
fix.getPosition()).getNauticalMiles();
distanceInNauticalMiles += distanceBetweenAdjacentFixesInNauticalMiles;
Distance distanceBetweenAdjacentFixes = fromPos.getDistance(
fix.getPosition());
distance = distance.add(distanceBetweenAdjacentFixes);
fromPos = fix.getPosition();
}
Position toPos = getEstimatedPosition(to, false);
distanceInNauticalMiles += fromPos.getDistance(toPos).getNauticalMiles();
result = new NauticalMileDistance(distanceInNauticalMiles);
distance = distance.add(fromPos.getDistance(toPos));
}
} else {
result = Distance.NULL;
return distance;
}
}
} finally {
unlockAfterRead();
}
if (!perfectCacheHit && recursionDepth == 0) {
getDistanceCache().cache(from, to, result);
}
}
return result;
});
}
@Override
public Distance getRawDistanceTraveled(TimePoint from, TimePoint to) {
lockForRead();
@@ -1065,7 +1023,7 @@ public abstract class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends
}
}
protected DistanceCache getDistanceCache() {
protected TimeRangeCache<Distance> getDistanceCache() {
return distanceCache;
}
@@ -44,7 +44,6 @@ import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sailing.domain.tracking.WindPositionMode;
@@ -98,7 +97,7 @@ public class TrackedLegImpl implements TrackedLeg {
}
@Override
public TrackedRace getTrackedRace() {
public TrackedRaceImpl getTrackedRace() {
return trackedRace;
}
@@ -29,7 +29,6 @@ import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sse.common.Duration;
@@ -68,7 +67,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
return trackedLeg.getLeg();
}
private TrackedRace getTrackedRace() {
private TrackedRaceImpl getTrackedRace() {
return getTrackedLeg().getTrackedRace();
}
@@ -268,29 +267,28 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
/**
* Projects <code>speed</code> onto the wind direction for upwind/downwind legs to see how fast a boat travels
* "along the wind's direction." For reaching legs (neither upwind nor downwind), the speed is projected onto
* the leg's direction.
* @param windPositionMode see {@link #getWind(Position, TimePoint, Set)}
* "along the wind's direction." For reaching legs (neither upwind nor downwind), the speed is projected onto the
* leg's direction.
*
* @throws NoWindException in case the wind direction is not known
* @param speed
* if {@code null} then {@code null} will be returned
* @param windPositionMode
* see {@link #getWind(Position, TimePoint, Set)}
*
* @throws NoWindException
* in case the wind direction is not known
*/
private SpeedWithBearing getWindwardSpeed(SpeedWithBearing speed, final TimePoint at, WindPositionMode windPositionMode,
WindLegTypeAndLegBearingCache cache) {
SpeedWithBearing result = null;
final SpeedWithBearing result;
if (speed != null) {
Bearing projectToBearing;
try {
if (cache.getLegType(getTrackedLeg(), at) != LegType.REACHING) {
final Wind wind;
if (windPositionMode == WindPositionMode.EXACT) {
wind = cache.getWind(getTrackedRace(), getCompetitor(), at);
} else {
wind = getTrackedRace().getWind(
getTrackedLeg().getEffectiveWindPosition(
() -> getTrackedRace().getTrack(getCompetitor())
.getEstimatedPosition(at, false), at, windPositionMode), at);
}
final Wind wind = getTrackedRace().getWind(windPositionMode, getTrackedLeg(), getCompetitor(), at, cache);
if (wind == null) {
// This is not really likely to happen because wind==null would have let the call
// to cache.getLegType(...) fail with a NoWindException
throw new NoWindException("Need at least wind direction to determine windward speed");
}
projectToBearing = wind.getBearing();
@@ -307,7 +305,11 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
projectToBearing = projectToBearing.reverse();
}
result = new KnotSpeedWithBearingImpl(Math.abs(speed.getKnots() * cos), projectToBearing);
} else {
result = null;
}
} else {
result = null;
}
return result;
}
@@ -645,7 +647,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
}
@Override
public Speed getVelocityMadeGood(TimePoint at, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) {
public SpeedWithBearing getVelocityMadeGood(TimePoint at, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) {
if (hasStartedLeg(at)) {
TimePoint timePoint;
if (hasFinishedLeg(at)) {
@@ -709,7 +711,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
public Distance getRideHeight(TimePoint at) {
final Distance result;
if (hasStartedLeg(at)) {
TimePoint timePoint =hasFinishedLeg(at) ? getMarkPassingForLegEnd().getTimePoint() : at;
TimePoint timePoint = hasFinishedLeg(at) ? getMarkPassingForLegEnd().getTimePoint() : at;
BravoFixTrack<Competitor> track = getTrackedRace()
.<BravoFix, BravoFixTrack<Competitor>> getSensorTrack(competitor, BravoFixTrack.TRACK_NAME);
result = track == null ? null : track.getRideHeight(timePoint);
@@ -719,6 +721,37 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
return result;
}
@Override
public Distance getDistanceFoiled(TimePoint at) {
final Distance result;
if (hasStartedLeg(at)) {
TimePoint timePoint = hasFinishedLeg(at) ? getMarkPassingForLegEnd().getTimePoint() : at;
BravoFixTrack<Competitor> track = getTrackedRace()
.<BravoFix, BravoFixTrack<Competitor>> getSensorTrack(competitor, BravoFixTrack.TRACK_NAME);
result = track == null ? null
: track.getDistanceSpentFoiling(getMarkPassingForLegStart().getTimePoint(),
timePoint);
} else {
result = null;
}
return result;
}
@Override
public Duration getDurationFoiled(TimePoint at) {
final Duration result;
if (hasStartedLeg(at)) {
TimePoint timePoint = hasFinishedLeg(at) ? getMarkPassingForLegEnd().getTimePoint() : at;
BravoFixTrack<Competitor> track = getTrackedRace()
.<BravoFix, BravoFixTrack<Competitor>> getSensorTrack(competitor, BravoFixTrack.TRACK_NAME);
result = track == null ? null
: track.getTimeSpentFoiling(getMarkPassingForLegStart().getTimePoint(), timePoint);
} else {
result = null;
}
return result;
}
@Override
public Duration getEstimatedTimeToNextMark(TimePoint timePoint, WindPositionMode windPositionMode) {
return getEstimatedTimeToNextMark(timePoint, windPositionMode, new LeaderboardDTOCalculationReuseCache(timePoint));
@@ -1212,7 +1212,50 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
private Distance getDistanceTraveled(Competitor competitor, TimePoint timePoint, boolean considerGateStart) {
final Distance result;
return getValueFromStartToTimePointOrEnd(competitor, timePoint,
(from, to)->{
final Distance result;
final Distance preResult = getTrack(competitor).getDistanceTraveled(from, to);
if (considerGateStart && preResult != null) {
result = preResult.add(getAdditionalGateStartDistance(competitor, timePoint));
} else {
result = preResult;
}
return result;
});
}
@Override
public Distance getDistanceFoiled(Competitor competitor, TimePoint timePoint) {
return getBravoValue(competitor, timePoint, BravoFixTrack::getDistanceSpentFoiling);
}
@Override
public Duration getDurationFoiled(Competitor competitor, TimePoint timePoint) {
return getBravoValue(competitor, timePoint, BravoFixTrack::getTimeSpentFoiling);
}
@FunctionalInterface
private static interface BravoFromToValueCalculator<T> {
T getValue(BravoFixTrack<Competitor> bravoFixTrack, TimePoint from, TimePoint to);
}
private <T> T getBravoValue(Competitor competitor, TimePoint timePoint, BravoFromToValueCalculator<T> bravoValueCalculator) {
return getValueFromStartToTimePointOrEnd(competitor, timePoint,
(from, to)->{
final T result;
final BravoFixTrack<Competitor> bravoFixTrack = getSensorTrack(competitor, BravoFixTrack.TRACK_NAME);
if (bravoFixTrack != null) {
result = bravoValueCalculator.getValue(bravoFixTrack, from, to);
} else {
result = null;
}
return result;
});
}
private <T> T getValueFromStartToTimePointOrEnd(Competitor competitor, TimePoint timePoint, Track.TimeRangeValueCalculator<T> valueCalculator) {
final T result;
NavigableSet<MarkPassing> markPassings = getMarkPassings(competitor);
try {
lockForRead(markPassings);
@@ -1239,12 +1282,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
if (end == null) {
result = null;
} else {
final Distance preResult = getTrack(competitor).getDistanceTraveled(markPassings.first().getTimePoint(), end);
if (considerGateStart && preResult != null) {
result = preResult.add(getAdditionalGateStartDistance(competitor, timePoint));
} else {
result = preResult;
}
result = valueCalculator.calculate(markPassings.first().getTimePoint(), end);
}
}
return result;
@@ -3570,12 +3608,14 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
@Override
public Speed getSpeed(Competitor competitor, long millisecondsBeforeRaceStart) {
final Speed result;
if (getStartOfRace() == null) {
return null;
result = null;
} else {
TimePoint beforeStart = new MillisecondsTimePoint(getStartOfRace().asMillis() - millisecondsBeforeRaceStart);
result = getTrack(competitor).getEstimatedSpeed(beforeStart);
}
TimePoint beforeStart = new MillisecondsTimePoint(getStartOfRace().asMillis() - millisecondsBeforeRaceStart);
return getTrack(competitor).getEstimatedSpeed(beforeStart);
return result;
}
@Override
@@ -4207,4 +4247,58 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
return result;
}
@Override
public SpeedWithBearing getVelocityMadeGood(Competitor competitor, TimePoint timePoint,
WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) {
TrackedLegOfCompetitor trackedLeg = getTrackedLeg(competitor, timePoint);
final SpeedWithBearing result;
if (trackedLeg != null) {
result = trackedLeg.getVelocityMadeGood(timePoint, windPositionMode, cache);
} else {
// check if wind information is available; if so, compute a VMG only based on wind data:
if (windPositionMode == WindPositionMode.LEG_MIDDLE) {
result = null;
} else {
final Wind wind = getWind(windPositionMode, /* trackedLeg */ null, competitor, timePoint, cache);
result = projectOnto(getTrack(competitor).getEstimatedSpeed(timePoint), wind.getBearing());
}
}
return result;
}
SpeedWithBearing projectOnto(SpeedWithBearing speed, Bearing projectToBearing) {
final SpeedWithBearing result;
if (speed != null && speed.getBearing() != null && projectToBearing != null) {
double cos = Math.cos(speed.getBearing().getRadians() - projectToBearing.getRadians());
if (cos < 0) {
projectToBearing = projectToBearing.reverse();
}
result = new KnotSpeedWithBearingImpl(Math.abs(speed.getKnots() * cos), projectToBearing);
} else {
result = null;
}
return result;
}
/**
* @param trackedLeg
* The caller is expected to obtain the {@link #getTrackedLeg(Competitor, TimePoint) tracked leg} the
* {@code competitor} is sailing in at time point {@code at}. If {@code null}, any
* non-{@link WindPositionMode#EXACT exact} wind position mode will use {@code null} for the wind
* position, defaulting to the "COMBINED" wind source at the middle of the course. In particular, it then
* obviously makes no real sense to request {@link WindPositionMode#LEG_MIDDLE} because no leg is known.
*/
Wind getWind(WindPositionMode windPositionMode, TrackedLegImpl trackedLeg, Competitor competitor, TimePoint at, WindLegTypeAndLegBearingCache cache) {
final Wind wind;
if (windPositionMode == WindPositionMode.EXACT) {
wind = cache.getWind(this, competitor, at);
} else {
wind = getWind(trackedLeg == null ? null :
trackedLeg.getEffectiveWindPosition(
() -> getTrack(competitor)
.getEstimatedPosition(at, false), at, windPositionMode), at);
}
return wind;
}
}
@@ -37,6 +37,7 @@
<plugin id="com.sap.sailing.barbados.resultimport" autoStart="true" startLevel="3" />
<plugin id="com.sap.sailing.dashboards.gwt" autoStart="true" startLevel="5" />
<plugin id="com.sap.sailing.datamining" autoStart="true" startLevel="4" />
<plugin id="com.sap.sailing.domain.bravoadapter" autoStart="true" startLevel="4" />
<plugin id="com.sap.sailing.domain.deckmanadapter" autoStart="true" startLevel="4" />
<plugin id="com.sap.sailing.domain.expeditionadapter" autoStart="true" startLevel="4" />
<plugin id="com.sap.sailing.domain.igtimiadapter" autoStart="true" startLevel="3" />
+7
View File
@@ -416,4 +416,11 @@ Computes leaderboard information for sailing races and offers RESTful APIs to al
version="0.0.0"
unpack="false"/>
<plugin
id="com.sap.sailing.domain.bravoadapter"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
</feature>
@@ -70,6 +70,7 @@ import com.sap.sailing.domain.tracking.WindStore;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sailing.domain.tracking.impl.WindTrackImpl;
import com.sap.sse.common.Duration;
import com.sap.sse.common.IsManagedByCache;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
@@ -406,6 +407,16 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public Distance getDistanceFoiled(Competitor competitor, TimePoint timePoint) {
return null;
}
@Override
public Duration getDurationFoiled(Competitor competitor, TimePoint timePoint) {
return null;
}
@Override
public Distance getWindwardDistanceToCompetitorFarthestAhead(Competitor competitor, TimePoint timePoint, WindPositionMode windPositionMode) {
return null;
@@ -885,4 +896,10 @@ public class MockedTrackedRace implements DynamicTrackedRace {
public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) {
return null;
}
@Override
public SpeedWithBearing getVelocityMadeGood(Competitor competitor, TimePoint timePoint,
WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) {
return null;
}
}
@@ -53,6 +53,8 @@
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.polars.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining.annotations/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
@@ -38,6 +38,7 @@
<inherits name='com.sap.sailing.gwt.ui.SourceActions'/>
<inherits name='com.sap.sailing.gwt.ui.SourceSimulator'/>
<inherits name="com.sap.sailing.gwt.ui.SourceAdminConsole"/>
<!-- TODO: probably only used for flag images. Move RegattaRaceStatesFlagsResources to shared -->
<inherits name="com.sap.sailing.gwt.regattaoverview.SourceRegattaOverview"/>
<inherits name="com.sap.sailing.gwt.ui.SourceLeaderboard"/>
<inherits name="com.sap.sailing.gwt.ui.SourceRaceboard"/>
@@ -13,6 +13,17 @@
state of the currently opened page to get lost.
After a sign out (and another page reload) the language preference will stop to take effect.
</li>
<li>There are additional column types in the leaderboard that show per leg, per race and overall statistics
for distance foiled and duration foiled. This allows for quick competitor comparisons regarding these
figures which are so important for any foiling boat class. Note, that this feature is also available
on overall leaderboards, providing foiling stats across an entire season or event series.
</li>
<li>When a leaderboard belongs to a leaderboard group with an overall (series) leaderboard and the
leaderboard has no scores at all yet for a competitor, that competitor won't be assigned a score
in the overall (series) leaderboard either. This way, when an act or event has its leaderboard already
linked to the series leaderboard group, no scores will show in the series leaderboard before the
act/event starts producing scores for the competitors.
</li>
</ul>
<h5 class="articleSubheadline">October 2017</h5>
@@ -6,13 +6,13 @@
padding: 0 1em !important;
max-width: 80em;
}
h2, .container > div, p > span:first-child {
.container > h2, .container > div, .container > p > span:first-child {
text-transform: uppercase;
}
.container > div, p > span:first-child {
.container > div, .container > p > span:first-child {
text-decoration: underline;
}
p > span:first-child:after {
.container > p > span:first-child:after {
content: ":"
}
</ui:style>
@@ -46,17 +46,17 @@ public abstract class LeaderboardSettings extends AbstractGenericSerializableSet
@Override
protected void addChildSettings() {
isShowCompetitorNationality = new BooleanSetting("showCompetitorNationality", this, false);
List<DetailType> maneuverDetails = new ArrayList<DetailType>();
List<DetailType> maneuverDetails = new ArrayList<>();
maneuverDetails.add(DetailType.TACK);
maneuverDetails.add(DetailType.JIBE);
maneuverDetails.add(DetailType.PENALTY_CIRCLE);
maneuverDetailsToShow = new EnumSetSetting<>("maneuverDetailsToShow", this, maneuverDetails, DetailType::valueOf);
List<DetailType> legDetails = new ArrayList<DetailType>();
List<DetailType> legDetails = new ArrayList<>();
legDetails.add(DetailType.DISTANCE_TRAVELED);
legDetails.add(DetailType.AVERAGE_SPEED_OVER_GROUND_IN_KNOTS);
legDetails.add(DetailType.RANK_GAIN);
legDetailsToShow = new EnumSetSetting<>("legDetailsToShow", this, legDetails, DetailType::valueOf);
List<DetailType> raceDetails = new ArrayList<DetailType>();
List<DetailType> raceDetails = new ArrayList<>();
raceDetails.add(DetailType.DISPLAY_LEGS);
raceDetailsToShow = new EnumSetSetting<>("raceDetailsToShow", this, raceDetails, DetailType::valueOf);
List<DetailType> overallDetails = new ArrayList<>();
@@ -12,6 +12,7 @@ import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FileUpload;
import com.google.gwt.user.client.ui.FormPanel;
@@ -35,6 +36,8 @@ public abstract class AbstractFileImportWidget extends Composite {
@UiField FileUpload fileUploadUi;
@UiField ListBox preferredImporterUi;
@UiField Button importButtonUi;
@UiField
CheckBox downsampleUi;
@UiField Image loadingImageUi;
@UiField FormPanel formPanelUi;
private final TrackFileImportDeviceIdentifierTableWrapper table;
@@ -72,6 +75,10 @@ public abstract class AbstractFileImportWidget extends Composite {
formPanelUi.submit();
}
protected void setDownsampleOptionVisible(boolean visible) {
downsampleUi.setVisible(visible);
}
@UiHandler("formPanelUi")
void onFileImportComplete(SubmitCompleteEvent event) {
if (shouldClearListOnNewUploadComplete) {
@@ -22,6 +22,7 @@
<g:HTMLPanel addStyleNames="{style.formPanelContent}">
<g:FileUpload name="file" ui:field="fileUploadUi"/>
<g:ListBox name="preferredImporter" ui:field="preferredImporterUi" height="21px"/>
<g:CheckBox name="downsample" checked="true" ui:field="downsampleUi">Downsample</g:CheckBox>
<g:Button styleName="" ui:field="importButtonUi"/>
<g:Image resource="{res.transparentGif}" addStyleNames="{style.loadingImage}" ui:field="loadingImageUi"/>
</g:HTMLPanel>
@@ -48,6 +48,10 @@ interface AdminConsoleResources extends ClientBundle {
@Source("com/sap/sailing/gwt/ui/client/images/clock.png")
ImageResource clockIcon();
// Smaller variant of RegattaRaceStatesFlagsResources.flagBlue to solve layouting issues
@Source("com/sap/sailing/gwt/ui/client/images/blue_small.png")
ImageResource blueSmall();
@Source("com/sap/sailing/gwt/ui/client/images/flag_blue.png")
ImageResource flagIcon();
@@ -60,9 +60,11 @@ import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardEntryPoint;
import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.RaceLogSetFinishingAndFinishTimeDTO;
import com.sap.sailing.gwt.ui.shared.RaceLogSetStartTimeAndProcedureDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.util.NaturalComparator;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
@@ -473,6 +475,8 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
refreshRaceLog(object.getA(), object.getB(), true);
} else if (LeaderboardRaceConfigImagesBarCell.ACTION_SET_STARTTIME.equals(value)) {
setStartTime(object.getA(), object.getB());
} else if (LeaderboardRaceConfigImagesBarCell.ACTION_SET_FINISHING_AND_FINISH_TIME.equals(value)) {
setEndTime(object.getA(), object.getB());
} else if (LeaderboardRaceConfigImagesBarCell.ACTION_SHOW_RACELOG.equals(value)) {
showRaceLog(object.getA(), object.getB());
}
@@ -594,28 +598,56 @@ TrackedRaceChangedListener, LeaderboardsDisplayer {
}
private void setStartTime(RaceColumnDTO raceColumnDTO, FleetDTO fleetDTO) {
new SetStartTimeDialog(sailingService, errorReporter, getSelectedLeaderboardName(), raceColumnDTO.getName(),
new SetStartTimeDialog(sailingService, errorReporter, getSelectedLeaderboardName(), raceColumnDTO.getName(),
fleetDTO.getName(), stringMessages, new DialogCallback<RaceLogSetStartTimeAndProcedureDTO>() {
@Override
public void ok(RaceLogSetStartTimeAndProcedureDTO editedObject) {
sailingService.setStartTimeAndProcedure(editedObject, new AsyncCallback<Boolean>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(caught.getMessage());
public void ok(RaceLogSetStartTimeAndProcedureDTO editedObject) {
sailingService.setStartTimeAndProcedure(editedObject, new AsyncCallback<Boolean>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(caught.getMessage());
}
@Override
public void onSuccess(Boolean result) {
if (!result) {
Window.alert(stringMessages.failedToSetNewStartTime());
}
}
});
}
@Override
public void onSuccess(Boolean result) {
if (!result) {
Window.alert(stringMessages.failedToSetNewStartTime());
}
public void cancel() {
}
});
}
}).show();
}
private void setEndTime(RaceColumnDTO raceColumnDTO, FleetDTO fleetDTO) {
new SetFinishingAndFinishedTimeDialog(sailingService, errorReporter, getSelectedLeaderboardName(), raceColumnDTO.getName(),
fleetDTO.getName(), stringMessages, new DialogCallback<RaceLogSetFinishingAndFinishTimeDTO>() {
@Override
public void ok(RaceLogSetFinishingAndFinishTimeDTO editedObject) {
sailingService.setFinishingAndEndTime(editedObject, new AsyncCallback<Pair<Boolean, Boolean>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(caught.getMessage());
}
@Override
public void cancel() { }
}).show();
@Override
public void onSuccess(Pair<Boolean, Boolean> result) {
if (!result.getA() || !result.getB()) {
Window.alert(stringMessages.failedToSetNewFinishingAndFinishTime());
}
}
});
}
@Override
public void cancel() {
}
}).show();
}
private void removeRaceColumn(final RaceColumnDTO raceColumnDTO) {
@@ -15,6 +15,7 @@ public class LeaderboardRaceConfigImagesBarCell extends ImagesBarCell {
public final static String ACTION_EDIT = "ACTION_EDIT";
public final static String ACTION_REFRESH_RACELOG = "ACTION_REFRESH_RACE_LOG";
public final static String ACTION_SET_STARTTIME = "ACTION_SET_STARTTIME";
public final static String ACTION_SET_FINISHING_AND_FINISH_TIME = "ACTION_SET_FINISHING_AND_FINISH_TIME";
public final static String ACTION_SHOW_RACELOG = "ACTION_SHOW_RACELOG";
private final StringMessages stringMessages;
@@ -39,6 +40,7 @@ public class LeaderboardRaceConfigImagesBarCell extends ImagesBarCell {
}
result.add(new ImageSpec(ACTION_REFRESH_RACELOG, stringMessages.refreshRaceLog(), makeImagePrototype(resources.reloadIcon())));
result.add(new ImageSpec(ACTION_SET_STARTTIME, stringMessages.setStartTime(), makeImagePrototype(resources.clockIcon())));
result.add(new ImageSpec(ACTION_SET_FINISHING_AND_FINISH_TIME, stringMessages.setFinishingAndFinishTime(), makeImagePrototype(resources.blueSmall())));
result.add(new ImageSpec(ACTION_SHOW_RACELOG, stringMessages.raceLog(), makeImagePrototype(resources.flagIcon())));
return result;
}
@@ -19,6 +19,7 @@ public class RaceLogTrackingEventManagementRaceImagesBarCell extends ImagesBarCe
public final static String ACTION_EDIT = "ACTION_EDIT";
public final static String ACTION_REFRESH_RACELOG = "ACTION_REFRESH_RACE_LOG";
public final static String ACTION_SET_STARTTIME = "ACTION_SET_STARTTIME";
public final static String ACTION_SET_FINISHING_AND_FINISH_TIME = "ACTION_SET_FINISHING_AND_FINISH_TIME";
public final static String ACTION_SHOW_RACELOG = "ACTION_SHOW_RACELOG";
public final static String ACTION_SET_TRACKING_TIMES = "ACTION_SET_TRACKING_TIMES";
public final static String ACTION_STOP_TRACKING = "ACTION_STOP_TRACKING";
@@ -50,6 +51,7 @@ public class RaceLogTrackingEventManagementRaceImagesBarCell extends ImagesBarCe
result.add(new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())));
result.add(new ImageSpec(ACTION_REFRESH_RACELOG, stringMessages.refreshRaceLog(), makeImagePrototype(resources.reloadIcon())));
result.add(new ImageSpec(ACTION_SET_STARTTIME, stringMessages.setStartTime(), makeImagePrototype(resources.clockIcon())));
result.add(new ImageSpec(ACTION_SET_FINISHING_AND_FINISH_TIME, stringMessages.setFinishingAndFinishTime(), makeImagePrototype(resources.blueSmall())));
result.add(new ImageSpec(ACTION_SHOW_RACELOG, stringMessages.raceLog(), makeImagePrototype(resources.flagIcon())));
result.add(new ImageSpec(ACTION_SET_TRACKING_TIMES, stringMessages.setTrackingTimes(), makeImagePrototype(resources.setTrackingTimes())));
@@ -15,6 +15,7 @@ public class SensorDataImportWidget extends AbstractFileImportWidget implements
super("/sailingserver/sensordata/import", stringMessages, table, sailingService, errorReporter);
setMultipleFileUploadEnabled(true);
setShouldClearListOnNewUploadComplete(false);
setDownsampleOptionVisible(true);
}
@Override
@@ -0,0 +1,125 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Date;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Label;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.RaceLogSetFinishingAndFinishTimeDTO;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.controls.datetime.DateAndTimeInput;
import com.sap.sse.gwt.client.controls.datetime.DateTimeInput;
import com.sap.sse.gwt.client.controls.datetime.DateTimeInput.Accuracy;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
public class SetFinishingAndFinishedTimeDialog extends SetTimeDialog<RaceLogSetFinishingAndFinishTimeDTO> {
private DateAndTimeInput finishTimeBox;
private Label currentFinishTimeLabel;
public SetFinishingAndFinishedTimeDialog(SailingServiceAsync service, ErrorReporter errorReporter, String leaderboardName,
String raceColumnName, String fleetName, StringMessages stringMessages,
DataEntryDialog.DialogCallback<RaceLogSetFinishingAndFinishTimeDTO> callback) {
super(service, errorReporter, stringMessages.setFinishingAndFinishTime(), stringMessages.setFinishingAndFinishTimeDescription(),
stringMessages.setFinishingAndFinishTime(), stringMessages.cancel(), leaderboardName, raceColumnName, fleetName,
stringMessages, new EndTimeValidator(stringMessages), callback);
this.ensureDebugId("SetFinishingAndFinishedTimeDialog");
}
@Override
protected RaceLogSetFinishingAndFinishTimeDTO getResult() {
RaceLogSetFinishingAndFinishTimeDTO dto = new RaceLogSetFinishingAndFinishTimeDTO();
dto.leaderboardName = leaderboardName;
dto.raceColumnName = raceColumnName;
dto.fleetName = fleetName;
dto.authorName = authorNameBox.getValue();
dto.authorPriority = authorPriorityBox.getValue();
dto.finishTime = finishTimeBox.getValue();
dto.finishingTime = timeBox.getValue();
dto.passId = currentPassId;
return dto;
}
@Override
protected void refreshCurrentTime() {
service.getFinishingAndFinishTime(leaderboardName, raceColumnName, fleetName,
new AsyncCallback<com.sap.sse.common.Util.Triple<Date, Date, Integer>>() {
@Override
public void onSuccess(com.sap.sse.common.Util.Triple<Date, Date, Integer> result) {
if (result == null) {
currentFinishTimeLabel.setText(stringMessages.notAvailable());
currentPassIdBox.setText(stringMessages.notAvailable());
} else {
setDate(result.getA(), currentStartOrFinishingTimeLabel, timeBox);
setDate(result.getB(), currentFinishTimeLabel, finishTimeBox);
currentPassId = result.getC().intValue();
currentPassIdBox.setText(result.getC().toString());
}
}
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(caught.getMessage());
}
});
}
void setDate(Date date, Label currentValueLabel, DateTimeInput dateTimeBox) {
if (date == null) {
currentValueLabel.setText(stringMessages.unknown());
} else {
dateTimeBox.setValue(date);
currentValueLabel.setText(DateTimeFormat
.getFormat(DateTimeFormat.PredefinedFormat.DATE_TIME_MEDIUM).format(date));
}
}
private static class EndTimeValidator implements Validator<RaceLogSetFinishingAndFinishTimeDTO> {
private final StringMessages stringMessages;
public EndTimeValidator(StringMessages stringMessages) {
this.stringMessages = stringMessages;
}
@Override
public String getErrorMessage(RaceLogSetFinishingAndFinishTimeDTO dto) {
if (dto.authorName == null || dto.authorName.isEmpty() || dto.authorPriority == null
|| dto.finishTime == null || dto.finishingTime == null) {
return stringMessages.pleaseEnterAValue();
}
// both times are != null at this point
if (dto.finishTime.before(dto.finishingTime)) {
return stringMessages.finishTimeMustBeAtOrAfterFinishingTime();
}
return null;
}
}
@Override
protected String getTimeLabel() {
return stringMessages.finishingTime();
}
@Override
protected void additionalCurrentTimeLabel(Grid content) {
currentFinishTimeLabel = new Label("");
content.setWidget(1, 0, createLabel(stringMessages.finishTimeString()));
content.setWidget(1, 1, currentFinishTimeLabel);
}
@Override
protected void addAdditionalInput(Grid content) {
}
@Override
protected void additionalInput(Grid content) {
finishTimeBox = createDateTimeBox(new Date(), Accuracy.SECONDS);
finishTimeBox.ensureDebugId("FinishTimeBox");
content.setWidget(1, 0, createLabel(stringMessages.finishTimeString()));
content.setWidget(1, 1, finishTimeBox);
}
}
@@ -0,0 +1,134 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Date;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.CaptionPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PushButton;
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.gwt.ui.client.DataEntryDialogWithDateTimeBox;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.controls.datetime.DateTimeInput;
import com.sap.sse.gwt.client.controls.datetime.DateTimeInput.Accuracy;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
public abstract class SetTimeDialog<T> extends DataEntryDialogWithDateTimeBox<T> {
private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class);
protected final SailingServiceAsync service;
protected final ErrorReporter errorReporter;
protected final String leaderboardName;
protected final String raceColumnName;
protected final String fleetName;
protected final StringMessages stringMessages;
protected int currentPassId = -1;
protected Label currentStartOrFinishingTimeLabel;
protected Label currentPassIdBox;
protected DateTimeInput timeBox;
protected TextBox authorNameBox;
protected com.sap.sse.gwt.client.controls.IntegerBox authorPriorityBox;
public SetTimeDialog(SailingServiceAsync service, ErrorReporter errorReporter, String title, String message,
String okButtonName, String cancelButtonName, String leaderboardName, String raceColumnName,
String fleetName, StringMessages stringMessages,
com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator<T> validator,
DataEntryDialog.DialogCallback<T> callback) {
super(title, message, okButtonName, cancelButtonName, validator, callback);
this.service = service;
this.errorReporter = errorReporter;
this.stringMessages = stringMessages;
this.leaderboardName = leaderboardName;
this.raceColumnName = raceColumnName;
this.fleetName = fleetName;
}
@Override
protected Widget getAdditionalWidget() {
VerticalPanel content = new VerticalPanel();
content.add(createCurrentPanel());
content.add(createInputPanel());
refreshCurrentTime();
return content;
}
private Widget createCurrentPanel() {
CaptionPanel current = new CaptionPanel(stringMessages.liveData());
HorizontalPanel currentPanel = new HorizontalPanel();
currentPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
Grid grid = new Grid(3, 2);
currentStartOrFinishingTimeLabel = new Label("");
grid.setWidget(0, 0, createLabel(getTimeLabel()));
grid.setWidget(0, 1, currentStartOrFinishingTimeLabel);
grid.setText(1, 0, "");
grid.setText(1, 1, "");
additionalCurrentTimeLabel(grid);
currentPassIdBox = new Label("");
grid.setWidget(2, 0, createLabel(stringMessages.currentPass()));
grid.setWidget(2, 1, currentPassIdBox);
currentPanel.add(grid);
PushButton refreshButton = new PushButton(new Image(resources.reloadIcon()));
refreshButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
refreshCurrentTime();
}
});
currentPanel.add(refreshButton);
current.add(currentPanel);
return current;
}
protected abstract String getTimeLabel();
private Widget createInputPanel() {
Grid content = new Grid(7, 2);
timeBox = createDateTimeBox(new Date(), Accuracy.SECONDS);
content.setWidget(0, 0, createLabel(getTimeLabel()));
content.setWidget(0, 1, timeBox);
content.setText(1, 0, "");
content.setText(1, 1, "");
additionalInput(content);
authorNameBox = createTextBox("Shore");
authorNameBox.ensureDebugId("AuthorNameTextBox");
content.setWidget(2, 0, createLabel(stringMessages.authorName()));
content.setWidget(2, 1, authorNameBox);
content.setWidget(3, 0, createLabel(stringMessages.authorPriority()));
authorPriorityBox = createIntegerBox(4, 2);
authorPriorityBox.ensureDebugId("AuthorPriorityIntegerBox");
content.setWidget(3, 1, authorPriorityBox);
addAdditionalInput(content);
return content;
}
protected void additionalCurrentTimeLabel(Grid content) {
}
protected abstract void additionalInput(Grid content);
protected abstract void addAdditionalInput(Grid content);
protected abstract void refreshCurrentTime();
}
@@ -55,6 +55,7 @@ import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapSettings;
import com.sap.sailing.gwt.ui.shared.ControlPointDTO;
import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.RaceLogSetFinishingAndFinishTimeDTO;
import com.sap.sailing.gwt.ui.shared.RaceLogSetStartTimeAndProcedureDTO;
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
@@ -380,6 +381,8 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC
refreshRaceLog(raceColumnDTOAndFleetDTO.getA(), raceColumnDTOAndFleetDTO.getB(), true);
} else if (RaceLogTrackingEventManagementRaceImagesBarCell.ACTION_SET_STARTTIME.equals(value)) {
setStartTime(getSelectedRaceColumnWithFleet().getA(), getSelectedRaceColumnWithFleet().getB());
} else if (RaceLogTrackingEventManagementRaceImagesBarCell.ACTION_SET_FINISHING_AND_FINISH_TIME.equals(value)) {
setEndTime(getSelectedRaceColumnWithFleet().getA(), getSelectedRaceColumnWithFleet().getB());
} else if (LeaderboardRaceConfigImagesBarCell.ACTION_SHOW_RACELOG.equals(value)) {
showRaceLog(raceColumnDTOAndFleetDTO.getA(), raceColumnDTOAndFleetDTO.getB());
} else if (RaceLogTrackingEventManagementRaceImagesBarCell.ACTION_SET_TRACKING_TIMES.equals(value)) {
@@ -756,6 +759,34 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC
}).show();
}
private void setEndTime(RaceColumnDTO raceColumnDTO, FleetDTO fleetDTO) {
new SetFinishingAndFinishedTimeDialog(sailingService, errorReporter, getSelectedLeaderboardName(), raceColumnDTO.getName(),
fleetDTO.getName(), stringMessages, new DialogCallback<RaceLogSetFinishingAndFinishTimeDTO>() {
@Override
public void ok(RaceLogSetFinishingAndFinishTimeDTO editedObject) {
sailingService.setFinishingAndEndTime(editedObject, new AsyncCallback<Pair<Boolean, Boolean>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(caught.getMessage());
}
@Override
public void onSuccess(Pair<Boolean, Boolean> result) {
if (!result.getA() || !result.getB()) {
Window.alert(stringMessages.failedToSetNewFinishingAndFinishTime());
} else {
trackedRacesListComposite.regattaRefresher.fillRegattas();
}
}
});
}
@Override
public void cancel() {
}
}).show();
}
private void refreshTrackingActionButtons(){
leaderboardSelectionChanged();
}
@@ -14,6 +14,7 @@ public class TrackFileImportWidget extends AbstractFileImportWidget implements I
public TrackFileImportWidget(TrackFileImportDeviceIdentifierTableWrapper table, StringMessages stringMessages,
final SailingServiceAsync sailingService, final ErrorReporter errorReporter) {
super("/sailingserver/trackfiles/import", stringMessages, table, sailingService, errorReporter);
setDownsampleOptionVisible(false);
}
@Override
@@ -40,12 +40,20 @@ public class DetailTypeFormatter {
return stringMessages.currentSpeedOverGroundInKnots();
case CURRENT_RIDE_HEIGHT_IN_METERS:
return stringMessages.currentOrAverageRideHeightInMeters();
case CURRENT_DISTANCE_FOILED_IN_METERS:
return stringMessages.currentDistanceFoiledInMeters();
case CURRENT_DURATION_FOILED_IN_SECONDS:
return stringMessages.currentDurationFoiled();
case CURRENT_HEEL_IN_DEGREES:
return stringMessages.currentHeelInDegree();
case CURRENT_PITCH_IN_DEGREES:
return stringMessages.currentPitchInDegree();
case RACE_CURRENT_RIDE_HEIGHT_IN_METERS:
return stringMessages.currentRideHeightInMeters();
case RACE_CURRENT_DURATION_FOILED_IN_SECONDS:
return stringMessages.currentDurationFoiled();
case RACE_CURRENT_DISTANCE_FOILED_IN_METERS:
return stringMessages.currentDistanceFoiledInMeters();
case CURRENT_PORT_DAGGERBOARD_RAKE:
return stringMessages.currentPortDaggerboardRake();
case CURRENT_STBD_DAGGERBOARD_RAKE:
@@ -158,6 +166,10 @@ public class DetailTypeFormatter {
return stringMessages.totalTimeSailedInSeconds();
case TOTAL_AVERAGE_SPEED_OVER_GROUND:
return stringMessages.totalAverageSpeedOverGround();
case TOTAL_DURATION_FOILED_IN_SECONDS:
return stringMessages.totalDurationFoiledInSeconds();
case TOTAL_DISTANCE_FOILED_IN_METERS:
return stringMessages.totalDistanceFoiledInMeters();
case AVERAGE_MANEUVER_LOSS_IN_METERS:
return stringMessages.averageManeuverLossInMeters();
case AVERAGE_TACK_LOSS_IN_METERS:
@@ -188,8 +200,6 @@ public class DetailTypeFormatter {
return stringMessages.beatAngle();
case COURSE_OVER_GROUND_TRUE_DEGREES:
return stringMessages.courseOverGroundTrueDegrees();
default:
break;
}
return null;
}
@@ -241,7 +251,10 @@ public class DetailTypeFormatter {
case DISTANCE_TO_START_LINE:
case CURRENT_RIDE_HEIGHT_IN_METERS:
case RACE_CURRENT_RIDE_HEIGHT_IN_METERS:
case RACE_CURRENT_DISTANCE_FOILED_IN_METERS:
case CURRENT_DEPTH_IN_METERS:
case CURRENT_DISTANCE_FOILED_IN_METERS:
case TOTAL_DISTANCE_FOILED_IN_METERS:
return stringMessages.metersUnit();
case CURRENT_DEFLECTOR_IN_MILLIMETERS:
@@ -296,6 +309,9 @@ public class DetailTypeFormatter {
case RACE_TIME_TRAVELED_REACHING:
case RACE_TIME_TRAVELED_UPWIND:
case TIME_TRAVELED:
case CURRENT_DURATION_FOILED_IN_SECONDS:
case RACE_CURRENT_DURATION_FOILED_IN_SECONDS:
case TOTAL_DURATION_FOILED_IN_SECONDS:
return stringMessages.hhmmssUnit();
// Cases for detail types without unit, so that an empty string is returned.
@@ -385,8 +401,16 @@ public class DetailTypeFormatter {
return stringMessages.currentSpeedOverGroundInKnotsTooltip();
case CURRENT_RIDE_HEIGHT_IN_METERS:
return stringMessages.currentOrAverageRideHeightInMetersTooltip();
case CURRENT_DISTANCE_FOILED_IN_METERS:
return stringMessages.currentDistanceFoiledInMetersTooltip();
case CURRENT_DURATION_FOILED_IN_SECONDS:
return stringMessages.currentDurationFoiledTooltip();
case RACE_CURRENT_RIDE_HEIGHT_IN_METERS:
return stringMessages.currentRideHeightInMetersTooltip();
case RACE_CURRENT_DURATION_FOILED_IN_SECONDS:
return stringMessages.currentDurationFoiledTooltip();
case RACE_CURRENT_DISTANCE_FOILED_IN_METERS:
return stringMessages.currentDistanceFoiledInMetersTooltip();
case CURRENT_PORT_DAGGERBOARD_RAKE:
return stringMessages.currentPortDaggerboardRakeTooltip();
case CURRENT_STBD_DAGGERBOARD_RAKE:
@@ -483,6 +507,10 @@ public class DetailTypeFormatter {
return stringMessages.totalDistanceTraveledTooltip();
case TOTAL_AVERAGE_SPEED_OVER_GROUND:
return stringMessages.totalAverageSpeedOverGroundTooltip();
case TOTAL_DURATION_FOILED_IN_SECONDS:
return stringMessages.totalDurationFoiledInSecondsTooltip();
case TOTAL_DISTANCE_FOILED_IN_METERS:
return stringMessages.totalDistanceFoiledInMetersTooltip();
case TIME_ON_TIME_FACTOR:
return stringMessages.timeOnTimeFactorTooltip();
case TIME_ON_DISTANCE_ALLOWANCE_IN_SECONDS_PER_NAUTICAL_MILE:
@@ -74,6 +74,7 @@ import com.sap.sailing.gwt.ui.shared.MarkDTO;
import com.sap.sailing.gwt.ui.shared.RaceCourseDTO;
import com.sap.sailing.gwt.ui.shared.RaceGroupDTO;
import com.sap.sailing.gwt.ui.shared.RaceLogDTO;
import com.sap.sailing.gwt.ui.shared.RaceLogSetFinishingAndFinishTimeDTO;
import com.sap.sailing.gwt.ui.shared.RaceLogSetStartTimeAndProcedureDTO;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
import com.sap.sailing.gwt.ui.shared.RaceboardDataDTO;
@@ -452,7 +453,11 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
boolean setStartTimeAndProcedure(RaceLogSetStartTimeAndProcedureDTO dto);
Pair<Boolean, Boolean> setFinishingAndEndTime(RaceLogSetFinishingAndFinishTimeDTO dto);
Util.Triple<Date, Integer, RacingProcedureType> getStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName);
Util.Triple<Date, Date, Integer> getFinishingAndFinishTime(String leaderboardName, String raceColumnName, String fleetName);
Iterable<String> getAllIgtimiAccountEmailAddresses();
@@ -63,6 +63,7 @@ import com.sap.sailing.gwt.ui.shared.MarkDTO;
import com.sap.sailing.gwt.ui.shared.RaceCourseDTO;
import com.sap.sailing.gwt.ui.shared.RaceGroupDTO;
import com.sap.sailing.gwt.ui.shared.RaceLogDTO;
import com.sap.sailing.gwt.ui.shared.RaceLogSetFinishingAndFinishTimeDTO;
import com.sap.sailing.gwt.ui.shared.RaceLogSetStartTimeAndProcedureDTO;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
import com.sap.sailing.gwt.ui.shared.RaceboardDataDTO;
@@ -619,6 +620,15 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan
*/
void setStartTimeAndProcedure(RaceLogSetStartTimeAndProcedureDTO dto, AsyncCallback<Boolean> callback);
/**
* Sets the a new finishing and end time.
*
* @param dto
* {@link RaceLogSetFinishingAndFinishTimeDTO} identifying the race and the new finishing and
* end time.
*/
void setFinishingAndEndTime(RaceLogSetFinishingAndFinishTimeDTO editedObject, AsyncCallback<Pair<Boolean, Boolean>> asyncCallback);
/**
* Gets the race's current start time, current pass identifier and racing procedure. If no start time is set, the
* pass identifier will still be returned, but the start time field will be <code>null</code>.
@@ -626,6 +636,13 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan
void getStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback<Util.Triple<Date, Integer, RacingProcedureType>> callback);
/**
* Gets the race's current finishing and finish times as well as the current pass identifier. If no finishing or finish time is set, the
* pass identifier will still be returned, but the finishing/finish time field will be <code>null</code>.
*/
void getFinishingAndFinishTime(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback<Util.Triple<Date, Date, Integer>> asyncCallback);
void getAllIgtimiAccountEmailAddresses(AsyncCallback<Iterable<String>> callback);
void getIgtimiAuthorizationUrl(String redirectProtocol, String redirectHostname, String redirectPort, AsyncCallback<String> callback);
@@ -1022,8 +1022,12 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String overwriteRacingProceduresConfigurationHelpText();
String setStartTime();
String finishTimeString();
String setFinishingAndFinishTime();
String finishTimeMustBeAtOrAfterFinishingTime();
String currentPass();
String setStartTimeDescription();
String setFinishingAndFinishTimeDescription();
String setTrackingTimesDescription();
String hasAdditionalGolfDownTime();
String additionalGolfDownTimeHelpText();
@@ -1049,6 +1053,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String doYouReallyWantToRemoveLeaderboard(String name);
String metaLeaderboardCannotBeChanged();
String failedToSetNewStartTime();
String failedToSetNewFinishingAndFinishTime();
String doYouReallyWantToRemoveIgtimiAccount(String accountEmail);
String igtimiWindImport();
String seeIgtimiTabForAccountSettings();
@@ -1933,6 +1938,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String currentStbdRudderRakeTooltip();
String currentMastRotationInDegree();
String currentMastRotationInDegreeTooltip();
String finishingTime();
String boatNameMustNotBeEmpty();
String errorUpdatingExpeditionDeviceConfiguration(String name, String message);
String doYouReallyWantToRemoveExpeditionDeviceConfiguration(String name);
@@ -1972,4 +1978,12 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String currentDriftInDegreesTooltip();
String currentDepthInMetersTooltip();
String tonsUnit();
String currentDistanceFoiledInMeters();
String currentDurationFoiled();
String currentDistanceFoiledInMetersTooltip();
String currentDurationFoiledTooltip();
String totalDurationFoiledInSeconds();
String totalDistanceFoiledInMeters();
String totalDurationFoiledInSecondsTooltip();
String totalDistanceFoiledInMetersTooltip();
}
@@ -995,7 +995,8 @@ overwriteRacingProceduresConfiguration=Activate regatta configuration
overwriteRacingProceduresConfigurationHelpText=Enabling this option will provide a base regatta configuration for all options unconfigured in the regatta-specific configuration.
setStartTime=Set start time
currentPass=Current pass
setStartTimeDescription=Select a new start time for the race. If you want to set the start time to a timepoint in the past then you have to enable the checkbox to update the pass count. Please be aware of the fact that changing the pass count will have you loose all information from the previous pass (like flag events).
setFinishingAndFinishTimeDescription=Select new finishing and finish times for the race.
setStartTimeDescription=Select a new start time for the race. If you want to set the start time to a timepoint in the past then you have to enable the checkbox to update the pass count. Please be aware of the fact that changing the pass count will have you lose all information from the previous pass (like flag events).
setTrackingTimesDescription=Set the start and end of tracking for the race.
hasAdditionalGolfDownTime=Activate additional golf down time
additionalGolfDownTimeHelpText=Activate additional golf down time
@@ -1023,6 +1024,7 @@ searchTag=Search tag
doYouReallyWantToRemoveLeaderboard=Do you really want to remove the leaderboard: ''{0}''?
metaLeaderboardCannotBeChanged=This is a meta leaderboard. It can''t be changed here.
failedToSetNewStartTime=Failed to set new start time. This can happen when the same start time is already present in the race log or someone has added a start time that is newer (in terms of a pass id and logical time) than the one that you just added.
failedToSetNewFinishingAndFinishTime=Failed to set new finishing and finished time.
doYouReallyWantToRemoveIgtimiAccount=Do you really want to remove Igtimi account granting access to data of user {0}?
igtimiWindImport=Import Wind from Igtimi
seeIgtimiTabForAccountSettings=See the ''Igtimi'' tab for account settings determining which data from Igtimi will be considered.
@@ -1918,6 +1920,10 @@ currentStbdRudderRake=Stbd rudder rake
currentStbdRudderRakeTooltip=The current starboard rudder rake
currentMastRotationInDegree=Mast rotation
currentMastRotationInDegreeTooltip=The current mast rotation in degrees
finishingTime=Finishing time
finishTimeString=Finish time
setFinishingAndFinishTime=Set finishing and finish time
finishTimeMustBeAtOrAfterFinishingTime=Finish time must be at or after finishing time
boatNameMustNotBeEmpty=Please provide a non-empty boat name
errorUpdatingExpeditionDeviceConfiguration=Error updating Expedition device configuration "{0}": {1}
doYouReallyWantToRemoveExpeditionDeviceConfiguration=Do you really want to remove Expedition device configuration "{0}"?
@@ -1956,4 +1962,12 @@ currentRudderInDegreesTooltip=Rudder angle; negative values mean to port, positi
currentLeewayInDegreesTooltip=Leeway
currentDriftInDegreesTooltip=Angle between heading and course over ground
currentDepthInMetersTooltip=Water depth
tonsUnit=t
tonsUnit=t
currentDistanceFoiledInMeters=Distance Foiled
currentDurationFoiled=Time Foiled
currentDistanceFoiledInMetersTooltip=Distance spent foiling in meters
currentDurationFoiledTooltip=Time spent foiling
totalDurationFoiledInSeconds=Total Time Foiled
totalDistanceFoiledInMeters=Total Distance Foiled
totalDurationFoiledInSecondsTooltip=Total time foiled, across the entire leaderboard
totalDistanceFoiledInMetersTooltip=Total distance foiled, across the entire leaderboard
@@ -1012,6 +1012,7 @@ doYouReallyWantToRemoveTrackedRaces=Möchten Sie die getrackten Rennen wirklich
doYouReallyWantToRemoveNonVisibleTrackedRaces=Sie entfernen auch getrackte Rennen, die während des Filterns nicht angezeigt werden. Die folgenden getrackten Rennen werden entfernt. Möchten Sie fortfahren?\n\n{0}
metaLeaderboardCannotBeChanged=Dies ist eine Meta-Rangliste, das hier nicht geändert werden kann.
failedToSetNewStartTime=Neue Startzeit konnte nicht gesetzt werden.
failedToSetNewFinishingAndFinishTime=Neue Zieleinlaufzeit und Endzeit konnte nicht gesetzt werden.
doYouReallyWantToRemoveIgtimiAccount=Möchten Sie wirklich den Zugang zu den Daten des Benutzers {0} löschen?
igtimiWindImport=Wind von Igtimi importieren
seeIgtimiTabForAccountSettings=Siehe den ''Igtimi'' Reiter für die Einstellungen, von welchen Igtimi-Konten Winddaten geladen werden sollen.
@@ -1912,6 +1913,11 @@ elements={0} Elemente
chooseDifferentDimensionTitle=Andere Dimension auswählen
chooseDifferentDimensionMessage=Zum Gruppieren eine andere Dimension auswählen
pleaseSelectADimension=Bitte eine Dimension auswählen
finishingTime=Zieleinlaufzeit
finishTimeString=Endzeitpunkt
setFinishingAndFinishTime=Setzte Zieleinlaufzeit und Endzeitpunkt
finishTimeMustBeAtOrAfterFinishingTime=Der Endzeitpunkt muss nach der Zieleinlaufzeit liegen oder gleich sein
setFinishingAndFinishTimeDescription=Wähle neue Werte für Zieleinlaufzeit und Endzeitpunkt aus.
boatNameMustNotBeEmpty=Bitte einen nicht leeren Bootsnamen angeben
errorUpdatingExpeditionDeviceConfiguration=Fehler beim Speichern der Expedition Gerätekonfiguration "{0}": {1}
doYouReallyWantToRemoveExpeditionDeviceConfiguration=Möchten Sie wirklich die Expedition Gerätekonfiguration "{0}" löschen?
@@ -1942,12 +1948,20 @@ currentTargetBoatspeedPercentageTooltip=Anteil der erreichten berechneten Zielge
currentRakeInDegreesTooltip=Mastneigung
currentForestayLoadTooltip=Gemessene Vorstagsspannung
currentForestayPressureTooltip=Druck am Vorstag
currentDeflectorPercentageTooltip=Prozentualer Anteil Abweiser (???)
currentDeflectorInMillimetersTooltip=Abweiser Abstand (???)
currentDeflectorPercentageTooltip=Prozentualer Anteil Deflektor
currentDeflectorInMillimetersTooltip=Deflektor-Abstand
currentTackAngleInDegreesTooltip=Berechneter optimaler Wendewinkel
currentSetTooltip=Set (???)
currentRudderInDegreesTooltip=Ruderwinkel; negative Werte bedeuten Ruder nach Backbord, positive Ruder nach Steuerbord
currentLeewayInDegreesTooltip=Leeway (???)
currentDriftInDegreesTooltip=Winkel zwischen Kurs durchs Wasser und Kurs über Grund
currentDepthInMetersTooltip=Wassertiefe
tonsUnit=t
tonsUnit=t
currentDistanceFoiledInMeters=Gefoilte Strecke
currentDurationFoiled=Gefoilte Zeit
currentDistanceFoiledInMetersTooltip=Gefoilte Strecke in Metern
currentDurationFoiledTooltip=Gefoilte Zeit
totalDurationFoiledInSeconds=Gesamt gefoilte Zeit
totalDistanceFoiledInMeters=Gesamt gefoilte Strecke
totalDurationFoiledInSecondsTooltip=Insgesamt gefoilte Zeit, über die gesamte Ergebnisliste kumuliert
totalDistanceFoiledInMetersTooltip=Insgesamt gefoilte Strecke, über die gesamte Ergebnisliste kumuliert
@@ -1877,6 +1877,7 @@ moreLoginInformationSectionUserSettingsDescription=Muchos elementos de la interf
moreLoginInformationSectionStrategySimulatorDescription=El simulador permite a los navegantes simular estrategias para tramos basados en condiciones de viento configurables. El simulador se basa en todos los datos históricos disponibles en SAP Sailing Analytics para proporcionar simulaciones en el comportamiento exacto de clases de embarcación específicas.
moreLoginInformationSectionUserNotificationsHeading=Notificaciones de usuario
moreLoginInformationSectionUserNotificationsDescription=Las notificaciones de usuario son una forma sencilla de mantenerle al tanto de su competidor/equipo o clase de embarcación favorita. Puede suscribirse a notificaciones basadas en e-mail sobre resultados nuevos para su navegante/equipo o clase de embarcación favorita, así como para próximas carreras de clases de embarcación específicas.
moreLoginInformationRegisterControlText=Regístrese ahora
anniversaryUnitText=Carreras
anniversaryUnitText[one]=Carrera
anniversaryMajorCountdownTeaser=Cuenta atrás. Solo faltan {0,number,#,###} carreras hasta la {1,number,#,###} carrera.
@@ -1914,3 +1915,14 @@ currentStbdRudderRake=Inclinación del timón a estribor
currentStbdRudderRakeTooltip=La inclinación actual del timón a estribor
currentMastRotationInDegree=Rotación del mástil
currentMastRotationInDegreeTooltip=La rotación actual del mástil en grados
boatNameMustNotBeEmpty=Proporcione un nombre de embarcación que no esté en blanco
errorUpdatingExpeditionDeviceConfiguration=Error al actualizar la configuración del dispositivo con la expedición "{0}": {1}
doYouReallyWantToRemoveExpeditionDeviceConfiguration=¿Desea realmente eliminar la configuración del dispositivo con la expedición "{0}"?
errorTryingToRemoveExpeditionDeviceConfiguration=Error al intentar eliminar la configuración del dispositivo con la expedición "{0}": {1}
errorFetchingExpeditionDeviceConfigurations=Error al buscar las configuraciones del dispositivo con la expedición: {0}
expeditionDeviceConfigurations=Configuraciones de dispositivo con expedición
addExpeditionDeviceConfiguration=Añadir configuración de dispositivo con expedición
editExpeditionDeviceConfiguration=Tratar configuración de dispositivo con expedición
expeditionBoatId=ID de embarcación (por ejemplo 0, 1, o 2)
doYouReallyWantToRemoveTheSelectedExpeditionDeviceConfigurations=¿Desea realmente eliminar las configuraciones del dispositivo con expedición seleccionadas?
doYouReallyWantToRemoveTheSelectedIgtimiAccounts=¿Desea realmente eliminar las cuentas Igtimi seleccionadas?
@@ -895,7 +895,7 @@ 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 de l''expédition
windImport_Title=Importer vent à partir de Expedition
windImport_BoatId=N° du bateau :
windImport_SelectedRacesWarning=Voulez-vous réellement importer les données de vent dans les {0} courses ?\nNotez que seules les courses dont l''intervalle est pertinent seront prises en compte.
windImport_AllRacesWarning=Voulez-vous réellement importer les données de vent dans toutes les courses ?\nNotez que seules les courses dont l''intervalle est pertinent seront prises en compte.
@@ -1877,6 +1877,7 @@ moreLoginInformationSectionUserSettingsDescription=Certaines éléments de l''in
moreLoginInformationSectionStrategySimulatorDescription=Le simulateur permet aux navigateurs de simuler des stratégies sur des portions de parcours grâce à des conditions de vent configurables. Le simulateur se base sur l''ensemble des données historiques disponibles dans SAP Sailing Analytics pour fournir des simulations de comportements de catégories de bateaux spécifiques les plus précises possibles.
moreLoginInformationSectionUserNotificationsHeading=Notifications utilisateur
moreLoginInformationSectionUserNotificationsDescription=Grâce aux notifications utilisateur, vous pouvez rester informé sur votre catégorie de bateau, votre équipe ou votre concurrent favoris. Vous avez la possibilité de vous abonner afin de recevoir des notifications par e-mail sur votre catégorie de bateau, votre équipe ou votre concurrent favoris, ainsi que sur les différentes courses à venir.
moreLoginInformationRegisterControlText=S''inscrire maintenant
anniversaryUnitText=Courses
anniversaryUnitText[one]=Course
anniversaryMajorCountdownTeaser=Attention ! Plus que {0,number,#,###} courses avant la {1,number,#,###}e course.
@@ -1914,3 +1915,14 @@ currentStbdRudderRake=Inclinaison du gouvernail tribord
currentStbdRudderRakeTooltip=Inclinaison actuelle du gouvernail tribord
currentMastRotationInDegree=Rotation du mât
currentMastRotationInDegreeTooltip=Rotation actuelle du mât en degrés
boatNameMustNotBeEmpty=Veuillez indiquer un nom de bateau.
errorUpdatingExpeditionDeviceConfiguration=Erreur lors de la mise à jour de la configuration du terminal Expedition "{0}" : {1}
doYouReallyWantToRemoveExpeditionDeviceConfiguration=Voulez-vous vraiment supprimer la configuration du terminal Expedition "{0}" ?
errorTryingToRemoveExpeditionDeviceConfiguration=Erreur lors de la tentative de suppression de la configuration du terminal Expedition "{0}" : {1}
errorFetchingExpeditionDeviceConfigurations=Erreur lors de l''accès aux configurations du terminal Expedition : {0}
expeditionDeviceConfigurations=Configurations du terminal Expedition
addExpeditionDeviceConfiguration=Ajouter configuration du terminal Expedition
editExpeditionDeviceConfiguration=Modifier configuration du terminal Expedition
expeditionBoatId=ID du bateau (par exemple 0, 1 ou 2)
doYouReallyWantToRemoveTheSelectedExpeditionDeviceConfigurations=Voulez-vous vraiment supprimer les configurations du terminal Expedition sélectionnées ?
doYouReallyWantToRemoveTheSelectedIgtimiAccounts=Voulez-vous vraiment supprimer les comptes lgtimi sélectionnés ?
@@ -1877,6 +1877,7 @@ moreLoginInformationSectionUserSettingsDescription=ユーザーインタフェ
moreLoginInformationSectionStrategySimulatorDescription=シミュレータにより、セーラーは設定可能な風況に基づいて各レグの戦略をシミュレートすることが可能です。このシミュレータは SAP Sailing Analytics で利用可能なすべての履歴データに基づいており、特定の艇種の動作について正確なシミュレーションが提供されます。
moreLoginInformationSectionUserNotificationsHeading=ユーザー通知
moreLoginInformationSectionUserNotificationsDescription=ユーザー通知は、お気に入りの競技者/チームまたは艇種についての最新情報を得るための簡単な方法です。お気に入りのセーラー/チームまたは艇種についての新しい結果、特定の艇種で予定されているレースに関するメールベースの通知を購読できます。
moreLoginInformationRegisterControlText=今すぐご登録を
anniversaryUnitText=レース
anniversaryUnitText[one]=レース
anniversaryMajorCountdownTeaser=カウントダウン情報です。{1,number,#,###} 番目のレースまでもうわずか {0,number,#,###} レースです。
@@ -1914,3 +1915,14 @@ currentStbdRudderRake=右舷ラダー傾斜角
currentStbdRudderRakeTooltip=現在の右舷ラダー傾斜角
currentMastRotationInDegree=マスト回転
currentMastRotationInDegreeTooltip=現在のマスト回転 (度)
boatNameMustNotBeEmpty=空でない艇名を指定してください
errorUpdatingExpeditionDeviceConfiguration=Expedition デバイス設定 "{0}" の更新でエラーが発生: {1}
doYouReallyWantToRemoveExpeditionDeviceConfiguration=Expedition デバイス設定 "{0}" を削除しますか
errorTryingToRemoveExpeditionDeviceConfiguration=Expedition デバイス設定 "{0}" を削除しようとしてエラーが発生: {1}
errorFetchingExpeditionDeviceConfigurations=Expedition デバイス設定のフェッチでエラーが発生: {0}
expeditionDeviceConfigurations=Expedition デバイス設定
addExpeditionDeviceConfiguration=Expedition デバイス設定の追加
editExpeditionDeviceConfiguration=Expedition デバイス設定の編集
expeditionBoatId=艇 ID (たとえば 0、1、2 など)
doYouReallyWantToRemoveTheSelectedExpeditionDeviceConfigurations=選択した Expedition デバイス設定を削除しますか
doYouReallyWantToRemoveTheSelectedIgtimiAccounts=選択した Igtimi アカウントを削除しますか
@@ -1877,6 +1877,7 @@ moreLoginInformationSectionUserSettingsDescription=Muitas partes da interface do
moreLoginInformationSectionStrategySimulatorDescription=O simulador permite que os velejadores simulem estratégias para pernas com base em condições do vento configuráveis. O simulador se baseia em todos os dados históricos disponíveis no SAP Sailing Analytics para fornecer simulações do comportamento exato de classes de barcos específicas.
moreLoginInformationSectionUserNotificationsHeading=Notificações do usuário
moreLoginInformationSectionUserNotificationsDescription=As notificações do usuário são uma forma fácil de manter você atualizado em relação a seu competidor/equipe ou classe de barcos favoritos. Você pode assinar notificações com base no e-mail sobre novos resultados para seu velejador/equipe ou classe de barcos favoritos e sobre as próximas corridas de classes de barcos específicas.
moreLoginInformationRegisterControlText=Inscreva-se agora
anniversaryUnitText=Corridas
anniversaryUnitText[one]=Corrida
anniversaryMajorCountdownTeaser=Contagem regressiva! Só restam {0,number,#,###} corridas até a {1,number,#,###}ª corrida.
@@ -1914,3 +1915,14 @@ currentStbdRudderRake=Inclinação leme boreste
currentStbdRudderRakeTooltip=A inclinação atual do leme para boreste
currentMastRotationInDegree=Rotação do mastro
currentMastRotationInDegreeTooltip=A rotação atual do mastro em graus
boatNameMustNotBeEmpty=Forneça um nome de barco não vazio
errorUpdatingExpeditionDeviceConfiguration=Erro ao atualizar configuração de dispositivo Expedition "{0}": {1}
doYouReallyWantToRemoveExpeditionDeviceConfiguration=Você quer realmente remover a configuração do dispositivo Expedition "{0}"?
errorTryingToRemoveExpeditionDeviceConfiguration=Erro ao tentar remover a configuração do dispositivo Expedition "{0}": {1}
errorFetchingExpeditionDeviceConfigurations=Erro ao chamar configurações do dispositivo Expedition: {0}
expeditionDeviceConfigurations=Configurações do dispositivo Expedition
addExpeditionDeviceConfiguration=Adicionar configuração do dispositivo Expedition
editExpeditionDeviceConfiguration=Processar configuração do dispositivo Expedition
expeditionBoatId=ID do barco (por ex., 0, 1 ou 2)
doYouReallyWantToRemoveTheSelectedExpeditionDeviceConfigurations=Você quer realmente remover as configurações do dispositivo Expedition selecionado?
doYouReallyWantToRemoveTheSelectedIgtimiAccounts=Você quer realmente remover as contas de Igtimi selecionadas?
@@ -1877,6 +1877,7 @@ moreLoginInformationSectionUserSettingsDescription=Пользователи мо
moreLoginInformationSectionStrategySimulatorDescription=Симулятор позволяет мореходам моделировать стратегии для разных отрезков на основе настраиваемых атмосферных условий. Симулятор основан на исторических данных SAP Sailing Analytics и позволяет точно предсказать поведение лодок разных классов.
moreLoginInformationSectionUserNotificationsHeading=Уведомления для пользователя
moreLoginInformationSectionUserNotificationsDescription=Уведомления — это удобный способ вовремя получать информацию о любимых участниках, командах и классах лодок. Подпишитесь на рассылку по электронной почте, чтобы следить за результатами нужных участников, команд или лодок определенного класса, а также о будущих гонках в определенном классе.
moreLoginInformationRegisterControlText=Зарегистрироваться сейчас
anniversaryUnitText=Гонки
anniversaryUnitText[one]=Гонка
anniversaryMajorCountdownTeaser=Обратный отсчет! До {1,number,#,###} гонки осталось всего {0,number,#,###} гонки(ок).
@@ -1914,3 +1915,14 @@ currentStbdRudderRake=Скос задней кромки руля на прав
currentStbdRudderRakeTooltip=Текущий скос задней кромки руля на правый борт
currentMastRotationInDegree=Вращение мачты
currentMastRotationInDegreeTooltip=Текущее значение вращения мачты в градусах
boatNameMustNotBeEmpty=Укажите непустое имя лодки
errorUpdatingExpeditionDeviceConfiguration=Ошибка при обновлении конфигурации устройства Expedition "{0}": {1}
doYouReallyWantToRemoveExpeditionDeviceConfiguration=Действительно удалить конфигурацию устройства Expedition "{0}"?
errorTryingToRemoveExpeditionDeviceConfiguration=Ошибка при попытке удалить конфигурацию устройства Expedition "{0}": {1}
errorFetchingExpeditionDeviceConfigurations=Ошибка при вызове конфигураций устройства Expedition: {0}
expeditionDeviceConfigurations=Конфигурации устройства Expedition
addExpeditionDeviceConfiguration=Добавить конфигурацию устройства Expedition
editExpeditionDeviceConfiguration=Редактировать конфигурацию устройства Expedition
expeditionBoatId=Ид. лодки (например, 0, 1, или 2)
doYouReallyWantToRemoveTheSelectedExpeditionDeviceConfigurations=Действительно удалить выбранные конфигурации устройства Expedition?
doYouReallyWantToRemoveTheSelectedIgtimiAccounts=Действительно удалить выбранные учетные записи Igtimi?
@@ -1877,6 +1877,7 @@ moreLoginInformationSectionUserSettingsDescription=用户界面的许多部分
moreLoginInformationSectionStrategySimulatorDescription=水手可以使用模拟器根据可配置的风力条件模拟航段策略。此模拟器基于 SAP Sailing Analytics 中可用的所有历史数据,以提供有关特定船只级别准确行为的模拟。
moreLoginInformationSectionUserNotificationsHeading=用户通知
moreLoginInformationSectionUserNotificationsDescription=用户通知是让您及时了解最喜欢的参赛队/船队或船只级别最新动态的简单方法。您可以订阅基于邮件的通知,了解最喜爱的水手/船队或船只级别的新结果,以及即将进行的特定船只级别的比赛。
moreLoginInformationRegisterControlText=立即注册
anniversaryUnitText=比赛轮次
anniversaryUnitText[one]=比赛轮次
anniversaryMajorCountdownTeaser=倒计时!距离第{1,number,#,###}轮比赛仅剩{0,number,#,###}轮比赛。
@@ -1914,3 +1915,14 @@ currentStbdRudderRake=右舵倾斜度
currentStbdRudderRakeTooltip=当前右舵倾斜度
currentMastRotationInDegree=桅杆旋转度
currentMastRotationInDegreeTooltip=当前桅杆旋转度数
boatNameMustNotBeEmpty=请提供非空船只名称
errorUpdatingExpeditionDeviceConfiguration=更新导航图设备配置 "{0}" 出错:{1}
doYouReallyWantToRemoveExpeditionDeviceConfiguration=是否确定移除导航图设备配置 "{0}"?
errorTryingToRemoveExpeditionDeviceConfiguration=尝试移除导航图设备配置 "{0}" 出错:{1}
errorFetchingExpeditionDeviceConfigurations=获取导航图设备配置出错:{0}
expeditionDeviceConfigurations=导航图设备配置
addExpeditionDeviceConfiguration=添加导航图设备配置
editExpeditionDeviceConfiguration=编辑导航图设备配置
expeditionBoatId=船只编号(例如 0、1 或 2)
doYouReallyWantToRemoveTheSelectedExpeditionDeviceConfigurations=是否确定移除所选的导航图设备配置?
doYouReallyWantToRemoveTheSelectedIgtimiAccounts=是否确定移除所选的 Igtimi 账户?
@@ -1118,6 +1118,7 @@ public abstract class LeaderboardPanel<LS extends LeaderboardSettings> extends A
DetailType.RACE_CALCULATED_TIME_TRAVELED,
DetailType.RACE_CALCULATED_TIME_AT_ESTIMATED_ARRIVAL_AT_COMPETITOR_FARTHEST_AHEAD,
DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS, DetailType.RACE_CURRENT_RIDE_HEIGHT_IN_METERS,
DetailType.RACE_CURRENT_DISTANCE_FOILED_IN_METERS, DetailType.RACE_CURRENT_DURATION_FOILED_IN_SECONDS,
DetailType.RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS, DetailType.NUMBER_OF_MANEUVERS,
DetailType.DISPLAY_LEGS, DetailType.CURRENT_LEG,
DetailType.RACE_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS,
@@ -1138,6 +1139,7 @@ public abstract class LeaderboardPanel<LS extends LeaderboardSettings> extends A
public static DetailType[] getAvailableOverallDetailColumnTypes() {
return new DetailType[] { DetailType.REGATTA_RANK, DetailType.TOTAL_DISTANCE_TRAVELED,
DetailType.TOTAL_AVERAGE_SPEED_OVER_GROUND, DetailType.TOTAL_TIME_SAILED_IN_SECONDS,
DetailType.TOTAL_DURATION_FOILED_IN_SECONDS, DetailType.TOTAL_DISTANCE_FOILED_IN_METERS,
DetailType.MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS, DetailType.TIME_ON_TIME_FACTOR,
DetailType.TIME_ON_DISTANCE_ALLOWANCE_IN_SECONDS_PER_NAUTICAL_MILE };
}
@@ -1206,6 +1208,14 @@ public abstract class LeaderboardPanel<LS extends LeaderboardSettings> extends A
new RideHeightColumn(DetailType.RACE_CURRENT_RIDE_HEIGHT_IN_METERS,
new RaceCurrentRideHeightInMeters(), LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE,
LeaderboardPanel.this));
result.put(DetailType.RACE_CURRENT_DISTANCE_FOILED_IN_METERS,
new FormattedDoubleDetailTypeColumn(DetailType.RACE_CURRENT_DISTANCE_FOILED_IN_METERS,
new RaceDistanceFoiledInMeters(), LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE,
LeaderboardPanel.this));
result.put(DetailType.RACE_CURRENT_DURATION_FOILED_IN_SECONDS,
new TotalTimeColumn(DetailType.RACE_CURRENT_DURATION_FOILED_IN_SECONDS,
new RaceDurationFoiledInSeconds(), LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE,
LeaderboardPanel.this));
result.put(DetailType.RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS,
new FormattedDoubleDetailTypeColumn(DetailType.RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS,
new RaceDistanceToCompetitorFarthestAheadInMeters(), LEG_COLUMN_HEADER_STYLE,
@@ -1347,7 +1357,7 @@ public abstract class LeaderboardPanel<LS extends LeaderboardSettings> extends A
private class RaceAverageSpeedInKnots implements LegDetailField<Double> {
@Override
public Double get(LeaderboardRowDTO row) {
final Distance distanceTraveledInMeters = row.getDistanceTraveledInMeters(getRaceColumnName());
final Distance distanceTraveledInMeters = row.getDistanceTraveled(getRaceColumnName());
final Duration time = row.getTimeSailed(getRaceColumnName());
final Double result;
if (distanceTraveledInMeters != null && time != null) {
@@ -1537,11 +1547,37 @@ public abstract class LeaderboardPanel<LS extends LeaderboardSettings> extends A
private class RaceDistanceTraveledInMeters implements LegDetailField<Double> {
@Override
public Double get(LeaderboardRowDTO row) {
Distance distanceForRaceColumn = row.getDistanceTraveledInMeters(getRaceColumnName());
Distance distanceForRaceColumn = row.getDistanceTraveled(getRaceColumnName());
return distanceForRaceColumn == null ? null : distanceForRaceColumn.getMeters();
}
}
/**
* Accumulates the distance foiled over all legs of a race
*
* @author Axel Uhl (D043530)
*/
private class RaceDistanceFoiledInMeters implements LegDetailField<Double> {
@Override
public Double get(LeaderboardRowDTO row) {
Distance distanceForRaceColumn = row.getDistanceFoiled(getRaceColumnName());
return distanceForRaceColumn == null ? null : distanceForRaceColumn.getMeters();
}
}
/**
* Accumulates the duration foiled over all legs of a race
*
* @author Axel Uhl (D043530)
*/
private class RaceDurationFoiledInSeconds implements LegDetailField<Double> {
@Override
public Double get(LeaderboardRowDTO row) {
Duration durationForRaceColumn = row.getDurationFoiled(getRaceColumnName());
return durationForRaceColumn == null ? null : durationForRaceColumn.asSeconds();
}
}
private class RaceCalculatedTimeTraveledInSeconds implements LegDetailField<Double> {
@Override
public Double get(LeaderboardRowDTO row) {
@@ -2055,6 +2091,20 @@ public abstract class LeaderboardPanel<LS extends LeaderboardSettings> extends A
}
}
private static class TotalDistanceFoiledInMetersField implements LegDetailField<Double> {
@Override
public Double get(LeaderboardRowDTO row) {
return row.totalDistanceFoiledInMeters;
}
}
private static class TotalDurationFoiledInSecondsField implements LegDetailField<Double> {
@Override
public Double get(LeaderboardRowDTO row) {
return row.totalDurationFoiledInSeconds;
}
}
private static class TimeOnTimeFactorColumn implements LegDetailField<Double> {
@Override
public Double get(LeaderboardRowDTO row) {
@@ -2096,6 +2146,10 @@ public abstract class LeaderboardPanel<LS extends LeaderboardSettings> extends A
result.put(DetailType.MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS,
new MaxSpeedOverallColumn(RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE, this));
result.put(DetailType.TOTAL_TIME_SAILED_IN_SECONDS, createOverallTimeTraveledColumn());
result.put(DetailType.TOTAL_DURATION_FOILED_IN_SECONDS, new TotalTimeColumn(DetailType.TOTAL_DURATION_FOILED_IN_SECONDS,
new TotalDurationFoiledInSecondsField(), RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE, this));
result.put(DetailType.TOTAL_DISTANCE_FOILED_IN_METERS, new FormattedDoubleDetailTypeColumn(DetailType.TOTAL_DISTANCE_FOILED_IN_METERS,
new TotalDistanceFoiledInMetersField(), RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE, this));
result.put(DetailType.TIME_ON_TIME_FACTOR, new FormattedDoubleDetailTypeColumn(DetailType.TIME_ON_TIME_FACTOR,
new TimeOnTimeFactorColumn(), RACE_COLUMN_HEADER_STYLE, RACE_COLUMN_STYLE, this));
result.put(DetailType.TIME_ON_DISTANCE_ALLOWANCE_IN_SECONDS_PER_NAUTICAL_MILE,
@@ -122,6 +122,20 @@ public class LegColumn extends ExpandableSortableColumn<String> {
}
}
private class CurrentDistanceFoiledInMeters extends AbstractLegDetailField<Double> {
@Override
protected Double getFromNonNullEntry(LegEntryDTO entry) {
return entry.currentDistanceFoiledInMeters;
}
}
private class CurrentDurationFoiled extends AbstractLegDetailField<Double> {
@Override
protected Double getFromNonNullEntry(LegEntryDTO entry) {
return entry.currentDurationFoiledInSeconds;
}
}
private class EstimatedTimeToNextWaypointInSeconds extends AbstractLegDetailField<Double> {
@Override
protected Double getFromNonNullEntry(LegEntryDTO entry) {
@@ -272,6 +286,7 @@ public class LegColumn extends ExpandableSortableColumn<String> {
DetailType.GAP_CHANGE_SINCE_LEG_START_IN_SECONDS, DetailType.SIDE_TO_WHICH_MARK_AT_LEG_START_WAS_ROUNDED,
DetailType.CURRENT_SPEED_OVER_GROUND_IN_KNOTS, DetailType.CURRENT_HEEL_IN_DEGREES,
DetailType.CURRENT_PITCH_IN_DEGREES, DetailType.CURRENT_RIDE_HEIGHT_IN_METERS,
DetailType.CURRENT_DISTANCE_FOILED_IN_METERS, DetailType.CURRENT_DURATION_FOILED_IN_SECONDS,
DetailType.WINDWARD_DISTANCE_TO_GO_IN_METERS, DetailType.NUMBER_OF_MANEUVERS,
DetailType.ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS, DetailType.VELOCITY_MADE_GOOD_IN_KNOTS,
DetailType.TIME_TRAVELED, DetailType.CORRECTED_TIME_TRAVELED, DetailType.AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS,
@@ -299,6 +314,10 @@ public class LegColumn extends ExpandableSortableColumn<String> {
detailHeaderStyle, detailColumnStyle, leaderboardPanel));
result.put(DetailType.CURRENT_RIDE_HEIGHT_IN_METERS, new RideHeightColumn(DetailType.CURRENT_RIDE_HEIGHT_IN_METERS,
new CurrentRideHeightInMeters(), detailHeaderStyle, detailColumnStyle, leaderboardPanel));
result.put(DetailType.CURRENT_DISTANCE_FOILED_IN_METERS, new FormattedDoubleDetailTypeColumn(DetailType.CURRENT_DISTANCE_FOILED_IN_METERS,
new CurrentDistanceFoiledInMeters(), detailHeaderStyle, detailColumnStyle, leaderboardPanel));
result.put(DetailType.CURRENT_DURATION_FOILED_IN_SECONDS, new TotalTimeColumn(DetailType.CURRENT_DURATION_FOILED_IN_SECONDS,
new CurrentDurationFoiled(), detailHeaderStyle, detailColumnStyle, leaderboardPanel));
result.put(DetailType.ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS,
new FormattedDoubleDetailTypeColumn(DetailType.ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS, new EstimatedTimeToNextWaypointInSeconds(),
detailHeaderStyle, detailColumnStyle, leaderboardPanel));
@@ -4,6 +4,12 @@ import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.dto.LeaderboardRowDTO;
import com.sap.sailing.gwt.ui.client.DurationAsHoursMinutesSecondsFormatter;
/**
* Displays a {@code double} value that is assumed to represent seconds as a time in hours, minutes and seconds.
*
* @author Axel Uhl (D043530)
*
*/
public class TotalTimeColumn extends FormattedDoubleDetailTypeColumn {
protected TotalTimeColumn(DetailType detailType, LegDetailField<Double> field, String headerStyle,
@@ -354,6 +354,7 @@ import com.sap.sailing.gwt.ui.shared.RaceInfoDTO.LineStartInfoDTO;
import com.sap.sailing.gwt.ui.shared.RaceInfoDTO.RaceInfoExtensionDTO;
import com.sap.sailing.gwt.ui.shared.RaceLogDTO;
import com.sap.sailing.gwt.ui.shared.RaceLogEventDTO;
import com.sap.sailing.gwt.ui.shared.RaceLogSetFinishingAndFinishTimeDTO;
import com.sap.sailing.gwt.ui.shared.RaceLogSetStartTimeAndProcedureDTO;
import com.sap.sailing.gwt.ui.shared.RaceMapDataDTO;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
@@ -473,7 +474,6 @@ import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.Util.Triple;
import com.sap.sse.common.WithID;
import com.sap.sse.common.impl.MillisecondsDurationImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.common.impl.TimeRangeImpl;
import com.sap.sse.common.mail.MailException;
@@ -2898,10 +2898,14 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
}
break;
case VELOCITY_MADE_GOOD_IN_KNOTS:
final Speed velocityMadeGood;
if (trackedLeg != null) {
Speed velocityMadeGood = trackedLeg.getVelocityMadeGood(timePoint, WindPositionMode.EXACT, cache);
result = (velocityMadeGood == null) ? null : velocityMadeGood.getKnots();
velocityMadeGood = trackedLeg.getVelocityMadeGood(timePoint, WindPositionMode.EXACT, cache);
} else {
// check if wind information is available; if so, compute a VMG only based on wind data:
velocityMadeGood = trackedRace.getVelocityMadeGood(competitor, timePoint, cache);
}
result = (velocityMadeGood == null) ? null : velocityMadeGood.getKnots();
break;
case DISTANCE_TRAVELED:
if (trackedLeg != null) {
@@ -3097,7 +3101,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
TimePoint newestEvent = trackedRace.getTimePointOfNewestEvent();
final TimePoint startTime = from == null ? trackedRace.getStartOfTracking() : new MillisecondsTimePoint(from);
final TimePoint endTime = (to == null || to.after(newestEvent.asDate())) ? newestEvent : new MillisecondsTimePoint(to);
final long adjustedStepSizeInMillis = (long) Math.max((double) stepSizeInMillis, startTime.until(endTime).divide(new MillisecondsDurationImpl(stepSizeInMillis)));
final long adjustedStepSizeInMillis = (long) Math.max((double) stepSizeInMillis, startTime.until(endTime).divide(SailingServiceConstants.MAX_NUMBER_OF_FIXES_TO_QUERY).asMillis());
result = new CompetitorsRaceDataDTO(detailType, startTime==null?null:startTime.asDate(), endTime==null?null:endTime.asDate());
final int MAX_CACHE_SIZE = SailingServiceConstants.MAX_NUMBER_OF_FIXES_TO_QUERY;
final ConcurrentHashMap<TimePoint, WindLegTypeAndLegBearingCache> cachesByTimePoint = new ConcurrentHashMap<>();
@@ -5166,6 +5170,20 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
dto.racingProcedure);
return new MillisecondsTimePoint(dto.startTime).equals(newStartTime);
}
@Override
public Pair<Boolean, Boolean> setFinishingAndEndTime(RaceLogSetFinishingAndFinishTimeDTO dto) {
TimePoint newFinsihingTime = getService().setFinishingTime(dto.leaderboardName, dto.raceColumnName,
dto.fleetName, dto.authorName, dto.authorPriority,
dto.passId, new MillisecondsTimePoint(dto.finishingTime));
TimePoint newEndTime = getService().setEndTime(dto.leaderboardName, dto.raceColumnName,
dto.fleetName, dto.authorName, dto.authorPriority,
dto.passId, new MillisecondsTimePoint(dto.finishTime));
return new Pair<Boolean, Boolean>(new MillisecondsTimePoint(dto.finishingTime).equals(newFinsihingTime),
new MillisecondsTimePoint(dto.finishTime).equals(newEndTime));
}
@Override
public void setTrackingTimes(RaceLogSetTrackingTimesDTO dto) throws NotFoundException {
@@ -5243,6 +5261,17 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
}
return new com.sap.sse.common.Util.Triple<Date, Integer, RacingProcedureType>(result.getA() == null ? null : result.getA().asDate(), result.getB(), result.getC());
}
@Override
public com.sap.sse.common.Util.Triple<Date, Date, Integer> getFinishingAndFinishTime(String leaderboardName, String raceColumnName, String fleetName) {
com.sap.sse.common.Util.Triple<TimePoint, TimePoint, Integer> result = getService().getFinishingAndFinishTime(leaderboardName, raceColumnName, fleetName);
if (result == null) {
return null;
}
return new com.sap.sse.common.Util.Triple<>(result.getA() == null ? null : result.getA().asDate(),
result.getB() == null ? null : result.getB().asDate(), result.getC());
}
@Override
public Iterable<String> getAllIgtimiAccountEmailAddresses() {
@@ -6696,6 +6725,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
}
if (hasBravoTrack) {
availableDetailsTypes.add(DetailType.RACE_CURRENT_RIDE_HEIGHT_IN_METERS);
availableDetailsTypes.add(DetailType.RACE_CURRENT_DURATION_FOILED_IN_SECONDS);
availableDetailsTypes.add(DetailType.RACE_CURRENT_DISTANCE_FOILED_IN_METERS);
availableDetailsTypes.add(DetailType.CURRENT_HEEL_IN_DEGREES);
availableDetailsTypes.add(DetailType.CURRENT_PITCH_IN_DEGREES);
}
@@ -0,0 +1,26 @@
package com.sap.sailing.gwt.ui.shared;
import java.util.Date;
import com.google.gwt.user.client.rpc.IsSerializable;
/**
* DTO holding all information to set a new start time on a specific race.
*/
public class RaceLogSetFinishingAndFinishTimeDTO implements IsSerializable {
public String leaderboardName;
public String raceColumnName;
public String fleetName;
public int passId;
public String authorName;
public Integer authorPriority;
public Date finishTime;
public Date finishingTime;
@Override
public String toString() {
return "RaceLogSetEndTimeDTO [leaderboardName=" + leaderboardName + ", raceColumnName=" + raceColumnName
+ ", fleetName=" + fleetName + ", passId=" + passId + ", authorName=" + authorName + ", authorPriority="
+ authorPriority + ", finishTime=" + finishTime + ", endTime=" + finishingTime + "]";
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

@@ -21,6 +21,7 @@ import com.mongodb.MongoException;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.race.CompetitorResult;
import com.sap.sailing.domain.abstractlog.race.CompetitorResult.MergeState;
import com.sap.sailing.domain.abstractlog.race.CompetitorResults;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent;
@@ -311,7 +312,7 @@ public class TestStoringAndRetrievingRaceLogInLeaderboards extends RaceLogMongoD
/* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null);
CompetitorResults storedPositioningList = new CompetitorResultsImpl();
storedPositioningList.add(new CompetitorResultImpl(storedCompetitor.getId(), storedCompetitor.getName(), /* rank */
1, MaxPointsReason.NONE, /* score */ null, /* finishingTimePoint */null, /* comment */null));
1, MaxPointsReason.NONE, /* score */ null, /* finishingTimePoint */null, /* comment */null, MergeState.OK));
RaceLogFinishPositioningConfirmedEvent event = new RaceLogFinishPositioningConfirmedEventImpl(now, author, 0, storedPositioningList);
addAndStoreRaceLogEvent(leaderboard, raceColumnName, event);
RaceLog loadedRaceLog = retrieveRaceLog();
@@ -337,7 +338,7 @@ public class TestStoringAndRetrievingRaceLogInLeaderboards extends RaceLogMongoD
final TimePoint finishingTimePoint = MillisecondsTimePoint.now();
final String comment = "This is a comment";
storedPositioningList.add(new CompetitorResultImpl(storedCompetitor.getId(), storedCompetitor.getName(), /* rank */
1, MaxPointsReason.NONE, /* score */ 12.3, finishingTimePoint, comment));
1, MaxPointsReason.NONE, /* score */ 12.3, finishingTimePoint, comment, MergeState.OK));
RaceLogFinishPositioningConfirmedEvent event = new RaceLogFinishPositioningConfirmedEventImpl(now, author, 0, storedPositioningList);
addAndStoreRaceLogEvent(leaderboard, raceColumnName, event);
RaceLog loadedRaceLog = retrieveRaceLog();
@@ -405,7 +406,7 @@ public class TestStoringAndRetrievingRaceLogInLeaderboards extends RaceLogMongoD
"someone@nowhere.de", null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null);
CompetitorResults storedPositioningList = new CompetitorResultsImpl();
storedPositioningList.add(new CompetitorResultImpl(storedCompetitor.getId(), storedCompetitor.getName(), /* rank */
1, MaxPointsReason.NONE, /* score */ null, /* finishingTimePoint */null, /* comment */null));
1, MaxPointsReason.NONE, /* score */ null, /* finishingTimePoint */null, /* comment */null, MergeState.OK));
RaceLogFinishPositioningListChangedEvent event = new RaceLogFinishPositioningListChangedEventImpl(now, author, 0, storedPositioningList);
addAndStoreRaceLogEvent(leaderboard, raceColumnName, event);
@@ -14,6 +14,7 @@ import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.MongoException;
import com.sap.sailing.domain.abstractlog.race.CompetitorResult.MergeState;
import com.sap.sailing.domain.abstractlog.race.CompetitorResults;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent;
@@ -210,7 +211,7 @@ public class TestStoringAndRetrievingRaceLogInRegatta extends AbstractTestStorin
Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.RED,
"someone@nowhere.de", null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null);
CompetitorResults storedPositioningList = new CompetitorResultsImpl();
storedPositioningList.add(new CompetitorResultImpl(storedCompetitor.getId(), storedCompetitor.getName(), /* rank */ 1, MaxPointsReason.NONE, /* score */ null, /* finishingTime */ null, /* comment */ null));
storedPositioningList.add(new CompetitorResultImpl(storedCompetitor.getId(), storedCompetitor.getName(), /* rank */ 1, MaxPointsReason.NONE, /* score */ null, /* finishingTime */ null, /* comment */ null, MergeState.OK));
RaceLogFinishPositioningListChangedEvent event = new RaceLogFinishPositioningListChangedEventImpl(now, author, 0, storedPositioningList);
addAndStoreRaceLogEvent(regatta, raceColumnName, event);
RaceLog loadedRaceLog = retrieveRaceLog();
@@ -234,7 +235,7 @@ public class TestStoringAndRetrievingRaceLogInRegatta extends AbstractTestStorin
Competitor storedCompetitor = DomainFactory.INSTANCE.getOrCreateCompetitor(UUID.randomUUID(), "SAP Extreme Sailing Team", Color.RED,
"someone@nowhere.de", null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null);
CompetitorResults storedPositioningList = new CompetitorResultsImpl();
storedPositioningList.add(new CompetitorResultImpl(storedCompetitor.getId(), storedCompetitor.getName(), /* rank */ 1, MaxPointsReason.NONE, /* score */ null, /* finishingTime */ null, /* comment */ null));
storedPositioningList.add(new CompetitorResultImpl(storedCompetitor.getId(), storedCompetitor.getName(), /* rank */ 1, MaxPointsReason.NONE, /* score */ null, /* finishingTime */ null, /* comment */ null, MergeState.OK));
RaceLogFinishPositioningConfirmedEvent event = new RaceLogFinishPositioningConfirmedEventImpl(now, author, 0, storedPositioningList);
addAndStoreRaceLogEvent(regatta, raceColumnName, event);
RaceLog loadedRaceLog = retrieveRaceLog();
@@ -7,6 +7,7 @@ import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.race.CompetitorResult.MergeState;
import com.sap.sailing.domain.abstractlog.race.CompetitorResults;
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningEvent;
@@ -54,8 +55,15 @@ public abstract class RaceLogFinishPositioningEventDeserializer extends BaseRace
final Long finishingTimePointAsMillis = (Long) jsonPositionedCompetitor.get(RaceLogFinishPositioningConfirmedEventSerializer.FIELD_FINISHING_TIME_POINT_AS_MILLIS);
final TimePoint finishingTime = finishingTimePointAsMillis == null ? null : new MillisecondsTimePoint(finishingTimePointAsMillis);
final String comment = (String) jsonPositionedCompetitor.get(RaceLogFinishPositioningConfirmedEventSerializer.FIELD_COMMENT);
final String mergeStateAsString = (String) jsonPositionedCompetitor.get(RaceLogFinishPositioningConfirmedEventSerializer.FIELD_MERGE_STATE);
final MergeState mergeState;
if (mergeStateAsString == null) {
mergeState = MergeState.OK;
} else {
mergeState = MergeState.valueOf(mergeStateAsString);
}
CompetitorResultImpl positionedCompetitor = new CompetitorResultImpl(
competitorId, competitorDisplayName, rank == null ? rankCounter : rank.intValue(), maxPointsReason, score, finishingTime, comment);
competitorId, competitorDisplayName, rank == null ? rankCounter : rank.intValue(), maxPointsReason, score, finishingTime, comment, mergeState);
rankCounter++;
positionedCompetitors.add(positionedCompetitor);
}
@@ -17,6 +17,7 @@ public abstract class RaceLogFinishPositioningEventSerializer extends BaseRaceLo
public static final String FIELD_SCORE_CORRECTIONS_MAX_POINTS_REASON = "maxPointsReason";
public static final String FIELD_SCORE = "score";
public static final String FIELD_COMMENT = "comment";
public static final String FIELD_MERGE_STATE = "mergeState";
public static final String FIELD_RANK = "rank";
public static final String FIELD_FINISHING_TIME_POINT_AS_MILLIS = "finishingTimePointAsMillis";
@@ -45,6 +46,7 @@ public abstract class RaceLogFinishPositioningEventSerializer extends BaseRaceLo
jsonPositionedCompetitor.put(FIELD_SCORE_CORRECTIONS_MAX_POINTS_REASON, positionedCompetitor.getMaxPointsReason().name());
jsonPositionedCompetitor.put(FIELD_SCORE, positionedCompetitor.getScore());
jsonPositionedCompetitor.put(FIELD_COMMENT, positionedCompetitor.getComment());
jsonPositionedCompetitor.put(FIELD_MERGE_STATE, positionedCompetitor.getMergeState().name());
jsonPositionedCompetitor.put(FIELD_RANK, positionedCompetitor.getOneBasedRank()); // writes it as an int, but deserializer will fetch it as a Long
jsonPositionedCompetitor.put(FIELD_FINISHING_TIME_POINT_AS_MILLIS, positionedCompetitor.getFinishingTime() == null ? null : positionedCompetitor.getFinishingTime().asMillis());
jsonPositionedCompetitors.add(jsonPositionedCompetitor);
@@ -15,6 +15,7 @@ import org.junit.Test;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.race.CompetitorResult;
import com.sap.sailing.domain.abstractlog.race.CompetitorResult.MergeState;
import com.sap.sailing.domain.abstractlog.race.CompetitorResults;
import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningConfirmedEvent;
import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultImpl;
@@ -67,7 +68,7 @@ public class RaceLogFinishPositioningConfirmedEventSerializerTest {
@Test
public void testSerializeAndDeserializeRaceLogFinishPositioningConfirmedEvent() throws JsonDeserializationException {
positioningList.add(new CompetitorResultImpl(UUID.randomUUID(), "SAP Extreme", /* rank */ 1,
MaxPointsReason.NONE, /* score */ null, /* finishingTime */ null, /* comment */ null));
MaxPointsReason.NONE, /* score */ null, /* finishingTime */ null, /* comment */ null, MergeState.OK));
event = new RaceLogFinishPositioningConfirmedEventImpl(now, author, 0, positioningList);
JSONObject jsonConfirmationEvent = serializer.serialize(event);
RaceLogFinishPositioningConfirmedEvent deserializedEvent = (RaceLogFinishPositioningConfirmedEvent) deserializer
@@ -66,4 +66,5 @@ Bundle-ClassPath: .
Web-ContextPath: /sailingserver
Export-Package: com.sap.sailing.server.gateway,
com.sap.sailing.server.gateway.jaxrs,
com.sap.sailing.server.gateway.jaxrs.spi
com.sap.sailing.server.gateway.jaxrs.spi,
com.sap.sailing.server.gateway.windimport.bravo
@@ -52,14 +52,14 @@ public class SensorDataImportServlet extends AbstractFileUploadServlet {
*
* @param files
* the file items together with the names of the importer to use for importing the respective file's
* contents; the importer names are matched against {@link DoubleVectorFixImporter#getType()} for
* all importers found registered in the OSGi registry. The first matching importer is used for the
* file. The importer is selected on a per-file basis.
* contents; the importer names are matched against {@link DoubleVectorFixImporter#getType()} for all
* importers found registered in the OSGi registry. The first matching importer is used for the file. The
* importer is selected on a per-file basis.
*
* @throws IOException
*/
private Iterable<TrackFileImportDeviceIdentifier> importFiles(Iterable<Pair<String, FileItem>> files)
throws IOException {
private Iterable<TrackFileImportDeviceIdentifier> importFiles(boolean enableDownsampler,
Iterable<Pair<String, FileItem>> files) throws IOException {
final Set<TrackFileImportDeviceIdentifier> deviceIds = new HashSet<>();
final Map<DeviceIdentifier, TimePoint> from = new HashMap<>();
final Map<DeviceIdentifier, TimePoint> to = new HashMap<>();
@@ -102,7 +102,7 @@ public class SensorDataImportServlet extends AbstractFileUploadServlet {
}
}
}
}, fi.getName(), requestedImporterName, /* downsample */ true);
}, fi.getName(), requestedImporterName, enableDownsampler);
logger.log(Level.INFO, "Successfully imported file " + requestedImporterName);
} catch (FormatNotSupportedException e) {
logger.log(Level.INFO, "Failed to import file " + requestedImporterName);
@@ -119,10 +119,14 @@ public class SensorDataImportServlet extends AbstractFileUploadServlet {
protected void process(List<FileItem> fileItems, HttpServletRequest req, HttpServletResponse resp)
throws IOException {
String importerName = null;
searchForPreferredImporter: for (FileItem fi : fileItems) {
if ("preferredImporter".equalsIgnoreCase(fi.getFieldName())) {
importerName = fi.getString();
break searchForPreferredImporter;
boolean enableDownsampler = false;
for (FileItem fi : fileItems) {
if (fi.isFormField()) {
if ("preferredImporter".equalsIgnoreCase(fi.getFieldName())) {
importerName = fi.getString();
} else if ("downsample".equalsIgnoreCase(fi.getFieldName())) {
enableDownsampler = "on".equalsIgnoreCase(fi.getString());
}
}
}
if (importerName == null) {
@@ -134,7 +138,8 @@ public class SensorDataImportServlet extends AbstractFileUploadServlet {
filesAndImporterNames.add(new Pair<>(importerName, fi));
}
}
final Iterable<TrackFileImportDeviceIdentifier> mappingList = importFiles(filesAndImporterNames);
final Iterable<TrackFileImportDeviceIdentifier> mappingList = importFiles(enableDownsampler,
filesAndImporterNames);
resp.setContentType("text/html");
for (TrackFileImportDeviceIdentifier mapping : mappingList) {
String stringRep = mapping.getId().toString();
@@ -31,6 +31,8 @@ import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class BravoWindImportServlet extends AbstractWindImportServlet {
private static final String BRAVO_WIND_IMPORT = "Bravo Wind Import";
private static final String GZIP_SUFFIX = ".gz";
private static final long serialVersionUID = -4547876638456305135L;
private static final Logger logger = Logger.getLogger(BravoWindImportServlet.class.getName());
@@ -42,7 +44,7 @@ public class BravoWindImportServlet extends AbstractWindImportServlet {
if (uploadRequest.files != null && !uploadRequest.files.isEmpty()) {
sourceName = uploadRequest.files.stream().map(f->f.getName()).collect(Collectors.joining(", "));
} else {
sourceName = "Bravo Wind Import";
sourceName = BRAVO_WIND_IMPORT;
}
windSource = new WindSourceWithAdditionalID(WindSourceType.EXPEDITION, sourceName + "@" + MillisecondsTimePoint.now());
return windSource;
@@ -75,7 +77,7 @@ public class BravoWindImportServlet extends AbstractWindImportServlet {
for (final Fields field : Fields.values()) {
columnsMap.put(field.name(), field.ordinal());
}
final BaseBravoDataImporterImpl importer = new BaseBravoDataImporterImpl(columnsMap, "BRAVO_WIND");
final BaseBravoDataImporterImpl importer = new BaseBravoDataImporterImpl(columnsMap, BRAVO_WIND_IMPORT);
final Callback callback = new Callback() {
@Override
public void addFixes(Iterable<DoubleVectorFix> fixes, TrackFileImportDeviceIdentifier device) {
@@ -91,22 +93,26 @@ public class BravoWindImportServlet extends AbstractWindImportServlet {
}
}
};
if (filename.toLowerCase().endsWith("zip")) {
if (filename.toLowerCase().endsWith(".zip")) {
logger.info("Bravo file "+filename+" is a ZIP file");
try (final ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
ZipEntry entry;
while ((entry=zipInputStream.getNextEntry()) != null) {
if (entry.getName().toLowerCase().endsWith(".txt")) {
logger.info("Reading Bravo wind data from "+filename+"'s ZIP entry "+entry.getName());
importer.importFixes(zipInputStream, callback, filename, filename, /* downsample */ false);
importer.importFixes(zipInputStream, callback, entry.getName(), BRAVO_WIND_IMPORT, /* downsample */ false);
}
}
}
} else {
if (filename.toLowerCase().endsWith("gz")) {
final String actualFileName;
if (filename.toLowerCase().endsWith(GZIP_SUFFIX)) {
inputStream = new GZIPInputStream(inputStream);
actualFileName = filename.substring(0, filename.length()-GZIP_SUFFIX.length());
} else {
actualFileName = filename;
}
importer.importFixes(inputStream, callback, filename, filename, /* downsample */ false);
importer.importFixes(inputStream, callback, actualFileName, BRAVO_WIND_IMPORT, /* downsample */ false);
}
return result;
}
@@ -15,6 +15,7 @@ import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.race.CompetitorResult.MergeState;
import com.sap.sailing.domain.abstractlog.race.CompetitorResults;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultImpl;
@@ -102,7 +103,7 @@ public class ApplyScoresFromRaceLogTest extends LeaderboardScoringAndRankingTest
scores.put(c, score);
mprs.put(c, mpr);
results.add(new CompetitorResultImpl(c.getId(), c.getName(),
oneBasedRank++, mpr, score, /* finishingTime */ null, /* comment */ null));
oneBasedRank++, mpr, score, /* finishingTime */ null, /* comment */ null, MergeState.OK));
}
final RaceLog f1RaceLog = f1Column.getRaceLog(f1Column.getFleets().iterator().next());
final LogEventAuthorImpl author = new LogEventAuthorImpl("Axel", 0);
@@ -231,6 +232,6 @@ public class ApplyScoresFromRaceLogTest extends LeaderboardScoringAndRankingTest
private void setResultForCompetitor(final Competitor competitor, int oneBasedRank,
final CompetitorResults results, MaxPointsReason maxPointsReason, Double explicitScore) {
results.add(new CompetitorResultImpl(competitor.getId(), competitor.getName(), oneBasedRank, maxPointsReason, explicitScore, /* finishingTime */ null, /* comment */ null));
results.add(new CompetitorResultImpl(competitor.getId(), competitor.getName(), oneBasedRank, maxPointsReason, explicitScore, /* finishingTime */ null, /* comment */ null, MergeState.OK));
}
}
@@ -35,6 +35,7 @@ import com.mongodb.DB;
import com.mongodb.WriteConcern;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.race.CompetitorResult.MergeState;
import com.sap.sailing.domain.abstractlog.race.CompetitorResults;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
@@ -918,9 +919,9 @@ public class MasterDataImportTest {
TimePoint logTimePoint2 = logTimePoint.plus(10);
CompetitorResults positionedCompetitors = new CompetitorResultsImpl();
positionedCompetitors.add(new CompetitorResultImpl(
competitor.getId(), competitor.getName(), /* rank */ 1, MaxPointsReason.DNS, /* score */ null, /* finishingTime */ null, /* comment */ null));
competitor.getId(), competitor.getName(), /* rank */ 1, MaxPointsReason.DNS, /* score */ null, /* finishingTime */ null, /* comment */ null, MergeState.OK));
positionedCompetitors.add(new CompetitorResultImpl(
competitor2.getId(), competitor2.getName(), /* rank */ 2, MaxPointsReason.NONE, /* score */ null, /* finishingTime */ null, /* comment */ null));
competitor2.getId(), competitor2.getName(), /* rank */ 2, MaxPointsReason.NONE, /* score */ null, /* finishingTime */ null, /* comment */ null, MergeState.OK));
RaceLogFinishPositioningConfirmedEvent finishPositioningConfirmedEvent = new RaceLogFinishPositioningConfirmedEventImpl(
logTimePoint2, author, 1, positionedCompetitors);
raceColumn.getRaceLog(testFleet1).add(finishPositioningConfirmedEvent);
Loaded 100 of 273 files, more files were not shown because too many files have changed in this diff. Show more