Merge branch 'master' into bug2842

This commit is contained in:
Daniel Lisunkin committed 2019-05-15 09:33:38 +02:00
commit 422fd3ef8b
33 files changed
+454 -358

No files matched your search

@@ -1,47 +0,0 @@
/*
SignUp.strings
SAPTracker
Created by Raimund Wege on 10.08.17.
Copyright © 2017 com.sap.sailing. All rights reserved.
*/
/* MARK: - Common */
"Common.Email" = "E-Mail";
"Common.Password" = "Passwort";
"Common.Server" = "Server";
"Common.UserName" = "Benutzername";
/* MARK: - ForgotPasswordView */
"ForgotPasswordView.Title" = "Passwort vergessen";
"ForgotPasswordView.InfoLabel.Text" = "Benutzernamen oder E-Mail Adresse angeben";
"ForgotPasswordView.ResetPasswordButton.Title" = "Passwort zurücksetzen";
/* MARK: - LoginView */
"LoginView.Title" = "Anmelden";
"LoginView.ForgotPasswordButton.Title" = "Passwort vergessen";
"LoginView.LoginButton.Title" = "Anmelden";
/* MARK: - SignUpControllerError */
"SignUpControllerError.LoginFailed" = "Anmeldung fehlgeschlagen. Zugangsdaten überprüfen.";
/* MARK: - SignUpView */
"SignUpView.Title" = "Registrieren";
"SignUpView.InfoLabel.Text" = "Herzlich Willkommen.";
"SignUpView.FullNameLabel.Text" = "Vollständiger Name";
"SignUpView.CompanyLabel.Text" = "Unternehmen";
"SignUpView.PasswordTextField.Placeholder" = "Geben Sie ein neues Passwort ein";
"SignUpView.RepeatPasswordLabel.Text" = "Passwort Wiederholung";
"SignUpView.RepeatPasswordTextField.Placeholder" = "Wiederholen Sie das Passwort";
"SignUpView.SignUpButton.Title" = "Registrieren";
/* MARK: - SignUpViewError */
"SignUpViewError.UserNameIsToShort" = "Bitte einen Benutzername mit mindestens 3 Zeichen eingeben.";
"SignUpViewError.PasswordIsToShort" = "Bitte ein Passwort mit mindestens 5 Zeichen eingeben.";
"SignUpViewError.PasswordsNotEqual" = "Passwort und Passwortwiederholung müssen übereinstimmen.";
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.common;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Speed;
import com.sap.sse.common.TimePoint;
import com.sap.sse.datamining.annotations.Statistic;
@@ -17,6 +18,10 @@ public interface SpeedWithBearing extends Speed {
* applied in reverse.
*/
Position travelTo(Position pos, TimePoint from, TimePoint to);
default Position travelTo(Position from, Duration duration) {
return from.translateGreatCircle(getBearing(), travel(duration));
}
/**
* Computes the minimal (in terms of bearing change) course and speed change required to reach the
@@ -108,6 +108,12 @@ public class LeaderboardEntryDTO implements Serializable {
* race up to the leg's end, regardless of where the leading and fastest boat are.
*/
public Duration gapToLeaderInOwnTime;
/**
* The time sailed since the race start time. When the competitor represented by this record has finished the
* race, this will be the duration between race start time and the time when the competitor finished the race.
*/
public Duration timeSailedSinceRaceStart;
/**
* The corrected time spent in the race; usually based on the current time and distance, calculated by the {@link RankingMetric}.
@@ -594,6 +600,7 @@ public class LeaderboardEntryDTO implements Serializable {
result = prime * result + ((averageSignedCrossTrackErrorInMeters == null) ? 0
: averageSignedCrossTrackErrorInMeters.hashCode());
result = prime * result + ((boat == null) ? 0 : boat.hashCode());
result = prime * result + ((timeSailedSinceRaceStart == null) ? 0 : timeSailedSinceRaceStart.hashCode());
result = prime * result + ((calculatedTime == null) ? 0 : calculatedTime.hashCode());
result = prime * result + ((calculatedTimeAtEstimatedArrivalAtCompetitorFarthestAhead == null) ? 0
: calculatedTimeAtEstimatedArrivalAtCompetitorFarthestAhead.hashCode());
@@ -666,6 +673,11 @@ public class LeaderboardEntryDTO implements Serializable {
return false;
} else if (!boat.equals(other.boat))
return false;
if (timeSailedSinceRaceStart == null) {
if (other.timeSailedSinceRaceStart != null)
return false;
} else if (!timeSailedSinceRaceStart.equals(other.timeSailedSinceRaceStart))
return false;
if (calculatedTime == null) {
if (other.calculatedTime != null)
return false;
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="2"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=com.sap.sailing.domain.test/src&lt;com.sap.sailing.domain.test"/>
<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value=""/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.domain.test"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080 -XX:+UseParallelGC -Dtractrac.tunnel=true -Dtractrac.tunnel.host=localhost -ea -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -DspatialWind=true"/>
</launchConfiguration>
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="2"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=com.sap.sailing.domain.test/src&lt;com.sap.sailing.domain.test"/>
<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value=""/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.domain.test"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080 -XX:+UseParallelGC"/>
</launchConfiguration>
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="2"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=com.sap.sailing.domain.test/src&lt;com.sap.sailing.domain.test"/>
<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/sapjvm_7"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value=""/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.domain.test"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y -Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080 -XX:+UseParallelGC -Dtractrac.tunnel=true -Dtractrac.tunnel.host=10.18.206.73 -ea -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties"/>
</launchConfiguration>
@@ -0,0 +1,78 @@
package com.sap.sailing.domain.markpassingcalculation.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import java.util.Comparator;
import java.util.NavigableSet;
import java.util.TreeSet;
import org.junit.Before;
import com.sap.sailing.domain.markpassingcalculation.Candidate;
import com.sap.sailing.domain.tracking.impl.TimedComparator;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class AbstractCandidateFilterTestSupport {
protected Candidate c1, c2, c3, c4, c5, c6, c7, c8, c9, c10;
protected NavigableSet<Candidate> competitorCandidates;
protected Comparator<Candidate> candidateComparator;
protected Candidate start;
protected Candidate end;
@Before
public void setUp() {
final TimePoint now = MillisecondsTimePoint.now();
start = new CandidateImpl(0, now.minus(Duration.ONE_HOUR), 1.0, null);
end = new CandidateImpl(6, now.plus(Duration.ONE_HOUR), 1.0, null);
candidateComparator = (c1, c2)->TimedComparator.INSTANCE.compare(c1, c2);
competitorCandidates = new TreeSet<>(candidateComparator);
final Duration gapless = MostProbableCandidatesInSmallTimeRangeFilter.CANDIDATE_FILTER_TIME_WINDOW.divide(2);
c1 = candidate(now, "c1");
c2 = candidate(c1.getTimePoint().plus(gapless), "c2");
c3 = candidate(c2.getTimePoint().plus(gapless), "c3");
c4 = candidate(c3.getTimePoint().plus(gapless), "c4");
c5 = candidate(c4.getTimePoint().plus(gapless), "c5");
// add a cap here that is greater than the threshold but not twice as long;
// this way, adding one in the middle of this gap is the interesting case
c6 = candidate(c5.getTimePoint().plus(MostProbableCandidatesInSmallTimeRangeFilter.CANDIDATE_FILTER_TIME_WINDOW.times(1.5)), "c6");
c7 = candidate(c6.getTimePoint().plus(gapless), "c7");
c8 = candidate(c7.getTimePoint().plus(gapless), "c8");
c9 = candidate(c8.getTimePoint().plus(gapless), "c9");
c10= candidate(c9.getTimePoint().plus(gapless), "c10");
competitorCandidates.add(c1);
competitorCandidates.add(c2);
competitorCandidates.add(c3);
competitorCandidates.add(c4);
competitorCandidates.add(c5);
competitorCandidates.add(c6);
competitorCandidates.add(c7);
competitorCandidates.add(c8);
competitorCandidates.add(c9);
competitorCandidates.add(c10);
}
protected void assertContainsExactly(Iterable<Candidate> candidates, Candidate... candidatesExpected) {
assertEquals(""+candidates+"'s size does not match that of "+Arrays.toString(candidatesExpected)+"; ",
candidatesExpected.length, Util.size(candidates));
assertContains(candidates, candidatesExpected);
}
protected void assertContains(Iterable<Candidate> candidates, Candidate...candidatesExpected) {
for (final Candidate candidateExpected : candidatesExpected) {
assertTrue("Expected "+candidateExpected+" to be in "+candidates+" but it wasn't; ",
Util.contains(candidates, candidateExpected));
}
}
protected Candidate candidate(TimePoint timePoint, String name) {
return new CandidateImpl(1, timePoint, 1.0, null) {
private static final long serialVersionUID = 8547646939054562751L;
@Override public String toString() { return name; }
};
}
}
@@ -1,76 +1,38 @@
package com.sap.sailing.domain.markpassingcalculation.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Comparator;
import java.util.NavigableSet;
import java.util.TreeSet;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.markpassingcalculation.Candidate;
import com.sap.sailing.domain.tracking.impl.TimedComparator;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class MostProbableCandidatesInSmallTimeRangeFilterTest {
private Candidate c1, c2, c3, c4, c5, c6, c7, c8, c9, c10;
private NavigableSet<Candidate> competitorCandidates;
public class MostProbableCandidatesInSmallTimeRangeFilterTest extends AbstractCandidateFilterTestSupport {
private MostProbableCandidatesInSmallTimeRangeFilter filter;
@Before
public void setUp() {
final TimePoint now = MillisecondsTimePoint.now();
final Candidate start = new CandidateImpl(0, now.minus(Duration.ONE_HOUR), 1.0, null);
final Candidate end = new CandidateImpl(6, now.plus(Duration.ONE_HOUR), 1.0, null);
final Comparator<Candidate> candidateComparator = (c1, c2)->TimedComparator.INSTANCE.compare(c1, c2);
super.setUp();
filter = new MostProbableCandidatesInSmallTimeRangeFilter(
candidateComparator, start, end);
competitorCandidates = new TreeSet<>(candidateComparator);
final Duration gapless = MostProbableCandidatesInSmallTimeRangeFilter.CANDIDATE_FILTER_TIME_WINDOW.divide(2);
c1 = candidate(now, "c1");
c2 = candidate(c1.getTimePoint().plus(gapless), "c2");
c3 = candidate(c2.getTimePoint().plus(gapless), "c3");
c4 = candidate(c3.getTimePoint().plus(gapless), "c4");
c5 = candidate(c4.getTimePoint().plus(gapless), "c5");
// add a cap here that is greater than the threshold but not twice as long;
// this way, adding one in the middle of this gap is the interesting case
c6 = candidate(c5.getTimePoint().plus(MostProbableCandidatesInSmallTimeRangeFilter.CANDIDATE_FILTER_TIME_WINDOW.times(1.5)), "c6");
c7 = candidate(c6.getTimePoint().plus(gapless), "c7");
c8 = candidate(c7.getTimePoint().plus(gapless), "c8");
c9 = candidate(c8.getTimePoint().plus(gapless), "c9");
c10= candidate(c9.getTimePoint().plus(gapless), "c10");
competitorCandidates.add(c1);
competitorCandidates.add(c2);
competitorCandidates.add(c3);
competitorCandidates.add(c4);
competitorCandidates.add(c5);
competitorCandidates.add(c6);
competitorCandidates.add(c7);
competitorCandidates.add(c8);
competitorCandidates.add(c9);
competitorCandidates.add(c10);
}
@Test
public void testGetTimeWiseContiguousDistanceCandidates() {
final NavigableSet<Candidate> result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, c3, /* includeStartFrom */ true);
assertContains(result, c1, c2, c3, c4, c5);
assertContainsExactly(result, c1, c2, c3, c4, c5);
}
@Test
public void testGetTimeWiseContiguousDistanceCandidatesOnRightBorder() {
final NavigableSet<Candidate> result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, c5, /* includeStartFrom */ true);
assertContains(result, c1, c2, c3, c4, c5);
assertContainsExactly(result, c1, c2, c3, c4, c5);
}
@Test
public void testGetTimeWiseContiguousDistanceCandidatesOnLeftBorder() {
final NavigableSet<Candidate> result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, c6, /* includeStartFrom */ true);
assertContains(result, c6, c7, c8, c9, c10);
assertContainsExactly(result, c6, c7, c8, c9, c10);
}
@Test
@@ -78,7 +40,7 @@ public class MostProbableCandidatesInSmallTimeRangeFilterTest {
final Candidate middleOfGap = candidate(c5.getTimePoint().plus(c5.getTimePoint().until(c6.getTimePoint()).divide(2)), "gap");
final NavigableSet<Candidate> result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, middleOfGap,
/* includeStartFrom */ true);
assertContains(result, c1, c2, c3, c4, c5, middleOfGap, c6, c7, c8, c9, c10);
assertContainsExactly(result, c1, c2, c3, c4, c5, middleOfGap, c6, c7, c8, c9, c10);
}
@Test
@@ -86,7 +48,7 @@ public class MostProbableCandidatesInSmallTimeRangeFilterTest {
final Candidate middleOfGap = candidate(c5.getTimePoint().plus(c5.getTimePoint().until(c6.getTimePoint()).divide(2)), "gap");
final NavigableSet<Candidate> result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, middleOfGap,
/* includeStartFrom */ false);
assertContains(result, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10);
assertContainsExactly(result, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10);
}
@Test
@@ -95,7 +57,7 @@ public class MostProbableCandidatesInSmallTimeRangeFilterTest {
competitorCandidates.add(middleOfGap);
final NavigableSet<Candidate> result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, middleOfGap,
/* includeStartFrom */ true);
assertContains(result, c1, c2, c3, c4, c5, middleOfGap, c6, c7, c8, c9, c10);
assertContainsExactly(result, c1, c2, c3, c4, c5, middleOfGap, c6, c7, c8, c9, c10);
}
@Test
@@ -105,20 +67,6 @@ public class MostProbableCandidatesInSmallTimeRangeFilterTest {
final Candidate middleOfGap = candidate(c5.getTimePoint().plus(c5.getTimePoint().until(c6.getTimePoint()).divide(2)), "gap");
final NavigableSet<Candidate> result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, middleOfGap,
/* includeStartFrom */ true);
assertContains(result, middleOfGap);
}
private void assertContains(NavigableSet<Candidate> candidates, Candidate... candidatesExpected) {
assertEquals(candidatesExpected.length, candidates.size());
for (final Candidate candidateExpected : candidatesExpected) {
assertTrue(candidates.contains(candidateExpected));
}
}
private Candidate candidate(TimePoint timePoint, String name) {
return new CandidateImpl(1, timePoint, 1.0, null) {
private static final long serialVersionUID = 8547646939054562751L;
@Override public String toString() { return name; }
};
assertContainsExactly(result, middleOfGap);
}
}
@@ -0,0 +1,122 @@
package com.sap.sailing.domain.markpassingcalculation.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import java.util.NavigableSet;
import java.util.TreeSet;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
import com.sap.sailing.domain.markpassingcalculation.Candidate;
import com.sap.sailing.domain.test.TrackBasedTest;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Speed;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.DegreeBearingImpl;
public class StationarySequenceBasedFilterTest extends AbstractCandidateFilterTestSupport {
private StationarySequence stationarySequence;
private DynamicGPSFixTrack<Competitor, GPSFixMoving> track;
private final static int FIXES_BETWEEN_CANDIDATES = 3;
@Before
public void setUp() {
super.setUp();
track = new DynamicGPSFixMovingTrackImpl<Competitor>(TrackBasedTest.createCompetitorWithBoat("Someone"), /* millisecondsOverWhichToAverage */ 15000);
// construct a track such that all ten candidates at first end up within a single stationary sequence:
Position position = new DegreePosition(0, 0);
final SpeedWithBearing verySmallSpeed = new KnotSpeedWithBearingImpl(StationarySequence.CANDIDATE_FILTER_DISTANCE.scale(0.5/ /* number of candidates */ 10. / (double) FIXES_BETWEEN_CANDIDATES).
inTime(c1.getTimePoint().until(c10.getTimePoint())).getKnots(), new DegreeBearingImpl(0));
TimePoint previousTimePoint = c1.getTimePoint();
for (final Candidate candidate : new Candidate[] { c2, c3, c4, c5, c6, c7, c8, c9, c10 }) {
final Duration durationBetweenFixes = previousTimePoint.until(candidate.getTimePoint()).divide(FIXES_BETWEEN_CANDIDATES);
TimePoint timePoint = previousTimePoint;
for (int i=0; i<FIXES_BETWEEN_CANDIDATES; i++) {
if (previousTimePoint != null) {
position = verySmallSpeed.travelTo(position, durationBetweenFixes);
}
track.add(new GPSFixMovingImpl(position, timePoint, verySmallSpeed));
timePoint = timePoint.plus(durationBetweenFixes);
}
previousTimePoint = candidate.getTimePoint();
}
stationarySequence = new StationarySequence(c1, candidateComparator, track);
TreeSet<Candidate> candidatesEffectivelyAdded = new TreeSet<>(candidateComparator);
TreeSet<Candidate> candidatesEffectivelyRemoved = new TreeSet<>(candidateComparator);
for (final Candidate candidate : new Candidate[] { c2, c3, c4, c5, c6, c7, c8, c9, c10 }) {
assertTrue(stationarySequence.tryToExtendAfterLast(candidate, candidatesEffectivelyAdded, candidatesEffectivelyRemoved));
}
}
@Test
public void basicStationarySequenceTest() {
assertEquals(10, stationarySequence.size());
assertContainsExactly(stationarySequence.getAllCandidates(), c1, c2, c3, c4, c5, c6, c7, c8, c9, c10);
final Iterable<Candidate> validCandidates = stationarySequence.getValidCandidates();
assertContains(validCandidates, c1, c10); // at least the first and the last are expected to be contained
// but those in the middle, particularly with the gap between c5 and c6, are not expected to be contained
assertFalse(Util.contains(validCandidates, c5));
assertFalse(Util.contains(validCandidates, c6));
}
@Test
public void testAddingOutlierFix() {
// additional set-up:
NavigableSet<Candidate> originalValidCandidates = new TreeSet<>(candidateComparator);
Util.addAll(stationarySequence.getValidCandidates(), originalValidCandidates);
final GPSFixMoving fixAtC2 = track.getFirstFixAtOrAfter(c2.getTimePoint());
final GPSFixMoving fixAfterFixAtC2 = track.getFirstFixAfter(fixAtC2.getTimePoint());
assertNotSame(fixAtC2, fixAfterFixAtC2);
final Bearing outlierCourse = fixAtC2.getSpeed().getBearing().add(new DegreeBearingImpl(90));
// add fix in between, traveling more than StationarySequence.CANDIDATE_FILTER_DISTANCE
final Position outlierPosition = fixAtC2.getPosition().translateGreatCircle(
outlierCourse, StationarySequence.CANDIDATE_FILTER_DISTANCE.scale(2));
final TimePoint inBetweenTimePoint = fixAtC2.getTimePoint().plus(fixAtC2.getTimePoint().until(fixAfterFixAtC2.getTimePoint()).divide(2));
final Speed outlierSpeed = fixAtC2.getPosition().getDistance(outlierPosition).inTime(fixAtC2.getTimePoint().until(inBetweenTimePoint));
final GPSFixMoving fixInBetween = new GPSFixMovingImpl(outlierPosition, inBetweenTimePoint, new KnotSpeedWithBearingImpl(outlierSpeed.getKnots(), outlierCourse));
track.add(fixInBetween);
TreeSet<Candidate> candidatesEffectivelyAdded = new TreeSet<>(candidateComparator);
TreeSet<Candidate> candidatesEffectivelyRemoved = new TreeSet<>(candidateComparator);
final StationarySequence splitResult = stationarySequence.tryToAddFix(fixInBetween, candidatesEffectivelyAdded, candidatesEffectivelyRemoved,
/* stationarySequenceSetToUpdate */ null, /* isReplacement */ false);
// assertions:
assertEquals(2, stationarySequence.size());
assertContainsExactly(stationarySequence.getAllCandidates(), c1, c2);
assertContainsExactly(stationarySequence.getValidCandidates(), c1, c2);
assertEquals(8, splitResult.size());
assertContainsExactly(splitResult.getAllCandidates(), c3, c4, c5, c6, c7, c8, c9, c10);
assertContains(splitResult.getValidCandidates(), c3, c10);
NavigableSet<Candidate> validCandidates = new TreeSet<>(candidateComparator);
Util.addAll(stationarySequence.getValidCandidates(), validCandidates);
Util.addAll(splitResult.getValidCandidates(), validCandidates);
assertCorrectDiff(originalValidCandidates, validCandidates, candidatesEffectivelyAdded, candidatesEffectivelyRemoved);
}
private void assertCorrectDiff(NavigableSet<Candidate> originalValidCandidates,
NavigableSet<Candidate> validCandidates, TreeSet<Candidate> candidatesEffectivelyAdded,
TreeSet<Candidate> candidatesEffectivelyRemoved) {
NavigableSet<Candidate> newValidCandidates = new TreeSet<>(candidateComparator);
newValidCandidates.addAll(validCandidates);
newValidCandidates.removeAll(originalValidCandidates);
assertContainsExactly(candidatesEffectivelyAdded, newValidCandidates.toArray(new Candidate[0]));
NavigableSet<Candidate> removedValidCandidates = new TreeSet<>(candidateComparator);
removedValidCandidates.addAll(originalValidCandidates);
removedValidCandidates.removeAll(validCandidates);
assertContainsExactly(candidatesEffectivelyRemoved, removedValidCandidates.toArray(new Candidate[0]));
}
}
@@ -38,6 +38,7 @@ import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
import com.sap.sailing.domain.tracking.impl.TimedComparator;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.util.impl.ArrayListNavigableSet;
@@ -98,6 +99,12 @@ public class LeaderboardScoringAndRankingTestBase extends AbstractLeaderboardTes
result.add(new MarkPassingImpl(lastMarkPassingTimes.get(competitor), finish, competitor));
return result;
}
@Override
public Duration getTimeSailedSinceRaceStart(Competitor competitor, TimePoint timePoint) {
final TimePoint timePointOfFinishMarkPassing = getMarkPassings(competitor).last().getTimePoint();
final TimePoint to = timePointOfFinishMarkPassing.before(timePoint) ? timePointOfFinishMarkPassing : timePoint;
return to.before(getStartOfRace()) ? null : getStartOfRace().until(to);
}
};
trackedRace.getRace().getCourse().addWaypoint(0, start);
trackedRace.getRace().getCourse().addWaypoint(1, finish);
@@ -118,13 +118,14 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
private final Distance averageAbsoluteCrossTrackError;
private final Distance averageSignedCrossTrackError;
private final Duration gapToLeaderInOwnTime;
private final Duration timeSailedSinceRaceStart;
private final Duration correctedTime;
private final Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead;
public RaceDetails(List<LegEntryDTO> legDetails, Distance windwardDistanceToCompetitorFarthestAhead,
Distance averageAbsoluteCrossTrackError, Distance averageSignedCrossTrackError,
Duration gapToLeaderInOwnTime, Duration correctedTime,
Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead) {
Duration gapToLeaderInOwnTime, Duration timeSailedSinceRaceStart,
Duration correctedTime, Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead) {
super();
this.legDetails = legDetails;
this.windwardDistanceToCompetitorFarthestAhead = windwardDistanceToCompetitorFarthestAhead;
@@ -132,6 +133,7 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
this.averageSignedCrossTrackError = averageSignedCrossTrackError;
this.gapToLeaderInOwnTime = gapToLeaderInOwnTime;
this.correctedTime = correctedTime;
this.timeSailedSinceRaceStart = timeSailedSinceRaceStart;
this.correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead;
}
public List<LegEntryDTO> getLegDetails() {
@@ -149,6 +151,9 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
public Duration getGapToLeaderInOwnTime() {
return gapToLeaderInOwnTime;
}
public Duration getTimeSailedSinceRaceStart() {
return timeSailedSinceRaceStart;
}
public Duration getCorrectedTime() {
return correctedTime;
}
@@ -618,6 +623,7 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
: raceDetails.getAverageAbsoluteCrossTrackError().getMeters();
entryDTO.averageSignedCrossTrackErrorInMeters = raceDetails.getAverageSignedCrossTrackError() == null ? null
: raceDetails.getAverageSignedCrossTrackError().getMeters();
entryDTO.timeSailedSinceRaceStart = raceDetails.getTimeSailedSinceRaceStart();
entryDTO.calculatedTime = raceDetails.getCorrectedTime();
entryDTO.calculatedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = raceDetails.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead();
entryDTO.gapToLeaderInOwnTime = raceDetails.getGapToLeaderInOwnTime();
@@ -874,6 +880,7 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
final CompetitorRankingInfo competitorRankingInfo = rankingInfo.getCompetitorRankingInfo().apply(competitor);
return new RaceDetails(legDetails, windwardDistanceToCompetitorFarthestAhead, averageAbsoluteCrossTrackError, averageSignedCrossTrackError,
trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache),
trackedRace.getTimeSailedSinceRaceStart(competitor, timePoint),
trackedRace.getRankingMetric().getCorrectedTime(competitor, timePoint),
competitorRankingInfo == null ? null : competitorRankingInfo.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead());
} finally {
@@ -1202,32 +1209,12 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
Duration result = null;
for (TrackedRace trackedRace : getTrackedRaces()) {
if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) {
NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
if (!markPassings.isEmpty()) {
TimePoint from = trackedRace.getStartOfRace(); // start counting at race start, not when the competitor passed the line
if (from != null && !timePoint.before(from)) { // but only if the race started after timePoint
TimePoint to;
if (timePoint.after(markPassings.last().getTimePoint())
&& markPassings.last().getWaypoint() == trackedRace.getRace().getCourse()
.getLastWaypoint()) {
// stop counting when competitor finished the race
to = markPassings.last().getTimePoint();
} else {
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
break;
} else {
to = timePoint;
}
}
Duration timeSpent = from.until(to);
if (result == null) {
result = timeSpent;
} else {
result=result.plus(timeSpent);
}
final Duration timeSpent = trackedRace.getTimeSailedSinceRaceStart(competitor, timePoint);
if (timeSpent != null) {
if (result == null) {
result = timeSpent;
} else {
result=result.plus(timeSpent);
}
}
}
@@ -799,10 +799,11 @@ public class CandidateFinderImpl implements CandidateFinder {
// case we can quickly obtain the next "firstFixAfter" by simply calling next() on the iterator.
firstFixAfterIterator = track.getFixesIterator(t, /* inclusive */ false);
}
if (firstFixAfterIterator.hasNext()) {
fixAfter = firstFixAfterIterator.next();
} else {
fixAfter = null;
try {
fixAfter = Util.nextOrNull(firstFixAfterIterator);
} catch (ConcurrentModificationException e) {
firstFixAfterIterator = track.getFixesIterator(t, /* inclusive */ false);
fixAfter = Util.nextOrNull(firstFixAfterIterator);
}
lastIterationFix = fix;
lastIterationAfterFix = fixAfter;
@@ -38,9 +38,9 @@ public class StationarySequence {
* If we identify several consecutive candidates that all lie in a bounding box with a {@link Bounds#getDiameter()
* diameter} less than or equal to this distance, only the first and the last of those candidates pass the filter.
*/
private static final Distance CANDIDATE_FILTER_DISTANCE = new MeterDistance(30);
public static final Distance CANDIDATE_FILTER_DISTANCE = new MeterDistance(30);
private static final Duration CANDIDATE_FILTER_TIME_WINDOW = Duration.ONE_SECOND.times(10);
public static final Duration CANDIDATE_FILTER_TIME_WINDOW = Duration.ONE_SECOND.times(10);
private final NavigableSet<Candidate> candidates;
@@ -55,7 +55,7 @@ public class StationarySequence {
* first} and {@link #getLast() last} element at the same time. Therefore, the track segment spanned by the
* resulting sequence is empty which trivially fulfills the bounding box criterion.
*/
StationarySequence(Candidate seed, Comparator<Candidate> candidateComparator, GPSFixTrack<Competitor, GPSFixMoving> track) {
public StationarySequence(Candidate seed, Comparator<Candidate> candidateComparator, GPSFixTrack<Competitor, GPSFixMoving> track) {
this.candidates = new TreeSet<>(candidateComparator);
this.candidateComparator = candidateComparator;
this.track = track;
@@ -75,7 +75,7 @@ public class StationarySequence {
* {@code candidatesEffectivelyRemoved}; {@code true} is returned in this case, indicating that the sequence
* has been extended successfully.
*/
boolean tryToExtendAfterLast(Candidate candidateAfterSequence,
public boolean tryToExtendAfterLast(Candidate candidateAfterSequence,
Set<Candidate> candidatesEffectivelyAdded, Set<Candidate> candidatesEffectivelyRemoved) {
Bounds bounds = computeExtendedBoundsForFixesBetweenCandidates(getLast(), candidateAfterSequence);
if (bounds != null) {
@@ -141,7 +141,7 @@ public class StationarySequence {
* method maintains the set referenced by this parameter accordingly, assuming that the position in the
* set may change, or, if this sequence runs empty, it has to be removed from the set altogether.
*/
boolean tryToExtendBeforeFirst(Candidate candidateBeforeSequence,
public boolean tryToExtendBeforeFirst(Candidate candidateBeforeSequence,
Set<Candidate> candidatesEffectivelyAdded, Set<Candidate> candidatesEffectivelyRemoved,
NavigableSet<StationarySequence> stationarySequenceSetToUpdate) {
Bounds bounds = computeExtendedBoundsForFixesBetweenCandidates(candidateBeforeSequence, getFirst());
@@ -218,7 +218,7 @@ public class StationarySequence {
return candidatesNoLongerPassingFilter;
}
Iterable<Candidate> getValidCandidates() {
public Iterable<Candidate> getValidCandidates() {
return ()->candidates.stream().filter(c->isCloseEnoughToSequenceBorder(c)).iterator();
}
@@ -227,11 +227,11 @@ public class StationarySequence {
candidate.getTimePoint().until(getLast().getTimePoint()).abs().compareTo(CANDIDATE_FILTER_TIME_WINDOW) < 0;
}
int size() {
public int size() {
return candidates.size();
}
boolean isEmpty() {
public boolean isEmpty() {
return candidates.isEmpty();
}
@@ -365,8 +365,9 @@ public class StationarySequence {
* no split took place, or the split didn't leave more than one candidate for a second sequence}; the new
* sequence created by a split otherwise.
*/
StationarySequence tryToAddFix(GPSFixMoving newFix, Set<Candidate> candidatesEffectivelyAdded,
Set<Candidate> candidatesEffectivelyRemoved, NavigableSet<StationarySequence> stationarySequenceSetToUpdate) {
public StationarySequence tryToAddFix(GPSFixMoving newFix, Set<Candidate> candidatesEffectivelyAdded,
Set<Candidate> candidatesEffectivelyRemoved, NavigableSet<StationarySequence> stationarySequenceSetToUpdate,
boolean isReplacement) {
assert !newFix.getTimePoint().before(getFirst().getTimePoint());
assert !newFix.getTimePoint().after(getLast().getTimePoint());
final Bounds newBounds = boundingBoxOfTrackSpanningCandidates.extend(newFix.getPosition());
@@ -374,6 +375,12 @@ public class StationarySequence {
if (newBounds.getDiameter().compareTo(CANDIDATE_FILTER_DISTANCE) < 0) {
boundingBoxOfTrackSpanningCandidates = newBounds; // ...and we're done
tailSequence = null;
if (isReplacement) {
// the fix could have replaced another one, and the bounding box may shrink now;
// later additions may unnecessarily split, so we should be kind and refresh the bounding
// box in this case:
refreshBoundingBox();
}
} else {
// split:
final Set<Candidate> oldValidCandidates = new TreeSet<>(candidateComparator);
@@ -413,7 +420,7 @@ public class StationarySequence {
}
final Set<Candidate> candidatesAdded = new TreeSet<>(candidateComparator);
candidatesAdded.addAll(newValidCandidates);
candidatesAdded.removeAll(newValidCandidates);
candidatesAdded.removeAll(oldValidCandidates);
final Set<Candidate> 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.<p>
* returned. Otherwise, a dummy candidate is created that has the {@code timePoint} provided.
* <p>
*
* 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<Candidate> getAllCandidates() {
public Iterable<Candidate> getAllCandidates() {
return Collections.unmodifiableCollection(candidates);
}
@@ -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);
}
}
}
@@ -261,12 +261,15 @@ public abstract class AbstractRankingMetric implements RankingMetric {
/**
* The time from the {@link TrackedRace#getStartOfRace() race start} until <code>timePoint</code> or until
* the point in time when <code>competitor</code> passed the finish mark, whichever comes first.
* the point in time when <code>competitor</code> 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 <code>timePoint</code>, determines how long in real, uncorrected time <code>who</code> lags
* behind <code>to</code> in the leg identified by <code>legWho</code>. If both are still sailing in the leg at
* <code>timePoint</code>, this is the time <code>who</code> needs with constant average VMG to reach
* <code>to</code>'s position at <code>timePoint</code>. If only <code>to</code> has already finished the leg and no
* mark passing time is known yet for <code>who</code> for the end of the leg then <code>who</code> is projected to
* the end of the leg using her average VMG on the leg, and the difference between <code>who</code>'s projected and
* <code>to</code>'s actual mark passing times is returned.
* <p>
*
* If leg finish mark passings are available for both, <code>who</code> and <code>to</code>, the difference between
* them is returned.
* <code>to</code>'s position at <code>timePoint</code>. If only <code>to</code> has already finished the leg at
* {@code timePoint} then <code>who</code> is projected to the end of the leg using her average VMG on the leg, and
* the difference between <code>who</code>'s projected and <code>to</code>'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.
* <p>
*
* The result may be a negative duration in case <code>who</code> reached the position in question before
* <code>timePoint</code>.<p>
* <code>timePoint</code>.
* <p>
*
* Precondition: <code>who</code> and <code>to</code> have both started sailing the leg at <code>timePoint</code>
* and <code>to</code> has sailed a greater or equal windward distance compared to <code>who</code>. 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
@@ -197,7 +197,6 @@ public class OneDesignRankingMetric extends AbstractRankingMetric {
public Duration getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead() {
return getEstimatedActualDurationFromRaceStartToCompetitorFarthestAhead();
}
};
}
@@ -154,6 +154,14 @@ public interface RankingMetric extends Serializable {
Comparator<TrackedLegOfCompetitor> 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 <code>competitor</code> spend in the {@link TrackedRace#getRace() race} described by
* <code>trackedRace</code> at <code>timePoint</code>, corrected by the handicapping rules defined by this ranking
@@ -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.
*
@@ -1097,8 +1097,9 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
@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<Competitor, GPSFixMoving> {
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);
@@ -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.<p>
*
* 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();
}
}
}
@@ -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 {
@@ -4,6 +4,13 @@
<div id="mainContent">
<h4 class="articleHeadline">What's New - SAP Sailing Analytics</h4>
<div class="innerContent">
<h5 class="articleSubheadline">May 2019</h5>
<ul class="bulletList">
<li>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.</li>
</ul>
<h5 class="articleSubheadline">April 2019</h5>
<ul class="bulletList">
<li>Competitor tails on the map can now be colored according to a metric. This enables quick and intuitive
@@ -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:
@@ -1265,17 +1265,17 @@ public abstract class LeaderboardPanel<LS extends LeaderboardSettings> 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<LS extends LeaderboardSettings> 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) {
@@ -50,7 +50,6 @@ public class OverallTimeTraveledColumn extends ExpandableSortableColumn<String>
LeaderboardPanel<?> leaderboardPanel, StringMessages stringConstants, String detailHeaderStyle,
String detailColumnStyle) {
Map<DetailType, AbstractSortableColumnWithMinMax<LeaderboardRowDTO, ?>> 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<String>
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;
}
@@ -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<String> implements HasStringAndDoubleValue<LeaderboardRowDTO> {
public class TimeTraveledRaceColumnInSeconds extends ExpandableSortableColumn<String> implements HasStringAndDoubleValue<LeaderboardRowDTO> {
private static final DetailType DETAIL_TYPE = DetailType.RACE_TIME_TRAVELED;
@@ -31,7 +31,7 @@ public class TimeTraveledRaceColumn extends ExpandableSortableColumn<String> imp
private String headerStyle;
private MinMaxRenderer<LeaderboardRowDTO> 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<String> 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<String> imp
private class RaceTimeTraveledUpwindInSeconds implements DataExtractor<Double, LeaderboardRowDTO> {
@Override
public Double get(LeaderboardRowDTO row) {
return getTimeTraveledFor(row, LegType.UPWIND);
return getTimeTraveledForLegTypeInSeconds(row, LegType.UPWIND);
}
}
private class RaceTimeTraveledDownwindInSeconds implements DataExtractor<Double, LeaderboardRowDTO> {
@Override
public Double get(LeaderboardRowDTO row) {
return getTimeTraveledFor(row, LegType.DOWNWIND);
return getTimeTraveledForLegTypeInSeconds(row, LegType.DOWNWIND);
}
}
private class RaceTimeTraveledReachingInSeconds implements DataExtractor<Double, LeaderboardRowDTO> {
@Override
public Double get(LeaderboardRowDTO row) {
return getTimeTraveledFor(row, LegType.REACHING);
return getTimeTraveledForLegTypeInSeconds(row, LegType.REACHING);
}
}
@@ -12,9 +12,9 @@ import com.sap.sailing.gwt.ui.client.DurationAsHoursMinutesSecondsFormatter;
*/
public class TotalTimeColumn extends FormattedDoubleLeaderboardRowDTODetailTypeColumn {
protected TotalTimeColumn(DetailType detailType, DataExtractor<Double, LeaderboardRowDTO> field, String headerStyle,
protected TotalTimeColumn(DetailType detailType, DataExtractor<Double, LeaderboardRowDTO> fieldWithTimeInSeconds, String headerStyle,
String columnStyle, DisplayedLeaderboardRowsProvider displayedLeaderboardRowsProvider) {
super(detailType, field, headerStyle, columnStyle, displayedLeaderboardRowsProvider);
super(detailType, fieldWithTimeInSeconds, headerStyle, columnStyle, displayedLeaderboardRowsProvider);
}
@Override
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.selenium.test"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=com.sap.sailing.selenium.test"/>
<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value=""/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.selenium.test"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Dselenium.test.environment.configuration=local-test-environment.xml -Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080 -Dwebdriver.firefox.profile=Selenium"/>
</launchConfiguration>
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.server.replication.test"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=com.sap.sailing.server.replication.test"/>
<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value=""/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.server.replication.test"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080 -XX:+UseParallelGC -Dtractrac.tunnel=true -Dtractrac.tunnel.host=10.18.22.156 -ea -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties"/>
</launchConfiguration>
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.server.trackfiles.test/src/com/sap/sailing/server/trackfiles/test"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="2"/>
</listAttribute>
<mapAttribute key="org.eclipse.debug.core.preferred_launchers">
<mapEntry key="[run]" value="org.eclipse.jdt.junit.launchconfig"/>
</mapAttribute>
<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=com.sap.sailing.server.trackfiles.test/src&lt;com.sap.sailing.server.trackfiles.test"/>
<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value=""/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.server.trackfiles.test"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Dhttp.proxyHost=proxy -Dhttp.proxyPort=8080 -Dtractrac.tunnel.host=10.18.22.156 -Dtractrac.tunnel=true"/>
</launchConfiguration>
@@ -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;
@@ -21,6 +21,15 @@
<div class="mainContent">
<h2 class="releaseHeadline">Release Notes - Administration Console</h2>
<div class="innerContent">
<h2 class="articleSubheadline">May 2019</h2>
<ul class="bulletList">
<li>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.
</li>
</ul>
<h2 class="articleSubheadline">April 2019</h2>
<ul class="bulletList">
<li>Filtering the leaderboard groups during a Master Data Import now also
+18 -1
View File
@@ -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"
],