candidatesRemoved = new TreeSet<>(candidateComparator);
candidatesRemoved.addAll(oldValidCandidates);
candidatesRemoved.removeAll(newValidCandidates);
@@ -450,19 +457,20 @@ public class StationarySequence {
/**
* If this sequence contains a {@link Candidate} that matches the {@code timePoint} exactly, that candidate is
- * returned. Otherwise, a dummy candidate is created that has the {@code timePoint} provided.
+ * returned. Otherwise, a dummy candidate is created that has the {@code timePoint} provided.
+ *
*
- * Note that depending on the {@link #candidateComparator} used, two candidates are not necessarily considered
- * equal only because they have equal time points.
+ * Note that depending on the {@link #candidateComparator} used, two candidates are not necessarily considered equal
+ * only because they have equal time points.
*/
private Candidate getCandidateMatchingTimePointOrCreateDummy(TimePoint timePoint) {
final Candidate dummy = new CandidateImpl(/* one-based index of waypoint */ 1, timePoint, /* probability */ 0, /* waypoint */ null);
final Candidate floorCandidate = candidates.floor(dummy);
final Candidate ceilingCandidate = candidates.ceiling(dummy);
final Candidate result;
- if (floorCandidate.getTimePoint().equals(timePoint)) {
+ if (floorCandidate != null && floorCandidate.getTimePoint().equals(timePoint)) {
result = floorCandidate;
- } else if (ceilingCandidate.getTimePoint().equals(timePoint)) {
+ } else if (ceilingCandidate != null && ceilingCandidate.getTimePoint().equals(timePoint)) {
result = ceilingCandidate;
} else {
result = dummy;
@@ -484,7 +492,7 @@ public class StationarySequence {
return new CandidateImpl(/* one-based index of waypoint */ 1, timePoint, /* probability */ 0, /* waypoint */ null);
}
- Iterable getAllCandidates() {
+ public Iterable getAllCandidates() {
return Collections.unmodifiableCollection(candidates);
}
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequenceBasedFilter.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequenceBasedFilter.java
index 830ce564e10..b51a8a00627 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequenceBasedFilter.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequenceBasedFilter.java
@@ -526,7 +526,10 @@ public class StationarySequenceBasedFilter {
StationarySequence.createDummyCandidate(newFix.getTimePoint())));
if (lastSequenceStartingAtOrBeforeFix != null && !lastSequenceStartingAtOrBeforeFix.getLast().getTimePoint().before(newFix.getTimePoint())) {
// fix falls into the existing StationarySequence; update its bounding box:
- final StationarySequence splitResult = lastSequenceStartingAtOrBeforeFix.tryToAddFix(newFix, candidatesEffectivelyAdded, candidatesEffectivelyRemoved, /* StationarySequence set to update */ stationarySequences);
+ final StationarySequence splitResult = lastSequenceStartingAtOrBeforeFix.tryToAddFix(
+ newFix, candidatesEffectivelyAdded, candidatesEffectivelyRemoved,
+ /* StationarySequence set to update */ stationarySequences,
+ /* was replacement */ fixesReplacingExistingOnes != null && Util.contains(fixesReplacingExistingOnes, newFix));
assert !stationarySequences.contains(lastSequenceStartingAtOrBeforeFix) || lastSequenceStartingAtOrBeforeFix.size() > 1;
if (splitResult != null) {
assert splitResult.size() > 1;
@@ -549,29 +552,34 @@ public class StationarySequenceBasedFilter {
final boolean fixIsInStationarySequence = lastSequenceStartingAtOrBeforeFix != null &&
!lastSequenceStartingAtOrBeforeFix.getLast().getTimePoint().before(fixReplacingExistingOne.getTimePoint());
if (!fixIsInStationarySequence) {
- final StationarySequence lastSequenceEndingBeforeFix = // null in case fix is within a stationary sequence
- lastSequenceStartingAtOrBeforeFix != null && fixIsInStationarySequence ? null : lastSequenceStartingAtOrBeforeFix;
+ final StationarySequence lastSequenceEndingBeforeFix = lastSequenceStartingAtOrBeforeFix; // null in case there is no sequence coming before the fix
final Candidate lastCandidateBeforeReplacementFix = candidates.lower(dummyCandidateForReplacementFix);
- if (lastCandidateBeforeReplacementFix != null) {
+ if (lastCandidateBeforeReplacementFix != null) { // no need to try to extend a later sequence to "the left" if there is nothing
final Candidate firstCandidateAfterReplacementFix = candidates.higher(dummyCandidateForReplacementFix);
- if (firstCandidateAfterReplacementFix != null) {
- // the fix is between two candidates, so we may try to extend or create a stationary sequence:
- if (lastCandidateBeforeReplacementFix == lastSequenceEndingBeforeFix.getLast()) {
- // previous candidate is end of a sequence; try to extend
- lastSequenceEndingBeforeFix.tryToExtendAfterLast(firstCandidateAfterReplacementFix, candidatesEffectivelyAdded, candidatesEffectivelyRemoved);
+ if (firstCandidateAfterReplacementFix != null) { // no need to try to extend an earlier sequence to "the right" if there is nothing
+ // the fix is between two candidates, so we may try to extend or create a stationary sequence if not the next
+ // candidate is already part of a sequence. We don't merge sequences as of now, and sequences must be disjoint regarding
+ // their set of candidates.
+ final StationarySequence firstSequenceStartingAfterFix = stationarySequences.higher(dummyStationarySequenceForFix);
+ final boolean lookRight;
+ final boolean previousCandidateIsLastInSequence = lastSequenceEndingBeforeFix != null && lastCandidateBeforeReplacementFix == lastSequenceEndingBeforeFix.getLast();
+ final boolean nextCandidateIsFirstInSequence = firstSequenceStartingAfterFix != null && firstCandidateAfterReplacementFix == firstSequenceStartingAfterFix.getFirst();
+ if (previousCandidateIsLastInSequence && !nextCandidateIsFirstInSequence) {
+ // previous candidate is end of a sequence and next one is not part of a sequence; try to extend previous sequence to "the right":
+ lookRight = !lastSequenceEndingBeforeFix.tryToExtendAfterLast(firstCandidateAfterReplacementFix, candidatesEffectivelyAdded, candidatesEffectivelyRemoved);
} else {
- final StationarySequence firstSequenceStartingAfterFix = stationarySequences.higher(dummyStationarySequenceForFix);
- if (firstCandidateAfterReplacementFix == firstSequenceStartingAfterFix.getFirst()) {
- // next candidate is start of a sequence; try to extend
- firstSequenceStartingAfterFix.tryToExtendBeforeFirst(lastCandidateBeforeReplacementFix,
- candidatesEffectivelyAdded, candidatesEffectivelyRemoved,
- /* StationarySequence set to update */ stationarySequences);
- } else {
- // none of the adjacent candidates is part of a sequence; try to create a new one:
- final StationarySequence newSequence = createStationarySequence(lastCandidateBeforeReplacementFix);
- if (newSequence.tryToExtendAfterLast(firstCandidateAfterReplacementFix, candidatesEffectivelyAdded, candidatesEffectivelyRemoved)) {
- stationarySequences.add(newSequence);
- }
+ lookRight = true;
+ }
+ if (lookRight && nextCandidateIsFirstInSequence && !previousCandidateIsLastInSequence) {
+ // next candidate is start of a sequence and previous candidate is not part of a sequence; try to extend next sequence to "the left":
+ firstSequenceStartingAfterFix.tryToExtendBeforeFirst(lastCandidateBeforeReplacementFix,
+ candidatesEffectivelyAdded, candidatesEffectivelyRemoved,
+ /* StationarySequence set to update */ stationarySequences);
+ } else if (!previousCandidateIsLastInSequence && !nextCandidateIsFirstInSequence) {
+ // none of the adjacent candidates is part of a sequence; try to create a new one:
+ final StationarySequence newSequence = createStationarySequence(lastCandidateBeforeReplacementFix);
+ if (newSequence.tryToExtendAfterLast(firstCandidateAfterReplacementFix, candidatesEffectivelyAdded, candidatesEffectivelyRemoved)) {
+ stationarySequences.add(newSequence);
}
}
}
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/AbstractRankingMetric.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/AbstractRankingMetric.java
index a5a26187e77..b8b460e45c0 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/AbstractRankingMetric.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/AbstractRankingMetric.java
@@ -261,12 +261,15 @@ public abstract class AbstractRankingMetric implements RankingMetric {
/**
* The time from the {@link TrackedRace#getStartOfRace() race start} until timePoint or until
- * the point in time when competitor passed the finish mark, whichever comes first.
+ * the point in time when competitor passed the finish mark, whichever comes first. If there is
+ * no mark passing for {@code competitor} for the last waypoint or no {@link TrackedRace#getStartOfRace()} is
+ * known, {@code null} is returned.
*/
- protected Duration getActualTimeSinceStartOfRace(Competitor competitor, TimePoint timePoint) {
+ @Override
+ public Duration getActualTimeSinceStartOfRace(Competitor competitor, TimePoint timePoint) {
final Duration result;
final TimePoint startOfRace = getTrackedRace().getStartOfRace();
- if (startOfRace == null) {
+ if (startOfRace == null || timePoint.before(startOfRace)) {
result = null;
} else {
final Waypoint finish = getTrackedRace().getRace().getCourse().getLastWaypoint();
@@ -277,7 +280,11 @@ public abstract class AbstractRankingMetric implements RankingMetric {
if (finishingMarkPassing != null && finishingMarkPassing.getTimePoint().before(timePoint)) {
result = startOfRace.until(finishingMarkPassing.getTimePoint());
} else {
- result = startOfRace.until(timePoint);
+ if (trackedRace.getEndOfTracking() != null && timePoint.after(trackedRace.getEndOfTracking())) {
+ result = null; // race not finished until end of tracking; no reasonable value can be computed for competitor
+ } else {
+ result = startOfRace.until(timePoint);
+ }
}
}
}
@@ -435,7 +442,9 @@ public abstract class AbstractRankingMetric implements RankingMetric {
getTrackedRace().getRace().getCourse().getIndexOfWaypoint(legWho.getTrackedLeg().getLeg().getFrom()) <=
getTrackedRace().getRace().getCourse().getIndexOfWaypoint(legTo.getTrackedLeg().getLeg().getFrom());
final Duration result;
- if (legWho == null || legTo == null || !legWho.hasStartedLeg(timePoint) || !legTo.hasStartedLeg(timePoint)) {
+ if (legWho == null || legTo == null ||
+ !(legWho.hasStartedLeg(timePoint) || legWho.hasFinishedLeg(timePoint)) ||
+ !(legTo.hasStartedLeg(timePoint) || legTo.hasFinishedLeg(timePoint))) {
result = null;
} else {
final Competitor who = legWho.getCompetitor();
@@ -494,18 +503,17 @@ public abstract class AbstractRankingMetric implements RankingMetric {
* For the situation at timePoint, determines how long in real, uncorrected time who lags
* behind to in the leg identified by legWho. If both are still sailing in the leg at
* timePoint, this is the time who needs with constant average VMG to reach
- * to's position at timePoint. If only to has already finished the leg and no
- * mark passing time is known yet for who for the end of the leg then who is projected to
- * the end of the leg using her average VMG on the leg, and the difference between who's projected and
- * to's actual mark passing times is returned.
- *
- *
- * If leg finish mark passings are available for both, who and to, the difference between
- * them is returned.
+ * to's position at timePoint. If only to has already finished the leg at
+ * {@code timePoint} then who is projected to the end of the leg using her average VMG on the leg, and
+ * the difference between who's projected and to's actual mark passing times is returned.
+ * Note that in this latter case it doesn't matter whether {@code who} already has a mark passing for the end of the
+ * leg or not; the idea is to not "rewrite history" by letting the mark passing have an impact on the rankings prior
+ * to the mark passing.
*
*
* The result may be a negative duration in case who reached the position in question before
- * timePoint.
+ * timePoint.
+ *
*
* Precondition: who and to have both started sailing the leg at timePoint
* and to has sailed a greater or equal windward distance compared to who. If not, the
@@ -513,8 +521,8 @@ public abstract class AbstractRankingMetric implements RankingMetric {
*/
protected Duration getPredictedDurationToEndOfLegOrTo(TimePoint timePoint, final TrackedLegOfCompetitor legWho, final TrackedLegOfCompetitor legTo,
WindLegTypeAndLegBearingCache cache) {
- assert legWho.hasStartedLeg(timePoint);
- assert legTo.hasStartedLeg(timePoint);
+ assert legWho.hasStartedLeg(timePoint) || legWho.hasFinishedLeg(timePoint);
+ assert legTo.hasStartedLeg(timePoint) || legTo.hasFinishedLeg(timePoint);
final Duration toEndOfLegOrTo;
if (legTo.hasFinishedLeg(timePoint)) {
// calculate actual time it takes who to reach the end of the leg starting at timePoint:
@@ -527,9 +535,8 @@ public abstract class AbstractRankingMetric implements RankingMetric {
getWindwardDistanceTraveled(legWho.getCompetitor(), legWho.hasFinishedLeg(timePoint)?legWho.getFinishTime():timePoint, cache)) >= 0;
// estimate who's leg finishing time by extrapolating with the average VMG (if available) or the current VMG
// (if no average VMG can currently be computed, e.g., because the time point is exactly at the leg start)
- final Position windwardPositionToReachInWhosCurrentLeg =
- getTrackedRace().getApproximatePosition(legWho.getLeg().getTo(), timePoint);
- toEndOfLegOrTo = getDurationToReach(windwardPositionToReachInWhosCurrentLeg, timePoint, legWho, cache);
+ final Position positionOfEndOfLeg = getTrackedRace().getApproximatePosition(legWho.getLeg().getTo(), timePoint);
+ toEndOfLegOrTo = getDurationToReach(positionOfEndOfLeg, timePoint, legWho, cache);
}
} else {
// competitor "to" is still in same leg; project "who" to "to"'s position using VMG
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/OneDesignRankingMetric.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/OneDesignRankingMetric.java
index 2f38163ebef..dbfdb71615b 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/OneDesignRankingMetric.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/OneDesignRankingMetric.java
@@ -197,7 +197,6 @@ public class OneDesignRankingMetric extends AbstractRankingMetric {
public Duration getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead() {
return getEstimatedActualDurationFromRaceStartToCompetitorFarthestAhead();
}
-
};
}
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/RankingMetric.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/RankingMetric.java
index 0dd2269675b..9c9d8208e3a 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/RankingMetric.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/ranking/RankingMetric.java
@@ -154,6 +154,14 @@ public interface RankingMetric extends Serializable {
Comparator getLegRankingComparator(TrackedLeg trackedLeg, TimePoint timePoint, WindLegTypeAndLegBearingCache cache);
+ /**
+ * Determine the time sailed for the {@code competitor} at {@code timePoint} in this race. This ignores whether or not
+ * the race has recorded a start mark passing for the {@code competitor}. If no finish mark passing is found either, the
+ * duration between the {@link #getStartOfRace() race start time} and {@code timePoint} is returned; otherwise the duration
+ * between the {@link #getStartOfRace() race start time} and the time when the {@code competitor} finished the race.
+ */
+ Duration getActualTimeSinceStartOfRace(Competitor competitor, TimePoint timePoint);
+
/**
* How much time did the competitor spend in the {@link TrackedRace#getRace() race} described by
* trackedRace at timePoint, corrected by the handicapping rules defined by this ranking
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java
index bde6658fda8..120e12fa6e2 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java
@@ -999,6 +999,18 @@ public interface TrackedRace
*/
TargetTimeInfo getEstimatedTimeToComplete(TimePoint timepoint) throws NotEnoughDataHasBeenAddedException, NoWindException;
+ /**
+ * Determine the time sailed for the {@code competitor} at {@code timePoint} in this race. This ignores whether or
+ * not the race has recorded a start mark passing for the {@code competitor}. If no finish mark passing is found
+ * either, the duration between the {@link #getStartOfRace() race start time} and {@code timePoint} is returned;
+ * otherwise the duration between the {@link #getStartOfRace() race start time} and the time when the
+ * {@code competitor} finished the race. If there is no mark passing for {@code competitor} for the last waypoint or
+ * no {@link TrackedRace#getStartOfRace()} is known, {@code null} is returned.
+ */
+ default Duration getTimeSailedSinceRaceStart(Competitor competitor, TimePoint timePoint) {
+ return null;
+ }
+
/**
* Calculates the estimated distance it takes a competitor to sail the race, from start to finish.
*
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java
index 25686b0339d..bfce78a01cf 100644
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java
@@ -1097,8 +1097,9 @@ DynamicTrackedRace, GPSTrackListener {
@Override
public void attachRaceLog(RaceLog raceLog) {
- logListener.addTo(raceLog);
+ logListener.beforeAttaching(raceLog);
super.attachRaceLog(raceLog);
+ logListener.afterAttaching(raceLog);
getRaceState(raceLog).addChangedListener(raceStateBasedStartTimeChangedListener);
}
@@ -1106,9 +1107,12 @@ DynamicTrackedRace, GPSTrackListener {
public RaceLog detachRaceLog(Serializable identifier) {
final RaceLog attachedRaceLog = attachedRaceLogs.get(identifier);
if (attachedRaceLog != null) {
- logListener.removeFrom(attachedRaceLog);
+ logListener.beforeDetaching(attachedRaceLog);
}
final RaceLog raceLogDetached = super.detachRaceLog(identifier);
+ if (attachedRaceLog != null) {
+ logListener.afterDetaching(attachedRaceLog);
+ }
assert raceLogDetached == attachedRaceLog;
synchronized (raceStates) {
final ReadonlyRaceState raceState = raceStates.remove(attachedRaceLog);
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java
index bcc66c88fa7..f9ced9a8709 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java
@@ -47,7 +47,7 @@ import com.sap.sse.common.Util.Triple;
* Listens for changes on a {@link RaceLog} and forwards the relevant ones to a {@link TrackedRace}. Examples: start time changes;
* fixed mark passings changes; course design changes; wind fixes entered by the race committee.
*
- * For each {@link RaceLog} {@link #addTo(RaceLog) attached}, a {@link ReadonlyRaceState} is constructed that is used to observe
+ * For each {@link RaceLog} {@link #beforeAttaching(RaceLog) attached}, a {@link ReadonlyRaceState} is constructed that is used to observe
* some properties in an easier to use form than observing the {@link RaceLog}s directly. For example, the {@link ReadonlyRaceState}
* can tell the {@link ReadonlyRaceState#getFinishedTime() finished time} and it is possible to receive change notifications for
* this value.
@@ -74,7 +74,7 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor {
raceCommitteeWindSource = new WindSourceImpl(WindSourceType.RACECOMMITTEE);
}
- public void addTo(RaceLog raceLog) {
+ public void beforeAttaching(RaceLog raceLog) {
if (raceLog == null) {
logger.severe("Trying to add " + this + " as listener to a null race log for tracked race " + trackedRace.getRace());
} else {
@@ -87,12 +87,15 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor {
trackedRace.setFinishedTime(state.getFinishedTime());
}
});
- trackedRace.invalidateStartTime();
- trackedRace.invalidateEndTime();
- analyze(raceLog);
}
}
+ public void afterAttaching(RaceLog raceLog) {
+ trackedRace.invalidateStartTime();
+ trackedRace.invalidateEndTime();
+ analyze(raceLog);
+ }
+
/**
* @return the first non-{@code null} {@link ReadonlyRaceState#getFinishedTime() finished time} that is found in any
* of the {@link RaceState}s constructed for all the {@link RaceLog}s observed by this listener, or {@code null}
@@ -139,7 +142,7 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor {
}
}
- public void removeFrom(RaceLog raceLog) {
+ public void beforeDetaching(RaceLog raceLog) {
// Gets called whenever a RaceColumn was already linked to a TrackedRace on linkage.
// Maybe we need to reset the status of the old race somehow? There might be no new
// TrackedRace to be linked...
@@ -147,13 +150,19 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor {
// TODO:
// ??? trackedRace.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.PREPARED, 0.0));
if (raceLog != null) {
- trackedRace.invalidateStartTime();
- removeAllWindFixesFromWindTrack(raceLog);
raceLog.removeListener(this);
raceLogs.remove(raceLog);
if (markPassingUpdateListener != null) {
removeMarkPassingEvents();
}
+ }
+ }
+
+
+ public void afterDetaching(RaceLog raceLog) {
+ if (raceLog != null) {
+ trackedRace.invalidateStartTime();
+ removeAllWindFixesFromWindTrack(raceLog);
trackedRace.updateMarkPassingsAfterRaceLogChanges();
}
}
@@ -356,5 +365,4 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor {
trackedRace.invalidateEndTime();
}
}
-
}
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java
index 012947267cc..d8297647876 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java
@@ -3548,6 +3548,11 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
return new TargetTimeInfoImpl(legTargetTimes);
}
+ @Override
+ public Duration getTimeSailedSinceRaceStart(Competitor competitor, TimePoint timePoint) {
+ return getRankingMetric().getActualTimeSinceStartOfRace(competitor, timePoint);
+ }
+
@Override
public Distance getEstimatedDistanceToComplete(final TimePoint timepoint)
throws NotEnoughDataHasBeenAddedException, NoWindException {
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html
index 7935c90bbea..b046b1848c8 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html
@@ -4,6 +4,13 @@
What's New - SAP Sailing Analytics
+
May 2019
+
+ - Improvements for handicap regattas: when finishing times have been entered, leading to finish line
+ mark passings, those times will now be considered during calculating the corrected times. This way
+ it has become possible to fix results even if tracking was incomplete.
+
+
April 2019
- Competitor tails on the map can now be colored according to a metric. This enables quick and intuitive
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java
index 1cfb170489e..b9f3053564c 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java
@@ -461,9 +461,7 @@ public class DetailTypeFormatter {
case RACE_GAP_TO_LEADER_IN_SECONDS:
case LEG_ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS:
case LEG_CORRECTED_TIME_TRAVELED:
- case RACE_CALCULATED_TIME_TRAVELED:
case TIME_BETWEEN_RACE_START_AND_COMPETITOR_START:
- case RACE_CALCULATED_TIME_AT_ESTIMATED_ARRIVAL_AT_COMPETITOR_FARTHEST_AHEAD:
case EXPEDITION_RACE_TIME_TO_BURN_TO_LINE:
case EXPEDITION_LEG_TIME_TO_BURN_TO_LINE:
case EXPEDITION_RACE_TIME_TO_GUN:
@@ -484,6 +482,8 @@ public class DetailTypeFormatter {
case RACE_TIME_TRAVELED_DOWNWIND:
case RACE_TIME_TRAVELED_REACHING:
case RACE_TIME_TRAVELED_UPWIND:
+ case RACE_CALCULATED_TIME_TRAVELED:
+ case RACE_CALCULATED_TIME_AT_ESTIMATED_ARRIVAL_AT_COMPETITOR_FARTHEST_AHEAD:
case LEG_TIME_TRAVELED:
case BRAVOEXTENDED_LEG_CURRENT_DURATION_FOILED_IN_SECONDS:
case RACE_CURRENT_DURATION_FOILED_IN_SECONDS:
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java
index 547e5880dbf..e841fe0cdfa 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java
@@ -1265,17 +1265,17 @@ public abstract class LeaderboardPanel extends A
result.put(DetailType.RACE_CURRENT_LEG, new FormattedDoubleLeaderboardRowDTODetailTypeColumn(DetailType.RACE_CURRENT_LEG,
new CurrentLeg(), LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this));
result.put(DetailType.RACE_TIME_TRAVELED,
- new TimeTraveledRaceColumn(getLeaderboardPanel(), this, stringMessages, LEG_COLUMN_HEADER_STYLE,
+ new TimeTraveledRaceColumnInSeconds(getLeaderboardPanel(), this, stringMessages, LEG_COLUMN_HEADER_STYLE,
LEG_COLUMN_STYLE, LEG_DETAIL_COLUMN_HEADER_STYLE, LEG_DETAIL_COLUMN_STYLE));
result.put(DetailType.RACE_CALCULATED_TIME_TRAVELED,
- new FormattedDoubleLeaderboardRowDTODetailTypeColumn(DetailType.RACE_CALCULATED_TIME_TRAVELED,
+ new TotalTimeColumn(DetailType.RACE_CALCULATED_TIME_TRAVELED,
new RaceCalculatedTimeTraveledInSeconds(), LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE,
LeaderboardPanel.this));
result.put(DetailType.RACE_CALCULATED_TIME_AT_ESTIMATED_ARRIVAL_AT_COMPETITOR_FARTHEST_AHEAD,
- new FormattedDoubleLeaderboardRowDTODetailTypeColumn(
- DetailType.RACE_CALCULATED_TIME_AT_ESTIMATED_ARRIVAL_AT_COMPETITOR_FARTHEST_AHEAD,
+ new TotalTimeColumn(DetailType.RACE_CALCULATED_TIME_AT_ESTIMATED_ARRIVAL_AT_COMPETITOR_FARTHEST_AHEAD,
new RaceCalculatedTimeAtEstimatedArrivalAtCompetitorFarthestAheadInSeconds(),
- LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this));
+ LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE,
+ LeaderboardPanel.this));
result.put(DetailType.EXPEDITION_RACE_AWA,
new FormattedDoubleLeaderboardRowDTODetailTypeColumn(DetailType.EXPEDITION_RACE_AWA,
new DoubleTextRaceDetailTypeExtractor(LeaderboardEntryDTO::getExpeditionAWA),
@@ -3030,12 +3030,13 @@ public abstract class LeaderboardPanel extends A
}
/**
- * To be expandable, a race column needs to have one or more tracked races associated and needs to have at least
- * some GPS data available. Wind data is not required for expandability because several metrics can reasonably be
- * determined even without reliable wind information.
+ * To be expandable, a race column needs to have one or more tracked races associated. No GPS data is required
+ * (anymore) because there are some interesting columns that may be fed solely based on the Race Log contents, such
+ * as finishing times and start times, resulting in time sailed and calculated times. Wind data is not required for
+ * expandability either because several metrics can reasonably be determined even without reliable wind information.
*/
private boolean shallExpandRaceColumn(RaceColumnDTO raceColumnDTO) {
- return showRaceDetails && raceColumnDTO.hasTrackedRaces() && raceColumnDTO.hasGPSData();
+ return showRaceDetails && raceColumnDTO.hasTrackedRaces();
}
private void removeUnusedRaceColumns(LeaderboardDTO leaderboard) {
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/OverallTimeTraveledColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/OverallTimeTraveledColumn.java
index 955061d4576..32be47e2b1a 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/OverallTimeTraveledColumn.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/OverallTimeTraveledColumn.java
@@ -50,7 +50,6 @@ public class OverallTimeTraveledColumn extends ExpandableSortableColumn
LeaderboardPanel> leaderboardPanel, StringMessages stringConstants, String detailHeaderStyle,
String detailColumnStyle) {
Map> result = new HashMap<>();
-
result.put(DetailType.TOTAL_TIME_SAILED_UPWIND_IN_SECONDS,
new TotalTimeColumn(DetailType.TOTAL_TIME_SAILED_UPWIND_IN_SECONDS, new TotalTimeSailedUpwindInSeconds(),
detailHeaderStyle, detailColumnStyle, leaderboardPanel));
@@ -60,7 +59,6 @@ public class OverallTimeTraveledColumn extends ExpandableSortableColumn
result.put(DetailType.TOTAL_TIME_SAILED_REACHING_IN_SECONDS,
new TotalTimeColumn(DetailType.TOTAL_TIME_SAILED_REACHING_IN_SECONDS, new TotalTimeSailedReachingInSeconds(),
detailHeaderStyle, detailColumnStyle, leaderboardPanel));
-
return result;
}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TimeTraveledRaceColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TimeTraveledRaceColumnInSeconds.java
similarity index 85%
rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TimeTraveledRaceColumn.java
rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TimeTraveledRaceColumnInSeconds.java
index 8802fa01d27..9442c5f6bc5 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TimeTraveledRaceColumn.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TimeTraveledRaceColumnInSeconds.java
@@ -20,7 +20,7 @@ import com.sap.sse.common.InvertibleComparator;
import com.sap.sse.common.impl.InvertibleComparatorAdapter;
import com.sap.sse.gwt.client.celltable.AbstractSortableColumnWithMinMax;
-public class TimeTraveledRaceColumn extends ExpandableSortableColumn implements HasStringAndDoubleValue {
+public class TimeTraveledRaceColumnInSeconds extends ExpandableSortableColumn implements HasStringAndDoubleValue {
private static final DetailType DETAIL_TYPE = DetailType.RACE_TIME_TRAVELED;
@@ -31,7 +31,7 @@ public class TimeTraveledRaceColumn extends ExpandableSortableColumn imp
private String headerStyle;
private MinMaxRenderer minmaxRenderer;
- public TimeTraveledRaceColumn(LeaderboardPanel> leaderboardPanel, RaceNameProvider raceNameProvider, StringMessages stringMessages, String headerStyle, String columnStyle,
+ public TimeTraveledRaceColumnInSeconds(LeaderboardPanel> leaderboardPanel, RaceNameProvider raceNameProvider, StringMessages stringMessages, String headerStyle, String columnStyle,
String detailHeaderStyle, String detailColumnStyle) {
super(leaderboardPanel, /* expandable */true, new TextCell(), DETAIL_TYPE.getDefaultSortingOrder(),
stringMessages, detailHeaderStyle, detailColumnStyle,
@@ -46,24 +46,17 @@ public class TimeTraveledRaceColumn extends ExpandableSortableColumn imp
@Override
public Double getDoubleValue(LeaderboardRowDTO row) {
- Long result = null;
+ final Double result;
LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName());
- if (fieldsForRace != null && fieldsForRace.legDetails != null) {
- for (LegEntryDTO legDetail : fieldsForRace.legDetails) {
- if (legDetail != null) {
- if (legDetail.timeInMilliseconds != null) {
- if (result == null) {
- result = 0l;
- }
- result += legDetail.timeInMilliseconds;
- }
- }
- }
+ if (fieldsForRace != null && fieldsForRace.timeSailedSinceRaceStart != null) {
+ result = fieldsForRace.timeSailedSinceRaceStart.asSeconds();
+ } else {
+ result = null;
}
- return result == null ? null : new Long(result / 1000).doubleValue();
+ return result;
}
- private Double getTimeTraveledFor(LeaderboardRowDTO row, LegType legType) {
+ private Double getTimeTraveledForLegTypeInSeconds(LeaderboardRowDTO row, LegType legType) {
Long result = null;
LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName());
if (fieldsForRace != null && fieldsForRace.legDetails != null) {
@@ -101,21 +94,21 @@ public class TimeTraveledRaceColumn extends ExpandableSortableColumn imp
private class RaceTimeTraveledUpwindInSeconds implements DataExtractor {
@Override
public Double get(LeaderboardRowDTO row) {
- return getTimeTraveledFor(row, LegType.UPWIND);
+ return getTimeTraveledForLegTypeInSeconds(row, LegType.UPWIND);
}
}
private class RaceTimeTraveledDownwindInSeconds implements DataExtractor {
@Override
public Double get(LeaderboardRowDTO row) {
- return getTimeTraveledFor(row, LegType.DOWNWIND);
+ return getTimeTraveledForLegTypeInSeconds(row, LegType.DOWNWIND);
}
}
private class RaceTimeTraveledReachingInSeconds implements DataExtractor {
@Override
public Double get(LeaderboardRowDTO row) {
- return getTimeTraveledFor(row, LegType.REACHING);
+ return getTimeTraveledForLegTypeInSeconds(row, LegType.REACHING);
}
}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TotalTimeColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TotalTimeColumn.java
index c80a6b93e6d..dc1fcc04d3b 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TotalTimeColumn.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/TotalTimeColumn.java
@@ -12,9 +12,9 @@ import com.sap.sailing.gwt.ui.client.DurationAsHoursMinutesSecondsFormatter;
*/
public class TotalTimeColumn extends FormattedDoubleLeaderboardRowDTODetailTypeColumn {
- protected TotalTimeColumn(DetailType detailType, DataExtractor field, String headerStyle,
+ protected TotalTimeColumn(DetailType detailType, DataExtractor fieldWithTimeInSeconds, String headerStyle,
String columnStyle, DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider) {
- super(detailType, field, headerStyle, columnStyle, displayedLeaderboardRowsProvider);
+ super(detailType, fieldWithTimeInSeconds, headerStyle, columnStyle, displayedLeaderboardRowsProvider);
}
@Override
diff --git a/java/com.sap.sailing.selenium.test/com.sap.sailing.selenium.test (Proxy).launch b/java/com.sap.sailing.selenium.test/com.sap.sailing.selenium.test (Proxy).launch
deleted file mode 100644
index 3cd0d9e57aa..00000000000
--- a/java/com.sap.sailing.selenium.test/com.sap.sailing.selenium.test (Proxy).launch
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/java/com.sap.sailing.server.replication.test/ReplicationTests (Proxy).launch b/java/com.sap.sailing.server.replication.test/ReplicationTests (Proxy).launch
deleted file mode 100644
index a152a7fd218..00000000000
--- a/java/com.sap.sailing.server.replication.test/ReplicationTests (Proxy).launch
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/java/com.sap.sailing.server.trackfiles.test/com.sap.sailing.server.trackfiles.test (Proxy).launch b/java/com.sap.sailing.server.trackfiles.test/com.sap.sailing.server.trackfiles.test (Proxy).launch
deleted file mode 100644
index 375504975e1..00000000000
--- a/java/com.sap.sailing.server.trackfiles.test/com.sap.sailing.server.trackfiles.test (Proxy).launch
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java
index a43cbbed654..3e59c3e8796 100644
--- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java
+++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java
@@ -16,7 +16,9 @@ import com.sap.sailing.domain.base.RaceColumnListener;
import com.sap.sailing.domain.base.impl.RaceColumnListenerWithDefaultAction;
import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.common.MaxPointsReason;
+import com.sap.sailing.domain.common.RankingMetrics;
import com.sap.sailing.domain.leaderboard.Leaderboard;
+import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.server.interfaces.RacingEventService;
@@ -176,8 +178,17 @@ public class RaceLogScoringReplicator implements RaceColumnListenerWithDefaultAc
// let scoring scheme calculate the penalty score dynamically
scoreByRaceCommittee = null;
} else {
- scoreByRaceCommittee = leaderboard.getScoringScheme().getScoreForRank(leaderboard, raceColumn, competitor,
- oneBasedRankByRaceCommittee, ()->numberOfCompetitorsInRace, leaderboard.getNumberOfCompetitorsInLeaderboardFetcher(), timePoint);
+ // Check whether it's a non-one-design regatta. In that case, "rank by race committee" just means finish line
+ // crossing order, but that's not relevant for the actual *ranking* in a handicapped regatta. Corrected/calculated
+ // times are. Therefore, for handicap regattas, don't ask the scoring scheme to map the finishing order
+ // to a score.
+ if (leaderboard instanceof RegattaLeaderboard &&
+ ((RegattaLeaderboard) leaderboard).getRegatta().getRankingMetricType() != RankingMetrics.ONE_DESIGN) {
+ scoreByRaceCommittee = null;
+ } else {
+ scoreByRaceCommittee = leaderboard.getScoringScheme().getScoreForRank(leaderboard, raceColumn, competitor,
+ oneBasedRankByRaceCommittee, ()->numberOfCompetitorsInRace, leaderboard.getNumberOfCompetitorsInLeaderboardFetcher(), timePoint);
+ }
}
} else {
scoreByRaceCommittee = optionalExplicitScore;
diff --git a/java/com.sap.sailing.www/release_notes_admin.html b/java/com.sap.sailing.www/release_notes_admin.html
index dccb8b9490f..62b9ecc88ad 100755
--- a/java/com.sap.sailing.www/release_notes_admin.html
+++ b/java/com.sap.sailing.www/release_notes_admin.html
@@ -21,6 +21,15 @@
Release Notes - Administration Console
+
May 2019
+
+ - Ranks (finishing orders) entered by the Race Manager app user are no longer
+ considered for handicap regattas. Only an explicit score will be accepted,
+ or a finishing time can be provided which will then let the handicap ranking
+ metric infer the correct rank from that.
+
+
+
April 2019
- Filtering the leaderboard groups during a Master Data Import now also
diff --git a/translation_v2.json b/translation_v2.json
index bcb4436a3b7..1f487c83550 100644
--- a/translation_v2.json
+++ b/translation_v2.json
@@ -18,6 +18,23 @@
}
]
},
+ {
+ "collectionName":"DataMiningGwtUi",
+ "folders":[
+ {
+ "startingFolderPath":"java/com.sap.sse.datamining.ui/java/com/sap/sse/datamining/ui/client",
+ "sourceFilters":[
+ "**/StringMessages*.properties"
+ ],
+ "excludedSourceFilters":[
+ "**/*_zh.properties"
+ ],
+ "xlfFolderPath":"/XLF/[fullPath]/[targetFile]",
+ "targetFolderPath":".",
+ "pseudoLocFolderPath":"."
+ }
+ ]
+ },
{
"collectionName":"RacelogTracking",
"folders":[
@@ -85,7 +102,7 @@
"collectionName":"FileStorage",
"folders":[
{
- "startingFolderPath":"java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage",
+ "startingFolderPath":"java/com.sap.sse.gwt/src/com/sap/sse/gwt/client",
"sourceFilters":[
"**/StringMessages*.properties"
],