mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-08 20:12:23 +00:00
Merge branch 'master' into bug2842
This commit is contained in:
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.";
|
||||
+5
@@ -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
|
||||
|
||||
+12
@@ -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<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<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<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>
|
||||
+78
@@ -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; }
|
||||
};
|
||||
}
|
||||
}
|
||||
+9
-61
@@ -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);
|
||||
}
|
||||
}
|
||||
+122
@@ -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]));
|
||||
}
|
||||
}
|
||||
+7
@@ -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);
|
||||
|
||||
+15
-28
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-4
@@ -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;
|
||||
|
||||
+25
-17
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+29
-21
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+26
-19
@@ -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
|
||||
|
||||
-1
@@ -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.
|
||||
*
|
||||
|
||||
+6
-2
@@ -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);
|
||||
|
||||
+17
-9
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+5
@@ -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 {
|
||||
|
||||
+7
@@ -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
|
||||
|
||||
+2
-2
@@ -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:
|
||||
|
||||
+10
-9
@@ -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) {
|
||||
|
||||
-2
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+12
-19
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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>
|
||||
-19
@@ -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<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>
|
||||
+13
-2
@@ -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
@@ -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"
|
||||
],
|
||||
|
||||
Reference in new issue
Block a user