null}, the result will be {@code NaN}.
+ */
+ double divide(Distance other);
+
+ /**
+ * If this distance is negative, returns a positive difference with the same absolute value; for positive
+ * distances, this distance is returned.
+ */
+ Distance abs();
}
diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PassingInstruction.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PassingInstruction.java
index 6a1a4cf576e..d8d11bf4472 100644
--- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PassingInstruction.java
+++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PassingInstruction.java
@@ -14,6 +14,7 @@ package com.sap.sailing.domain.common;
public enum PassingInstruction {
None(1, 2),
Port(1),
+ Single_Unknown(1),
Starboard(1),
Gate(2),
Line(2),
diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Position.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Position.java
index b25873ad0c1..b2fb909baec 100755
--- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Position.java
+++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Position.java
@@ -69,8 +69,8 @@ public interface Position extends Serializable {
Distance alongTrackDistance(Position from, Bearing bearing);
/**
- * Computes the distance from this position to the line between left and right. This distance
- * is positive if left is actually the position farther to the left, as seen from this position.
+ * Computes the distance from this position to the line between left and right. This distance
+ * is positive if left is actually the position farther to the left, as seen from this position.
*/
Distance getDistanceToLine(Position left, Position right);
diff --git a/java/com.sap.sailing.domain.test/resources/event_20130424_IDMStarboo-Race_5.mtb b/java/com.sap.sailing.domain.test/resources/event_20130424_IDMStarboo-Race_5.mtb
index adae6c1cef3..e9912906b44 100755
Binary files a/java/com.sap.sailing.domain.test/resources/event_20130424_IDMStarboo-Race_5.mtb and b/java/com.sap.sailing.domain.test/resources/event_20130424_IDMStarboo-Race_5.mtb differ
diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImplWhiteBoxTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImplWhiteBoxTest.java
new file mode 100644
index 00000000000..23788331f99
--- /dev/null
+++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImplWhiteBoxTest.java
@@ -0,0 +1,109 @@
+package com.sap.sailing.domain.markpassingcalculation.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.sap.sailing.domain.base.BoatClass;
+import com.sap.sailing.domain.base.DomainFactory;
+import com.sap.sailing.domain.base.RaceDefinition;
+import com.sap.sailing.domain.common.impl.MeterDistance;
+import com.sap.sailing.domain.markpassingcalculation.impl.CandidateFinderImpl.AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine;
+import com.sap.sailing.domain.tracking.DynamicTrackedRace;
+
+public class CandidateFinderImplWhiteBoxTest {
+ private CandidateFinderImpl finder;
+
+ @Before
+ public void setUp() {
+ DynamicTrackedRace trackedRace = mock(DynamicTrackedRace.class);
+ final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29er");
+ RaceDefinition race = mock(RaceDefinition.class);
+ when(trackedRace.getRace()).thenReturn(race);
+ when(race.getBoatClass()).thenReturn(boatClass);
+ when(race.getCompetitors()).thenReturn(Collections.emptySet());
+ finder = new CandidateFinderImpl(trackedRace);
+ }
+
+ @Test
+ public void testOneProbabilityForEmptyList() {
+ List emptyDistancesToStartLineOfOtherCompetitors = new ArrayList<>();
+ final double probability = finder.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(emptyDistancesToStartLineOfOtherCompetitors,
+ /* startIsLine */ true);
+ assertEquals(1.0, probability, 0.00001);
+ }
+
+ @Test
+ public void testHighProbabilityForCloseProximityAndNoLine() {
+ List distancesToStartLineOfOtherCompetitors = new ArrayList<>();
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(3.0, null));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(5.0, null));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(2.0, null));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(8.0, null));
+ final double probability = finder.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(distancesToStartLineOfOtherCompetitors,
+ /* startIsLine */ false);
+ assertTrue(probability > 0.95);
+ }
+
+ @Test
+ public void testHighProbabilityForCloseProximityExceptOneAndNoLine() {
+ List distancesToStartLineOfOtherCompetitors = new ArrayList<>();
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(3.0, null));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(5.0, null));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(2.0, null));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(8.0, null));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(1.0, null));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(2.5, null));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(3.2, null));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(2739.0, null));
+ final double probability = finder.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(distancesToStartLineOfOtherCompetitors,
+ /* startIsLine */ false);
+ assertTrue("Expected probability to exceed 55% but got "+probability, probability > 0.55);
+ }
+
+ @Test
+ public void testLowProbabilityForMostFarAwayAndNoLine() {
+ List distancesToStartLineOfOtherCompetitors = new ArrayList<>();
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(50.0, null));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(250.0, null));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(120.0, null));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(200.0, null));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(300.0, null));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(2.5, null));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(3.2, null));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(2739.0, null));
+ final double probability = finder.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(distancesToStartLineOfOtherCompetitors,
+ /* startIsLine */ false);
+ assertTrue("Expected probability to be below 10% but got "+probability, probability <= 0.1);
+ }
+
+ @Test
+ public void testHighProbabilityForLateStartAndStartIsLine() {
+ List distancesToStartLineOfOtherCompetitors = new ArrayList<>();
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(100.0, -80.));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(120.0, -82.));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(130.0, -81.));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(90.0, -79.));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(140.0, -75.));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(150, -88.));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(135, -77.));
+ distancesToStartLineOfOtherCompetitors.add(createDistancePair(20.0, -10.)); // another late starter just across the line
+ final double probability = finder.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(distancesToStartLineOfOtherCompetitors,
+ /* startIsLine */ true);
+ assertTrue("Expected probability to be above 85% but got "+probability, probability >= 0.85);
+ }
+
+ private AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine createDistancePair(double absoluteDistanceToLine,
+ Double signedXTEDistanceFromLine) {
+ return new AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine(
+ new MeterDistance(absoluteDistanceToLine), signedXTEDistanceFromLine == null ? null : new MeterDistance(signedXTEDistanceFromLine));
+ }
+}
diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java
index 8a8a930bf72..9849a0e3322 100755
--- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java
+++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java
@@ -55,7 +55,8 @@ public abstract class AbstractTracTracLiveTest extends StoredTrackBasedTest {
private IRaceSubscriber raceSubscriber;
private final Collection receivers;
- @Rule public Timeout AbstractTracTracLiveTestTimeout = new Timeout(3 * 60 * 1000);
+ // TODO uncomment when done testing
+// @Rule public Timeout AbstractTracTracLiveTestTimeout = new Timeout(3 * 60 * 1000);
protected AbstractTracTracLiveTest() throws URISyntaxException, MalformedURLException {
receivers = new HashSet();
diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMarkPassingTest.java
index 4d2bdf4f4e2..bcab5fcf473 100755
--- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMarkPassingTest.java
+++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMarkPassingTest.java
@@ -64,7 +64,7 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest {
simpleName = getClass().getSimpleName();
}
- private void setUp(String raceNumber) throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException {
+ protected void setUp(String raceNumber) throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException {
super.setUp();
URI storedUri = new URI("file:///"
+ new File("resources/" + getFileName() + raceNumber + ".mtb").getCanonicalPath().replace('\\', '/'));
@@ -90,6 +90,21 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest {
}
}
+ protected Iterable getWaypoints() {
+ return waypoints;
+ }
+
+ protected Competitor getCompetitorByName(final String name) {
+ Competitor result = null;
+ for (Competitor c : getTrackedRace().getRace().getCompetitors()) {
+ if (c.getName().equals(name)) {
+ result = c;
+ break;
+ }
+ }
+ return result;
+ }
+
protected abstract String getFileName();
protected void testRace(String raceNumber) throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException {
@@ -214,29 +229,19 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest {
}
private void testMiddleOfRace(int zeroBasedIndexOfLastWaypointToBePassed) {
+ int mistakes = 0;
CandidateFinder finder = new CandidateFinderImpl(getTrackedRace());
CandidateChooser chooser = new CandidateChooserImpl(getTrackedRace());
- int mistakes = 0;
Waypoint lastWaypointToBePassed = waypoints.get(zeroBasedIndexOfLastWaypointToBePassed);
Waypoint wayPointAfterwards = waypoints.get(zeroBasedIndexOfLastWaypointToBePassed + 1);
for (Competitor c : getRace().getCompetitors()) {
MarkPassing markPassingAfter = givenPasses.get(c).get(wayPointAfterwards);
if (markPassingAfter != null) {
if (givenPasses.get(c).get(lastWaypointToBePassed) != null) {
- TimePoint beforeNextPassing = markPassingAfter.getTimePoint().minus(20000);
- List fixes = new ArrayList();
- try {
- getTrackedRace().getTrack(c).lockForRead();
- for (GPSFixMoving fix : getTrackedRace().getTrack(c).getFixes()) {
- if (fix.getTimePoint().before(beforeNextPassing)) {
- fixes.add(fix);
- }
- }
- } finally {
- getTrackedRace().getTrack(c).unlockAfterRead();
- }
- Util.Pair, Iterable> f = finder.getCandidateDeltas(c, fixes);
- chooser.calculateMarkPassDeltas(c, f.getA(), f.getB());
+ final TimePoint lastGivenPassingToDetectAt = givenPasses.get(c).get(lastWaypointToBePassed).getTimePoint();
+ // add fixes up to 50% into the leg starting after the last passing to be detected
+ final TimePoint beforeNextPassing = lastGivenPassingToDetectAt.plus(lastGivenPassingToDetectAt.until(markPassingAfter.getTimePoint()).times(0.5));
+ calculateMarkPassingsForPartialTrack(c, beforeNextPassing, finder, chooser);
boolean gotPassed = true;
boolean gotOther = false;
System.out.println(c);
@@ -270,6 +275,23 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest {
}
+ protected void calculateMarkPassingsForPartialTrack(Competitor c, final TimePoint upToTimePoint,
+ CandidateFinder finder, CandidateChooser chooser) {
+ List fixes = new ArrayList();
+ try {
+ getTrackedRace().getTrack(c).lockForRead();
+ for (GPSFixMoving fix : getTrackedRace().getTrack(c).getFixes()) {
+ if (fix.getTimePoint().before(upToTimePoint)) {
+ fixes.add(fix);
+ }
+ }
+ } finally {
+ getTrackedRace().getTrack(c).unlockAfterRead();
+ }
+ Util.Pair, Iterable> f = finder.getCandidateDeltas(c, fixes);
+ chooser.calculateMarkPassDeltas(c, f.getA(), f.getB());
+ }
+
@AfterClass
public static void createXML() throws IOException {
double accuracy = correct / totalPasses;
diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/Kiel2013505Test.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/Kiel2013505Test.java
index 9844a4266f7..42c1801ec38 100755
--- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/Kiel2013505Test.java
+++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/Kiel2013505Test.java
@@ -7,17 +7,33 @@ import java.text.ParseException;
import org.junit.Test;
+import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
+import com.sap.sailing.domain.abstractlog.race.RaceLog;
+import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl;
+import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartProcedureChangedEventImpl;
+import com.sap.sailing.domain.common.racelog.RacingProcedureType;
+import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
public class Kiel2013505Test extends AbstractMarkPassingTest {
- public Kiel2013505Test() throws MalformedURLException, URISyntaxException {
+ public Kiel2013505Test() throws MalformedURLException, URISyntaxException {
super();
}
- //Takes longer than 2 minutes, so the rule in AbstractTracTracLiveTest needs to be deactivated first.
-
+ /**
+ * Marks the race as using a gate start
+ */
+ @Override
+ protected void setUp(String raceNumber) throws IOException, InterruptedException, URISyntaxException,
+ ParseException, SubscriberInitializationException, CreateModelException {
+ super.setUp(raceNumber);
+ final RaceLog raceLog = new RaceLogImpl("505 Race Log");
+ getTrackedRace().attachRaceLog(raceLog);
+ raceLog.add(new RaceLogStartProcedureChangedEventImpl(MillisecondsTimePoint.now(), new LogEventAuthorImpl("Me", 0), 0, RacingProcedureType.GateStart));
+ }
+
@Test
public void testRace5() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException {
testRace("5");
diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/StarIDM2013MarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/StarIDM2013MarkPassingTest.java
index 600111324cb..6a3d28e5684 100755
--- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/StarIDM2013MarkPassingTest.java
+++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/StarIDM2013MarkPassingTest.java
@@ -1,12 +1,31 @@
package com.sap.sailing.domain.test.markpassing;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.text.ParseException;
+import java.util.Collections;
+import java.util.Date;
import org.junit.Test;
+import com.sap.sailing.domain.base.Competitor;
+import com.sap.sailing.domain.base.Waypoint;
+import com.sap.sailing.domain.markpassingcalculation.CandidateChooser;
+import com.sap.sailing.domain.markpassingcalculation.CandidateFinder;
+import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
+import com.sap.sailing.domain.markpassingcalculation.impl.CandidateChooserImpl;
+import com.sap.sailing.domain.markpassingcalculation.impl.CandidateFinderImpl;
+import com.sap.sailing.domain.tracking.MarkPassing;
+import com.sap.sse.InvalidDateException;
+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;
+import com.sap.sse.util.DateParser;
import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
@@ -18,8 +37,82 @@ public class StarIDM2013MarkPassingTest extends AbstractMarkPassingTest {
@Test
public void testRace5() throws IOException, InterruptedException, URISyntaxException, ParseException,
- SubscriberInitializationException, CreateModelException {
- testRace("5");
+ SubscriberInitializationException, CreateModelException, InvalidDateException {
+ // Race 5 is really broken on TracTrac's side. I've tried re-calculating the mark passing to no avail.
+ // Now it's even worse, and only the windward mark passings and the start mark passings are recognized
+ // half-way reasonably, but no mark passings seem to have been detected for subsequent waypoints.
+ // To still use this race for testing purposes, let's do spot checks based on manually identified mark
+ // passings, based on https://www.sapsailing.com/gwt/RaceBoard.html?eventId=a43b8f96-1e77-448a-9b8a-0f586f1bb18f&leaderboardName=Star%20IDM%202013&leaderboardGroupName=Star%20IDM%202013&raceName=Race%205&viewShowMapControls=true&viewShowNavigationPanel=true®attaName=IDM%20Starboot%202013%20%28Star%29
+ setUp("5");
+ synchronized (getSemaphor()) {
+ while (!isStoredDataLoaded()) {
+ getSemaphor().wait();
+ }
+ }
+ testWholeRace5();
+ testOnlyStartOfRace5();
+ }
+
+ private void testWholeRace5() throws InvalidDateException {
+ new MarkPassingCalculator(getTrackedRace(), false); // do our calculation, injecting into TrackedRace
+ final Competitor GER7897 = getCompetitorByName("Tusch, Frank,Winkelmann, Sven");
+ assertMarkPassing(GER7897, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:10+0200"));
+ assertMarkPassing(GER7897, Util.get(getWaypoints(), 1), DateParser.parse("2013-05-04T14:57:12+0200"));
+ assertMarkPassing(GER7897, Util.get(getWaypoints(), 3), DateParser.parse("2013-05-04T15:06:32+0200"));
+ assertMarkPassing(GER7897, Util.get(getWaypoints(), 4), DateParser.parse("2013-05-04T15:15:01+0200"));
+ assertMarkPassing(GER7897, Util.get(getWaypoints(), 6), DateParser.parse("2013-05-04T15:21:17+0200"));
+ assertMarkPassing(GER7897, Util.get(getWaypoints(), 7), DateParser.parse("2013-05-04T15:29:18+0200"));
+ assertMarkPassing(GER7897, Util.get(getWaypoints(), 9), DateParser.parse("2013-05-04T15:36:36+0200"));
+ assertMarkPassing(GER7897, Util.get(getWaypoints(), 10), DateParser.parse("2013-05-04T15:38:09+0200"));
+
+ final Competitor GER7616 = getCompetitorByName("Seefelder, Jürgen,Seefelder, N"); // starts late
+ assertMarkPassing(GER7616, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:57+0200"));
+ assertMarkPassing(GER7616, Util.get(getWaypoints(), 1), DateParser.parse("2013-05-04T14:59:05+0200"));
+ assertMarkPassing(GER7616, Util.get(getWaypoints(), 3), DateParser.parse("2013-05-04T15:07:32+0200"));
+ assertMarkPassing(GER7616, Util.get(getWaypoints(), 4), DateParser.parse("2013-05-04T15:16:40+0200"));
+ assertMarkPassing(GER7616, Util.get(getWaypoints(), 6), DateParser.parse("2013-05-04T15:23:26+0200"));
+ assertMarkPassing(GER7616, Util.get(getWaypoints(), 7), DateParser.parse("2013-05-04T15:33:01+0200"));
+ assertMarkPassing(GER7616, Util.get(getWaypoints(), 9), DateParser.parse("2013-05-04T15:39:25+0200"));
+ assertMarkPassing(GER7616, Util.get(getWaypoints(), 10), DateParser.parse("2013-05-04T15:40:51+0200"));
+
+ final Competitor GER8340 = getCompetitorByName("Griese, Achim,Marcour, Michael"); // starts rather early
+ assertMarkPassing(GER8340, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:04+0200"));
+ assertMarkPassing(GER8340, Util.get(getWaypoints(), 1), DateParser.parse("2013-05-04T14:56:57+0200"));
+ assertMarkPassing(GER8340, Util.get(getWaypoints(), 3), DateParser.parse("2013-05-04T15:06:33+0200"));
+ assertMarkPassing(GER8340, Util.get(getWaypoints(), 4), DateParser.parse("2013-05-04T15:14:31+0200"));
+ assertMarkPassing(GER8340, Util.get(getWaypoints(), 6), DateParser.parse("2013-05-04T15:20:29+0200"));
+ assertMarkPassing(GER8340, Util.get(getWaypoints(), 7), DateParser.parse("2013-05-04T15:28:34+0200"));
+ assertMarkPassing(GER8340, Util.get(getWaypoints(), 9), DateParser.parse("2013-05-04T15:35:52+0200"));
+ assertMarkPassing(GER8340, Util.get(getWaypoints(), 10), DateParser.parse("2013-05-04T15:37:22+0200"));
+ }
+
+ private void testOnlyStartOfRace5() throws InvalidDateException {
+ // clear mark passings
+ for (Competitor c : getRace().getCompetitors()) {
+ getTrackedRace().updateMarkPassings(c, Collections.emptySet());
+ }
+ CandidateFinder finder = new CandidateFinderImpl(getTrackedRace());
+ CandidateChooser chooser = new CandidateChooserImpl(getTrackedRace());
+ final TimePoint afterStart = new MillisecondsTimePoint(DateParser.parse("2013-05-04T14:52:30+0200"));
+ for (Competitor c : getRace().getCompetitors()) {
+ calculateMarkPassingsForPartialTrack(c, afterStart, finder, chooser);
+ }
+ final Competitor GER7897 = getCompetitorByName("Tusch, Frank,Winkelmann, Sven");
+ assertMarkPassing(GER7897, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:10+0200"));
+ final Competitor GER7616 = getCompetitorByName("Seefelder, Jürgen,Seefelder, N"); // starts late
+ assertMarkPassing(GER7616, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:57+0200"));
+ final Competitor GER8340 = getCompetitorByName("Griese, Achim,Marcour, Michael"); // starts rather early
+ assertMarkPassing(GER8340, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:04+0200"));
+ }
+
+ private void assertMarkPassing(Competitor competitor, Waypoint waypoint, Date when) {
+ final MarkPassing markPassing = getTrackedRace().getMarkPassing(competitor, waypoint);
+ if (markPassing == null) {
+ fail("Expected mark passing for "+competitor+" and "+waypoint+" around "+when+" but no mark passing found");
+ }
+ final Duration offset = new MillisecondsTimePoint(when).until(markPassing.getTimePoint()).abs();
+ assertTrue("Expected mark passing for "+competitor+" and "+waypoint+" around "+when+
+ " but found one at "+markPassing.getTimePoint()+" which is "+offset+" off.", offset.asSeconds() < 10);
}
@Test
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java
index e96ad5ba319..093f167be45 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java
@@ -67,7 +67,7 @@ public class CandidateChooserImpl implements CandidateChooser {
*/
private static final Duration DELAY_AFTER_WHICH_PROBABILITY_OF_START_HALVES = Duration.ONE_MINUTE;
- private final static double MINIMUM_PROBABILITY = 1 - Edge.getPenaltyForSkipping();
+ private final static double MINIMUM_PROBABILITY = Edge.getPenaltyForSkipping();
private static final Logger logger = Logger.getLogger(CandidateChooserImpl.class.getName());
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java
index 98abb9c3b45..447b99753cf 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.markpassingcalculation.impl;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
@@ -35,6 +36,7 @@ import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.Util;
+import com.sap.sse.common.Util.Pair;
/**
* The standard implemantation of {@link CandidateFinder}. There are two kinds of {@link Candidate}s. First of all,
@@ -53,11 +55,36 @@ public class CandidateFinderImpl implements CandidateFinder {
// All of the penalties are multiplied onto the probability of a Candidate. A value of 0 excludes Candidates that do
// not fit, a value of 1 imposes no penalty on each criteria
- private final double PENALTY_FOR_WRONG_SIDE = 0.8;
- private final double PENALTY_FOR_WRONG_DIRECTION = 0.8;
- private final double PENALTY_FOR_DISTANCE_CANDIDATES = 0.8;
+ private static final double PENALTY_FOR_WRONG_SIDE = 0.8;
+
+ /**
+ * The penalty factor multiplied with a candidate's probability in case the competitor passes the line of an XTE
+ * candidate with passing instructions other than {@link PassingInstruction#Line} in the wrong direction, e.g.,
+ * passing a single mark that is to be passed to port in the wrong direction.
+ */
+ private static final double PENALTY_FOR_WRONG_DIRECTION = 0.7;
+
+ /**
+ * The penalty factor multiplied with a candidate's probability in case the competitor passes an XTE candidate for a
+ * {@link PassingInstruction#Line line} in the wrong direction, e.g., passing a start line the wrong way.
+ */
+ private static final double PENALTY_FOR_LINE_PASSED_IN_WRONG_DIRECTION = 0.3;
+
+ /**
+ * Normally, for each distance candidate there should also be a proper XTE candidate that also tells about the
+ * direction in which the boat crosses the line. XTE candidates are more precise and get a penalty in case the competitor
+ * crosses the virtual line in the wrong direction: {@link #PENALTY_FOR_WRONG_DIRECTION}. The penalty a competitor should
+ * get for not triggering a proper XTE candidate and only producing a candidate by getting near a mark and then
+ * moving away again should be penalized at least as badly as {@link #PENALTY_FOR_WRONG_DIRECTION}.
+ */
+ private static final double PENALTY_FOR_DISTANCE_CANDIDATES = 0.9 * PENALTY_FOR_WRONG_DIRECTION;
+
+ private static final double WORST_PENALTY_FOR_OTHER_COMPETITORS_BEING_FAR_FROM_START = 0.1;
+ private static final double NUMBER_OF_HULL_LENGTHS_DISTANCE_FROM_START_AT_WHICH_WORST_PENALTY_APPLIES = 10;
private static final Logger logger = Logger.getLogger(CandidateFinderImpl.class.getName());
+
+
private Map>>> distanceCache = new LinkedHashMap<>();
private Map>>> xteCache = new LinkedHashMap<>();
@@ -291,7 +318,7 @@ public class CandidateFinderImpl implements CandidateFinder {
if (numberofMarks == 2) {
instruction = PassingInstruction.Gate;
} else if (numberofMarks == 1) {
- instruction = PassingInstruction.Port;
+ instruction = PassingInstruction.Single_Unknown;
} else {
instruction = PassingInstruction.None;
}
@@ -368,7 +395,9 @@ public class CandidateFinderImpl implements CandidateFinder {
Boolean isCan = false;
Candidate oldCan = null;
Double probability = null;
+ double sidePenalty = 1;
Distance distance = null;
+ Double startProbabilityBasedOnOtherCompetitors = null;
double onCorrectSideOfWaypoint = 0.8;
List waypointDistances = fixDistances.get(w);
List waypointDistancesBefore = fixDistancesBefore.get(w);
@@ -397,12 +426,23 @@ public class CandidateFinderImpl implements CandidateFinder {
// FIXME why not generate the candidate here where we have all information at hand?
final double newOnCorrectSideOfWaypointPenalty = getSidePenalty(w, p, t, portMark);
newProbability *= newOnCorrectSideOfWaypointPenalty * PENALTY_FOR_DISTANCE_CANDIDATES;
+ final Double newStartProbabilityBasedOnOtherCompetitors;
+ if (isStartWaypoint(w)) {
+ newStartProbabilityBasedOnOtherCompetitors = getProbabilityForStartCandidateBasedOnOtherCompetitorsBehavior(c, t);
+ if (newStartProbabilityBasedOnOtherCompetitors != null) {
+ newProbability *= newStartProbabilityBasedOnOtherCompetitors;
+ }
+ } else {
+ newStartProbabilityBasedOnOtherCompetitors = null;
+ }
if (newProbability > penaltyForSkipping
&& (probability == null || newProbability > probability)) {
isCan = true;
probability = newProbability;
+ sidePenalty = newOnCorrectSideOfWaypointPenalty;
onCorrectSideOfWaypoint = newOnCorrectSideOfWaypointPenalty;
distance = dis;
+ startProbabilityBasedOnOtherCompetitors = newStartProbabilityBasedOnOtherCompetitors;
}
}
}
@@ -415,7 +455,7 @@ public class CandidateFinderImpl implements CandidateFinder {
}
if (!wasCan && isCan) {
Candidate newCan = new DistanceCandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1,
- t, probability, w, onCorrectSideOfWaypoint, distance);
+ t, probability, startProbabilityBasedOnOtherCompetitors, w, sidePenalty, distance);
getDistanceCandidates(c, w).put(fix, newCan);
result.getA().add(newCan);
logger.finest("Added distance" + newCan.toString() + "for " + c);
@@ -424,7 +464,7 @@ public class CandidateFinderImpl implements CandidateFinder {
result.getB().add(oldCan);
} else if (wasCan && isCan && oldCan.getProbability() != probability) {
Candidate newCan = new DistanceCandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1,
- t, probability, w, onCorrectSideOfWaypoint, distance);
+ t, probability, startProbabilityBasedOnOtherCompetitors, w, onCorrectSideOfWaypoint, distance);
getDistanceCandidates(c, w).put(fix, newCan);
result.getA().add(newCan);
logger.finest("Added distance" + newCan.toString() + "for " + c);
@@ -632,11 +672,229 @@ public class CandidateFinderImpl implements CandidateFinder {
final List distances = calculateDistance(p, w, t);
final Distance d = portMark ? distances.get(0) : distances.get(1);
final double sidePenalty = getSidePenalty(w, p, t, portMark);
- final boolean passesInRightDirection = passesInTheRightDirection(w, xte1, xte2, portMark);
double probability = getDistanceBasedProbability(w, t, d) * sidePenalty;
- probability = passesInRightDirection ? probability: probability* PENALTY_FOR_WRONG_DIRECTION ;
-
- return new XTECandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1, t, probability, w, sidePenalty, passesInRightDirection);
+ final Double passesInTheRightDirectionProbability = passesInTheRightDirection(w, xte1, xte2, portMark);
+ // null would mean "unknown"; no penalty for those cases
+ probability = passesInTheRightDirectionProbability == null ? probability : probability * passesInTheRightDirectionProbability;
+ final Double startProbabilityBasedOnOtherCompetitors;
+ if (isStartWaypoint(w)) {
+ // add a penalty for a start candidate if we don't know the start time, it's not a gate start and
+ // at time point t the other competitors are largely not even close to the start waypoint;
+ // this will make start candidates much more likely if many other competitors are also very close to the
+ // start, and it will help ruling out those candidates where someone is practicing a start just for themselves
+ startProbabilityBasedOnOtherCompetitors = getProbabilityForStartCandidateBasedOnOtherCompetitorsBehavior(c, t);
+ if (startProbabilityBasedOnOtherCompetitors != null) {
+ probability *= startProbabilityBasedOnOtherCompetitors;
+ }
+ } else {
+ startProbabilityBasedOnOtherCompetitors = null;
+ }
+ return new XTECandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1, t, probability,
+ startProbabilityBasedOnOtherCompetitors, w, sidePenalty, passesInTheRightDirectionProbability);
+ }
+
+ static class AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine {
+ private final Distance absoluteGeometricDistance;
+ private final Distance signedProjectedDistance;
+ public AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine(Distance absoluteGeometricDistance,
+ Distance signedProjectedDistance) {
+ super();
+ this.absoluteGeometricDistance = absoluteGeometricDistance;
+ this.signedProjectedDistance = signedProjectedDistance;
+ }
+ public Distance getAbsoluteGeometricDistance() {
+ return absoluteGeometricDistance;
+ }
+ public Distance getSignedProjectedDistance() {
+ return signedProjectedDistance;
+ }
+ @Override
+ public String toString() {
+ return "AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine [absoluteGeometricDistance="
+ + absoluteGeometricDistance + ", signedProjectedDistance=" + signedProjectedDistance + "]";
+ }
+ }
+
+ /**
+ * For a fixed line start it is very likely that all competitors pass the start waypoint at about the same time, or
+ * that only a few competitors lag behind, e.g., because of start time penalties. This requires everyone to at least
+ * be pretty close to the start waypoint at the start time, of if we're looking at a competitor starting late, the
+ * field would at least have little variance and be consistently on the course side of the start line.
+ *
+ *
+ * Therefore, a start mark passing candidate is very likely when a large share of competitors is very close to the
+ * start line. If the other competitors are not very close to the start line, a start may still be likely if the
+ * others are on the course side and have similar distance to the start line, whereas it becomes pretty unlikely
+ * when everyone is sailing around anywhere on the course or behind the line but not being in close proximity to the
+ * line and not having similar distances to the start line while being on course side.
+ *
+ *
+ * - check distance variance of other competitors; low variance could mean they started jointly
+ * - for start *line*, consider the side on which the other competitors are; don't rule out a late start if other
+ * competitors have low distance variance and are on course side of the line
+ * - generally consider distance of other competitors to line
+ * - dampen "outliers" such as trackers forgotten in harbor or trackers not currently sending (80% of competitors
+ * that look like they are starting seems like a good guess)
+ *
+ *
+ * Approach: compute signed distances to start line (positive meaning on course side). Sort by absolute distance and
+ * compute a {@link #weight(double) weighted} average where the boats farthest away are considered less relevant than
+ * the ones closer, leading to a natural outlier removal.
+ *
+ * For the special case of a late starter, consider the weighted variance of the signed distance from the start line.
+ * A low variance with a positive average (meaning on course side) indicates that everyone else may have started around the
+ * same time, making this a probable late starter's candidate. Therefore, low variance with positive average shall increase
+ * a probability that was low because of a large weighted average distance.
+ *
+ * @param t
+ * the time point at which to consider the other competitors behavior relative to the start waypoint
+ * @return {@code null} if the {@link #race} has a gate start or the start time is known; a probability between 0..1
+ * (inclusive) where 0 means that based on all but {@code c}'s relation to the start line it seems
+ * completely unlikely that a candidate for {@code c} at time {@code t} could have been a start candidate; 1
+ * meaning that based on the other competitors' relation to the start line at time point {@code t} is seems
+ * a fact that this must have been the start.
+ *
+ */
+ private Double getProbabilityForStartCandidateBasedOnOtherCompetitorsBehavior(Competitor c, TimePoint t) {
+ final Double result;
+ if (race.getStartOfRace(/* inferred */ false) == null && race.isGateStart() != Boolean.TRUE) {
+ final Waypoint start = race.getRace().getCourse().getFirstWaypoint();
+ final Iterable> crossingInformationForStart = getCrossingInformation(start, t);
+ if (start == null) {
+ result = 1.0;
+ } else {
+ // if the start waypoint's passing instructions are unknown, assume it's a line
+ final boolean startIsLine = start.getPassingInstructions() == PassingInstruction.Line || start.getPassingInstructions() == PassingInstruction.None;
+ final List distancesToStartLineOfOtherCompetitors = new ArrayList<>();
+ for (final Competitor otherCompetitor : race.getRace().getCompetitors()) {
+ if (otherCompetitor != c) {
+ final DynamicGPSFixTrack track = race.getTrack(otherCompetitor);
+ if (track != null) {
+ final Position estimatedPositionAtT = track.getEstimatedPosition(t, /* extrapolate */ true);
+ final Distance otherCompetitorsDistanceToStartAtT = getMinDistanceOrNull(calculateDistance(estimatedPositionAtT, start, t));
+ if (otherCompetitorsDistanceToStartAtT != null) {
+ final Distance crossTrackError;
+ if (startIsLine) {
+ Pair crossingInformationForStartLine = crossingInformationForStart.iterator().next();
+ crossTrackError = estimatedPositionAtT.crossTrackError(crossingInformationForStartLine.getA(),
+ crossingInformationForStartLine.getB());
+ } else {
+ crossTrackError = null;
+ }
+ distancesToStartLineOfOtherCompetitors.add(new
+ AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine(otherCompetitorsDistanceToStartAtT, crossTrackError));
+ }
+ }
+ }
+ }
+ result = getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(distancesToStartLineOfOtherCompetitors, startIsLine);
+ }
+ } else {
+ result = null;
+ }
+ return result;
+ }
+
+ /**
+ * The method computes two probabilities of the candidate being a start: one based on the other competitors largely
+ * being close to the start line, meaning a regular start where the competitor starts roughly at the same time as
+ * everybody else; the other probability computed is based on the other competitors largely being in a similar
+ * distance to the start line on the course side, suggesting that the competitor is starting late, but mostly
+ * everybody else did start earlier and around the same time. The probability returned is the probability of
+ * the start being the one or the other scenario which mathematically is represented by using the inverted
+ * probabilities of the two start options, multiplying them and inverting the resulting probability.
+ */
+ Double getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(
+ final List distancesToStartLineOfOtherCompetitors, boolean startIsLine) {
+ final Double result;
+ // sort by the absolute distance to start line
+ // boats within one hull length of the start line are great indicators that we're at the start:
+ final Distance hullLength = race.getRace().getBoatClass().getHullLength();
+ final Distance weightedAverageAbsoluteDistance;
+ if (!distancesToStartLineOfOtherCompetitors.isEmpty()) {
+ Collections.sort(distancesToStartLineOfOtherCompetitors, (a, b)->a.getAbsoluteGeometricDistance().compareTo(b.getAbsoluteGeometricDistance()));
+ Distance weightedAbsoluteDistanceSum = Distance.NULL;
+ int i=0;
+ double weightSum = 0;
+ for (final AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine d : distancesToStartLineOfOtherCompetitors) {
+ final double weight = weight(((double) i)/distancesToStartLineOfOtherCompetitors.size());
+ final Distance weightedDistance = d.getAbsoluteGeometricDistance().scale(weight);
+ weightedAbsoluteDistanceSum = weightedAbsoluteDistanceSum.add(weightedDistance);
+ weightSum += weight;
+ i++;
+ }
+ weightedAverageAbsoluteDistance = weightedAbsoluteDistanceSum.scale(1. / weightSum);
+ final double probabilityOfStartWithOthers = Math.max(WORST_PENALTY_FOR_OTHER_COMPETITORS_BEING_FAR_FROM_START, Math.min(1.0,
+ (1.-(1.-WORST_PENALTY_FOR_OTHER_COMPETITORS_BEING_FAR_FROM_START)/NUMBER_OF_HULL_LENGTHS_DISTANCE_FROM_START_AT_WHICH_WORST_PENALTY_APPLIES
+ * (weightedAverageAbsoluteDistance.divide(hullLength)-1))));
+ if (startIsLine) {
+ // Now look at the variance, particularly on course side: low variance may mean we are looking at a late starter
+ // sort such that the boats with the least value (greatest negative amount) comes first and the last ~20% are largely ignored
+ Collections.sort(distancesToStartLineOfOtherCompetitors, (a, b)->a.getSignedProjectedDistance().compareTo(b.getSignedProjectedDistance()));
+ Distance weightedSignedDistanceSum = Distance.NULL;
+ weightSum = 0;
+ i=0;
+ for (final AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine d : distancesToStartLineOfOtherCompetitors) {
+ final double weight = weight(((double) i)/distancesToStartLineOfOtherCompetitors.size());
+ weightedSignedDistanceSum = weightedSignedDistanceSum.add(d.getSignedProjectedDistance().scale(
+ weight));
+ weightSum += weight;
+ i++;
+ }
+ final Distance weightedAverageSignedDistance = weightedSignedDistanceSum.scale(1. / weightSum);
+ i=0;
+ double signedDistanceVarianceSumInSquareMeters = 0;
+ weightSum = 0;
+ for (final AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine d : distancesToStartLineOfOtherCompetitors) {
+ final double differenceFromWeightedAverageInMeters = d.getSignedProjectedDistance().getMeters() - weightedAverageSignedDistance.getMeters();
+ final double weight = weight(((double) i)/distancesToStartLineOfOtherCompetitors.size());
+ signedDistanceVarianceSumInSquareMeters += weight*differenceFromWeightedAverageInMeters*differenceFromWeightedAverageInMeters;
+ weightSum += weight;
+ i++;
+ }
+ final double weightedVarianceInSquareMeters = signedDistanceVarianceSumInSquareMeters / weightSum;
+ final double weightedVarianceRatioToSquaredHullLength = weightedVarianceInSquareMeters / hullLength.getMeters() / hullLength.getMeters();
+ final double probabilityOfLateStart = Math.max(WORST_PENALTY_FOR_OTHER_COMPETITORS_BEING_FAR_FROM_START, Math.min(1.0,
+ (1.-(1.-WORST_PENALTY_FOR_OTHER_COMPETITORS_BEING_FAR_FROM_START)/NUMBER_OF_HULL_LENGTHS_DISTANCE_FROM_START_AT_WHICH_WORST_PENALTY_APPLIES
+ * (weightedVarianceRatioToSquaredHullLength-1))));
+ result = 1. - (1.-probabilityOfLateStart) * (1.-probabilityOfStartWithOthers);
+ } else {
+ result = probabilityOfStartWithOthers;
+ }
+ } else {
+ // no distance for any other competitor from the start line was available
+ result = 1.0;
+ }
+ return result;
+ }
+
+ /**
+ * Idea: sort and cut off the worst 20%. We played with weight functions for averaging the distances such that after sorting them
+ * in increasing order the smaller ones get greater weight and the "outlier" ones towards the end of the sorted set receive small
+ * weights. Still, if even the "best" (least) distances are far away, the result will be a low probability.
+ * A function that could be helpful is this: -atan(20*(x-0.8)) + Pi/2)/Pi. The 0.8 represents the 80% starting from which the
+ * weight declines sharply. The factor of 20 represents how sharp the decline is. x is between 0 and 1, representing the start
+ * and the end of the sorted distance collection, respectively. Outputs are between 0 and 1, representing the weight to assign
+ * to a distance while averaging.
+ */
+ private double weight(double relativePositionInDistanceCollectionInIncreasingOrder) {
+ final double SHARPNESS_OF_DECLINE = 100;
+ final double CENTER_OF_DECLINE = 0.8;
+ return (-Math.atan(SHARPNESS_OF_DECLINE*(relativePositionInDistanceCollectionInIncreasingOrder-CENTER_OF_DECLINE))+Math.PI/2) / Math.PI;
+ }
+
+ private Distance getMinDistanceOrNull(List distances) {
+ Distance result = null;
+ for (Distance d : distances) {
+ if (d != null && (result == null || result.compareTo(d) > 0)) {
+ result = d;
+ }
+ }
+ return result;
+ }
+
+ private boolean isStartWaypoint(Waypoint w) {
+ return race.getRace().getCourse().getFirstWaypoint() == w;
}
/**
@@ -678,7 +936,7 @@ public class CandidateFinderImpl implements CandidateFinder {
} else {
Mark m = null;
- if (instruction == PassingInstruction.Port || instruction == PassingInstruction.Starboard
+ if (instruction == PassingInstruction.Single_Unknown || instruction == PassingInstruction.Port || instruction == PassingInstruction.Starboard
|| instruction == PassingInstruction.FixedBearing || instruction == PassingInstruction.Offset) {
m = w.getMarks().iterator().next();
} else if (instruction == PassingInstruction.Gate) {
@@ -706,19 +964,31 @@ public class CandidateFinderImpl implements CandidateFinder {
}
/**
- * Determines whether a candidate passes a waypoint in the right direction. Can only be applied to XTE-Candidates.
- * For marks passed on port, the cross-track error should switch from positive to negative and vice versa. Lines are
- * also from positive to negative as the cross-track error to a line is always positive when approaching it from the
- * correct side.
+ * Determines whether a candidate passes a waypoint in the right direction. A probability of 1 is assigned in case
+ * it does. Otherwise, depending on the passing instructions, {@link #PENALTY_FOR_WRONG_DIRECTION} or
+ * {@link #PENALTY_FOR_LINE_PASSED_IN_WRONG_DIRECTION} is returned as a "probability penalty" for passing the
+ * waypoint in the wrong direction. The penalty is worse if {@link PassingInstruction#Line lines} are passed in the
+ * wrong direction. Can only be applied to XTE-Candidates. For marks passed on port, the cross-track error should
+ * switch from positive to negative and vice versa. Lines are also from positive to negative as the cross-track
+ * error to a line is always positive when approaching it from the correct side.
+ *
+ * @return {@code null} in case the passing instructions aren't defined and therefore the correct direction is not
+ * known
*/
- private boolean passesInTheRightDirection(Waypoint w, double xte1, double xte2, boolean portMark) {
- boolean result = true;
+ private Double passesInTheRightDirection(Waypoint w, double xte1, double xte2, boolean portMark) {
+ final Double result;
PassingInstruction instruction = getPassingInstructions(w);
- if (instruction == PassingInstruction.Port || instruction == PassingInstruction.Line
+ if (instruction == PassingInstruction.Single_Unknown) {
+ result = null;
+ } else if (instruction == PassingInstruction.Port
|| (instruction == PassingInstruction.Gate && portMark)) {
- result = xte1 > xte2 ? true : false;
+ result = xte1 > xte2 ? 1 : PENALTY_FOR_WRONG_DIRECTION;
} else if (instruction == PassingInstruction.Starboard || (instruction == PassingInstruction.Gate && !portMark)) {
- result = xte1 < xte2 ? true : false;
+ result = xte1 < xte2 ? 1 : PENALTY_FOR_WRONG_DIRECTION;
+ } else if (instruction == PassingInstruction.Line) {
+ result = xte1 > xte2 ? 1 : PENALTY_FOR_LINE_PASSED_IN_WRONG_DIRECTION;
+ } else {
+ result = null;
}
return result;
}
@@ -761,11 +1031,12 @@ public class CandidateFinderImpl implements CandidateFinder {
* Calculates the distance from p to each mark of w at the timepoint t.
*/
private List calculateDistance(Position p, Waypoint w, TimePoint t) {
- List distances = new ArrayList<>();
+ final List distances = new ArrayList<>();
PassingInstruction instruction = getPassingInstructions(w);
boolean singleMark = false;
switch (instruction) {
case Port:
+ case Single_Unknown:
case Starboard:
case FixedBearing:
singleMark = true;
@@ -791,7 +1062,7 @@ public class CandidateFinderImpl implements CandidateFinder {
Position portLinePosition = race.getOrCreateTrack(posLine.getA()).getEstimatedPosition(t, false);
Position starboardLinePosition = race.getOrCreateTrack(posLine.getB()).getEstimatedPosition(t, false);
distances.add((portLinePosition != null && starboardLinePosition != null) ? p.getDistanceToLine(
- portLinePosition, starboardLinePosition) : null);
+ portLinePosition, starboardLinePosition).abs() : null);
}
break;
case Offset:
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateWithSide.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateWithSide.java
index 9e80f01f60e..cf82ee24fc6 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateWithSide.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateWithSide.java
@@ -5,15 +5,18 @@ import com.sap.sse.common.TimePoint;
public abstract class CandidateWithSide extends CandidateImpl {
private final double onCorrectSideOfWaypoint;
+ private final Double startProbabilityBasedOnOtherCompetitors;
- public CandidateWithSide(int oneBasedIndexOfWaypoint, TimePoint p, double probability, Waypoint w,
- double onCorrectSideOfWaypoint) {
+ public CandidateWithSide(int oneBasedIndexOfWaypoint, TimePoint p, double probability, Double startProbabilityBasedOnOtherCompetitors,
+ Waypoint w, double onCorrectSideOfWaypoint) {
super(oneBasedIndexOfWaypoint, p, probability, w);
+ this.startProbabilityBasedOnOtherCompetitors = startProbabilityBasedOnOtherCompetitors;
this.onCorrectSideOfWaypoint = onCorrectSideOfWaypoint;
}
@Override
public String toString() {
- return ""+getClass().getSimpleName()+" "+super.toString()+", onCorrectSideOfWaypoint: "+onCorrectSideOfWaypoint;
+ return ""+getClass().getSimpleName()+" "+super.toString()+", onCorrectSideOfWaypoint: "+onCorrectSideOfWaypoint+
+ (startProbabilityBasedOnOtherCompetitors==null?"":", startProbabilityBasedOnOtherCompetitors="+startProbabilityBasedOnOtherCompetitors);
}
}
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/DistanceCandidateImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/DistanceCandidateImpl.java
index d550cab17a4..c9cca5c756c 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/DistanceCandidateImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/DistanceCandidateImpl.java
@@ -7,9 +7,9 @@ import com.sap.sse.common.TimePoint;
public class DistanceCandidateImpl extends CandidateWithSide {
private final Distance distance;
- public DistanceCandidateImpl(int oneBasedIndexOfWaypoint, TimePoint p, double probability, Waypoint w,
- double onCorrectSideOfWaypoint, Distance distance) {
- super(oneBasedIndexOfWaypoint, p, probability, w, onCorrectSideOfWaypoint);
+ public DistanceCandidateImpl(int oneBasedIndexOfWaypoint, TimePoint p, double probability, Double startProbabilityBasedOnOtherCompetitors,
+ Waypoint w, double onCorrectSideOfWaypoint, Distance distance) {
+ super(oneBasedIndexOfWaypoint, p, probability, startProbabilityBasedOnOtherCompetitors, w, onCorrectSideOfWaypoint);
this.distance = distance;
}
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/Edge.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/Edge.java
index 1b8d76c3f3a..20589f0c145 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/Edge.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/Edge.java
@@ -13,14 +13,30 @@ import com.sap.sailing.domain.markpassingcalculation.Candidate;
* @author Nicolas Klose
*
*/
-
public class Edge implements Comparable {
private final Candidate start;
private final Candidate end;
- // TODO what is the meaning of this constant?
- private final static double PENALTY_FOR_SKIPPED = 0.16;
- // TODO what is the meaning of this constant?
- private final static double PENALTY_FOR_SKIPPED_TO_END = 0.25;
+
+ /**
+ * The penalty for an edge's probability in case the edge skips one or more waypoints and does not
+ * end at the end proxy node. Skipping a waypoint that is not the last means that either the waypoint
+ * was passed in a very strange, unrecognized way or we got a tracker outage. This is both pretty
+ * unlikely and is penalized by this factor being multiplied to the edge's general probability.
+ *
+ * This factor is raised to the n-th power for n waypoints skipped.
+ */
+ private final static double PENALTY_FOR_SKIPPED = 0.1;
+
+ /**
+ * Similar to {@link #PENALTY_FOR_SKIPPED}, but applied to edges that skip to the end proxy node. Such skips
+ * are regular business while the race is still going on, and paths that are otherwise likely but don't lead
+ * up to the end shall be possible and therefore must not be penalized harshly, receiving less of a penalty
+ * (greater factor).
+ *
+ * As with {@link #PENALTY_FOR_SKIPPED}, this factor is raised to the n-th power for n waypoints skipped.
+ */
+ private final static double PENALTY_FOR_SKIPPED_TO_END = 0.5;
+
private final double estimatedDistanceAndStartTimingProbability;
private final int numberOfWaypoints;
@@ -44,8 +60,10 @@ public class Edge implements Comparable {
*/
public Double getProbability() {
final double penalty = end.getOneBasedIndexOfWaypoint() == numberOfWaypoints + 1 ? PENALTY_FOR_SKIPPED_TO_END : PENALTY_FOR_SKIPPED;
- return start.getProbability() * end.getProbability() * estimatedDistanceAndStartTimingProbability * Math.pow(penalty, (end.getOneBasedIndexOfWaypoint() - start.getOneBasedIndexOfWaypoint() - 1));
-
+ // See bug 3241 comment #38: only use the edge's end candidate's probability; the start candidate's probability is the
+ // previous edge's end probability. The only probability we'll miss this way is that of the start proxy node and that is always 1.
+ return end.getProbability() * estimatedDistanceAndStartTimingProbability *
+ Math.pow(penalty, (end.getOneBasedIndexOfWaypoint() - start.getOneBasedIndexOfWaypoint() - 1));
}
public Candidate getStart() {
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/XTECandidateImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/XTECandidateImpl.java
index 0b4168b8b9f..e478568d72b 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/XTECandidateImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/XTECandidateImpl.java
@@ -4,15 +4,17 @@ import com.sap.sailing.domain.base.Waypoint;
import com.sap.sse.common.TimePoint;
public class XTECandidateImpl extends CandidateWithSide {
- private final boolean passesInTheRightDirection;
+ private final Double passesInTheRightDirectionProbability;
- public XTECandidateImpl(int oneBasedIndexOfWaypoint, TimePoint p, double probability, Waypoint w, double onCorrectSideOfWaypoint, boolean passesInTheRightDirection) {
- super(oneBasedIndexOfWaypoint, p, probability, w, onCorrectSideOfWaypoint);
- this.passesInTheRightDirection = passesInTheRightDirection;
+ public XTECandidateImpl(int oneBasedIndexOfWaypoint, TimePoint p, double probability,
+ Double startProbabilityBasedOnOtherCompetitors, Waypoint w, double onCorrectSideOfWaypoint,
+ Double passesInTheRightDirectionProbability) {
+ super(oneBasedIndexOfWaypoint, p, probability, startProbabilityBasedOnOtherCompetitors, w, onCorrectSideOfWaypoint);
+ this.passesInTheRightDirectionProbability = passesInTheRightDirectionProbability;
}
@Override
public String toString() {
- return super.toString()+", passesInTheRightDirection: "+passesInTheRightDirection;
+ return super.toString()+", passesInTheRightDirection: "+passesInTheRightDirectionProbability;
}
}
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java
index c7199a2a5ed..c6b2997f97c 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java
@@ -575,7 +575,7 @@ DynamicTrackedRace, GPSTrackListener {
neededToCreateOrUpdateFinishMarkPassing = true;
} else {
finishMarkPassingToUse = originalMarkPassing.getOriginal();
- if (copyOfMarkPassings != originalMarkPassing) {
+ if (finishMarkPassingToUse != originalMarkPassing) {
logger.info(getRace().getName()+": Reverting race log-based finish mark passing "+originalMarkPassing+" to "+finishMarkPassingToUse+
" because no finishing time found anymore for that competitor in race log");
neededToCreateOrUpdateFinishMarkPassing = true;
diff --git a/java/com.sap.sailing.gwt.ui/Leaderboard.css b/java/com.sap.sailing.gwt.ui/Leaderboard.css
index 92b5cf8adae..79ed640da2c 100755
--- a/java/com.sap.sailing.gwt.ui/Leaderboard.css
+++ b/java/com.sap.sailing.gwt.ui/Leaderboard.css
@@ -52,7 +52,7 @@ table {
}
.leaderboardContent td, .leaderboardContent th {
- vertical-align: bottom;
+/* vertical-align: bottom; */
line-height: 1.3;
/* padding-top: 2px; */
}
diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch
index 2bd988c6083..263825f093c 100644
--- a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch
+++ b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch
@@ -1,78 +1,78 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT.launch b/java/com.sap.sailing.gwt.ui/SailingGWT.launch
index 6adb642328e..b14028404ea 100644
--- a/java/com.sap.sailing.gwt.ui/SailingGWT.launch
+++ b/java/com.sap.sailing.gwt.ui/SailingGWT.launch
@@ -1,75 +1,75 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.properties
index cd6e7af5c5a..7365690323b 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.properties
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.properties
@@ -163,7 +163,7 @@ searchResultSortLabel=Sort by
searchResultSortNewest=Recent
searchResultSortOldest=Oldest
searchResultSortRelevance=Relevance
-searchResultCount=About 2,450 results where found for 'Schnitzel'
+searchResultCount=About 2,450 results where found for ''Schnitzel''
sharingEmail=Send an email
sharingFacebook=Share on Facebook
sharingTwitter=Tweet about it
@@ -197,7 +197,7 @@ solutionsSAPText1=By delivering real time analytics around race rankings, speeds
solutionsSAPText2=SAP Sailing Analytics provide insights and transparency to the world of sailing by utilizing Cloud and In-Memory Technology, processing GPS and wind measurement data in real time and visualizing contents in various frontends accessible from anywhere.
solutionsRaceText=The SAP Race Committee App delivers greater efficiency and control to race organizers by automating and simplifying their manual tasks and communication during running sailing regattas. The Race Committee App helps simplify the operation of running and managing race events to help race committees operate smarter.
solutionsPostText=Access to data collected at sailing events gives sailors the opportunity to assess and analyse their performances to help develop strategies and sail better.
SAP Business Analytics solutions provide sailors, coaches, fans and media with tools to browse and analyse historical data across many regattas and races. SAP HANA allows them to learn about performance patterns, tendencies and sailor profiles in a simple, fast and efficient way in order to optimize their own performance and develop successful strategies for future regattas.
-solutionsTrainingText=The STG Training Diary supports the daily documentation and analysis of an athlete\u2019s physical condition and training efforts in the areas of water training, physical training, boat work, mental, theory as well as other activities like education or travel.
+solutionsTrainingText=The STG Training Diary supports the daily documentation and analysis of an athlete''s physical condition and training efforts in the areas of water training, physical training, boat work, mental, theory as well as other activities like education or travel.
solutionsSimulatorText=Choosing the right strategy can provide great competitive advantage during a race. The Strategy Simulator simplifies determining the best sailing strategy for various wind conditions and water currents. Since wind and current can be changed in the simulation right away, the robustness of a strategy can be easily evaluated even for uncertain weather conditions.
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.ui.xml
index 0c9ed27ee32..f90fce8f834 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.ui.xml
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.ui.xml
@@ -43,10 +43,10 @@
-
+
-
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.ui.xml
index 307e1bb0bf2..8d1b7a99c63 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.ui.xml
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.ui.xml
@@ -24,7 +24,7 @@
styleName="{local_res.css.sitenavigation_link}" text="{i18n.events}" />
-
|
-
+ |
+
+
+
td:FIRST-CHILD {
- border-left: 1px solid rgba(33,160,228,0.6) !important;
-}
-
-.cellTableSelectedRow > td:LAST-CHILD {
- border-right: 1px solid rgba(33,160,228,0.6) !important;
-}
-.cellTableSelectedRow > td {
-}
.cellTableCell {
- overflow: inherit;
font-size: 12px !important;
line-height: 1.5em !important;
-}
-.cellTableSelectedRowCell {
- border-top: 2px double #21a0e4 !important;
- border-bottom: 2px double #21a0e4 !important;
- padding: 7px 7px 6px 7px !important;
+ vertical-align: middle;
}
+.cellTableSelectedRowCell {
+ box-shadow: inset 0px -3px 3px -3px #21a0e4, inset 0px 3px 3px -3px #21a0e4;
+ padding: 7px !important;
+ }
+
.cellTableEvenRowCell, .cellTableOddRowCell {
- border: 1px solid #c9caca;
+ border-top: none;
+ border-right: none;
+ border-bottom: 1px solid #d6d6d6;
+ border-left: 1px solid #d6d6d6;
padding: 7px;
white-space: nowrap;
}
.cellTableCheckboxColumnCell {
- padding: 1px 1px 1px !important;
- vertical-align: middle !important;
- text-align: center;
}
.cellTableCheckboxSelected {
- margin: auto auto;
height: 13px;
width: 13px;
- border: 1px solid #fcbb22;
- background-color: #fcbb22;
- vertical-align: middle;
+ margin: auto auto;
+/* border: 1px solid #fcbb22; */
+/* background-color: #fcbb22; */
+ border: solid 1px #acacac;
+ background-color: #cacaca;
+
}
.cellTableCheckboxDeselected {
- margin: auto auto;
height: 13px;
width: 13px;
+ margin: auto auto;
border: solid 1px #acacac;
- vertical-align: middle;
}
.cellTableHoveredRow {
@@ -135,7 +123,6 @@
.cellTableLegColumnHeader {
background-color: #efefef;
-
}
.cellTableLegDetailColumnHeader {
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties
index 316d07f3a69..ae1417c1b07 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties
@@ -1444,7 +1444,6 @@ profile=Profile
showOnlyCompetitorsOfLog=Show only competitors registered in Log
confirmLosingCompetitorEditsWhenTogglingLogBasedView=This will reset your current, unsaved changes to the competitors.\nDo you really want to continue?
removalOfMarkDisabledMayBeUsedInRaces=Removal deavticated, marks are used in races {0}
-pleaseCreateAtLeastOneMappingBy=Plase create at list one mapping by first selecting a track on the left hand side and then selecting a mark or competitor on the right hand side
createDefaultLeaderboardGroup=Would you like to create a default LeaderboardGroup for your event?
pleaseCreateAtLeastOneMappingBy=Please create at least one mapping by first selecting a track on the left hand side and then selecting a mark or competitor on the right hand side
matcherType=Matcher type
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties
index de43e303420..e43b12f8808 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties
@@ -1366,7 +1366,6 @@ scoringSchemeHighPointFirstGetsTwelveOrEight=High Point, winner gets 12 points (
scoringSchemeHighPointFirstGetsTwelveOrEightDescription=Score in points. Winner scores 12 points, 2nd - 11 points, .... A tie is broken in favour of the competitor having won most races, if tie remains then tie is broken by the rank in the last race scored.
scoringSchemeHighPointEssOverall12=High Point System ESS Overall (12)
scoringSchemeHighPointEssOverall12Description=Score in points. Winner of an act scores 12 points, 2nd - 11 points, .... Any tie in the overall Extreme Sailing Series is broken in favour of the competitor with the most Act wins. If that does not break the tie, the result in the last Act will be used.
-pleaseCreateAtLeastOneMappingBy=Plase create at list one mapping by first selecting a track on the left hand side and then selecting a mark or competitor on the right hand side
createDefaultLeaderboardGroup=Would you like to create a default LeaderboardGroup for your event?
pleaseCreateAtLeastOneMappingBy=Please create at least one mapping by first selecting a track on the left hand side and then selecting a mark or competitor on the right hand side
matcherType=Matcher type
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties
index 72dd9f19f23..1db85e36f16 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties
@@ -1381,7 +1381,6 @@ scoringSchemeHighPointFirstGetsTwelveOrEight=High Point, winner gets 12 points (
scoringSchemeHighPointFirstGetsTwelveOrEightDescription=Score in points. Winner scores 12 points, 2nd - 11 points, .... A tie is broken in favour of the competitor having won most races, if tie remains then tie is broken by the rank in the last race scored.
scoringSchemeHighPointEssOverall12=High Point System ESS Overall (12)
scoringSchemeHighPointEssOverall12Description=Score in points. Winner of an act scores 12 points, 2nd - 11 points, .... Any tie in the overall Extreme Sailing Series is broken in favour of the competitor with the most Act wins. If that does not break the tie, the result in the last Act will be used.
-pleaseCreateAtLeastOneMappingBy=Plase create at list one mapping by first selecting a track on the left hand side and then selecting a mark or competitor on the right hand side
createDefaultLeaderboardGroup=Would you like to create a default LeaderboardGroup for your event?
pleaseCreateAtLeastOneMappingBy=Please create at least one mapping by first selecting a track on the left hand side and then selecting a mark or competitor on the right hand side
matcherType=Matcher type
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
index d8a3d76289a..d44004fe6e7 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
@@ -2033,7 +2033,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
+ " -> " + NumberFormat.getDecimalFormat().format(after.speedInKnots) + " "
+ stringMessages.knotsUnit() + ")"));
if (maneuver.maneuverLossInMeters != null) {
- vPanel.add(createInfoWindowLabelAndValue(stringMessages.maneuverLoss(), ""+maneuver.maneuverLossInMeters+"m"));
+ vPanel.add(createInfoWindowLabelAndValue(stringMessages.maneuverLoss(), NumberFormat.getFormat("0.0").format(maneuver.maneuverLossInMeters)+"m"));
}
return vPanel;
}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardTable.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardTable.css
index 11d9c412cda..2d8c4698a2b 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardTable.css
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardTable.css
@@ -1,7 +1,7 @@
.cellTableWidget {
- border-left: 1px solid #c9caca;
border-top: 1px solid #c9caca;
- border-collapse: collapse;
+ border-right: 1px solid #c9caca;
+ border-collapse: separate;
}
@@ -16,20 +16,19 @@
border-right: 0px solid #c9caca !important;
border-top: none !important;
border-bottom: 1px solid #c9caca !important;
+ border-left: 1px solid #c9caca !important;
padding: 3px 5px;
padding-bottom: 5px;
text-align: center;
text-shadow: none !important;
+ font-weight: bold;
+ font-size: 12px!important;
}
.cellTableFirstColumnHeader {
position: relative;
vertical-align: bottom;
background-color: #e5e5e5;
- border-left: none !important;
- border-right: 1px solid #c9caca !important;
- border-top: none !important;
- border-bottom: 1px solid #c9caca !important;
text-align: center;
}
@@ -37,9 +36,6 @@
position: relative;
vertical-align: bottom;
background-color: #e5e5e5;
- border-right: 1px solid #c9caca !important;
- border-top: none !important;
- border-bottom: 1px solid #c9caca !important;
text-align: center;
}
@@ -58,57 +54,49 @@
.cellTableSelectedRow {
color: #000000;
- background-color: rgba(229,242,249,0.6);
+ background-color: rgba(229,242,249,0.5);
}
-.cellTableSelectedRow > td:FIRST-CHILD {
- border-left: 1px solid rgba(33,160,228,0.6) !important;
-}
-
-.cellTableSelectedRow > td:LAST-CHILD {
- border-right: 1px solid rgba(33,160,228,0.6) !important;
-}
-.cellTableSelectedRow > td {
-}
.cellTableCell {
- overflow: inherit;
font-size: 12px !important;
line-height: 1.5em !important;
-}
-.cellTableSelectedRowCell {
- border-top: 2px double #21a0e4 !important;
- border-bottom: 2px double #21a0e4 !important;
- padding: 7px 7px 6px 7px !important;
+ vertical-align: middle;
}
+.cellTableSelectedRowCell {
+ box-shadow: inset 0px -3px 3px -3px #21a0e4, inset 0px 3px 3px -3px #21a0e4;
+ padding: 7px !important;
+ }
+
.cellTableEvenRowCell, .cellTableOddRowCell {
- border: 1px solid #c9caca;
+ border-top: none;
+ border-right: none;
+ border-bottom: 1px solid #d6d6d6;
+ border-left: 1px solid #d6d6d6;
padding: 7px;
white-space: nowrap;
}
.cellTableCheckboxColumnCell {
- padding: 1px 1px 1px !important;
- vertical-align: middle !important;
- text-align: center;
}
.cellTableCheckboxSelected {
+ height: 13px;
+ width: 13px;
margin: auto auto;
- height: 13px;
- width: 13px;
- border: 1px solid #fcbb22;
- background-color: #fcbb22;
- vertical-align: middle;
+ border: 1px solid #fcbb22;
+ background-color: #fcbb22;
+/* border: solid 1px #acacac; */
+/* background-color: #cacaca; */
+
}
.cellTableCheckboxDeselected {
- margin: auto auto;
- height: 13px;
- width: 13px;
+ height: 13px;
+ width: 13px;
+ margin: auto auto;
border: solid 1px #acacac;
- vertical-align: middle;
}
.cellTableHoveredRow {
@@ -135,7 +123,6 @@
.cellTableLegColumnHeader {
background-color: #efefef;
-
}
.cellTableLegDetailColumnHeader {
diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RaceStateSerializer.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RaceStateSerializer.java
index b48aae155d1..0c76e3d5a3a 100644
--- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RaceStateSerializer.java
+++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RaceStateSerializer.java
@@ -38,6 +38,7 @@ public class RaceStateSerializer implements JsonSerializer
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/java/com.sap.sse.gwt.test/.settings/com.google.gwt.eclipse.core.prefs b/java/com.sap.sse.gwt.test/.settings/com.google.gwt.eclipse.core.prefs
index 7913a01d44c..0a706b8b4ac 100644
--- a/java/com.sap.sse.gwt.test/.settings/com.google.gwt.eclipse.core.prefs
+++ b/java/com.sap.sse.gwt.test/.settings/com.google.gwt.eclipse.core.prefs
@@ -1,2 +1,3 @@
eclipse.preferences.version=1
entryPointModules=com.sap.sse.gwt.test.TestSSE
+filesCopiedToWebInfLib=
diff --git a/java/com.sap.sse.security.ui/Security UI sdm.launch b/java/com.sap.sse.security.ui/Security UI sdm.launch
old mode 100755
new mode 100644
index 63fc7dc7116..fdc04ec8bf3
--- a/java/com.sap.sse.security.ui/Security UI sdm.launch
+++ b/java/com.sap.sse.security.ui/Security UI sdm.launch
@@ -1,48 +1,48 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/coursedesign/CourseElementAdapter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/coursedesign/CourseElementAdapter.java
index c44c51f9a7d..18eddf8d1da 100644
--- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/coursedesign/CourseElementAdapter.java
+++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/coursedesign/CourseElementAdapter.java
@@ -179,6 +179,9 @@ public class CourseElementAdapter extends BaseDraggableSwipeAdapter