mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-18 02:00:58 +00:00
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:
commit
027022e40a
273 files changed
+4449
-2766
No files matched your search
+2
-2
@@ -56,7 +56,7 @@ public class BravoFixTrackWithContext implements HasBravoFixTrackContext {
|
||||
end = endOfRace;
|
||||
}
|
||||
return bravoFixTrack.getDistanceSpentFoiling(
|
||||
getRaceOfCompetitorContext().getTrackedRaceContext().getTrackedRace().getTrack(getRaceOfCompetitorContext().getCompetitor()),
|
||||
startOfRace, end);
|
||||
startOfRace,
|
||||
end);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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
@@ -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
@@ -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>
|
||||
+7
@@ -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
|
||||
+3
@@ -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
@@ -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>
|
||||
java/com.sap.sailing.domain.bravoadapter/src/com/sap/sailing/domain/bravoadapter/impl/Activator.java
Executable
+17
@@ -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 {
|
||||
}
|
||||
}
|
||||
+50
@@ -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";
|
||||
}
|
||||
}
|
||||
+6
-2
@@ -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),
|
||||
|
||||
+43
-2
@@ -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()) {
|
||||
|
||||
+41
-3
@@ -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;
|
||||
}
|
||||
|
||||
+14
@@ -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;
|
||||
|
||||
+1
-1
@@ -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();
|
||||
|
||||
+4
@@ -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;
|
||||
|
||||
+10
-2
@@ -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++;
|
||||
}
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
+1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+48
-86
@@ -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
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
+11
-10
@@ -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);
|
||||
|
||||
+29
@@ -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();
|
||||
}
|
||||
+17
-2
@@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
+8
@@ -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);
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -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
|
||||
|
||||
+27
@@ -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();
|
||||
|
||||
}
|
||||
+221
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+59
@@ -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);
|
||||
|
||||
+9
-7
@@ -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);
|
||||
|
||||
+2
-1
@@ -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());
|
||||
}
|
||||
|
||||
+16
@@ -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;
|
||||
}
|
||||
}
|
||||
+17
@@ -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;
|
||||
}
|
||||
}
|
||||
+27
-1
@@ -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);
|
||||
}
|
||||
+45
-6
@@ -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;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+4
-8
@@ -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.
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+10
-3
@@ -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}.
|
||||
|
||||
+6
-2
@@ -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);
|
||||
}
|
||||
+151
-54
@@ -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
|
||||
|
||||
-237
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+18
-60
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+51
-18
@@ -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));
|
||||
|
||||
+105
-11
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
+17
@@ -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="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt.adminconsole/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.security.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.polars.datamining.shared/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.datamining.annotations/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.expeditionconnector.common/src" path="3" type="2"/> "/>
|
||||
</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"/>
|
||||
|
||||
+11
@@ -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>
|
||||
|
||||
+3
-3
@@ -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>
|
||||
|
||||
+3
-3
@@ -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<>();
|
||||
|
||||
+7
@@ -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) {
|
||||
|
||||
+1
@@ -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>
|
||||
|
||||
+4
@@ -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();
|
||||
|
||||
+47
-15
@@ -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) {
|
||||
|
||||
+2
@@ -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;
|
||||
}
|
||||
|
||||
+2
@@ -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())));
|
||||
|
||||
|
||||
+1
@@ -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
|
||||
|
||||
+125
@@ -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);
|
||||
}
|
||||
}
|
||||
Executable
+134
@@ -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();
|
||||
}
|
||||
+31
@@ -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();
|
||||
}
|
||||
|
||||
+1
@@ -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
|
||||
|
||||
+30
-2
@@ -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:
|
||||
|
||||
+5
@@ -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();
|
||||
|
||||
|
||||
+17
@@ -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);
|
||||
|
||||
+14
@@ -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();
|
||||
}
|
||||
+16
-2
@@ -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
|
||||
+17
-3
@@ -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
|
||||
+12
@@ -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?
|
||||
+13
-1
@@ -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 ?
|
||||
+12
@@ -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 アカウントを削除しますか
|
||||
+12
@@ -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?
|
||||
+12
@@ -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?
|
||||
+12
@@ -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 账户?
|
||||
+56
-2
@@ -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,
|
||||
|
||||
+19
@@ -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));
|
||||
|
||||
+6
@@ -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,
|
||||
|
||||
+35
-4
@@ -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);
|
||||
}
|
||||
|
||||
+26
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 160 B |
+4
-3
@@ -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);
|
||||
|
||||
+3
-2
@@ -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();
|
||||
|
||||
+9
-1
@@ -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);
|
||||
}
|
||||
|
||||
+2
@@ -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);
|
||||
|
||||
+2
-1
@@ -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
|
||||
+16
-11
@@ -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();
|
||||
|
||||
+12
-6
@@ -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;
|
||||
}
|
||||
|
||||
+3
-2
@@ -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));
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -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
Reference in new issue
Block a user