mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-14 22:43:57 +00:00
Merge branch 'mark-passing-probability'
This commit is contained in:
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="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" path="1" type="4"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/resources" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.server/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="com.sap.sailing.dashboards.gwt"/> </runtimeClasspathEntry> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-servlet.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-user.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.moxieapps.gwt.highcharts/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt.adminconsole/src" path="3" type="2"/> "/>
|
||||
</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 "${project_loc:com.sap.sailing.dashboards.gwt}" -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -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="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" path="1" type="4"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/resources" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.server/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="com.sap.sailing.dashboards.gwt"/> </runtimeClasspathEntry> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-servlet.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-user.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.moxieapps.gwt.highcharts/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt.adminconsole/src" path="3" type="2"/> "/>
|
||||
</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 "${project_loc:com.sap.sailing.dashboards.gwt}" -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -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="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" path="1" type="4"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/resources" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.server/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="com.sap.sailing.dashboards.gwt"/> </runtimeClasspathEntry> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-servlet.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-user.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.moxieapps.gwt.highcharts/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt.adminconsole/src" path="3" type="2"/> "/>
|
||||
</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 "${project_loc:com.sap.sailing.dashboards.gwt}" -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -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="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" path="1" type="4"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/resources" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.server/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="com.sap.sailing.dashboards.gwt"/> </runtimeClasspathEntry> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-servlet.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-user.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.moxieapps.gwt.highcharts/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt.adminconsole/src" path="3" type="2"/> "/>
|
||||
</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 "${project_loc:com.sap.sailing.dashboards.gwt}" -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -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>
|
||||
+16
@@ -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;
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -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();
|
||||
}
|
||||
+1
@@ -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
|
||||
Binary file not shown.
+121
@@ -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));
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
+49
-17
@@ -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;
|
||||
|
||||
+6
-4
@@ -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);
|
||||
|
||||
-1
@@ -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";
|
||||
|
||||
+19
-3
@@ -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");
|
||||
|
||||
+6
-6
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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
|
||||
|
||||
+95
-2
@@ -1,12 +1,31 @@
|
||||
package com.sap.sailing.domain.test.markpassing;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.markpassingcalculation.CandidateChooser;
|
||||
import com.sap.sailing.domain.markpassingcalculation.CandidateFinder;
|
||||
import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
|
||||
import com.sap.sailing.domain.markpassingcalculation.impl.CandidateChooserImpl;
|
||||
import com.sap.sailing.domain.markpassingcalculation.impl.CandidateFinderImpl;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sse.InvalidDateException;
|
||||
import com.sap.sse.common.Duration;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
import com.sap.sse.util.DateParser;
|
||||
import com.tractrac.model.lib.api.event.CreateModelException;
|
||||
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
|
||||
|
||||
@@ -18,8 +37,82 @@ public class StarIDM2013MarkPassingTest extends AbstractMarkPassingTest {
|
||||
|
||||
@Test
|
||||
public void testRace5() throws IOException, InterruptedException, URISyntaxException, ParseException,
|
||||
SubscriberInitializationException, CreateModelException {
|
||||
testRace("5");
|
||||
SubscriberInitializationException, CreateModelException, InvalidDateException {
|
||||
// Race 5 is really broken on TracTrac's side. I've tried re-calculating the mark passing to no avail.
|
||||
// Now it's even worse, and only the windward mark passings and the start mark passings are recognized
|
||||
// half-way reasonably, but no mark passings seem to have been detected for subsequent waypoints.
|
||||
// To still use this race for testing purposes, let's do spot checks based on manually identified mark
|
||||
// passings, based on https://www.sapsailing.com/gwt/RaceBoard.html?eventId=a43b8f96-1e77-448a-9b8a-0f586f1bb18f&leaderboardName=Star%20IDM%202013&leaderboardGroupName=Star%20IDM%202013&raceName=Race%205&viewShowMapControls=true&viewShowNavigationPanel=true®attaName=IDM%20Starboot%202013%20%28Star%29
|
||||
setUp("5");
|
||||
synchronized (getSemaphor()) {
|
||||
while (!isStoredDataLoaded()) {
|
||||
getSemaphor().wait();
|
||||
}
|
||||
}
|
||||
testWholeRace5();
|
||||
testOnlyStartOfRace5();
|
||||
}
|
||||
|
||||
private void testWholeRace5() throws InvalidDateException {
|
||||
new MarkPassingCalculator(getTrackedRace(), false); // do our calculation, injecting into TrackedRace
|
||||
final Competitor GER7897 = getCompetitorByName("Tusch, Frank,Winkelmann, Sven");
|
||||
assertMarkPassing(GER7897, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:10+0200"));
|
||||
assertMarkPassing(GER7897, Util.get(getWaypoints(), 1), DateParser.parse("2013-05-04T14:57:12+0200"));
|
||||
assertMarkPassing(GER7897, Util.get(getWaypoints(), 3), DateParser.parse("2013-05-04T15:06:32+0200"));
|
||||
assertMarkPassing(GER7897, Util.get(getWaypoints(), 4), DateParser.parse("2013-05-04T15:15:01+0200"));
|
||||
assertMarkPassing(GER7897, Util.get(getWaypoints(), 6), DateParser.parse("2013-05-04T15:21:17+0200"));
|
||||
assertMarkPassing(GER7897, Util.get(getWaypoints(), 7), DateParser.parse("2013-05-04T15:29:18+0200"));
|
||||
assertMarkPassing(GER7897, Util.get(getWaypoints(), 9), DateParser.parse("2013-05-04T15:36:36+0200"));
|
||||
assertMarkPassing(GER7897, Util.get(getWaypoints(), 10), DateParser.parse("2013-05-04T15:38:09+0200"));
|
||||
|
||||
final Competitor GER7616 = getCompetitorByName("Seefelder, Jürgen,Seefelder, N"); // starts late
|
||||
assertMarkPassing(GER7616, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:57+0200"));
|
||||
assertMarkPassing(GER7616, Util.get(getWaypoints(), 1), DateParser.parse("2013-05-04T14:59:05+0200"));
|
||||
assertMarkPassing(GER7616, Util.get(getWaypoints(), 3), DateParser.parse("2013-05-04T15:07:32+0200"));
|
||||
assertMarkPassing(GER7616, Util.get(getWaypoints(), 4), DateParser.parse("2013-05-04T15:16:40+0200"));
|
||||
assertMarkPassing(GER7616, Util.get(getWaypoints(), 6), DateParser.parse("2013-05-04T15:23:26+0200"));
|
||||
assertMarkPassing(GER7616, Util.get(getWaypoints(), 7), DateParser.parse("2013-05-04T15:33:01+0200"));
|
||||
assertMarkPassing(GER7616, Util.get(getWaypoints(), 9), DateParser.parse("2013-05-04T15:39:25+0200"));
|
||||
assertMarkPassing(GER7616, Util.get(getWaypoints(), 10), DateParser.parse("2013-05-04T15:40:51+0200"));
|
||||
|
||||
final Competitor GER8340 = getCompetitorByName("Griese, Achim,Marcour, Michael"); // starts rather early
|
||||
assertMarkPassing(GER8340, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:04+0200"));
|
||||
assertMarkPassing(GER8340, Util.get(getWaypoints(), 1), DateParser.parse("2013-05-04T14:56:57+0200"));
|
||||
assertMarkPassing(GER8340, Util.get(getWaypoints(), 3), DateParser.parse("2013-05-04T15:06:33+0200"));
|
||||
assertMarkPassing(GER8340, Util.get(getWaypoints(), 4), DateParser.parse("2013-05-04T15:14:31+0200"));
|
||||
assertMarkPassing(GER8340, Util.get(getWaypoints(), 6), DateParser.parse("2013-05-04T15:20:29+0200"));
|
||||
assertMarkPassing(GER8340, Util.get(getWaypoints(), 7), DateParser.parse("2013-05-04T15:28:34+0200"));
|
||||
assertMarkPassing(GER8340, Util.get(getWaypoints(), 9), DateParser.parse("2013-05-04T15:35:52+0200"));
|
||||
assertMarkPassing(GER8340, Util.get(getWaypoints(), 10), DateParser.parse("2013-05-04T15:37:22+0200"));
|
||||
}
|
||||
|
||||
private void testOnlyStartOfRace5() throws InvalidDateException {
|
||||
// clear mark passings
|
||||
for (Competitor c : getRace().getCompetitors()) {
|
||||
getTrackedRace().updateMarkPassings(c, Collections.emptySet());
|
||||
}
|
||||
CandidateFinder finder = new CandidateFinderImpl(getTrackedRace());
|
||||
CandidateChooser chooser = new CandidateChooserImpl(getTrackedRace());
|
||||
final TimePoint afterStart = new MillisecondsTimePoint(DateParser.parse("2013-05-04T14:52:30+0200"));
|
||||
for (Competitor c : getRace().getCompetitors()) {
|
||||
calculateMarkPassingsForPartialTrack(c, afterStart, finder, chooser);
|
||||
}
|
||||
final Competitor GER7897 = getCompetitorByName("Tusch, Frank,Winkelmann, Sven");
|
||||
assertMarkPassing(GER7897, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:10+0200"));
|
||||
final Competitor GER7616 = getCompetitorByName("Seefelder, Jürgen,Seefelder, N"); // starts late
|
||||
assertMarkPassing(GER7616, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:57+0200"));
|
||||
final Competitor GER8340 = getCompetitorByName("Griese, Achim,Marcour, Michael"); // starts rather early
|
||||
assertMarkPassing(GER8340, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:04+0200"));
|
||||
}
|
||||
|
||||
private void assertMarkPassing(Competitor competitor, Waypoint waypoint, Date when) {
|
||||
final MarkPassing markPassing = getTrackedRace().getMarkPassing(competitor, waypoint);
|
||||
if (markPassing == null) {
|
||||
fail("Expected mark passing for "+competitor+" and "+waypoint+" around "+when+" but no mark passing found");
|
||||
}
|
||||
final Duration offset = new MillisecondsTimePoint(when).until(markPassing.getTimePoint()).abs();
|
||||
assertTrue("Expected mark passing for "+competitor+" and "+waypoint+" around "+when+
|
||||
" but found one at "+markPassing.getTimePoint()+" which is "+offset+" off.", offset.asSeconds() < 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+5
@@ -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;
|
||||
|
||||
+5
@@ -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;
|
||||
|
||||
+1
-2
@@ -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());
|
||||
|
||||
+81
-47
@@ -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;
|
||||
}
|
||||
|
||||
+360
-60
@@ -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:
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
+22
@@ -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);
|
||||
}
|
||||
}
|
||||
+20
@@ -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;
|
||||
}
|
||||
}
|
||||
+38
-18
@@ -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());
|
||||
}
|
||||
}
|
||||
+20
@@ -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
|
||||
|
||||
+33
-3
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
@@ -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;
|
||||
|
||||
+2
@@ -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:
|
||||
|
||||
+5
@@ -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.;
|
||||
|
||||
+3
@@ -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";
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user