Merge branch 'mark-passing-probability'

This commit is contained in:
Axel Uhl committed 2016-03-22 14:42:35 +01:00
commit 2853950b7a
37 files changed
+1068 -265

No files matched your search

@@ -1,47 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="false"/>
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9998"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
</listAttribute>
<booleanAttribute key="com.google.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/dashboards/RibDashboard.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</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>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.dashboards.gwt&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-servlet.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -war &quot;${project_loc:com.sap.sailing.dashboards.gwt}&quot; -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9998 com.sap.sailing.dashboards.gwt.RibDashboard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="false"/>
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9998"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
</listAttribute>
<booleanAttribute key="com.google.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/dashboards/RibDashboard.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</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>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.dashboards.gwt&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-servlet.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -war &quot;${project_loc:com.sap.sailing.dashboards.gwt}&quot; -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9998 com.sap.sailing.dashboards.gwt.RibDashboard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m"/>
</launchConfiguration>
</launchConfiguration>
@@ -1,47 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="false"/>
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9998"/>
<booleanAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT_AUTO" value="true"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
</listAttribute>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/dashboards/RibDashboard.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</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>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.dashboards.gwt&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-servlet.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -incremental -war &quot;${project_loc:com.sap.sailing.dashboards.gwt}&quot; -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort auto com.sap.sailing.dashboards.gwt.RibDashboard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="false"/>
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9998"/>
<booleanAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT_AUTO" value="true"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
</listAttribute>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/dashboards/RibDashboard.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</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>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.dashboards.gwt&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-servlet.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -incremental -war &quot;${project_loc:com.sap.sailing.dashboards.gwt}&quot; -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort auto com.sap.sailing.dashboards.gwt.RibDashboard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m"/>
</launchConfiguration>
</launchConfiguration>
@@ -79,4 +79,20 @@ public abstract class AbstractDistance implements Distance {
public Distance add(Distance d) {
return new NauticalMileDistance(getNauticalMiles()+d.getNauticalMiles());
}
@Override
public double divide(Distance other) {
return getMeters() / other.getMeters();
}
@Override
public Distance abs() {
final Distance result;
if (getMeters() >= 0) {
result = this;
} else {
result = scale(-1);
}
return result;
}
}
@@ -164,7 +164,8 @@ public class AbstractPosition implements Position {
@Override
public Distance getDistanceToLine(Position left, Position right) {
final Distance result;
final int factor = this.crossTrackError(left, left.getBearingGreatCircle(right)).getMeters()>0?1:-1;
final Distance crossTrackError = this.crossTrackError(left, left.getBearingGreatCircle(right));
final int factor = crossTrackError.getMeters()>0?1:-1;
double toLeft = Math.abs(left.getBearingGreatCircle(this).getDifferenceTo(left.getBearingGreatCircle(right))
.getDegrees());
double toRight = Math.abs(right.getBearingGreatCircle(this).getDifferenceTo(right.getBearingGreatCircle(left))
@@ -172,9 +173,9 @@ public class AbstractPosition implements Position {
if (toLeft > 90) {
result = this.getDistance(left).scale(factor);
} else if (toRight > 90) {
result = this.getDistance(right).scale(factor);
} else {
result = this.crossTrackError(left, left.getBearingGreatCircle(right));
result = this.getDistance(right).scale(factor);
} else {
result = crossTrackError;
}
return result;
}
@@ -80,6 +80,22 @@ public interface Distance extends Comparable<Distance>, Serializable {
public String toString() {
return "0m";
}
@Override
public double divide(Distance other) {
final double result;
if (other.getMeters() == 0) {
result = 0./0.;
} else {
result = 0;
}
return result;
}
@Override
public Distance abs() {
return this;
}
};
double getGeographicalMiles();
@@ -108,4 +124,16 @@ public interface Distance extends Comparable<Distance>, Serializable {
Speed inTime(Duration duration);
Distance add(Distance d);
/**
* Divides this distance by the {@code other} distance. Should the {@code other} distance
* be {@link #<code>null</code>}, 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();
}
@@ -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),
@@ -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 <code>left<code> and <code>right<code>. This distance
* is positive if <code>left<code> is actually the position farther to the left, as seen from this position.
* Computes the distance from this position to the line between <code>left</code> and <code>right</code>. This distance
* is positive if <code>left</code> is actually the position farther to the left, as seen from this position.
*/
Distance getDistanceToLine(Position left, Position right);
@@ -1,3 +1,3 @@
#Fri Jun 10 21:39:55 CEST 2011
eclipse.preferences.version=1
encoding//src/com/sap/sailing/domain/test/WindTest.java=UTF-8
encoding//src/com/sap/sailing/domain/test/markpassing/StarIDM2013MarkPassingTest.java=UTF-8
@@ -0,0 +1,121 @@
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 static class CandidateFinderWithPublicGetProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances extends CandidateFinderImpl {
public CandidateFinderWithPublicGetProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(
DynamicTrackedRace race) {
super(race);
}
public Double getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(
final List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> distancesToStartLineOfOtherCompetitors, boolean startIsLine) {
return super.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(distancesToStartLineOfOtherCompetitors, startIsLine);
}
}
private CandidateFinderWithPublicGetProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances 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 CandidateFinderWithPublicGetProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(trackedRace);
}
@Test
public void testOneProbabilityForEmptyList() {
List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> emptyDistancesToStartLineOfOtherCompetitors = new ArrayList<>();
final double probability = finder.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(emptyDistancesToStartLineOfOtherCompetitors,
/* startIsLine */ true);
assertEquals(1.0, probability, 0.00001);
}
@Test
public void testHighProbabilityForCloseProximityAndNoLine() {
List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> 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<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> 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<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> 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<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> 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));
}
}
@@ -54,7 +54,7 @@ public abstract class AbstractTracTracLiveTest extends StoredTrackBasedTest {
private IEventSubscriber eventSubscriber;
private IRaceSubscriber raceSubscriber;
private final Collection<Receiver> receivers;
@Rule public Timeout AbstractTracTracLiveTestTimeout = new Timeout(3 * 60 * 1000);
protected AbstractTracTracLiveTest() throws URISyntaxException, MalformedURLException {
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.test.markpassing;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
@@ -11,6 +12,7 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -62,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('\\', '/'));
@@ -83,9 +85,26 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest {
givenMarkPasses.put(wp, markPassing);
}
givenPasses.put(c, givenMarkPasses);
// now clear the mark passings in the TrackedRace for competitor c:
getTrackedRace().updateMarkPassings(c, Collections.emptySet());
}
}
protected Iterable<Waypoint> 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 {
@@ -210,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 && givenPasses.get(c).get(wayPointAfterwards) != null) {
TimePoint beforeNextPassing = markPassingAfter.getTimePoint().minus(20000);
List<GPSFix> fixes = new ArrayList<GPSFix>();
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<Candidate>, Iterable<Candidate>> f = finder.getCandidateDeltas(c, fixes);
chooser.calculateMarkPassDeltas(c, f.getA(), f.getB());
if (givenPasses.get(c).get(lastWaypointToBePassed) != null) {
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);
@@ -243,10 +252,16 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest {
if (waypoints.indexOf(w) <= zeroBasedIndexOfLastWaypointToBePassed) {
if ((old == null) != (newm == null)) {
gotPassed = false;
fail("Waypoint "+w+" was "+(old == null?"not ":"")+"passed by "+c+" originally"
+(old==null?"":": "+old)+"; we detected it "+(newm==null?"not ":"")+"having been passed"+
(newm == null ? "" : (": "+newm)));
}
} else {
if (w != wayPointAfterwards && newm != null) {
gotOther = true;
fail("Received a park passing "+newm+" for the "+waypoints.indexOf(w)+
"th waypoint "+w+" by "+c+" although only up to "+
zeroBasedIndexOfLastWaypointToBePassed +" were to be reported");
}
}
}
@@ -260,6 +275,23 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest {
}
protected void calculateMarkPassingsForPartialTrack(Competitor c, final TimePoint upToTimePoint,
CandidateFinder finder, CandidateChooser chooser) {
List<GPSFix> fixes = new ArrayList<GPSFix>();
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<Candidate>, Iterable<Candidate>> f = finder.getCandidateDeltas(c, fixes);
chooser.calculateMarkPassDeltas(c, f.getA(), f.getB());
}
@AfterClass
public static void createXML() throws IOException {
double accuracy = correct / totalPasses;
@@ -17,6 +17,7 @@ import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Sideline;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.BoatClassImpl;
import com.sap.sailing.domain.base.impl.BoatImpl;
import com.sap.sailing.domain.base.impl.CompetitorImpl;
import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl;
import com.sap.sailing.domain.base.impl.CourseAreaImpl;
@@ -30,6 +31,7 @@ import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
import com.sap.sailing.domain.leaderboard.impl.HighPoint;
import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore;
@@ -43,9 +45,10 @@ import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class AbstractMockedRaceMarkPassingTest {
protected Competitor ron = new CompetitorImpl("Ron", "Ron", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
protected Competitor tom = new CompetitorImpl("Tom", "Tom", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
protected Competitor ben = new CompetitorImpl("Ben", "Ben", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
final BoatClassImpl boatClass = new BoatClassImpl("boat", true, "boat", new MeterDistance(10), new MeterDistance(5), null);
protected Competitor ron = new CompetitorImpl("Ron", "Ron", null, null, null, null, new BoatImpl("boat", boatClass, "boot"), /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
protected Competitor tom = new CompetitorImpl("Tom", "Tom", null, null, null, null, new BoatImpl("boat", boatClass, "boot"), /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
protected Competitor ben = new CompetitorImpl("Ben", "Ben", null, null, null, null, new BoatImpl("boat", boatClass, "boot"), /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
protected Mark m = new MarkImpl("Mark");
protected Mark gate1 = new MarkImpl("Gate1");
@@ -64,7 +67,6 @@ public class AbstractMockedRaceMarkPassingTest {
Waypoint w4 = new WaypointImpl(m, PassingInstruction.Starboard);
Waypoint w5 = new WaypointImpl(cp, PassingInstruction.Line);
waypoints = Arrays.asList(w1, w2, w3, w4, w5);
final BoatClassImpl boatClass = new BoatClassImpl("boat", true);
Regatta r = new RegattaImpl(RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass,
/*startDate*/ null, /*endDate*/ null, Arrays.asList(new SeriesImpl("Series", true, Arrays.asList(new FleetImpl("fleet")),
new ArrayList<String>(), null)), true, new HighPoint(), "ID", new CourseAreaImpl("area", new UUID(5, 5)), OneDesignRankingMetric::new);
@@ -153,7 +153,6 @@ public class FlorianopolisMarkPassingTest extends AbstractMarkPassingTest {
*
* @Test public void testRace26() throws IOException, InterruptedException, URISyntaxException { testRace(26); }
*/
@Override
protected String getExpectedEventName() {
return "ESS Florianopolis 2013";
@@ -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");
@@ -45,7 +45,7 @@ public class MarkPassingCalculatorPerformanceTest extends AbstractMockedRaceMark
@AfterClass
public static void createXMLFile() throws IOException {
MeasurementXMLFile performanceReport = new MeasurementXMLFile(MarkPassingCalculatorPerformanceTest.class);
final MeasurementXMLFile performanceReport = new MeasurementXMLFile(MarkPassingCalculatorPerformanceTest.class);
for (String key : result.keySet()) {
MeasurementCase performanceReportCase = performanceReport.addCase(key);
performanceReportCase.addMeasurement(new Measurement(key, result.get(key)));
@@ -72,7 +72,7 @@ public class MarkPassingCalculatorPerformanceTest extends AbstractMockedRaceMark
@Test
public void testChooser() {
long time = timeToAddCandidatesToChooser(500, 1, 25);
final long time = timeToAddCandidatesToChooser(500, 1, 25);
System.out.println(time);
result.put("ChooserPerformance", time);
assertTrue(time < 5000);
@@ -96,16 +96,16 @@ public class MarkPassingCalculatorPerformanceTest extends AbstractMockedRaceMark
}
private CandidateImpl randomCan() {
int id = rnd.nextInt(3);
final int id = rnd.nextInt(3);
return new CandidateImpl(id + 1, new MillisecondsTimePoint((long) (rnd.nextDouble() * 200000)),
0.5 + 0.5 * rnd.nextDouble(), Util.get(race.getRace().getCourse().getWaypoints(), id));
}
private GPSFixMoving rndFix() {
DegreePosition position = new DegreePosition(0.001 - rnd.nextDouble() * 0.001,
final DegreePosition position = new DegreePosition(0.001 - rnd.nextDouble() * 0.001,
0.0002 - rnd.nextDouble() * 0.0004);
TimePoint p = new MillisecondsTimePoint((long) (rnd.nextDouble() * 200000));
SpeedWithBearing speed = new KnotSpeedWithBearingImpl(rnd.nextInt(11), new DegreeBearingImpl(rnd.nextInt(360)));
final TimePoint p = new MillisecondsTimePoint((long) (rnd.nextDouble() * 200000));
final SpeedWithBearing speed = new KnotSpeedWithBearingImpl(rnd.nextInt(11), new DegreeBearingImpl(rnd.nextInt(360)));
return new GPSFixMovingImpl(position, p, speed);
}
}
@@ -9,6 +9,7 @@ import java.util.List;
import java.util.NavigableSet;
import org.junit.Test;
import org.junit.Ignore;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.Waypoint;
@@ -34,6 +35,7 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
* Tests tricky situation that can fail easily. Created with help of http://itouchmap.com/latlong.html
*
*/
@Ignore
public class MarkPassingWhiteBoxTest extends AbstractMockedRaceMarkPassingTest {
@Test
@@ -78,7 +80,6 @@ public class MarkPassingWhiteBoxTest extends AbstractMockedRaceMarkPassingTest {
chooser.suppressMarkPassings(ron, 1);
NavigableSet<MarkPassing> markPassingsAfterSupressing = race.getMarkPassings(ron);
assertEquals(0, markPassingsAfterSupressing.size());
}
@Test
@@ -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&regattaName=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
@@ -100,6 +100,11 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public TimePoint getStartOfRace(boolean inferred) {
return null;
}
@Override
public Iterable<TrackedLeg> getTrackedLegs() {
return null;
@@ -111,6 +111,11 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
return startTime;
}
@Override
public TimePoint getStartOfRace(boolean inferred) {
return startTime;
}
@Override
public TimePoint getEndOfRace() {
return null;
@@ -231,8 +231,7 @@ public class MarkPassingCalculator {
@Override
public void run() {
logger.finer("Calculating MarkPassings for " + c + " (" + Util.size(fixes) + " new fixes)");
Util.Pair<Iterable<Candidate>, Iterable<Candidate>> candidateDeltas = finder.getCandidateDeltas(c,
fixes);
Util.Pair<Iterable<Candidate>, Iterable<Candidate>> candidateDeltas = finder.getCandidateDeltas(c, fixes);
logger.finer("Received " + Util.size(candidateDeltas.getA()) + " new Candidates and will remove "
+ Util.size(candidateDeltas.getB()) + " old Candidates for " + c);
chooser.calculateMarkPassDeltas(c, candidateDeltas.getA(), candidateDeltas.getB());
@@ -25,10 +25,12 @@ import com.sap.sailing.domain.markpassingcalculation.CandidateChooser;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsDurationImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
/**
@@ -44,9 +46,28 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
*
*/
public class CandidateChooserImpl implements CandidateChooser {
// TODO what is the meaning of this constant?
private static final int MILLISECONDS_BEFORE_STARTTIME = 5000;
private final static double MINIMUM_PROBABILITY = 1 - Edge.getPenaltyForSkipping();
/**
* Distance ratios of actual distance traveled and leg length above this threshold will receive
* penalties on their probability. Ratios below 1.0 receive the ratio as the penalty.
* See {@link #getDistanceEstimationBasedProbability(Competitor, Candidate, Candidate)}.
*/
final double MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH = 2.0;
/**
* Start mark passings will be considered this much before the actual race start. The race start
* as identified by {@link TrackedRace#getStartOfRace()} is therefore {@link #EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME}
* after {@link #raceStartTime}.
*/
private static final Duration EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME = new MillisecondsDurationImpl(5000);
/**
* The duration after which a start mark passing's probability is considered only 50%. A perfect start mark
* passing happening exactly at the race start time has time-wise probability of 1.0. Another delay of this much
* lets the probability drop to 1/3, and so on.
*/
private static final Duration DELAY_AFTER_WHICH_PROBABILITY_OF_START_HALVES = Duration.ONE_MINUTE;
private final static double MINIMUM_PROBABILITY = Edge.getPenaltyForSkipping();
private static final Logger logger = Logger.getLogger(CandidateChooserImpl.class.getName());
@@ -55,14 +76,20 @@ public class CandidateChooserImpl implements CandidateChooser {
private Map<Competitor, Set<Candidate>> candidates = new HashMap<>();
private Map<Competitor, NavigableSet<Candidate>> fixedPassings = new HashMap<>();
private Map<Competitor, Integer> suppressedPassings = new HashMap<>();
/**
* Set to {@link #EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME} milliseconds before the actual race start,
* in case the actual start of race is known; {@code null} otherwise.
*/
private TimePoint raceStartTime;
private final WaypointPositionAndDistanceCache waypointPositionAndDistanceCache;
/**
* An artificial proxy candidate that comes before the start mark passing. Its time point is set to
* {@link #MILLISECONDS_BEFORE_STARTTIME} milliseconds before the race start time or <code>null</code>
* {@link #EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME} milliseconds before the race start time or <code>null</code>
* in case the race start time is not known.
*/
private final CandidateWithSettableTime start;
private final CandidateWithSettableWaypointIndex end;
private final DynamicTrackedRace race;
@@ -70,7 +97,9 @@ public class CandidateChooserImpl implements CandidateChooser {
public CandidateChooserImpl(DynamicTrackedRace race) {
this.race = race;
waypointPositionAndDistanceCache = new WaypointPositionAndDistanceCache(race, Duration.ONE_MINUTE);
raceStartTime = race.getStartOfRace() != null ? race.getStartOfRace().minus(MILLISECONDS_BEFORE_STARTTIME) : null;
final TimePoint startOfRaceWithoutInference = race.getStartOfRace(/* inferred */ false);
raceStartTime = startOfRaceWithoutInference != null ? startOfRaceWithoutInference.
minus(EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME) : null;
start = new CandidateWithSettableTime(/* Index */0, raceStartTime, /* Probability */1, /* Waypoint */null);
end = new CandidateWithSettableWaypointIndex(race.getRace().getCourse().getNumberOfWaypoints() + 1, /* TimePoint */null,
/* Probability */1, /* Waypoint */null);
@@ -78,7 +107,16 @@ public class CandidateChooserImpl implements CandidateChooser {
List<Candidate> startAndEnd = Arrays.asList(start, end);
for (Competitor c : race.getRace().getCompetitors()) {
candidates.put(c, Collections.synchronizedSet(new TreeSet<Candidate>()));
currentMarkPasses.put(c, new HashMap<Waypoint, MarkPassing>());
final HashMap<Waypoint, MarkPassing> currentMarkPassesForCompetitor = new HashMap<Waypoint, MarkPassing>();
currentMarkPasses.put(c, currentMarkPassesForCompetitor);
// in case the tracked race already has mark passings, e.g., from another mark passing calculator,
// ensure consistency of the currentMarkPasses map with the TrackedRace:
for (final Waypoint w : race.getRace().getCourse().getWaypoints()) {
final MarkPassing mp = race.getMarkPassing(c, w);
if (mp != null) {
currentMarkPassesForCompetitor.put(w, mp);
}
}
TreeSet<Candidate> fixedPasses = new TreeSet<Candidate>(new Comparator<Candidate>() {
@Override
public int compare(Candidate o1, Candidate o2) {
@@ -106,10 +144,10 @@ public class CandidateChooserImpl implements CandidateChooser {
@Override
public void calculateMarkPassDeltas(Competitor c, Iterable<Candidate> newCans, Iterable<Candidate> oldCans) {
TimePoint startOfRace = race.getStartOfRace();
final TimePoint startOfRace = race.getStartOfRace(/* inference */ false);
if (startOfRace != null) {
if (raceStartTime == null || !startOfRace.minus(MILLISECONDS_BEFORE_STARTTIME).equals(raceStartTime)) {
raceStartTime = startOfRace.minus(MILLISECONDS_BEFORE_STARTTIME);
if (raceStartTime == null || !startOfRace.minus(EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME).equals(raceStartTime)) {
raceStartTime = startOfRace.minus(EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME);
List<Candidate> startList = new ArrayList<>();
startList.add(start);
for (Competitor com : candidates.keySet()) {
@@ -223,15 +261,15 @@ public class CandidateChooserImpl implements CandidateChooser {
// calculate the probability for the start being the start given its timing and multiply
// with the estimation for the distance-based probability:
final Duration timeGapBetweenStartOfRaceAndCandidateTimePoint = early.getTimePoint()
.plus(MILLISECONDS_BEFORE_STARTTIME).until(late.getTimePoint());
// Being MILLISECONDS_BEFORE_STARTTIME off means a probability of 1/2; being twice this time
.plus(EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME).until(late.getTimePoint()).abs();
// Being DELAY_AFTER_WHICH_PROBABILITY_OF_START_HALVES off means a probability of 1/2; being twice this time
// off means 1/3, and so on
startTimingProbability = (double) MILLISECONDS_BEFORE_STARTTIME
/ (double) (MILLISECONDS_BEFORE_STARTTIME + Math
.abs(timeGapBetweenStartOfRaceAndCandidateTimePoint.asMillis()));
startTimingProbability = DELAY_AFTER_WHICH_PROBABILITY_OF_START_HALVES.divide(
DELAY_AFTER_WHICH_PROBABILITY_OF_START_HALVES.plus(
timeGapBetweenStartOfRaceAndCandidateTimePoint));
estimatedDistanceProbability = 1;
} else {
startTimingProbability = 0.1; // can't really tell how well the start time was matched when
startTimingProbability = 1; // can't really tell how well the start time was matched when
// we don't have a start candidate
estimatedDistanceProbability = late == end ? 1 : getDistanceEstimationBasedProbability(c, early, late);
}
@@ -295,40 +333,40 @@ public class CandidateChooserImpl implements CandidateChooser {
if (oneBasedIndexOfSuppressedWaypoint <= endOfFixedInterval.getOneBasedIndexOfWaypoint()) {
endOfFixedInterval = end;
}
NavigableSet<Util.Pair<Edge, Double>> currentEdgesCheapestFirst = new TreeSet<>(new Comparator<Util.Pair<Edge, Double>>() {
NavigableSet<Util.Pair<Edge, Double>> currentEdgesMoreLikelyFirst = new TreeSet<>(new Comparator<Util.Pair<Edge, Double>>() {
@Override
public int compare(Util.Pair<Edge, Double> o1, Util.Pair<Edge, Double> o2) {
int result = o1.getB().compareTo(o2.getB());
int result = o2.getB().compareTo(o1.getB());
return result != 0 ? result : o1.getA().compareTo(o2.getA());
}
});
Map<Candidate, Util.Pair<Candidate, Double>> candidateWithParentAndSmallestTotalCost = new HashMap<>();
Map<Candidate, Util.Pair<Candidate, Double>> candidateWithParentAndHighestTotalProbability = new HashMap<>();
int indexOfEndOfFixedInterval = endOfFixedInterval.getOneBasedIndexOfWaypoint();
boolean endFound = false;
currentEdgesCheapestFirst.add(new Util.Pair<Edge, Double>(new Edge(new CandidateImpl(-1, null, /* estimated distance probability */ 1, null), startOfFixedInterval,
0, race.getRace().getCourse().getNumberOfWaypoints()), 0.0));
currentEdgesMoreLikelyFirst.add(new Util.Pair<Edge, Double>(new Edge(new CandidateImpl(-1, null, /* estimated distance probability */ 1, null), startOfFixedInterval,
1, race.getRace().getCourse().getNumberOfWaypoints()), 1.0));
while (!endFound) {
Util.Pair<Edge, Double> cheapestEdgeWithCost = currentEdgesCheapestFirst.pollFirst();
if (cheapestEdgeWithCost == null) {
Util.Pair<Edge, Double> mostLikelyEdgeWithProbability = currentEdgesMoreLikelyFirst.pollFirst();
if (mostLikelyEdgeWithProbability == null) {
endFound = true;
} else {
Edge currentCheapestEdge = cheapestEdgeWithCost.getA();
Double currentCheapestCost = cheapestEdgeWithCost.getB();
Edge currentMostLikelyEdge = mostLikelyEdgeWithProbability.getA();
Double currentHighestProbability = mostLikelyEdgeWithProbability.getB();
// If the shortest path to this candidate is already known the new edge is not added.
if (!candidateWithParentAndSmallestTotalCost.containsKey(currentCheapestEdge.getEnd())) {
// The cheapest edge taking us to currentCheapestEdge.getEnd() is found. Remember it.
candidateWithParentAndSmallestTotalCost.put(currentCheapestEdge.getEnd(), new Util.Pair<Candidate, Double>(
currentCheapestEdge.getStart(), currentCheapestCost));
if (!candidateWithParentAndHighestTotalProbability.containsKey(currentMostLikelyEdge.getEnd())) {
// The most likely edge taking us to currentMostLikelyEdge.getEnd() is found. Remember it.
candidateWithParentAndHighestTotalProbability.put(currentMostLikelyEdge.getEnd(), new Util.Pair<Candidate, Double>(
currentMostLikelyEdge.getStart(), currentHighestProbability));
if (logger.isLoggable(Level.FINEST)) {
logger.finest("Added "+ currentCheapestEdge + "as cheapest edge for " + c);
logger.finest("Added "+ currentMostLikelyEdge + " as most likely edge for " + c);
}
endFound = currentCheapestEdge.getEnd() == endOfFixedInterval;
endFound = currentMostLikelyEdge.getEnd() == endOfFixedInterval;
if (!endFound) {
// the end of the segment was not yet found; add edges leading away from
// currentCheapestEdge.getEnd(), summing up their cost with the cost required
// to reach currentCheapestEdge.getEnd()
Set<Edge> edgesForNewCandidate = allCompetitorEdges.get(currentCheapestEdge.getEnd());
// currentMostLikelyEdge.getEnd(), multiplying up their probabilities with the probability
// of reaching currentMostLikelyEdge.getEnd()
Set<Edge> edgesForNewCandidate = allCompetitorEdges.get(currentMostLikelyEdge.getEnd());
if (edgesForNewCandidate != null) {
for (Edge e : edgesForNewCandidate) {
int oneBasedIndexOfEndOfEdge = e.getEnd().getOneBasedIndexOfWaypoint();
@@ -336,7 +374,7 @@ public class CandidateChooserImpl implements CandidateChooser {
// the next fixed mark passing
if (oneBasedIndexOfEndOfEdge <= indexOfEndOfFixedInterval
&& (oneBasedIndexOfEndOfEdge < oneBasedIndexOfSuppressedWaypoint || e.getEnd() == end)) {
currentEdgesCheapestFirst.add(new Util.Pair<Edge, Double>(e, currentCheapestCost + e.getCost()));
currentEdgesMoreLikelyFirst.add(new Util.Pair<Edge, Double>(e, currentHighestProbability * e.getProbability()));
}
}
}
@@ -344,10 +382,10 @@ public class CandidateChooserImpl implements CandidateChooser {
}
}
}
Candidate marker = candidateWithParentAndSmallestTotalCost.get(endOfFixedInterval).getA();
Candidate marker = candidateWithParentAndHighestTotalProbability.get(endOfFixedInterval).getA();
while (marker.getOneBasedIndexOfWaypoint() > 0) {
mostLikelyCandidates.add(marker);
marker = candidateWithParentAndSmallestTotalCost.get(marker).getA();
marker = candidateWithParentAndHighestTotalProbability.get(marker).getA();
}
startOfFixedInterval = endOfFixedInterval;
endOfFixedInterval = fixedPasses.higher(endOfFixedInterval);
@@ -399,8 +437,8 @@ public class CandidateChooserImpl implements CandidateChooser {
first = c1.getWaypoint();
}
final Waypoint second = c2.getWaypoint();
Distance totalGreatCircleDistance = getApproximateTotalGreatCircleDistanceBetweenWaypoints(first, second, middleOfc1Andc2);
Distance actualDistanceTraveled = race.getTrack(c).getDistanceTraveled(c1.getTimePoint(), c2.getTimePoint());
final Distance totalGreatCircleDistance = getApproximateTotalGreatCircleDistanceBetweenWaypoints(first, second, middleOfc1Andc2);
final Distance actualDistanceTraveled = race.getTrack(c).getDistanceTraveled(c1.getTimePoint(), c2.getTimePoint());
result = getProbabilityOfActualDistanceGivenGreatCircleDistance(totalGreatCircleDistance, actualDistanceTraveled);
return result;
}
@@ -418,23 +456,19 @@ public class CandidateChooserImpl implements CandidateChooser {
* @return a number between 0 and 1 with 1 representing a "fair chance" that the actual distance sailed could have
* been sailed for the given great circle distance; 1 is returned for actual distances being in the range of
* 1..2 times the great circle distance. Actual distances outside this interval reduce probability linearly
* for smaller distances (gradient 3.5) and varies with the square root for distances that exceed twice the
* for smaller distances (gradient 0.5) and varies with the one over the ratio for distances that exceed twice the
* great circle distance.
*/
private double getProbabilityOfActualDistanceGivenGreatCircleDistance(Distance totalGreatCircleDistance,
Distance actualDistanceTraveled) {
final double result;
double differenceInMeters = actualDistanceTraveled.getMeters() - totalGreatCircleDistance.getMeters();
double ratio = differenceInMeters / totalGreatCircleDistance.getMeters();
final double ratio = actualDistanceTraveled.getMeters() / totalGreatCircleDistance.getMeters();
// A smaller distance than great circle from mark to mark is very unlikely, somewhere between the distance
// estimated and double that is likely and anything greater than that gradually becomes unlikely
if (ratio < 0) {
// TODO shouldn't these factors be constants in the class header for easy fine-tuning?
result = 3.5 * ratio + 1;
} else if (ratio > 1) {
result = Math.sqrt((-ratio + 3) / 2);
if (ratio <= MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH) {
result = Math.min(1.0, ratio); // maximum probability is 1; use for the interval between 1 and MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH
} else {
result = 1;
result = 1./(ratio-1.); // start at probability 1 for ratio==2
}
return result;
}
@@ -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,
@@ -49,22 +51,47 @@ import com.sap.sse.common.Util;
public class CandidateFinderImpl implements CandidateFinder {
// The higher this is, the closer the fixes have to be to waypoint to become a Candidate
private final int strictnessOfDistanceBasedProbability = 8;
private final int STRICTNESS_OF_DISTANCE_BASED_PROBABILITY = 7;
// 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 penaltyForWrongSide = 0.7;
private final double penaltyForWrongDirection = 0.7;
private final double penaltyForDistanceCandidates = 0.7;
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<Competitor, LinkedHashMap<GPSFix, Map<Waypoint, List<Distance>>>> distanceCache = new LinkedHashMap<>();
private Map<Competitor, LinkedHashMap<GPSFix, Map<Waypoint, List<Distance>>>> xteCache = new LinkedHashMap<>();
private Map<Competitor, Map<Waypoint, Map<List<GPSFix>, Candidate>>> xteCandidates = new HashMap<>();
private Map<Competitor, Map<Waypoint, Map<GPSFix, Candidate>>> distanceCandidates = new HashMap<>();
private final DynamicTrackedRace race;
private final double penaltyForSkipping = 1 - Edge.getPenaltyForSkipping();
private final double penaltyForSkipping = Edge.getPenaltyForSkipping();
private final Map<Waypoint, PassingInstruction> passingInstructions = new LinkedHashMap<>();
private final Comparator<GPSFix> comp = new Comparator<GPSFix>() {
@Override
@@ -122,8 +149,7 @@ public class CandidateFinderImpl implements CandidateFinder {
TimePoint end = null;
for (Entry<Mark, List<GPSFix>> fixes : markFixes.entrySet()) {
for (GPSFix fix : fixes.getValue()) {
TimeRange timePoints = race.getOrCreateTrack(fixes.getKey()).getEstimatedPositionTimePeriodAffectedBy(
fix);
TimeRange timePoints = race.getOrCreateTrack(fixes.getKey()).getEstimatedPositionTimePeriodAffectedBy(fix);
TimePoint newStart = timePoints.from();
TimePoint newEnd = timePoints.to();
start = start == null || start.after(newStart) ? newStart : start;
@@ -131,24 +157,24 @@ public class CandidateFinderImpl implements CandidateFinder {
}
}
for (Competitor c : race.getRace().getCompetitors()) {
List<GPSFix> comFixes = new ArrayList<>();
List<GPSFix> competitorFixes = new ArrayList<>();
DynamicGPSFixTrack<Competitor, GPSFixMoving> track = race.getTrack(c);
GPSFix comFix = track.getFirstFixAtOrAfter(start);
if (comFix != null) {
if (end != null) {
while (comFix != null && !comFix.getTimePoint().after(end)) {
comFixes.add(comFix);
competitorFixes.add(comFix);
comFix = track.getFirstFixAfter(comFix.getTimePoint());
}
} else {
while (comFix != null) {
comFixes.add(comFix);
competitorFixes.add(comFix);
comFix = track.getFirstFixAfter(comFix.getTimePoint());
}
}
}
if (!comFixes.isEmpty()) {
affectedFixes.put(c, comFixes);
if (!competitorFixes.isEmpty()) {
affectedFixes.put(c, competitorFixes);
}
}
return affectedFixes;
@@ -156,7 +182,6 @@ public class CandidateFinderImpl implements CandidateFinder {
@Override
public Util.Pair<Iterable<Candidate>, Iterable<Candidate>> getCandidateDeltas(Competitor c, Iterable<GPSFix> fixes) {
List<Candidate> newCans = new ArrayList<>();
List<Candidate> wrongCans = new ArrayList<>();
Course course = race.getRace().getCourse();
@@ -293,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;
}
@@ -370,6 +395,10 @@ 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<Distance> waypointDistances = fixDistances.get(w);
List<Distance> waypointDistancesBefore = fixDistancesBefore.get(w);
List<Distance> waypointDistancesAfter = fixDistancesAfter.get(w);
@@ -394,12 +423,26 @@ public class CandidateFinderImpl implements CandidateFinder {
p = fix.getPosition();
Double newProbability = getDistanceBasedProbability(w, t, dis);
if (newProbability != null) {
newProbability *= isOnCorrectSideOfWaypoint(w, p, t, portMark) ? penaltyForDistanceCandidates
: penaltyForDistanceCandidates * penaltyForWrongSide;
// 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;
}
}
}
@@ -411,8 +454,8 @@ public class CandidateFinderImpl implements CandidateFinder {
wasCan = true;
}
if (!wasCan && isCan) {
Candidate newCan = new CandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1,
t, probability, w);
Candidate newCan = new DistanceCandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1,
t, probability, startProbabilityBasedOnOtherCompetitors, w, sidePenalty, distance);
getDistanceCandidates(c, w).put(fix, newCan);
result.getA().add(newCan);
logger.finest("Added distance" + newCan.toString() + "for " + c);
@@ -420,8 +463,8 @@ public class CandidateFinderImpl implements CandidateFinder {
getDistanceCandidates(c, w).remove(fix);
result.getB().add(oldCan);
} else if (wasCan && isCan && oldCan.getProbability() != probability) {
Candidate newCan = new CandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1,
t, probability, w);
Candidate newCan = new DistanceCandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1,
t, probability, startProbabilityBasedOnOtherCompetitors, w, onCorrectSideOfWaypoint, distance);
getDistanceCandidates(c, w).put(fix, newCan);
result.getA().add(newCan);
logger.finest("Added distance" + newCan.toString() + "for " + c);
@@ -615,18 +658,245 @@ public class CandidateFinderImpl implements CandidateFinder {
* crossingInfo.getB()).getMeters()); } } } }
*/
/**
* {@code xte1} and {@code xte2} are expected to have different signums or both be 0. The method
* interpolates between the time points {@code t1} and {@code t2} according to where the XTE is
* assumed to have crossed 0. The candidate is then generated for this interpolated time point.
*/
private Candidate createCandidate(Competitor c, double xte1, double xte2, TimePoint t1, TimePoint t2, Waypoint w,
Boolean portMark) {
long differenceInMillis = t2.asMillis() - t1.asMillis();
double ratio = (Math.abs(xte1) / (Math.abs(xte1) + Math.abs(xte2)));
TimePoint t = t1.plus((long) (differenceInMillis * ratio));
Position p = race.getTrack(c).getEstimatedPosition(t, false);
List<Distance> distances = calculateDistance(p, w, t);
Distance d = portMark ? distances.get(0) : distances.get(1);
double cost = getDistanceBasedProbability(w, t, d);
cost = isOnCorrectSideOfWaypoint(w, p, t, portMark) ? cost : cost * penaltyForWrongSide;
cost = passesInTheRightDirection(w, xte1, xte2, portMark) ? cost : cost * penaltyForWrongDirection;
return new CandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1, t, cost, w);
final long differenceInMillis = t2.asMillis() - t1.asMillis();
final double ratio = (Math.abs(xte1) / (Math.abs(xte1) + Math.abs(xte2)));
final TimePoint t = t1.plus((long) (differenceInMillis * ratio));
final Position p = race.getTrack(c).getEstimatedPosition(t, false);
final List<Distance> distances = calculateDistance(p, w, t);
final Distance d = portMark ? distances.get(0) : distances.get(1);
final double sidePenalty = getSidePenalty(w, p, t, portMark);
double probability = getDistanceBasedProbability(w, t, d) * sidePenalty;
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);
}
public 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.
* <p>
*
* 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.
*
* <ul>
* <li>check distance variance of other competitors; low variance could mean they started jointly</li>
* <li>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</li>
* <li>generally consider distance of other competitors to line</li>
* <li>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)</li>
* </ul>
*
* 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.<p>
*
* 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<Pair<Position, Bearing>> 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 = getPassingInstructions(start) == PassingInstruction.Line;
final List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> distancesToStartLineOfOtherCompetitors = new ArrayList<>();
for (final Competitor otherCompetitor : race.getRace().getCompetitors()) {
if (otherCompetitor != c) {
final DynamicGPSFixTrack<Competitor, GPSFixMoving> 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<Position, Bearing> 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 <em>or</em> the other scenario which mathematically is represented by using the inverted
* probabilities of the two start options, multiplying them and inverting the resulting probability.<p>
*
* Access needs to be protected to satisfy Maven-based test cases in com.sap.sailing.domain.test bundle which is not a fragment
*/
protected Double getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(
final List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> 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<Distance> 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;
}
/**
@@ -635,32 +905,38 @@ public class CandidateFinderImpl implements CandidateFinder {
* to be negative. If the passing instructions are {@link PassingInstruction#Line}, it checks whether the boat
* passed between the two marks.
*/
private boolean isOnCorrectSideOfWaypoint(Waypoint w, Position p, TimePoint t, boolean portMark) {
boolean result = true;
private double getSidePenalty(Waypoint w, Position p, TimePoint t, boolean portMark) {
boolean isOnRightSide = true;
Distance onWrongSide = new MeterDistance(0);
PassingInstruction instruction = getPassingInstructions(w);
if (instruction == PassingInstruction.Line) {
List<Position> pos = new ArrayList<>();
for (Mark m : w.getMarks()) {
Position po = race.getOrCreateTrack(m).getEstimatedPosition(t, false);
if (po == null) {
return true;
isOnRightSide = true;
break;
}
pos.add(po);
}
if (pos.size() != 2){
return true;
if (pos.size() != 2) {
isOnRightSide = true;
}
Bearing diff1 = pos.get(0).getBearingGreatCircle(p)
.getDifferenceTo(pos.get(0).getBearingGreatCircle(pos.get(1)));
Bearing diff2 = pos.get(1).getBearingGreatCircle(p)
.getDifferenceTo(pos.get(1).getBearingGreatCircle(pos.get(0)));
Position leftMarkPos = pos.get(0);
Position rightMarkPos = pos.get(1);
Bearing diff1 = leftMarkPos.getBearingGreatCircle(p)
.getDifferenceTo(leftMarkPos.getBearingGreatCircle(rightMarkPos));
Bearing diff2 = rightMarkPos.getBearingGreatCircle(p)
.getDifferenceTo(rightMarkPos.getBearingGreatCircle(leftMarkPos));
if (Math.abs(diff1.getDegrees()) > 90 || Math.abs(diff2.getDegrees()) > 90) {
result = false;
isOnRightSide = false;
Distance leftDistance = p.getDistance(leftMarkPos);
Distance rightDistance = p.getDistance(rightMarkPos);
onWrongSide = leftDistance.getMeters() < rightDistance.getMeters() ? leftDistance : rightDistance;
}
} 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) {
@@ -669,27 +945,48 @@ public class CandidateFinderImpl implements CandidateFinder {
}
if (m != null) {
Util.Pair<Position, Bearing> crossingInfo = Util.get(getCrossingInformation(w, t), portMark ? 0 : 1);
result = p.crossTrackError(crossingInfo.getA(), crossingInfo.getB().add(new DegreeBearingImpl(90)))
.getMeters() < 0;
isOnRightSide = p.crossTrackError(crossingInfo.getA(), crossingInfo.getB().add(new DegreeBearingImpl(90))).getMeters() < 0;
if (isOnRightSide == false) {
onWrongSide = p.getDistance(crossingInfo.getA());
}
}
}
final double result;
if (isOnRightSide == true) {
result = 1;
} else {
//TODO There should be a constant (either the 1.5 or the -0.2) which controls how strict the the curve is.
result = (1-PENALTY_FOR_WRONG_SIDE) * Math.pow(1.5, -0.2 * onWrongSide.getMeters()) + PENALTY_FOR_WRONG_SIDE;
}
return result;
}
/**
* 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;
}
@@ -698,19 +995,21 @@ public class CandidateFinderImpl implements CandidateFinder {
* @return a probability based on the distance to <code>w</code> and the average leg lengths before and after.
*/
private Double getDistanceBasedProbability(Waypoint w, TimePoint t, Distance distance) {
Distance legLength = getLegLength(t, w);
final Double result;
Distance legLength = getAverageLengthOfAdjacentLegs(t, w);
if (legLength != null) {
double result = 1 / (strictnessOfDistanceBasedProbability/* Raising this will make it stricter */
result = 1 / (STRICTNESS_OF_DISTANCE_BASED_PROBABILITY/* Raising this will make it stricter */
* Math.abs(distance.getMeters() / legLength.getMeters()) + 1);
return result;
} else {
result = null;
}
return null;
return result;
}
/**
* @return an average of the estimated legs before and after <code>w</code>.
*/
private Distance getLegLength(TimePoint t, Waypoint w) {
private Distance getAverageLengthOfAdjacentLegs(TimePoint t, Waypoint w) {
Course course = race.getRace().getCourse();
if (w == course.getFirstWaypoint()) {
return race.getTrackedLegStartingAt(w).getGreatCircleDistance(t);
@@ -730,11 +1029,12 @@ public class CandidateFinderImpl implements CandidateFinder {
* Calculates the distance from p to each mark of w at the timepoint t.
*/
private List<Distance> calculateDistance(Position p, Waypoint w, TimePoint t) {
List<Distance> distances = new ArrayList<>();
final List<Distance> distances = new ArrayList<>();
PassingInstruction instruction = getPassingInstructions(w);
boolean singleMark = false;
switch (instruction) {
case Port:
case Single_Unknown:
case Starboard:
case FixedBearing:
singleMark = true;
@@ -760,7 +1060,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:
@@ -38,7 +38,8 @@ public class CandidateImpl implements Candidate {
}
public String toString() {
return "Candidate for waypoint " + getOneBasedIndexOfWaypoint() + " with cost " + getProbability() + " and Timepoint " + p;
return "Candidate for waypoint " + getOneBasedIndexOfWaypoint() + " with probability " +
getProbability() + " and timepoint " + getTimePoint();
}
@Override
@@ -0,0 +1,22 @@
package com.sap.sailing.domain.markpassingcalculation.impl;
import com.sap.sailing.domain.base.Waypoint;
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, 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+
(startProbabilityBasedOnOtherCompetitors==null?"":", startProbabilityBasedOnOtherCompetitors="+startProbabilityBasedOnOtherCompetitors);
}
}
@@ -0,0 +1,20 @@
package com.sap.sailing.domain.markpassingcalculation.impl;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.Distance;
import com.sap.sse.common.TimePoint;
public class DistanceCandidateImpl extends CandidateWithSide {
private final Distance distance;
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;
}
@Override
public String toString() {
return super.toString()+", distance: "+distance;
}
}
@@ -4,7 +4,7 @@ import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.markpassingcalculation.Candidate;
/**
* Represent the passage between two {@link Candidate}s, <code>start</code> and <code>end</code>. {@link #getCost()}
* Represent the passage between two {@link Candidate}s, <code>start</code> and <code>end</code>. {@link #getProbability()}
* returns the probability of this passage being correct, consisting of the probability of each Candidate, the
* probability of the passage between them (an estimate whether the distance sailed between the candidates matches the
* distance between their {@link Waypoint}s), and a penalty for skipping waypoints, which might happen if a tracker
@@ -13,14 +13,30 @@ import com.sap.sailing.domain.markpassingcalculation.Candidate;
* @author Nicolas Klose
*
*/
public class Edge implements Comparable<Edge> {
private final Candidate start;
private final Candidate end;
// TODO what is the meaning of this constant?
private final static double penaltyForSkipped = 0.7;
// TODO what is the meaning of this constant?
private final static double penaltyForSkippedToEnd = 0.6;
/**
* 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.<p>
*
* 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).<p>
*
* 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;
@@ -32,20 +48,22 @@ public class Edge implements Comparable<Edge> {
}
public static double getPenaltyForSkipping() {
return penaltyForSkipped;
return PENALTY_FOR_SKIPPED;
}
/**
* The cost for skipping a waypoint is 2*{@link #penaltyForSkipped} but is reduced to {@link #penaltyForSkippedToEnd} when
* skipping to the end. The reason for preferring skips to the end proxy node is that in live situations where the course
* hasn't been completed yet it is required to skip to the end. Additional cost comes from the inverse probability of
* the product of the start node, end node and distance-based probabilities. If these probabilities multiply to zero,
* an additional cost of 1.0 is added to any skip penalty.
* The probability-reducing factor for skipping a waypoint is {@link #PENALTY_FOR_SKIPPED} but is reduced to
* {@link #PENALTY_FOR_SKIPPED_TO_END} when skipping to the end. The reason for preferring skips to the end proxy node
* is that in live situations where the course hasn't been completed yet it is required to skip to the end. The main
* probability comes from the probability of the product of the start node, end node and distance-based
* probabilities.
*/
public Double getCost() {
double penalty = end.getOneBasedIndexOfWaypoint() == numberOfWaypoints + 1 ? penaltyForSkippedToEnd : penaltyForSkipped;
return 1 - (start.getProbability() * end.getProbability() * estimatedDistanceAndStartTimingProbability) + 2 * penalty
* (end.getOneBasedIndexOfWaypoint() - start.getOneBasedIndexOfWaypoint() - 1);
public Double getProbability() {
final double penalty = end.getOneBasedIndexOfWaypoint() == numberOfWaypoints + 1 ? PENALTY_FOR_SKIPPED_TO_END : PENALTY_FOR_SKIPPED;
// 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() {
@@ -57,11 +75,13 @@ public class Edge implements Comparable<Edge> {
}
public String toString() {
return "Edge from Waypoint " + start.getOneBasedIndexOfWaypoint() + " to " + end.getOneBasedIndexOfWaypoint() + ": " + getCost();
return "Edge from Waypoint " + start.getOneBasedIndexOfWaypoint() + " (" +
start.getTimePoint()+") to " + end.getOneBasedIndexOfWaypoint() + " (" +
end.getTimePoint()+"): " + getProbability();
}
@Override
public int compareTo(Edge o) {
return start != o.getStart() ? start.compareTo(o.getStart()) : end != o.getEnd() ? end.compareTo(o.getEnd()) : getCost().compareTo(o.getCost());
return start != o.getStart() ? start.compareTo(o.getStart()) : end != o.getEnd() ? end.compareTo(o.getEnd()) : getProbability().compareTo(o.getProbability());
}
}
@@ -0,0 +1,20 @@
package com.sap.sailing.domain.markpassingcalculation.impl;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sse.common.TimePoint;
public class XTECandidateImpl extends CandidateWithSide {
private final Double passesInTheRightDirectionProbability;
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: "+passesInTheRightDirectionProbability;
}
}
@@ -101,6 +101,14 @@ public interface TrackedRace extends Serializable, IsManagedByCache<SharedDomain
* If no start time can be determined this way, <code>null</code> is returned.
*/
TimePoint getStartOfRace();
/**
* Like {@link #getStartOfRace()}, but ignoring any inference from start mark passings in case {@code inferred} is
* {@code false}. In this case, if no official start time was set, e.g., in the {@link RaceLog} or explicitly using
* {@link #getStartTimeReceived()}, {@code null} will be returned by this method even if start mark passings are
* present.
*/
TimePoint getStartOfRace(boolean inferred);
/**
* Determine the race end time is tricky. Boats may sink, stop, not finish, although they started the race. We
@@ -202,7 +202,14 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
* The calculated race start time
*/
private TimePoint startTime;
/**
* Maintained in lock-step with {@link #startTime}, only that {@code null} will be contained if {@link #startTime}
* was only inferred from start mark passings and no other, more official, information such as
* {@link #getStartTimeReceived()} or a start time coming from an attached {@link RaceLog}.
*/
private TimePoint startTimeWithoutInferenceFromStartMarkPassings;
/**
* The calculated race end time
*/
@@ -732,6 +739,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
public void invalidateStartTime() {
startTime = null;
startTimeWithoutInferenceFromStartMarkPassings = null;
}
public void invalidateEndTime() {
@@ -771,13 +779,26 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
*/
@Override
public TimePoint getStartOfRace() {
if (startTime == null) {
return getStartOfRace(/* inferred */ true);
}
@Override
public TimePoint getStartOfRace(boolean inferred) {
final TimePoint result;
// check if we have in our caching fields what is asked:
if (inferred && startTime != null) {
result = startTime;
} else if (!inferred && startTimeWithoutInferenceFromStartMarkPassings != null) {
result = startTimeWithoutInferenceFromStartMarkPassings;
} else {
// no cache match; re-calculate
for (RaceLog raceLog : attachedRaceLogs.values()) {
if (logger.isLoggable(Level.FINEST)) {
logger.finest("Analyzing race log "+raceLog+" for race "+this.getRace().getName());
}
startTime = new StartTimeFinder(raceLogResolver, raceLog).analyze().getStartTime();
if (startTime != null) {
startTimeWithoutInferenceFromStartMarkPassings = startTime;
if (logger.isLoggable(Level.FINEST)) {
logger.finest("Found start time "+startTime+" in race log "+raceLog+" for race "+this.getRace().getName());
}
@@ -789,12 +810,16 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
logger.finest("No start time found in race logs for race "+getRace().getName());
}
startTime = getStartTimeReceived();
if (startTime != null) {
startTimeWithoutInferenceFromStartMarkPassings = startTime;
}
// If not null, check if the first mark passing for the start line is too much after the
// startTimeReceived; if so, return an adjusted, later start time.
// If no official start time was received, try to estimate the start time using the mark passings for
// the start line.
final Waypoint firstWaypoint;
if (getTrackedRegatta().getRegatta().useStartTimeInference() && (firstWaypoint = getRace().getCourse().getFirstWaypoint()) != null) {
// in this "if" branch update only startTime, not startTimeWithoutInferenceFromStartMarkPassings
if (startTimeReceived != null) {
// plausibility check for start time received, based on start mark passings; if no boat started within
// a grace period of MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS after the start time
@@ -828,8 +853,13 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
}
}
if (inferred) {
result = startTime;
} else {
result = startTimeWithoutInferenceFromStartMarkPassings;
}
}
return startTime;
return result;
}
/**
@@ -86,6 +86,11 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public TimePoint getStartOfRace(boolean inferred) {
return null;
}
@Override
public Iterable<TrackedLeg> getTrackedLegs() {
return null;
@@ -917,6 +917,8 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component
private String getPassingInstructionsAsText(PassingInstruction passingInstructions) {
switch (passingInstructions) {
case Single_Unknown:
return stringMessages.toSide() + " " + stringMessages.unknown();
case Port:
return stringMessages.toSide() + " " + stringMessages.portSide();
case Starboard:
@@ -101,6 +101,11 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
return null;
}
@Override
public TimePoint getStartOfRace(boolean inferred) {
return null;
}
@Override
public TimePoint getEndOfRace() {
return null;
@@ -30,6 +30,12 @@ public interface Duration extends Serializable, Comparable<Duration> {
double asDays();
/**
* In case this duration is positive, it is returned unmodified. Otherwise, the duration is returned negated, resulting
* in a positive duration.
*/
Duration abs();
Duration divide(long divisor);
Duration divide(double divisor);
double divide(Duration duration);
@@ -21,6 +21,9 @@ public interface TimePoint extends Comparable<TimePoint>, Serializable {
TimePoint minus(Duration duration);
/**
* The duration that {@code later} is after {@code this}. May be negative if {@code later} is before {@code this}.
*/
Duration until(TimePoint later);
boolean after(TimePoint other);
@@ -18,6 +18,11 @@ public class MillisecondsDurationImpl implements Duration {
return millis;
}
@Override
public Duration abs() {
return millis >= 0 ? this : new MillisecondsDurationImpl(-millis);
}
@Override
public double asSeconds() {
return ((double) asMillis()) / 1000.;
@@ -179,6 +179,9 @@ public class CourseElementAdapter extends BaseDraggableSwipeAdapter<RecyclerView
if (PassingInstruction.Port.equals(direction)) {
return "P";
}
if (PassingInstruction.Single_Unknown.equals(direction)) {
return "U";
}
if (PassingInstruction.Starboard.equals(direction)) {
return "S";
}