Merge branch 'master' into bug2610_lifecycle

Conflicts:
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPassingsPanel.java
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/WindChart.java
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java
	java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java
This commit is contained in:
Axel Uhl
2016-03-28 18:24:03 +02:00
153 changed files with 4939 additions and 1414 deletions
Binary file not shown.
@@ -25,7 +25,7 @@ Require-Bundle: com.sap.sailing.domain,
com.sap.sse.gwt.adminconsole,
org.apache.commons.math;bundle-version="2.1.0"
Bundle-ActivationPolicy: lazy
Import-Package: com.google.gwt.core.ext.linker,
Import-Package: com.google.gwt.core.ext.linker;resolution:=optional,
com.sap.sse.util,
javax.servlet;version="3.1.0",
javax.servlet.descriptor;version="3.1.0",
@@ -1,47 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="false"/>
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9998"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
</listAttribute>
<booleanAttribute key="com.google.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/dashboards/RibDashboard.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
</listAttribute>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.dashboards.gwt&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-servlet.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -war &quot;${project_loc:com.sap.sailing.dashboards.gwt}&quot; -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9998 com.sap.sailing.dashboards.gwt.RibDashboard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="false"/>
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9998"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
</listAttribute>
<booleanAttribute key="com.google.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/dashboards/RibDashboard.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
</listAttribute>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.dashboards.gwt&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-servlet.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -war &quot;${project_loc:com.sap.sailing.dashboards.gwt}&quot; -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9998 com.sap.sailing.dashboards.gwt.RibDashboard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m"/>
</launchConfiguration>
</launchConfiguration>
@@ -1,47 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="false"/>
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9998"/>
<booleanAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT_AUTO" value="true"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
</listAttribute>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/dashboards/RibDashboard.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
</listAttribute>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.dashboards.gwt&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-servlet.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -incremental -war &quot;${project_loc:com.sap.sailing.dashboards.gwt}&quot; -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort auto com.sap.sailing.dashboards.gwt.RibDashboard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="false"/>
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9998"/>
<booleanAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT_AUTO" value="true"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
</listAttribute>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/dashboards/RibDashboard.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
</listAttribute>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.dashboards.gwt&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-servlet.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -incremental -war &quot;${project_loc:com.sap.sailing.dashboards.gwt}&quot; -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort auto com.sap.sailing.dashboards.gwt.RibDashboard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m"/>
</launchConfiguration>
</launchConfiguration>
@@ -47,12 +47,13 @@ public final class StartAnalysisDTOFactory extends AbstractStartAnalysisCreation
List<MarkPassing> markPassingsInOrder = convertMarkpPassingsIteratorToList(trackedRace.getMarkPassingsInOrder(secondWaypoint).iterator());
boolean isCompetitorNullOrOneOfFirstThreeCompetitors = false;
for (int i = 0; i < MINIMUM_MARKPASSIINGS_AT_FIRST_MARK; i++) {
competitors.add(createStartAnalysisCompetitorDTO(dashboardDispatchContext, trackedRace, i + 1, markPassingsInOrder.get(i).getCompetitor()));
if (competitor == null || markPassingsInOrder.get(i).getCompetitor().equals(competitor)) {
isCompetitorNullOrOneOfFirstThreeCompetitors = true;
if (markPassingsInOrder.size() - 1 >= i) {
competitors.add(createStartAnalysisCompetitorDTO(dashboardDispatchContext, trackedRace, i+1, markPassingsInOrder.get(i).getCompetitor()));
if (competitor == null || markPassingsInOrder.get(i).getCompetitor().equals(competitor)) {
isCompetitorNullOrOneOfFirstThreeCompetitors = true;
}
}
}
if (competitor != null && !isCompetitorNullOrOneOfFirstThreeCompetitors) {
int rankOfCompetitorWhilePassingSecondWaypoint = getRankOfCompetitorWhilePassingSecondWaypoint(competitor, trackedRace);
competitors.add(createStartAnalysisCompetitorDTO(dashboardDispatchContext, trackedRace, rankOfCompetitorWhilePassingSecondWaypoint, competitor));
@@ -79,4 +79,20 @@ public abstract class AbstractDistance implements Distance {
public Distance add(Distance d) {
return new NauticalMileDistance(getNauticalMiles()+d.getNauticalMiles());
}
@Override
public double divide(Distance other) {
return getMeters() / other.getMeters();
}
@Override
public Distance abs() {
final Distance result;
if (getMeters() >= 0) {
result = this;
} else {
result = scale(-1);
}
return result;
}
}
@@ -164,7 +164,8 @@ public class AbstractPosition implements Position {
@Override
public Distance getDistanceToLine(Position left, Position right) {
final Distance result;
final int factor = this.crossTrackError(left, left.getBearingGreatCircle(right)).getMeters()>0?1:-1;
final Distance crossTrackError = this.crossTrackError(left, left.getBearingGreatCircle(right));
final int factor = crossTrackError.getMeters()>0?1:-1;
double toLeft = Math.abs(left.getBearingGreatCircle(this).getDifferenceTo(left.getBearingGreatCircle(right))
.getDegrees());
double toRight = Math.abs(right.getBearingGreatCircle(this).getDifferenceTo(right.getBearingGreatCircle(left))
@@ -172,9 +173,9 @@ public class AbstractPosition implements Position {
if (toLeft > 90) {
result = this.getDistance(left).scale(factor);
} else if (toRight > 90) {
result = this.getDistance(right).scale(factor);
} else {
result = this.crossTrackError(left, left.getBearingGreatCircle(right));
result = this.getDistance(right).scale(factor);
} else {
result = crossTrackError;
}
return result;
}
@@ -80,6 +80,22 @@ public interface Distance extends Comparable<Distance>, Serializable {
public String toString() {
return "0m";
}
@Override
public double divide(Distance other) {
final double result;
if (other.getMeters() == 0) {
result = 0./0.;
} else {
result = 0;
}
return result;
}
@Override
public Distance abs() {
return this;
}
};
double getGeographicalMiles();
@@ -108,4 +124,16 @@ public interface Distance extends Comparable<Distance>, Serializable {
Speed inTime(Duration duration);
Distance add(Distance d);
/**
* Divides this distance by the {@code other} distance. Should the {@code other} distance
* be {@link #<code>null</code>}, the result will be {@code NaN}.
*/
double divide(Distance other);
/**
* If this distance is negative, returns a positive difference with the same absolute value; for positive
* distances, this distance is returned.
*/
Distance abs();
}
@@ -0,0 +1,3 @@
package com.sap.sailing.domain.common;
public enum FixType { BUOY }
@@ -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);
@@ -28,6 +28,7 @@ public enum Permission implements com.sap.sse.security.shared.Permission {
MANAGE_USERS,
MANAGE_FILE_STORAGE,
MANAGE_MARK_PASSINGS,
MANAGE_MARK_POSITIONS,
CAN_REPLAY_DURING_LIVE_RACES,
// back-end permissions
@@ -35,7 +36,7 @@ public enum Permission implements com.sap.sse.security.shared.Permission {
REGATTA,
LEADERBOARD,
LEADERBOARD_GROUP,
TRACKED_RACE
TRACKED_RACE,
;
// TODO once we can use Java8 here, move this up into a "default" method on the Permission interface
@@ -19,6 +19,7 @@ public class SailingPermissionsForRoleProvider implements PermissionsForRoleProv
// RaceBoard:
Permission.MANAGE_MEDIA,
Permission.MANAGE_MARK_PASSINGS,
Permission.MANAGE_MARK_POSITIONS,
// AdminConsole:
Permission.MANAGE_ALL_COMPETITORS,
@@ -19,6 +19,7 @@ import org.junit.rules.ExpectedException;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartOfTrackingEventImpl;
import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogRegisterCompetitorEventImpl;
import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogUseCompetitorsFromRaceLogEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
@@ -158,7 +159,6 @@ public class CreateAndTrackWithRaceLogTest {
@Test
public void canDenote_Add_Track() throws MalformedURLException, FileNotFoundException, URISyntaxException,
Exception {
RaceColumn column = leaderboard.getRaceColumnByName(columnName);
RegattaLog regattaLog = leaderboard.getRegattaLike().getRegattaLog();
RaceLog raceLog = column.getRaceLog(fleet);
@@ -176,10 +176,11 @@ public class CreateAndTrackWithRaceLogTest {
addFixes0(dev1);
raceLog.add(new RaceLogUseCompetitorsFromRaceLogEventImpl(t(), author, t(), UUID.randomUUID(), 0));
raceLog.add(new RaceLogRegisterCompetitorEventImpl(t(), author, 0, comp1));
raceLog.add(new RaceLogStartOfTrackingEventImpl(t(0), author, /* passId */ 0));
// start tracking
adapter.startTracking(service, leaderboard, column, fleet);
// now there is a trackedrace
// now there is a tracked race
TrackedRace race = column.getTrackedRace(fleet);
assertNotNull(race);
@@ -211,6 +212,7 @@ public class CreateAndTrackWithRaceLogTest {
t(0), t(10)));
addFixes0(dev1);
regattaLog.add(new RegattaLogRegisterCompetitorEventImpl(t(), t(), author, UUID.randomUUID(), comp1));
raceLog.add(new RaceLogStartOfTrackingEventImpl(t(0), author, /* passId */ 0));
// start tracking
adapter.startTracking(service, leaderboard, column, fleet);
@@ -13,6 +13,8 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.domain.abstractlog.AbstractLog;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent;
@@ -20,7 +22,9 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.LastPublishedCourseDesignFinder;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.TrackingTimesFinder;
import com.sap.sailing.domain.abstractlog.race.impl.BaseRaceLogEventVisitor;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartOfTrackingEventImpl;
import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent;
import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogStartTrackingEvent;
import com.sap.sailing.domain.abstractlog.race.tracking.analyzing.impl.RaceInformationFinder;
@@ -107,6 +111,8 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
private final Map<DeviceIdentifier, List<DeviceMapping<Competitor>>> competitorMappingsByDevices = new HashMap<>();
private DynamicTrackedRace trackedRace;
private final AbstractLogEventAuthor raceLogEventAuthor = new LogEventAuthorImpl(
RaceLogRaceTracker.class.getName(), 0);
private static final Logger logger = Logger.getLogger(RaceLogRaceTracker.class.getName());
@@ -239,50 +245,6 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
return new RegattaLogDeviceMarkMappingFinder(params.getRegattaLog()).analyze();
}
/**
* Use mapping time ranges to set time start and end of tracking time for race
*/
private void updateStartAndEndOfTracking() {
// TODO bug 3196: this would need to be evaluated lazily in TrackedRaceImpl.getStartOfTracking / getEndOfTracking
final Pair<TimePoint, TimePoint> trackingTimesFromRaceLog = trackedRace.getTrackingTimesFromRaceLogs();
if (trackingTimesFromRaceLog == null) {
TimePoint earliestMappingStart = new MillisecondsTimePoint(Long.MAX_VALUE);
TimePoint latestMappingEnd = new MillisecondsTimePoint(Long.MIN_VALUE);
for (List<? extends DeviceMapping<?>> list : competitorMappings.values()) {
for (DeviceMapping<?> mapping : list) {
earliestMappingStart = getUpdatedEarliestMappingStart(earliestMappingStart, mapping.getTimeRange().from());
latestMappingEnd = getUpdatedLatestMappingEnd(latestMappingEnd, mapping.getTimeRange().to());
}
}
for (List<? extends DeviceMapping<?>> list : markMappings.values()) {
for (DeviceMapping<?> mapping : list) {
earliestMappingStart = getUpdatedEarliestMappingStart(earliestMappingStart, mapping.getTimeRange().from());
latestMappingEnd = getUpdatedLatestMappingEnd(latestMappingEnd, mapping.getTimeRange().to());
}
}
trackedRace.setStartOfTrackingReceived(earliestMappingStart == null ? TimePoint.BeginningOfTime : earliestMappingStart);
trackedRace.setEndOfTrackingReceived(latestMappingEnd == null ? TimePoint.EndOfTime : latestMappingEnd);
}
}
private TimePoint getUpdatedEarliestMappingStart(TimePoint earliestMappingStart, final TimePoint from) {
if (from == null) {
earliestMappingStart = null; // no further updates; one open interval means startOfTracking shall be the beginning of time
} else if (earliestMappingStart != null && from.before(earliestMappingStart)) {
earliestMappingStart = from;
}
return earliestMappingStart;
}
private TimePoint getUpdatedLatestMappingEnd(TimePoint latestMappingEnd, final TimePoint to) {
if (to == null) {
latestMappingEnd = null; // no further updates; one open interval means startOfTracking shall be the beginning of time
} else if (latestMappingEnd != null && to.after(latestMappingEnd)) {
latestMappingEnd = to;
}
return latestMappingEnd;
}
private <ItemT extends WithID, FixT extends GPSFix> boolean hasMappingAlreadyBeenLoaded(
DeviceMapping<ItemT> newMapping, List<DeviceMapping<ItemT>> oldMappings) {
if (oldMappings == null) {
@@ -354,8 +316,6 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
markMappingsByDevices.clear();
markMappingsByDevices.putAll(transformToMappingsByDevice(markMappings));
updateStartAndEndOfTracking();
}
private void onDeviceMarkMappingEvent(RegattaLogDeviceMarkMappingEvent event) {
@@ -429,7 +389,6 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
competitorMappings.putAll(newMappings);
competitorMappingsByDevices.clear();
competitorMappingsByDevices.putAll(transformToMappingsByDevice(competitorMappings));
updateStartAndEndOfTracking();
}
private void onStartTrackingEvent(RaceLogStartTrackingEvent event) {
@@ -439,11 +398,15 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
}
private void onStartOfTrackingEvent(RaceLogStartOfTrackingEvent event) {
trackedRace.setStartOfTrackingReceived(event.getLogicalTimePoint());
if (trackedRace != null) {
trackedRace.setStartOfTrackingReceived(event.getLogicalTimePoint());
}
}
private void onEndOfTrackingEvent(RaceLogEndOfTrackingEvent event) {
trackedRace.setEndOfTrackingReceived(event.getLogicalTimePoint());
if (trackedRace != null) {
trackedRace.setEndOfTrackingReceived(event.getLogicalTimePoint());
}
}
private void onCourseDesignChangedEvent(RaceLogCourseDesignChangedEvent event) {
@@ -466,6 +429,12 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
RaceColumn raceColumn = params.getRaceColumn();
Fleet fleet = params.getFleet();
RaceLogDenoteForTrackingEvent denoteEvent = new RaceInformationFinder(raceLog).analyze();
final Pair<TimePoint, TimePoint> trackingTimes = new TrackingTimesFinder(raceLog).analyze();
if (trackingTimes == null) {
// seems the first time tracking for this race is started; enter "now" as start of tracking
// into the race log
raceLog.add(new RaceLogStartOfTrackingEventImpl(MillisecondsTimePoint.now(), raceLogEventAuthor, /* passId */ 0));
}
BoatClass boatClass = denoteEvent.getBoatClass();
String raceName = denoteEvent.getRaceName();
CourseBase courseBase = new LastPublishedCourseDesignFinder(raceLog).analyze();
@@ -473,7 +442,7 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
courseBase = new CourseDataImpl("Default course for " + raceName);
logger.log(Level.FINE, "Using empty course in creation of race " + raceName);
}
Course course = new CourseImpl(raceName + " course", courseBase.getWaypoints());
final Course course = new CourseImpl(raceName + " course", courseBase.getWaypoints());
if (raceColumn.getTrackedRace(fleet) != null) {
if (event != null) {
try {
@@ -240,8 +240,9 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
public void pingMark(RegattaLog log, Mark mark, GPSFix gpsFix, RacingEventService service) {
final PingDeviceIdentifierImpl device = new PingDeviceIdentifierImpl();
final TimePoint timePoint = gpsFix.getTimePoint();
final RegattaLogEvent mapping = new RegattaLogDeviceMarkMappingEventImpl(timePoint,
timePoint, service.getServerAuthor(), UUID.randomUUID(), mark, device, timePoint, timePoint);
final TimePoint now = MillisecondsTimePoint.now();
final RegattaLogEvent mapping = new RegattaLogDeviceMarkMappingEventImpl(now,
now, service.getServerAuthor(), UUID.randomUUID(), mark, device, timePoint, timePoint);
log.add(mapping);
try {
service.getGPSFixStore().storeFix(device, gpsFix);
@@ -0,0 +1,37 @@
package com.sap.sailing.domain.regattalog.tracking.analyzing.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import java.util.Collections;
import java.util.Set;
import org.junit.Test;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEventVisitor;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRegisterCompetitorEventImpl;
import com.sap.sailing.domain.abstractlog.shared.analyzing.CompetitorDeregistrator;
import com.sap.sailing.domain.abstractlog.shared.analyzing.CompetitorsInLogAnalyzer;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.impl.CompetitorImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class CompetitorDeregistrationTest extends AbstractRegattaLogTrackingTest {
private final Competitor competitor = new CompetitorImpl("comp", "Comp", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
private final Competitor competitor2 = new CompetitorImpl("comp2", "Comp2", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
@Test
public void testCompetitorDeregistration() {
log.add(new RegattaLogRegisterCompetitorEventImpl(MillisecondsTimePoint.now(), author, competitor));
log.add(new RegattaLogRegisterCompetitorEventImpl(MillisecondsTimePoint.now(), author, competitor2));
final CompetitorDeregistrator<RegattaLog, RegattaLogEvent, RegattaLogEventVisitor> deregistrator = new CompetitorDeregistrator<>(log,
Collections.singleton(competitor2), author);
final Set<RegattaLogEvent> events = deregistrator.analyze();
deregistrator.deregister(events);
final Set<Competitor> competitors = new CompetitorsInLogAnalyzer<>(log).analyze();
assertEquals(1, competitors.size());
assertSame(competitor, competitors.iterator().next());
}
}
@@ -1,9 +1,11 @@
package com.sap.sailing.domain.regattalog.tracking.analyzing.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import junit.framework.Assert;
@@ -17,6 +19,7 @@ import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCo
import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDeviceCompetitorMappingFinder;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.impl.CompetitorImpl;
import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceMapping;
@@ -55,6 +58,226 @@ public class DeviceMappingFinderTest extends AbstractRegattaLogTrackingTest {
log.add(mapping);
}
/**
* Expecting that a single, closed mapping interval spanning a fix will be cut into two intervals excluding the fix time point
*/
@Test
public void testFixRemovalForMiddleOfInterval() throws NotRevokableException {
final RegattaLogDeviceCompetitorMappingFinder finder = new RegattaLogDeviceCompetitorMappingFinder(log);
addMapping(author, device, 100l, 200l, competitor);
{
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
assertEquals(1, mappings.size());
assertTrue(mappings.containsKey(competitor));
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
assertEquals(1, deviceMappings.size());
final DeviceMapping<Competitor> mapping = deviceMappings.iterator().next();
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
assertEquals(new MillisecondsTimePoint(200), mapping.getTimeRange().to());
}
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(150));
{
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
assertEquals(1, mappings.size());
assertTrue(mappings.containsKey(competitor));
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
assertEquals(2, deviceMappings.size());
boolean found100 = false;
for (final DeviceMapping<Competitor> mapping : deviceMappings) {
if (mapping.getTimeRange().from().asMillis() == 100) {
found100 = true;
assertEquals(149, mapping.getTimeRange().to().asMillis());
} else {
assertEquals(151, mapping.getTimeRange().from().asMillis());
assertEquals(200, mapping.getTimeRange().to().asMillis());
}
}
assertTrue(found100);
}
}
@Test
public void testFixRemovalForMiddleOfIntervalClosedSeparately() throws NotRevokableException {
final RegattaLogDeviceCompetitorMappingFinder finder = new RegattaLogDeviceCompetitorMappingFinder(log);
final Serializable mappingId = addMapping(author, device, 100l, null, competitor);
closeMapping(author, device, mappingId, 200);
{
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
assertEquals(1, mappings.size());
assertTrue(mappings.containsKey(competitor));
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
assertEquals(1, deviceMappings.size());
final DeviceMapping<Competitor> mapping = deviceMappings.iterator().next();
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
assertEquals(new MillisecondsTimePoint(200), mapping.getTimeRange().to());
}
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(150));
{
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
assertEquals(1, mappings.size());
assertTrue(mappings.containsKey(competitor));
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
assertEquals(2, deviceMappings.size());
for (final DeviceMapping<Competitor> mapping : deviceMappings) {
if (mapping.getTimeRange().from().asMillis() == 100) {
assertEquals(149, mapping.getTimeRange().to().asMillis());
} else {
assertEquals(151, mapping.getTimeRange().from().asMillis());
assertEquals(200, mapping.getTimeRange().to().asMillis());
}
}
}
}
@Test
public void testFixRemovalForStartOfInterval() throws NotRevokableException {
final RegattaLogDeviceCompetitorMappingFinder finder = new RegattaLogDeviceCompetitorMappingFinder(log);
addMapping(author, device, 100l, 200l, competitor);
{
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
assertEquals(1, mappings.size());
assertTrue(mappings.containsKey(competitor));
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
assertEquals(1, deviceMappings.size());
final DeviceMapping<Competitor> mapping = deviceMappings.iterator().next();
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
assertEquals(new MillisecondsTimePoint(200), mapping.getTimeRange().to());
}
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(100));
{
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
assertEquals(1, mappings.size());
assertTrue(mappings.containsKey(competitor));
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
assertEquals(1, deviceMappings.size());
assertEquals(101, deviceMappings.get(0).getTimeRange().from().asMillis());
assertEquals(200, deviceMappings.get(0).getTimeRange().to().asMillis());
}
}
@Test
public void testFixRemovalForStartOfIntervalClosedSeparately() throws NotRevokableException {
final RegattaLogDeviceCompetitorMappingFinder finder = new RegattaLogDeviceCompetitorMappingFinder(log);
final Serializable mappingId = addMapping(author, device, 100l, null, competitor);
closeMapping(author, device, mappingId, 200);
{
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
assertEquals(1, mappings.size());
assertTrue(mappings.containsKey(competitor));
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
assertEquals(1, deviceMappings.size());
final DeviceMapping<Competitor> mapping = deviceMappings.iterator().next();
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
assertEquals(new MillisecondsTimePoint(200), mapping.getTimeRange().to());
}
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(100));
{
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
assertEquals(1, mappings.size());
assertTrue(mappings.containsKey(competitor));
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
assertEquals(1, deviceMappings.size());
assertEquals(101, deviceMappings.get(0).getTimeRange().from().asMillis());
assertEquals(200, deviceMappings.get(0).getTimeRange().to().asMillis());
}
}
@Test
public void testFixRemovalForEndOfInterval() throws NotRevokableException {
final RegattaLogDeviceCompetitorMappingFinder finder = new RegattaLogDeviceCompetitorMappingFinder(log);
addMapping(author, device, 100l, 200l, competitor);
{
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
assertEquals(1, mappings.size());
assertTrue(mappings.containsKey(competitor));
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
assertEquals(1, deviceMappings.size());
final DeviceMapping<Competitor> mapping = deviceMappings.iterator().next();
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
assertEquals(new MillisecondsTimePoint(200), mapping.getTimeRange().to());
}
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(200));
{
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
assertEquals(1, mappings.size());
assertTrue(mappings.containsKey(competitor));
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
assertEquals(1, deviceMappings.size());
assertEquals(100, deviceMappings.get(0).getTimeRange().from().asMillis());
assertEquals(199, deviceMappings.get(0).getTimeRange().to().asMillis());
}
}
@Test
public void testFixRemovalForEndOfIntervalClosedSeparately() throws NotRevokableException {
final RegattaLogDeviceCompetitorMappingFinder finder = new RegattaLogDeviceCompetitorMappingFinder(log);
final Serializable mappingId = addMapping(author, device, 100l, null, competitor);
closeMapping(author, device, mappingId, 200);
{
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
assertEquals(1, mappings.size());
assertTrue(mappings.containsKey(competitor));
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
assertEquals(1, deviceMappings.size());
final DeviceMapping<Competitor> mapping = deviceMappings.iterator().next();
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
assertEquals(new MillisecondsTimePoint(200), mapping.getTimeRange().to());
}
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(200));
{
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
assertEquals(1, mappings.size());
assertTrue(mappings.containsKey(competitor));
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
assertEquals(1, deviceMappings.size());
assertEquals(100, deviceMappings.get(0).getTimeRange().from().asMillis());
assertEquals(199, deviceMappings.get(0).getTimeRange().to().asMillis());
}
}
@Test
public void testFixRemovalForMillisecondInterval() throws NotRevokableException {
final RegattaLogDeviceCompetitorMappingFinder finder = new RegattaLogDeviceCompetitorMappingFinder(log);
addMapping(author, device, 100l, 100l, competitor);
{
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
assertEquals(1, mappings.size());
assertTrue(mappings.containsKey(competitor));
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
assertEquals(1, deviceMappings.size());
final DeviceMapping<Competitor> mapping = deviceMappings.iterator().next();
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().to());
}
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(100));
{
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
assertTrue(mappings.isEmpty());
}
}
@Test
public void testFixRemovalForMillisecondIntervalClosedSeparately() throws NotRevokableException {
final RegattaLogDeviceCompetitorMappingFinder finder = new RegattaLogDeviceCompetitorMappingFinder(log);
final Serializable mappingId = addMapping(author, device, 100l, null, competitor);
closeMapping(author, device, mappingId, 100);
{
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
assertEquals(1, mappings.size());
assertTrue(mappings.containsKey(competitor));
final List<DeviceMapping<Competitor>> deviceMappings = mappings.get(competitor);
assertEquals(1, deviceMappings.size());
final DeviceMapping<Competitor> mapping = deviceMappings.iterator().next();
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().from());
assertEquals(new MillisecondsTimePoint(100), mapping.getTimeRange().to());
}
finder.removeTimePointFromMapping(competitor, new MillisecondsTimePoint(100));
{
final Map<Competitor, List<DeviceMapping<Competitor>>> mappings = finder.analyze();
assertTrue(mappings.isEmpty());
}
}
@Test
public void notDisturbedByMappingsForOtherItemsAndDevices() {
//two mappings for first competitor found? (competitor2 mappings should not "distract")
@@ -1,9 +1,16 @@
package com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl;
import java.util.UUID;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorMappingEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorMappingEventImpl;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class RegattaLogDeviceCompetitorMappingFinder extends RegattaLogDeviceMappingFinder<Competitor> {
@@ -15,4 +22,11 @@ public class RegattaLogDeviceCompetitorMappingFinder extends RegattaLogDeviceMap
protected boolean isValidMapping(RegattaLogDeviceMappingEvent<?> mapping) {
return mapping instanceof RegattaLogDeviceCompetitorMappingEvent;
}
@Override
protected RegattaLogDeviceCompetitorMappingEvent createDeviceMappingEvent(Competitor item, AbstractLogEventAuthor author,
TimePoint from, TimePoint to, DeviceIdentifier deviceId) {
final TimePoint now = MillisecondsTimePoint.now();
return new RegattaLogDeviceCompetitorMappingEventImpl(now, now, author, UUID.randomUUID(), item, deviceId, from, to);
}
}
@@ -8,11 +8,13 @@ import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogCloseOpenEndedDeviceMappingEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent;
import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.racelogtracking.DeviceMapping;
import com.sap.sailing.domain.racelogtracking.impl.DeviceMappingImpl;
@@ -34,7 +36,7 @@ import com.sap.sse.common.impl.TimeRangeImpl;
* {@code DeviceMapping}, the only changes being introduced by closing open time ranges in cases where an
* according {@code CloseOpenEndedDeviceMappingEvent} exists.
*/
public class RegattaLogDeviceMappingFinder<ItemT extends WithID> extends RegattaLogAnalyzer<Map<ItemT, List<DeviceMapping<ItemT>>>> {
public abstract class RegattaLogDeviceMappingFinder<ItemT extends WithID> extends RegattaLogAnalyzer<Map<ItemT, List<DeviceMapping<ItemT>>>> {
private static Logger logger = Logger.getLogger(RegattaLogDeviceMappingFinder.class.getName());
public RegattaLogDeviceMappingFinder(RegattaLog log) {
@@ -51,41 +53,46 @@ public class RegattaLogDeviceMappingFinder<ItemT extends WithID> extends Regatta
Collections.singletonList(originalEventId));
}
private <T, U> List<U> getItemSet(Map<T, List<U>> map, T item) {
List<U> list = map.get(item);
private List<RegattaLogDeviceMappingEvent<ItemT>> getMappingEventsForItem(Map<ItemT, List<RegattaLogDeviceMappingEvent<ItemT>>> map, ItemT item) {
List<RegattaLogDeviceMappingEvent<ItemT>> list = map.get(item);
if (list == null) {
list = new ArrayList<U>();
list = new ArrayList<RegattaLogDeviceMappingEvent<ItemT>>();
map.put(item, list);
}
return list;
}
@Override
protected Map<ItemT, List<DeviceMapping<ItemT>>> performAnalysis() {
Map<ItemT, List<RegattaLogDeviceMappingEvent<ItemT>>> events = new HashMap<ItemT, List<RegattaLogDeviceMappingEvent<ItemT>>>();
Map<ItemT, List<DeviceMapping<ItemT>>> mappings = new HashMap<ItemT, List<DeviceMapping<ItemT>>>();
Map<Serializable, RegattaLogCloseOpenEndedDeviceMappingEvent> closingEvents = new HashMap<Serializable, RegattaLogCloseOpenEndedDeviceMappingEvent>();
findUnrevokedMappingAndClosingEvents(events, closingEvents);
Map<ItemT, List<DeviceMapping<ItemT>>> mappings = new HashMap<ItemT, List<DeviceMapping<ItemT>>>();
for (ItemT item : events.keySet()) {
mappings.put(item, closeOpenRanges(events.get(item), item, closingEvents));
}
return mappings;
}
private void findUnrevokedMappingAndClosingEvents(Map<ItemT, List<RegattaLogDeviceMappingEvent<ItemT>>> events,
Map<Serializable, RegattaLogCloseOpenEndedDeviceMappingEvent> closingEvents) {
for (RegattaLogEvent e : getLog().getUnrevokedEvents()) {
if (e instanceof RegattaLogDeviceMappingEvent && isValidMapping(((RegattaLogDeviceMappingEvent<?>) e))) {
@SuppressWarnings("unchecked")
RegattaLogDeviceMappingEvent<ItemT> mappingEvent = (RegattaLogDeviceMappingEvent<ItemT>) e;
getItemSet(events, mappingEvent.getMappedTo()).add(mappingEvent);
getMappingEventsForItem(events, mappingEvent.getMappedTo()).add(mappingEvent);
} else if (e instanceof RegattaLogCloseOpenEndedDeviceMappingEvent) {
//a higher priority closing events for the same mapping event overwrites the lower priority one
RegattaLogCloseOpenEndedDeviceMappingEvent closingEvent = (RegattaLogCloseOpenEndedDeviceMappingEvent) e;
closingEvents.put(closingEvent.getDeviceMappingEventId(), closingEvent);
}
}
for (ItemT item : events.keySet()) {
mappings.put(item, closeOpenRanges(events.get(item), item, closingEvents));
}
return mappings;
}
private List<DeviceMapping<ItemT>> closeOpenRanges(List<RegattaLogDeviceMappingEvent<ItemT>> events, ItemT item,
Map<Serializable, RegattaLogCloseOpenEndedDeviceMappingEvent> closingEvents) {
List<DeviceMapping<ItemT>> result = new ArrayList<DeviceMapping<ItemT>>();
for (RegattaLogDeviceMappingEvent<ItemT> event : events) {
for (final RegattaLogDeviceMappingEvent<ItemT> event : events) {
TimePoint from = event.getFrom();
TimePoint to = event.getTo();
TimePoint closingTimePoint = closingEvents.containsKey(event.getId()) ? closingEvents.get(event.getId())
@@ -100,4 +107,38 @@ public class RegattaLogDeviceMappingFinder<ItemT extends WithID> extends Regatta
}
return result;
}
/**
* By revoking existing mappings for {@code item} and replacing them by mappings that exclude {@code fixTimePoint}, any
* fix at {@code fixTimePoint} will no longer be linked to {@code item}. If a mapping is limited to the exact fix time point,
* the mapping is only revoked and not replaced. If one boundary is exactly the fix time point, the interval is revoked and
* replaced by a single interval with that border adjusted so it excludes the fix.
*/
public void removeTimePointFromMapping(ItemT item, TimePoint fixTimePoint) throws NotRevokableException {
Map<ItemT, List<RegattaLogDeviceMappingEvent<ItemT>>> events = new HashMap<ItemT, List<RegattaLogDeviceMappingEvent<ItemT>>>();
Map<Serializable, RegattaLogCloseOpenEndedDeviceMappingEvent> closingEvents = new HashMap<Serializable, RegattaLogCloseOpenEndedDeviceMappingEvent>();
findUnrevokedMappingAndClosingEvents(events, closingEvents);
for (final RegattaLogDeviceMappingEvent<ItemT> event : events.get(item)) {
final TimePoint from = event.getFrom();
final RegattaLogCloseOpenEndedDeviceMappingEvent closingEvent = closingEvents.get(event.getId());
final TimePoint to = closingEvent!= null ? closingEvent.getClosingTimePoint() : event.getTo();
final TimeRange mappingTimeRange = new TimeRangeImpl(from, to);
if (mappingTimeRange.includes(fixTimePoint)) {
if (closingEvent != null) {
log.revokeEvent(closingEvent.getAuthor(), closingEvent, "removing single time point "+fixTimePoint+" from mapping for "+item);
}
log.revokeEvent(event.getAuthor(), event, "removing single time point "+fixTimePoint+" from mapping for "+item);
final TimePoint endOfFirstHalf = fixTimePoint.minus(1);
final TimePoint startOfSecondHalf = fixTimePoint.plus(1);
if (!endOfFirstHalf.before(from)) {
log.add(createDeviceMappingEvent(item, event.getAuthor(), from, endOfFirstHalf, event.getDevice()));
}
if (!to.before(startOfSecondHalf)) {
log.add(createDeviceMappingEvent(item, event.getAuthor(), startOfSecondHalf, to, event.getDevice()));
}
}
}
}
protected abstract RegattaLogDeviceMappingEvent<ItemT> createDeviceMappingEvent(ItemT item, AbstractLogEventAuthor author, TimePoint plus, TimePoint to, DeviceIdentifier deviceId);
}
@@ -1,9 +1,16 @@
package com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl;
import java.util.UUID;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMappingEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceMarkMappingEventImpl;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class RegattaLogDeviceMarkMappingFinder extends RegattaLogDeviceMappingFinder<Mark> {
@@ -15,4 +22,11 @@ public class RegattaLogDeviceMarkMappingFinder extends RegattaLogDeviceMappingFi
protected boolean isValidMapping(RegattaLogDeviceMappingEvent<?> mapping) {
return mapping instanceof RegattaLogDeviceMarkMappingEvent;
}
@Override
protected RegattaLogDeviceMarkMappingEvent createDeviceMappingEvent(Mark item, AbstractLogEventAuthor author,
TimePoint from, TimePoint to, DeviceIdentifier deviceId) {
final TimePoint now = MillisecondsTimePoint.now();
return new RegattaLogDeviceMarkMappingEventImpl(now, now, author, UUID.randomUUID(), item, deviceId, from, to);
}
}
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.abstractlog.shared.analyzing;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -14,14 +15,21 @@ import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
import com.sap.sse.common.Util;
/**
* Generates events that close the events that are responsible for the {@link #mapping} that is passed to the
* constructor.
* Finds the competitor registration events for a given set of {@link Competitor competitors} that must be
* {@link AbstractLog#revokeEvent(AbstractLogEventAuthor, AbstractLogEvent) revoked} in order to deregister the
* competitors. Note that the de-registration cannot be performed during analysis because
* {@link BaseLogAnalyzer#analyze()} obtains a read lock on the log whereas
* {@link AbstractLog#revokeEvent(AbstractLogEventAuthor, AbstractLogEvent)} requires a write lock, and upgrading from a
* read to a write lock is not possible.<p>
*
* Clients must call {@link #deregister} with the result of {@link #analyze()} in order to actually perform the
* deregistration.
*
* @author Jan Bross
*
*/
public class CompetitorDeregistrator<LogT extends AbstractLog<EventT, VisitorT>, EventT extends AbstractLogEvent<VisitorT>, VisitorT>
extends BaseLogAnalyzer<LogT, EventT, VisitorT, Void> {
extends BaseLogAnalyzer<LogT, EventT, VisitorT, Set<EventT>> {
private static final Logger logger = Logger.getLogger(CompetitorDeregistrator.class.getName());
protected final Iterable<Competitor> competitorsToDeregister;
@@ -34,24 +42,29 @@ extends BaseLogAnalyzer<LogT, EventT, VisitorT, Void> {
}
@Override
protected Void performAnalysis() {
HashSet<Competitor> competitorSet = new HashSet<Competitor>();
protected Set<EventT> performAnalysis() {
final Set<EventT> result = new HashSet<>();
final HashSet<Competitor> competitorSet = new HashSet<Competitor>();
Util.addAll(competitorsToDeregister, competitorSet);
for (EventT event : log.getUnrevokedEventsDescending()) {
if (event instanceof RegisterCompetitorEvent) {
RegisterCompetitorEvent<?> registerEvent = (RegisterCompetitorEvent<?>) event;
if (competitorSet.contains(registerEvent.getCompetitor())) {
try {
log.revokeEvent(eventAuthor, event,
"unregistering competitor because no longer selected for registration");
} catch (NotRevokableException e) {
logger.log(Level.WARNING, "could not unregister competitor by adding RevokeEvent", e);
}
result.add(event);
}
}
}
return null;
return result;
}
public void deregister(final Set<EventT> competitorRegistrationEvents) {
for (final EventT event : competitorRegistrationEvents) {
try {
log.revokeEvent(eventAuthor, event,
"unregistering competitor because no longer selected for registration");
} catch (NotRevokableException e) {
logger.log(Level.WARNING, "could not unregister competitor by adding RevokeEvent", e);
}
}
}
}
@@ -2,11 +2,14 @@ package com.sap.sailing.domain.base;
import com.sap.sailing.domain.common.BoatHullType;
import com.sap.sailing.domain.common.Distance;
import com.sap.sse.common.Duration;
import com.sap.sse.common.IsManagedByCache;
import com.sap.sse.common.Named;
import com.sap.sse.common.TimePoint;
public interface BoatClass extends Named, IsManagedByCache<SharedDomainFactory> {
final Duration APPROXIMATE_AVERAGE_MANEUVER_DURATION = Duration.ONE_SECOND.times(8); // as discussed with Dennis Gehrlein
/**
* The distance returned by this method should be appropriate for use in
* {@link TrackedRace#approximate(Competitor, Distance, TimePoint, TimePoint)} so that penalty circles and other
@@ -64,7 +64,7 @@ public class BoatClassImpl extends NamedImpl implements BoatClass {
this.hullLength = hullLength;
this.hullBeam = hullBeam;
this.hullType = hullType;
approximateManeuverDurationInMilliseconds = 8000; // as discussed with Dennis Gehrlein
approximateManeuverDurationInMilliseconds = APPROXIMATE_AVERAGE_MANEUVER_DURATION.asMillis();
}
@Override
@@ -313,19 +313,36 @@ public class TrackImpl<FixType extends Timed> implements Track<FixType> {
}
protected boolean add(FixType fix) {
return add(fix, /* replace */ false);
}
protected boolean add(FixType fix, boolean replace) {
lockForWrite();
try {
return addWithoutLocking(fix);
return addWithoutLocking(fix, replace);
} finally {
unlockAfterWrite();
}
}
/**
* The caller must ensure to hold the write lock for this track when calling this methos
* The caller must ensure to hold the write lock for this track when calling this method.
*
* @param replace
* whether or not to replace an existing fix in the track that is equal to {@link #fix} as defined by the
* comparator used for the {@link #fixes} set. By default this is a comparator only comparing the
* fixes' time stamps. Subclasses may use different comparator implementations.
*/
protected boolean addWithoutLocking(FixType fix) {
return getInternalRawFixes().add(fix);
protected boolean addWithoutLocking(FixType fix, boolean replace) {
final boolean result;
final boolean added = getInternalRawFixes().add(fix);
if (!added && replace) {
getInternalRawFixes().remove(fix);
result = getInternalRawFixes().add(fix);
} else {
result = added;
}
return result;
}
@Override
@@ -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
@@ -84,15 +84,15 @@ public class TimeRangeTest {
two = create(11, 16);
assertNull(one.union(two));
two = create(7, Long.MAX_VALUE);
two = create(7, TimePoint.EndOfTime.asMillis());
union = two.union(one);
TimeRange union2 = one.union(two);
assertEquals(5, union.from().asMillis());
assertEquals(5, union2.from().asMillis());
assertEquals(Long.MAX_VALUE, union.to().asMillis());
assertEquals(Long.MAX_VALUE, union2.to().asMillis());
assertEquals(TimePoint.EndOfTime.asMillis(), union.to().asMillis());
assertEquals(TimePoint.EndOfTime.asMillis(), union2.to().asMillis());
one = create(Long.MIN_VALUE, 10);
one = create(TimePoint.BeginningOfTime.asMillis(), 10);
union = two.union(one);
assertTrue(union.hasOpenBeginning());
assertTrue(union.hasOpenEnd());
@@ -0,0 +1,121 @@
package com.sap.sailing.domain.markpassingcalculation.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.markpassingcalculation.impl.CandidateFinderImpl.AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
public class CandidateFinderImplWhiteBoxTest {
private static class CandidateFinderWithPublicGetProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances extends CandidateFinderImpl {
public CandidateFinderWithPublicGetProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(
DynamicTrackedRace race) {
super(race);
}
public Double getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(
final List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> distancesToStartLineOfOtherCompetitors, boolean startIsLine) {
return super.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(distancesToStartLineOfOtherCompetitors, startIsLine);
}
}
private CandidateFinderWithPublicGetProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances finder;
@Before
public void setUp() {
DynamicTrackedRace trackedRace = mock(DynamicTrackedRace.class);
final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29er");
RaceDefinition race = mock(RaceDefinition.class);
when(trackedRace.getRace()).thenReturn(race);
when(race.getBoatClass()).thenReturn(boatClass);
when(race.getCompetitors()).thenReturn(Collections.emptySet());
finder = new CandidateFinderWithPublicGetProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(trackedRace);
}
@Test
public void testOneProbabilityForEmptyList() {
List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> emptyDistancesToStartLineOfOtherCompetitors = new ArrayList<>();
final double probability = finder.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(emptyDistancesToStartLineOfOtherCompetitors,
/* startIsLine */ true);
assertEquals(1.0, probability, 0.00001);
}
@Test
public void testHighProbabilityForCloseProximityAndNoLine() {
List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> distancesToStartLineOfOtherCompetitors = new ArrayList<>();
distancesToStartLineOfOtherCompetitors.add(createDistancePair(3.0, null));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(5.0, null));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(2.0, null));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(8.0, null));
final double probability = finder.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(distancesToStartLineOfOtherCompetitors,
/* startIsLine */ false);
assertTrue(probability > 0.95);
}
@Test
public void testHighProbabilityForCloseProximityExceptOneAndNoLine() {
List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> distancesToStartLineOfOtherCompetitors = new ArrayList<>();
distancesToStartLineOfOtherCompetitors.add(createDistancePair(3.0, null));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(5.0, null));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(2.0, null));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(8.0, null));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(1.0, null));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(2.5, null));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(3.2, null));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(2739.0, null));
final double probability = finder.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(distancesToStartLineOfOtherCompetitors,
/* startIsLine */ false);
assertTrue("Expected probability to exceed 55% but got "+probability, probability > 0.55);
}
@Test
public void testLowProbabilityForMostFarAwayAndNoLine() {
List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> distancesToStartLineOfOtherCompetitors = new ArrayList<>();
distancesToStartLineOfOtherCompetitors.add(createDistancePair(50.0, null));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(250.0, null));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(120.0, null));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(200.0, null));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(300.0, null));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(2.5, null));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(3.2, null));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(2739.0, null));
final double probability = finder.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(distancesToStartLineOfOtherCompetitors,
/* startIsLine */ false);
assertTrue("Expected probability to be below 10% but got "+probability, probability <= 0.1);
}
@Test
public void testHighProbabilityForLateStartAndStartIsLine() {
List<AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine> distancesToStartLineOfOtherCompetitors = new ArrayList<>();
distancesToStartLineOfOtherCompetitors.add(createDistancePair(100.0, -80.));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(120.0, -82.));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(130.0, -81.));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(90.0, -79.));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(140.0, -75.));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(150, -88.));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(135, -77.));
distancesToStartLineOfOtherCompetitors.add(createDistancePair(20.0, -10.)); // another late starter just across the line
final double probability = finder.getProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(distancesToStartLineOfOtherCompetitors,
/* startIsLine */ true);
assertTrue("Expected probability to be above 85% but got "+probability, probability >= 0.85);
}
private AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine createDistancePair(double absoluteDistanceToLine,
Double signedXTEDistanceFromLine) {
return new AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine(
new MeterDistance(absoluteDistanceToLine), signedXTEDistanceFromLine == null ? null : new MeterDistance(signedXTEDistanceFromLine));
}
}
@@ -54,7 +54,7 @@ public abstract class AbstractTracTracLiveTest extends StoredTrackBasedTest {
private IEventSubscriber eventSubscriber;
private IRaceSubscriber raceSubscriber;
private final Collection<Receiver> receivers;
@Rule public Timeout AbstractTracTracLiveTestTimeout = new Timeout(3 * 60 * 1000);
protected AbstractTracTracLiveTest() throws URISyntaxException, MalformedURLException {
+5 -7
View File
@@ -256,7 +256,7 @@ public class TrackTest {
protected Pair<GPSFixMoving, Speed> computeMaxSpeed(TimePoint from, TimePoint to) {
Pair<GPSFixMoving, Speed> result = super.computeMaxSpeed(from, to);
try {
Thread.sleep(500); // just wait a bit; can't lock really because that would cause a deadlock
Thread.sleep(1000); // just wait a bit; can't lock really because that would cause a deadlock
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
@@ -280,11 +280,9 @@ public class TrackTest {
};
}
};
new Thread(()->{
GPSFixMoving fix1 = new GPSFixMovingImpl(new DegreePosition(0, 0), new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(
GPSFixMoving fix1 = new GPSFixMovingImpl(new DegreePosition(0, 0), new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(
1, new DegreeBearingImpl(123)));
track.addGPSFix(fix1);
}).start();
track.addGPSFix(fix1);
// The following getMaximumSpeedOverGround call will trigger a computeMaxSpeed(...) and a cache(...) call
new Thread(()->
assertEquals(1., track.getMaximumSpeedOverGround(new MillisecondsTimePoint(0), new MillisecondsTimePoint(7200000)).
@@ -319,8 +317,8 @@ public class TrackTest {
throw new RuntimeException(e);
}
}).start();
cacheBarrier.await(10, TimeUnit.SECONDS);
cacheDone.await(10, TimeUnit.SECONDS);
cacheBarrier.await(20, TimeUnit.SECONDS);
cacheDone.await(20, TimeUnit.SECONDS);
testDone.await();
assertEquals(2., maxSpeed[0], 0.001);
}
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.test.markpassing;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
@@ -11,6 +12,7 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -62,7 +64,7 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest {
simpleName = getClass().getSimpleName();
}
private void setUp(String raceNumber) throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException {
protected void setUp(String raceNumber) throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException {
super.setUp();
URI storedUri = new URI("file:///"
+ new File("resources/" + getFileName() + raceNumber + ".mtb").getCanonicalPath().replace('\\', '/'));
@@ -83,9 +85,26 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest {
givenMarkPasses.put(wp, markPassing);
}
givenPasses.put(c, givenMarkPasses);
// now clear the mark passings in the TrackedRace for competitor c:
getTrackedRace().updateMarkPassings(c, Collections.emptySet());
}
}
protected Iterable<Waypoint> getWaypoints() {
return waypoints;
}
protected Competitor getCompetitorByName(final String name) {
Competitor result = null;
for (Competitor c : getTrackedRace().getRace().getCompetitors()) {
if (c.getName().equals(name)) {
result = c;
break;
}
}
return result;
}
protected abstract String getFileName();
protected void testRace(String raceNumber) throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException {
@@ -210,29 +229,19 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest {
}
private void testMiddleOfRace(int zeroBasedIndexOfLastWaypointToBePassed) {
int mistakes = 0;
CandidateFinder finder = new CandidateFinderImpl(getTrackedRace());
CandidateChooser chooser = new CandidateChooserImpl(getTrackedRace());
int mistakes = 0;
Waypoint lastWaypointToBePassed = waypoints.get(zeroBasedIndexOfLastWaypointToBePassed);
Waypoint wayPointAfterwards = waypoints.get(zeroBasedIndexOfLastWaypointToBePassed + 1);
for (Competitor c : getRace().getCompetitors()) {
MarkPassing markPassingAfter = givenPasses.get(c).get(wayPointAfterwards);
if (markPassingAfter != null) {
if (givenPasses.get(c).get(lastWaypointToBePassed) != null && givenPasses.get(c).get(wayPointAfterwards) != null) {
TimePoint beforeNextPassing = markPassingAfter.getTimePoint().minus(20000);
List<GPSFix> fixes = new ArrayList<GPSFix>();
try {
getTrackedRace().getTrack(c).lockForRead();
for (GPSFixMoving fix : getTrackedRace().getTrack(c).getFixes()) {
if (fix.getTimePoint().before(beforeNextPassing)) {
fixes.add(fix);
}
}
} finally {
getTrackedRace().getTrack(c).unlockAfterRead();
}
Util.Pair<Iterable<Candidate>, Iterable<Candidate>> f = finder.getCandidateDeltas(c, fixes);
chooser.calculateMarkPassDeltas(c, f.getA(), f.getB());
if (givenPasses.get(c).get(lastWaypointToBePassed) != null) {
final TimePoint lastGivenPassingToDetectAt = givenPasses.get(c).get(lastWaypointToBePassed).getTimePoint();
// add fixes up to 50% into the leg starting after the last passing to be detected
final TimePoint beforeNextPassing = lastGivenPassingToDetectAt.plus(lastGivenPassingToDetectAt.until(markPassingAfter.getTimePoint()).times(0.5));
calculateMarkPassingsForPartialTrack(c, beforeNextPassing, finder, chooser);
boolean gotPassed = true;
boolean gotOther = false;
System.out.println(c);
@@ -243,10 +252,16 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest {
if (waypoints.indexOf(w) <= zeroBasedIndexOfLastWaypointToBePassed) {
if ((old == null) != (newm == null)) {
gotPassed = false;
fail("Waypoint "+w+" was "+(old == null?"not ":"")+"passed by "+c+" originally"
+(old==null?"":": "+old)+"; we detected it "+(newm==null?"not ":"")+"having been passed"+
(newm == null ? "" : (": "+newm)));
}
} else {
if (w != wayPointAfterwards && newm != null) {
gotOther = true;
fail("Received a park passing "+newm+" for the "+waypoints.indexOf(w)+
"th waypoint "+w+" by "+c+" although only up to "+
zeroBasedIndexOfLastWaypointToBePassed +" were to be reported");
}
}
}
@@ -260,6 +275,23 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest {
}
protected void calculateMarkPassingsForPartialTrack(Competitor c, final TimePoint upToTimePoint,
CandidateFinder finder, CandidateChooser chooser) {
List<GPSFix> fixes = new ArrayList<GPSFix>();
try {
getTrackedRace().getTrack(c).lockForRead();
for (GPSFixMoving fix : getTrackedRace().getTrack(c).getFixes()) {
if (fix.getTimePoint().before(upToTimePoint)) {
fixes.add(fix);
}
}
} finally {
getTrackedRace().getTrack(c).unlockAfterRead();
}
Util.Pair<Iterable<Candidate>, Iterable<Candidate>> f = finder.getCandidateDeltas(c, fixes);
chooser.calculateMarkPassDeltas(c, f.getA(), f.getB());
}
@AfterClass
public static void createXML() throws IOException {
double accuracy = correct / totalPasses;
@@ -17,6 +17,7 @@ import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Sideline;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.BoatClassImpl;
import com.sap.sailing.domain.base.impl.BoatImpl;
import com.sap.sailing.domain.base.impl.CompetitorImpl;
import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl;
import com.sap.sailing.domain.base.impl.CourseAreaImpl;
@@ -30,6 +31,7 @@ import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
import com.sap.sailing.domain.leaderboard.impl.HighPoint;
import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore;
@@ -43,9 +45,10 @@ import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class AbstractMockedRaceMarkPassingTest {
protected Competitor ron = new CompetitorImpl("Ron", "Ron", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
protected Competitor tom = new CompetitorImpl("Tom", "Tom", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
protected Competitor ben = new CompetitorImpl("Ben", "Ben", null, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
final BoatClassImpl boatClass = new BoatClassImpl("boat", true, "boat", new MeterDistance(10), new MeterDistance(5), null);
protected Competitor ron = new CompetitorImpl("Ron", "Ron", null, null, null, null, new BoatImpl("boat", boatClass, "boot"), /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
protected Competitor tom = new CompetitorImpl("Tom", "Tom", null, null, null, null, new BoatImpl("boat", boatClass, "boot"), /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
protected Competitor ben = new CompetitorImpl("Ben", "Ben", null, null, null, null, new BoatImpl("boat", boatClass, "boot"), /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null);
protected Mark m = new MarkImpl("Mark");
protected Mark gate1 = new MarkImpl("Gate1");
@@ -64,7 +67,6 @@ public class AbstractMockedRaceMarkPassingTest {
Waypoint w4 = new WaypointImpl(m, PassingInstruction.Starboard);
Waypoint w5 = new WaypointImpl(cp, PassingInstruction.Line);
waypoints = Arrays.asList(w1, w2, w3, w4, w5);
final BoatClassImpl boatClass = new BoatClassImpl("boat", true);
Regatta r = new RegattaImpl(RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass,
/*startDate*/ null, /*endDate*/ null, Arrays.asList(new SeriesImpl("Series", true, Arrays.asList(new FleetImpl("fleet")),
new ArrayList<String>(), null)), true, new HighPoint(), "ID", new CourseAreaImpl("area", new UUID(5, 5)), OneDesignRankingMetric::new);
@@ -153,7 +153,6 @@ public class FlorianopolisMarkPassingTest extends AbstractMarkPassingTest {
*
* @Test public void testRace26() throws IOException, InterruptedException, URISyntaxException { testRace(26); }
*/
@Override
protected String getExpectedEventName() {
return "ESS Florianopolis 2013";
@@ -7,17 +7,33 @@ import java.text.ParseException;
import org.junit.Test;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartProcedureChangedEventImpl;
import com.sap.sailing.domain.common.racelog.RacingProcedureType;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
public class Kiel2013505Test extends AbstractMarkPassingTest {
public Kiel2013505Test() throws MalformedURLException, URISyntaxException {
public Kiel2013505Test() throws MalformedURLException, URISyntaxException {
super();
}
//Takes longer than 2 minutes, so the rule in AbstractTracTracLiveTest needs to be deactivated first.
/**
* Marks the race as using a gate start
*/
@Override
protected void setUp(String raceNumber) throws IOException, InterruptedException, URISyntaxException,
ParseException, SubscriberInitializationException, CreateModelException {
super.setUp(raceNumber);
final RaceLog raceLog = new RaceLogImpl("505 Race Log");
getTrackedRace().attachRaceLog(raceLog);
raceLog.add(new RaceLogStartProcedureChangedEventImpl(MillisecondsTimePoint.now(), new LogEventAuthorImpl("Me", 0), 0, RacingProcedureType.GateStart));
}
@Test
public void testRace5() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException {
testRace("5");
@@ -45,7 +45,7 @@ public class MarkPassingCalculatorPerformanceTest extends AbstractMockedRaceMark
@AfterClass
public static void createXMLFile() throws IOException {
MeasurementXMLFile performanceReport = new MeasurementXMLFile(MarkPassingCalculatorPerformanceTest.class);
final MeasurementXMLFile performanceReport = new MeasurementXMLFile(MarkPassingCalculatorPerformanceTest.class);
for (String key : result.keySet()) {
MeasurementCase performanceReportCase = performanceReport.addCase(key);
performanceReportCase.addMeasurement(new Measurement(key, result.get(key)));
@@ -72,7 +72,7 @@ public class MarkPassingCalculatorPerformanceTest extends AbstractMockedRaceMark
@Test
public void testChooser() {
long time = timeToAddCandidatesToChooser(500, 1, 25);
final long time = timeToAddCandidatesToChooser(500, 1, 25);
System.out.println(time);
result.put("ChooserPerformance", time);
assertTrue(time < 5000);
@@ -96,16 +96,16 @@ public class MarkPassingCalculatorPerformanceTest extends AbstractMockedRaceMark
}
private CandidateImpl randomCan() {
int id = rnd.nextInt(3);
final int id = rnd.nextInt(3);
return new CandidateImpl(id + 1, new MillisecondsTimePoint((long) (rnd.nextDouble() * 200000)),
0.5 + 0.5 * rnd.nextDouble(), Util.get(race.getRace().getCourse().getWaypoints(), id));
}
private GPSFixMoving rndFix() {
DegreePosition position = new DegreePosition(0.001 - rnd.nextDouble() * 0.001,
final DegreePosition position = new DegreePosition(0.001 - rnd.nextDouble() * 0.001,
0.0002 - rnd.nextDouble() * 0.0004);
TimePoint p = new MillisecondsTimePoint((long) (rnd.nextDouble() * 200000));
SpeedWithBearing speed = new KnotSpeedWithBearingImpl(rnd.nextInt(11), new DegreeBearingImpl(rnd.nextInt(360)));
final TimePoint p = new MillisecondsTimePoint((long) (rnd.nextDouble() * 200000));
final SpeedWithBearing speed = new KnotSpeedWithBearingImpl(rnd.nextInt(11), new DegreeBearingImpl(rnd.nextInt(360)));
return new GPSFixMovingImpl(position, p, speed);
}
}
@@ -9,6 +9,7 @@ import java.util.List;
import java.util.NavigableSet;
import org.junit.Test;
import org.junit.Ignore;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.Waypoint;
@@ -34,6 +35,7 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
* Tests tricky situation that can fail easily. Created with help of http://itouchmap.com/latlong.html
*
*/
@Ignore
public class MarkPassingWhiteBoxTest extends AbstractMockedRaceMarkPassingTest {
@Test
@@ -78,7 +80,6 @@ public class MarkPassingWhiteBoxTest extends AbstractMockedRaceMarkPassingTest {
chooser.suppressMarkPassings(ron, 1);
NavigableSet<MarkPassing> markPassingsAfterSupressing = race.getMarkPassings(ron);
assertEquals(0, markPassingsAfterSupressing.size());
}
@Test
@@ -1,12 +1,31 @@
package com.sap.sailing.domain.test.markpassing;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.util.Collections;
import java.util.Date;
import org.junit.Test;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.markpassingcalculation.CandidateChooser;
import com.sap.sailing.domain.markpassingcalculation.CandidateFinder;
import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator;
import com.sap.sailing.domain.markpassingcalculation.impl.CandidateChooserImpl;
import com.sap.sailing.domain.markpassingcalculation.impl.CandidateFinderImpl;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sse.InvalidDateException;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.util.DateParser;
import com.tractrac.model.lib.api.event.CreateModelException;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
@@ -18,8 +37,82 @@ public class StarIDM2013MarkPassingTest extends AbstractMarkPassingTest {
@Test
public void testRace5() throws IOException, InterruptedException, URISyntaxException, ParseException,
SubscriberInitializationException, CreateModelException {
testRace("5");
SubscriberInitializationException, CreateModelException, InvalidDateException {
// Race 5 is really broken on TracTrac's side. I've tried re-calculating the mark passing to no avail.
// Now it's even worse, and only the windward mark passings and the start mark passings are recognized
// half-way reasonably, but no mark passings seem to have been detected for subsequent waypoints.
// To still use this race for testing purposes, let's do spot checks based on manually identified mark
// passings, based on https://www.sapsailing.com/gwt/RaceBoard.html?eventId=a43b8f96-1e77-448a-9b8a-0f586f1bb18f&leaderboardName=Star%20IDM%202013&leaderboardGroupName=Star%20IDM%202013&raceName=Race%205&viewShowMapControls=true&viewShowNavigationPanel=true&regattaName=IDM%20Starboot%202013%20%28Star%29
setUp("5");
synchronized (getSemaphor()) {
while (!isStoredDataLoaded()) {
getSemaphor().wait();
}
}
testWholeRace5();
testOnlyStartOfRace5();
}
private void testWholeRace5() throws InvalidDateException {
new MarkPassingCalculator(getTrackedRace(), false); // do our calculation, injecting into TrackedRace
final Competitor GER7897 = getCompetitorByName("Tusch, Frank,Winkelmann, Sven");
assertMarkPassing(GER7897, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:10+0200"));
assertMarkPassing(GER7897, Util.get(getWaypoints(), 1), DateParser.parse("2013-05-04T14:57:12+0200"));
assertMarkPassing(GER7897, Util.get(getWaypoints(), 3), DateParser.parse("2013-05-04T15:06:32+0200"));
assertMarkPassing(GER7897, Util.get(getWaypoints(), 4), DateParser.parse("2013-05-04T15:15:01+0200"));
assertMarkPassing(GER7897, Util.get(getWaypoints(), 6), DateParser.parse("2013-05-04T15:21:17+0200"));
assertMarkPassing(GER7897, Util.get(getWaypoints(), 7), DateParser.parse("2013-05-04T15:29:18+0200"));
assertMarkPassing(GER7897, Util.get(getWaypoints(), 9), DateParser.parse("2013-05-04T15:36:36+0200"));
assertMarkPassing(GER7897, Util.get(getWaypoints(), 10), DateParser.parse("2013-05-04T15:38:09+0200"));
final Competitor GER7616 = getCompetitorByName("Seefelder, Jürgen,Seefelder, N"); // starts late
assertMarkPassing(GER7616, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:57+0200"));
assertMarkPassing(GER7616, Util.get(getWaypoints(), 1), DateParser.parse("2013-05-04T14:59:05+0200"));
assertMarkPassing(GER7616, Util.get(getWaypoints(), 3), DateParser.parse("2013-05-04T15:07:32+0200"));
assertMarkPassing(GER7616, Util.get(getWaypoints(), 4), DateParser.parse("2013-05-04T15:16:40+0200"));
assertMarkPassing(GER7616, Util.get(getWaypoints(), 6), DateParser.parse("2013-05-04T15:23:26+0200"));
assertMarkPassing(GER7616, Util.get(getWaypoints(), 7), DateParser.parse("2013-05-04T15:33:01+0200"));
assertMarkPassing(GER7616, Util.get(getWaypoints(), 9), DateParser.parse("2013-05-04T15:39:25+0200"));
assertMarkPassing(GER7616, Util.get(getWaypoints(), 10), DateParser.parse("2013-05-04T15:40:51+0200"));
final Competitor GER8340 = getCompetitorByName("Griese, Achim,Marcour, Michael"); // starts rather early
assertMarkPassing(GER8340, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:04+0200"));
assertMarkPassing(GER8340, Util.get(getWaypoints(), 1), DateParser.parse("2013-05-04T14:56:57+0200"));
assertMarkPassing(GER8340, Util.get(getWaypoints(), 3), DateParser.parse("2013-05-04T15:06:33+0200"));
assertMarkPassing(GER8340, Util.get(getWaypoints(), 4), DateParser.parse("2013-05-04T15:14:31+0200"));
assertMarkPassing(GER8340, Util.get(getWaypoints(), 6), DateParser.parse("2013-05-04T15:20:29+0200"));
assertMarkPassing(GER8340, Util.get(getWaypoints(), 7), DateParser.parse("2013-05-04T15:28:34+0200"));
assertMarkPassing(GER8340, Util.get(getWaypoints(), 9), DateParser.parse("2013-05-04T15:35:52+0200"));
assertMarkPassing(GER8340, Util.get(getWaypoints(), 10), DateParser.parse("2013-05-04T15:37:22+0200"));
}
private void testOnlyStartOfRace5() throws InvalidDateException {
// clear mark passings
for (Competitor c : getRace().getCompetitors()) {
getTrackedRace().updateMarkPassings(c, Collections.emptySet());
}
CandidateFinder finder = new CandidateFinderImpl(getTrackedRace());
CandidateChooser chooser = new CandidateChooserImpl(getTrackedRace());
final TimePoint afterStart = new MillisecondsTimePoint(DateParser.parse("2013-05-04T14:52:30+0200"));
for (Competitor c : getRace().getCompetitors()) {
calculateMarkPassingsForPartialTrack(c, afterStart, finder, chooser);
}
final Competitor GER7897 = getCompetitorByName("Tusch, Frank,Winkelmann, Sven");
assertMarkPassing(GER7897, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:10+0200"));
final Competitor GER7616 = getCompetitorByName("Seefelder, Jürgen,Seefelder, N"); // starts late
assertMarkPassing(GER7616, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:57+0200"));
final Competitor GER8340 = getCompetitorByName("Griese, Achim,Marcour, Michael"); // starts rather early
assertMarkPassing(GER8340, Util.get(getWaypoints(), 0), DateParser.parse("2013-05-04T14:50:04+0200"));
}
private void assertMarkPassing(Competitor competitor, Waypoint waypoint, Date when) {
final MarkPassing markPassing = getTrackedRace().getMarkPassing(competitor, waypoint);
if (markPassing == null) {
fail("Expected mark passing for "+competitor+" and "+waypoint+" around "+when+" but no mark passing found");
}
final Duration offset = new MillisecondsTimePoint(when).until(markPassing.getTimePoint()).abs();
assertTrue("Expected mark passing for "+competitor+" and "+waypoint+" around "+when+
" but found one at "+markPassing.getTimePoint()+" which is "+offset+" off.", offset.asSeconds() < 10);
}
@Test
@@ -100,6 +100,11 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public TimePoint getStartOfRace(boolean inferred) {
return null;
}
@Override
public Iterable<TrackedLeg> getTrackedLegs() {
return null;
@@ -111,6 +111,11 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
return startTime;
}
@Override
public TimePoint getStartOfRace(boolean inferred) {
return startTime;
}
@Override
public TimePoint getEndOfRace() {
return null;
@@ -792,6 +792,6 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
public void deregisterCompetitors(Iterable<Competitor> competitors) {
RegattaLog regattaLog = getRegattaLike().getRegattaLog();
CompetitorDeregistrator<RegattaLog, RegattaLogEvent, RegattaLogEventVisitor> deregisterer = new CompetitorDeregistrator<>(regattaLog, competitors, regattaLogEventAuthorForRegatta);
deregisterer.analyze();
deregisterer.deregister(deregisterer.analyze());
}
}
@@ -224,6 +224,6 @@ public abstract class AbstractLeaderboardImpl extends AbstractSimpleLeaderboardI
public void deregisterCompetitors(Iterable<Competitor> competitors) {
RegattaLog regattaLog = getRegattaLike().getRegattaLog();
CompetitorDeregistrator<RegattaLog, RegattaLogEvent, RegattaLogEventVisitor> deregisterer = new CompetitorDeregistrator<>(regattaLog, competitors, regattaLogEventAuthorForAbstractLeaderboard);
deregisterer.analyze();
deregisterer.deregister(deregisterer.analyze());
}
}
@@ -231,8 +231,7 @@ public class MarkPassingCalculator {
@Override
public void run() {
logger.finer("Calculating MarkPassings for " + c + " (" + Util.size(fixes) + " new fixes)");
Util.Pair<Iterable<Candidate>, Iterable<Candidate>> candidateDeltas = finder.getCandidateDeltas(c,
fixes);
Util.Pair<Iterable<Candidate>, Iterable<Candidate>> candidateDeltas = finder.getCandidateDeltas(c, fixes);
logger.finer("Received " + Util.size(candidateDeltas.getA()) + " new Candidates and will remove "
+ Util.size(candidateDeltas.getB()) + " old Candidates for " + c);
chooser.calculateMarkPassDeltas(c, candidateDeltas.getA(), candidateDeltas.getB());
@@ -25,10 +25,12 @@ import com.sap.sailing.domain.markpassingcalculation.CandidateChooser;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsDurationImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
/**
@@ -44,9 +46,28 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
*
*/
public class CandidateChooserImpl implements CandidateChooser {
// TODO what is the meaning of this constant?
private static final int MILLISECONDS_BEFORE_STARTTIME = 5000;
private final static double MINIMUM_PROBABILITY = 1 - Edge.getPenaltyForSkipping();
/**
* Distance ratios of actual distance traveled and leg length above this threshold will receive
* penalties on their probability. Ratios below 1.0 receive the ratio as the penalty.
* See {@link #getDistanceEstimationBasedProbability(Competitor, Candidate, Candidate)}.
*/
final double MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH = 2.0;
/**
* Start mark passings will be considered this much before the actual race start. The race start
* as identified by {@link TrackedRace#getStartOfRace()} is therefore {@link #EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME}
* after {@link #raceStartTime}.
*/
private static final Duration EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME = new MillisecondsDurationImpl(5000);
/**
* The duration after which a start mark passing's probability is considered only 50%. A perfect start mark
* passing happening exactly at the race start time has time-wise probability of 1.0. Another delay of this much
* lets the probability drop to 1/3, and so on.
*/
private static final Duration DELAY_AFTER_WHICH_PROBABILITY_OF_START_HALVES = Duration.ONE_MINUTE;
private final static double MINIMUM_PROBABILITY = Edge.getPenaltyForSkipping();
private static final Logger logger = Logger.getLogger(CandidateChooserImpl.class.getName());
@@ -55,14 +76,20 @@ public class CandidateChooserImpl implements CandidateChooser {
private Map<Competitor, Set<Candidate>> candidates = new HashMap<>();
private Map<Competitor, NavigableSet<Candidate>> fixedPassings = new HashMap<>();
private Map<Competitor, Integer> suppressedPassings = new HashMap<>();
/**
* Set to {@link #EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME} milliseconds before the actual race start,
* in case the actual start of race is known; {@code null} otherwise.
*/
private TimePoint raceStartTime;
private final WaypointPositionAndDistanceCache waypointPositionAndDistanceCache;
/**
* An artificial proxy candidate that comes before the start mark passing. Its time point is set to
* {@link #MILLISECONDS_BEFORE_STARTTIME} milliseconds before the race start time or <code>null</code>
* {@link #EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME} milliseconds before the race start time or <code>null</code>
* in case the race start time is not known.
*/
private final CandidateWithSettableTime start;
private final CandidateWithSettableWaypointIndex end;
private final DynamicTrackedRace race;
@@ -70,7 +97,9 @@ public class CandidateChooserImpl implements CandidateChooser {
public CandidateChooserImpl(DynamicTrackedRace race) {
this.race = race;
waypointPositionAndDistanceCache = new WaypointPositionAndDistanceCache(race, Duration.ONE_MINUTE);
raceStartTime = race.getStartOfRace() != null ? race.getStartOfRace().minus(MILLISECONDS_BEFORE_STARTTIME) : null;
final TimePoint startOfRaceWithoutInference = race.getStartOfRace(/* inferred */ false);
raceStartTime = startOfRaceWithoutInference != null ? startOfRaceWithoutInference.
minus(EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME) : null;
start = new CandidateWithSettableTime(/* Index */0, raceStartTime, /* Probability */1, /* Waypoint */null);
end = new CandidateWithSettableWaypointIndex(race.getRace().getCourse().getNumberOfWaypoints() + 1, /* TimePoint */null,
/* Probability */1, /* Waypoint */null);
@@ -78,7 +107,16 @@ public class CandidateChooserImpl implements CandidateChooser {
List<Candidate> startAndEnd = Arrays.asList(start, end);
for (Competitor c : race.getRace().getCompetitors()) {
candidates.put(c, Collections.synchronizedSet(new TreeSet<Candidate>()));
currentMarkPasses.put(c, new HashMap<Waypoint, MarkPassing>());
final HashMap<Waypoint, MarkPassing> currentMarkPassesForCompetitor = new HashMap<Waypoint, MarkPassing>();
currentMarkPasses.put(c, currentMarkPassesForCompetitor);
// in case the tracked race already has mark passings, e.g., from another mark passing calculator,
// ensure consistency of the currentMarkPasses map with the TrackedRace:
for (final Waypoint w : race.getRace().getCourse().getWaypoints()) {
final MarkPassing mp = race.getMarkPassing(c, w);
if (mp != null) {
currentMarkPassesForCompetitor.put(w, mp);
}
}
TreeSet<Candidate> fixedPasses = new TreeSet<Candidate>(new Comparator<Candidate>() {
@Override
public int compare(Candidate o1, Candidate o2) {
@@ -106,10 +144,10 @@ public class CandidateChooserImpl implements CandidateChooser {
@Override
public void calculateMarkPassDeltas(Competitor c, Iterable<Candidate> newCans, Iterable<Candidate> oldCans) {
TimePoint startOfRace = race.getStartOfRace();
final TimePoint startOfRace = race.getStartOfRace(/* inference */ false);
if (startOfRace != null) {
if (raceStartTime == null || !startOfRace.minus(MILLISECONDS_BEFORE_STARTTIME).equals(raceStartTime)) {
raceStartTime = startOfRace.minus(MILLISECONDS_BEFORE_STARTTIME);
if (raceStartTime == null || !startOfRace.minus(EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME).equals(raceStartTime)) {
raceStartTime = startOfRace.minus(EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME);
List<Candidate> startList = new ArrayList<>();
startList.add(start);
for (Competitor com : candidates.keySet()) {
@@ -223,15 +261,15 @@ public class CandidateChooserImpl implements CandidateChooser {
// calculate the probability for the start being the start given its timing and multiply
// with the estimation for the distance-based probability:
final Duration timeGapBetweenStartOfRaceAndCandidateTimePoint = early.getTimePoint()
.plus(MILLISECONDS_BEFORE_STARTTIME).until(late.getTimePoint());
// Being MILLISECONDS_BEFORE_STARTTIME off means a probability of 1/2; being twice this time
.plus(EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME).until(late.getTimePoint()).abs();
// Being DELAY_AFTER_WHICH_PROBABILITY_OF_START_HALVES off means a probability of 1/2; being twice this time
// off means 1/3, and so on
startTimingProbability = (double) MILLISECONDS_BEFORE_STARTTIME
/ (double) (MILLISECONDS_BEFORE_STARTTIME + Math
.abs(timeGapBetweenStartOfRaceAndCandidateTimePoint.asMillis()));
startTimingProbability = DELAY_AFTER_WHICH_PROBABILITY_OF_START_HALVES.divide(
DELAY_AFTER_WHICH_PROBABILITY_OF_START_HALVES.plus(
timeGapBetweenStartOfRaceAndCandidateTimePoint));
estimatedDistanceProbability = 1;
} else {
startTimingProbability = 0.1; // can't really tell how well the start time was matched when
startTimingProbability = 1; // can't really tell how well the start time was matched when
// we don't have a start candidate
estimatedDistanceProbability = late == end ? 1 : getDistanceEstimationBasedProbability(c, early, late);
}
@@ -295,40 +333,40 @@ public class CandidateChooserImpl implements CandidateChooser {
if (oneBasedIndexOfSuppressedWaypoint <= endOfFixedInterval.getOneBasedIndexOfWaypoint()) {
endOfFixedInterval = end;
}
NavigableSet<Util.Pair<Edge, Double>> currentEdgesCheapestFirst = new TreeSet<>(new Comparator<Util.Pair<Edge, Double>>() {
NavigableSet<Util.Pair<Edge, Double>> currentEdgesMoreLikelyFirst = new TreeSet<>(new Comparator<Util.Pair<Edge, Double>>() {
@Override
public int compare(Util.Pair<Edge, Double> o1, Util.Pair<Edge, Double> o2) {
int result = o1.getB().compareTo(o2.getB());
int result = o2.getB().compareTo(o1.getB());
return result != 0 ? result : o1.getA().compareTo(o2.getA());
}
});
Map<Candidate, Util.Pair<Candidate, Double>> candidateWithParentAndSmallestTotalCost = new HashMap<>();
Map<Candidate, Util.Pair<Candidate, Double>> candidateWithParentAndHighestTotalProbability = new HashMap<>();
int indexOfEndOfFixedInterval = endOfFixedInterval.getOneBasedIndexOfWaypoint();
boolean endFound = false;
currentEdgesCheapestFirst.add(new Util.Pair<Edge, Double>(new Edge(new CandidateImpl(-1, null, /* estimated distance probability */ 1, null), startOfFixedInterval,
0, race.getRace().getCourse().getNumberOfWaypoints()), 0.0));
currentEdgesMoreLikelyFirst.add(new Util.Pair<Edge, Double>(new Edge(new CandidateImpl(-1, null, /* estimated distance probability */ 1, null), startOfFixedInterval,
1, race.getRace().getCourse().getNumberOfWaypoints()), 1.0));
while (!endFound) {
Util.Pair<Edge, Double> cheapestEdgeWithCost = currentEdgesCheapestFirst.pollFirst();
if (cheapestEdgeWithCost == null) {
Util.Pair<Edge, Double> mostLikelyEdgeWithProbability = currentEdgesMoreLikelyFirst.pollFirst();
if (mostLikelyEdgeWithProbability == null) {
endFound = true;
} else {
Edge currentCheapestEdge = cheapestEdgeWithCost.getA();
Double currentCheapestCost = cheapestEdgeWithCost.getB();
Edge currentMostLikelyEdge = mostLikelyEdgeWithProbability.getA();
Double currentHighestProbability = mostLikelyEdgeWithProbability.getB();
// If the shortest path to this candidate is already known the new edge is not added.
if (!candidateWithParentAndSmallestTotalCost.containsKey(currentCheapestEdge.getEnd())) {
// The cheapest edge taking us to currentCheapestEdge.getEnd() is found. Remember it.
candidateWithParentAndSmallestTotalCost.put(currentCheapestEdge.getEnd(), new Util.Pair<Candidate, Double>(
currentCheapestEdge.getStart(), currentCheapestCost));
if (!candidateWithParentAndHighestTotalProbability.containsKey(currentMostLikelyEdge.getEnd())) {
// The most likely edge taking us to currentMostLikelyEdge.getEnd() is found. Remember it.
candidateWithParentAndHighestTotalProbability.put(currentMostLikelyEdge.getEnd(), new Util.Pair<Candidate, Double>(
currentMostLikelyEdge.getStart(), currentHighestProbability));
if (logger.isLoggable(Level.FINEST)) {
logger.finest("Added "+ currentCheapestEdge + "as cheapest edge for " + c);
logger.finest("Added "+ currentMostLikelyEdge + " as most likely edge for " + c);
}
endFound = currentCheapestEdge.getEnd() == endOfFixedInterval;
endFound = currentMostLikelyEdge.getEnd() == endOfFixedInterval;
if (!endFound) {
// the end of the segment was not yet found; add edges leading away from
// currentCheapestEdge.getEnd(), summing up their cost with the cost required
// to reach currentCheapestEdge.getEnd()
Set<Edge> edgesForNewCandidate = allCompetitorEdges.get(currentCheapestEdge.getEnd());
// currentMostLikelyEdge.getEnd(), multiplying up their probabilities with the probability
// of reaching currentMostLikelyEdge.getEnd()
Set<Edge> edgesForNewCandidate = allCompetitorEdges.get(currentMostLikelyEdge.getEnd());
if (edgesForNewCandidate != null) {
for (Edge e : edgesForNewCandidate) {
int oneBasedIndexOfEndOfEdge = e.getEnd().getOneBasedIndexOfWaypoint();
@@ -336,7 +374,7 @@ public class CandidateChooserImpl implements CandidateChooser {
// the next fixed mark passing
if (oneBasedIndexOfEndOfEdge <= indexOfEndOfFixedInterval
&& (oneBasedIndexOfEndOfEdge < oneBasedIndexOfSuppressedWaypoint || e.getEnd() == end)) {
currentEdgesCheapestFirst.add(new Util.Pair<Edge, Double>(e, currentCheapestCost + e.getCost()));
currentEdgesMoreLikelyFirst.add(new Util.Pair<Edge, Double>(e, currentHighestProbability * e.getProbability()));
}
}
}
@@ -344,10 +382,10 @@ public class CandidateChooserImpl implements CandidateChooser {
}
}
}
Candidate marker = candidateWithParentAndSmallestTotalCost.get(endOfFixedInterval).getA();
Candidate marker = candidateWithParentAndHighestTotalProbability.get(endOfFixedInterval).getA();
while (marker.getOneBasedIndexOfWaypoint() > 0) {
mostLikelyCandidates.add(marker);
marker = candidateWithParentAndSmallestTotalCost.get(marker).getA();
marker = candidateWithParentAndHighestTotalProbability.get(marker).getA();
}
startOfFixedInterval = endOfFixedInterval;
endOfFixedInterval = fixedPasses.higher(endOfFixedInterval);
@@ -399,8 +437,8 @@ public class CandidateChooserImpl implements CandidateChooser {
first = c1.getWaypoint();
}
final Waypoint second = c2.getWaypoint();
Distance totalGreatCircleDistance = getApproximateTotalGreatCircleDistanceBetweenWaypoints(first, second, middleOfc1Andc2);
Distance actualDistanceTraveled = race.getTrack(c).getDistanceTraveled(c1.getTimePoint(), c2.getTimePoint());
final Distance totalGreatCircleDistance = getApproximateTotalGreatCircleDistanceBetweenWaypoints(first, second, middleOfc1Andc2);
final Distance actualDistanceTraveled = race.getTrack(c).getDistanceTraveled(c1.getTimePoint(), c2.getTimePoint());
result = getProbabilityOfActualDistanceGivenGreatCircleDistance(totalGreatCircleDistance, actualDistanceTraveled);
return result;
}
@@ -418,23 +456,19 @@ public class CandidateChooserImpl implements CandidateChooser {
* @return a number between 0 and 1 with 1 representing a "fair chance" that the actual distance sailed could have
* been sailed for the given great circle distance; 1 is returned for actual distances being in the range of
* 1..2 times the great circle distance. Actual distances outside this interval reduce probability linearly
* for smaller distances (gradient 3.5) and varies with the square root for distances that exceed twice the
* for smaller distances (gradient 0.5) and varies with the one over the ratio for distances that exceed twice the
* great circle distance.
*/
private double getProbabilityOfActualDistanceGivenGreatCircleDistance(Distance totalGreatCircleDistance,
Distance actualDistanceTraveled) {
final double result;
double differenceInMeters = actualDistanceTraveled.getMeters() - totalGreatCircleDistance.getMeters();
double ratio = differenceInMeters / totalGreatCircleDistance.getMeters();
final double ratio = actualDistanceTraveled.getMeters() / totalGreatCircleDistance.getMeters();
// A smaller distance than great circle from mark to mark is very unlikely, somewhere between the distance
// estimated and double that is likely and anything greater than that gradually becomes unlikely
if (ratio < 0) {
// TODO shouldn't these factors be constants in the class header for easy fine-tuning?
result = 3.5 * ratio + 1;
} else if (ratio > 1) {
result = Math.sqrt((-ratio + 3) / 2);
if (ratio <= MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH) {
result = Math.min(1.0, ratio); // maximum probability is 1; use for the interval between 1 and MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH
} else {
result = 1;
result = 1./(ratio-1.); // start at probability 1 for ratio==2
}
return result;
}
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.markpassingcalculation.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
@@ -35,6 +36,7 @@ import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
/**
* The standard implemantation of {@link CandidateFinder}. There are two kinds of {@link Candidate}s. First of all,
@@ -49,22 +51,47 @@ import com.sap.sse.common.Util;
public class CandidateFinderImpl implements CandidateFinder {
// The higher this is, the closer the fixes have to be to waypoint to become a Candidate
private final int strictnessOfDistanceBasedProbability = 8;
private final int STRICTNESS_OF_DISTANCE_BASED_PROBABILITY = 7;
// All of the penalties are multiplied onto the probability of a Candidate. A value of 0 excludes Candidates that do
// not fit, a value of 1 imposes no penalty on each criteria
private final double penaltyForWrongSide = 0.7;
private final double penaltyForWrongDirection = 0.7;
private final double penaltyForDistanceCandidates = 0.7;
private static final double PENALTY_FOR_WRONG_SIDE = 0.8;
/**
* The penalty factor multiplied with a candidate's probability in case the competitor passes the line of an XTE
* candidate with passing instructions other than {@link PassingInstruction#Line} in the wrong direction, e.g.,
* passing a single mark that is to be passed to port in the wrong direction.
*/
private static final double PENALTY_FOR_WRONG_DIRECTION = 0.7;
/**
* The penalty factor multiplied with a candidate's probability in case the competitor passes an XTE candidate for a
* {@link PassingInstruction#Line line} in the wrong direction, e.g., passing a start line the wrong way.
*/
private static final double PENALTY_FOR_LINE_PASSED_IN_WRONG_DIRECTION = 0.3;
/**
* Normally, for each distance candidate there should also be a proper XTE candidate that also tells about the
* direction in which the boat crosses the line. XTE candidates are more precise and get a penalty in case the competitor
* crosses the virtual line in the wrong direction: {@link #PENALTY_FOR_WRONG_DIRECTION}. The penalty a competitor should
* get for not triggering a proper XTE candidate and only producing a candidate by getting near a mark and then
* moving away again should be penalized at least as badly as {@link #PENALTY_FOR_WRONG_DIRECTION}.
*/
private static final double PENALTY_FOR_DISTANCE_CANDIDATES = 0.9 * PENALTY_FOR_WRONG_DIRECTION;
private static final double WORST_PENALTY_FOR_OTHER_COMPETITORS_BEING_FAR_FROM_START = 0.1;
private static final double NUMBER_OF_HULL_LENGTHS_DISTANCE_FROM_START_AT_WHICH_WORST_PENALTY_APPLIES = 10;
private static final Logger logger = Logger.getLogger(CandidateFinderImpl.class.getName());
private Map<Competitor, LinkedHashMap<GPSFix, Map<Waypoint, List<Distance>>>> distanceCache = new LinkedHashMap<>();
private Map<Competitor, LinkedHashMap<GPSFix, Map<Waypoint, List<Distance>>>> xteCache = new LinkedHashMap<>();
private Map<Competitor, Map<Waypoint, Map<List<GPSFix>, Candidate>>> xteCandidates = new HashMap<>();
private Map<Competitor, Map<Waypoint, Map<GPSFix, Candidate>>> distanceCandidates = new HashMap<>();
private final DynamicTrackedRace race;
private final double penaltyForSkipping = 1 - Edge.getPenaltyForSkipping();
private final double penaltyForSkipping = Edge.getPenaltyForSkipping();
private final Map<Waypoint, PassingInstruction> passingInstructions = new LinkedHashMap<>();
private final Comparator<GPSFix> comp = new Comparator<GPSFix>() {
@Override
@@ -122,8 +149,7 @@ public class CandidateFinderImpl implements CandidateFinder {
TimePoint end = null;
for (Entry<Mark, List<GPSFix>> fixes : markFixes.entrySet()) {
for (GPSFix fix : fixes.getValue()) {
TimeRange timePoints = race.getOrCreateTrack(fixes.getKey()).getEstimatedPositionTimePeriodAffectedBy(
fix);
TimeRange timePoints = race.getOrCreateTrack(fixes.getKey()).getEstimatedPositionTimePeriodAffectedBy(fix);
TimePoint newStart = timePoints.from();
TimePoint newEnd = timePoints.to();
start = start == null || start.after(newStart) ? newStart : start;
@@ -131,24 +157,28 @@ 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);
distanceCache.get(c).remove(comFix);
xteCache.get(c).remove(comFix);
comFix = track.getFirstFixAfter(comFix.getTimePoint());
}
} else {
while (comFix != null) {
comFixes.add(comFix);
competitorFixes.add(comFix);
distanceCache.get(c).remove(comFix);
xteCache.get(c).remove(comFix);
comFix = track.getFirstFixAfter(comFix.getTimePoint());
}
}
}
if (!comFixes.isEmpty()) {
affectedFixes.put(c, comFixes);
if (!competitorFixes.isEmpty()) {
affectedFixes.put(c, competitorFixes);
}
}
return affectedFixes;
@@ -156,7 +186,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 +322,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 +399,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 +427,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 +458,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 +467,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 +662,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 +909,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 +949,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 +999,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 +1033,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 +1064,7 @@ public class CandidateFinderImpl implements CandidateFinder {
Position portLinePosition = race.getOrCreateTrack(posLine.getA()).getEstimatedPosition(t, false);
Position starboardLinePosition = race.getOrCreateTrack(posLine.getB()).getEstimatedPosition(t, false);
distances.add((portLinePosition != null && starboardLinePosition != null) ? p.getDistanceToLine(
portLinePosition, starboardLinePosition) : null);
portLinePosition, starboardLinePosition).abs() : null);
}
break;
case Offset:
@@ -38,7 +38,8 @@ public class CandidateImpl implements Candidate {
}
public String toString() {
return "Candidate for waypoint " + getOneBasedIndexOfWaypoint() + " with cost " + getProbability() + " and Timepoint " + p;
return "Candidate for waypoint " + getOneBasedIndexOfWaypoint() + " with probability " +
getProbability() + " and timepoint " + getTimePoint();
}
@Override
@@ -0,0 +1,22 @@
package com.sap.sailing.domain.markpassingcalculation.impl;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sse.common.TimePoint;
public abstract class CandidateWithSide extends CandidateImpl {
private final double onCorrectSideOfWaypoint;
private final Double startProbabilityBasedOnOtherCompetitors;
public CandidateWithSide(int oneBasedIndexOfWaypoint, TimePoint p, double probability, Double startProbabilityBasedOnOtherCompetitors,
Waypoint w, double onCorrectSideOfWaypoint) {
super(oneBasedIndexOfWaypoint, p, probability, w);
this.startProbabilityBasedOnOtherCompetitors = startProbabilityBasedOnOtherCompetitors;
this.onCorrectSideOfWaypoint = onCorrectSideOfWaypoint;
}
@Override
public String toString() {
return ""+getClass().getSimpleName()+" "+super.toString()+", onCorrectSideOfWaypoint: "+onCorrectSideOfWaypoint+
(startProbabilityBasedOnOtherCompetitors==null?"":", startProbabilityBasedOnOtherCompetitors="+startProbabilityBasedOnOtherCompetitors);
}
}
@@ -0,0 +1,20 @@
package com.sap.sailing.domain.markpassingcalculation.impl;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.Distance;
import com.sap.sse.common.TimePoint;
public class DistanceCandidateImpl extends CandidateWithSide {
private final Distance distance;
public DistanceCandidateImpl(int oneBasedIndexOfWaypoint, TimePoint p, double probability, Double startProbabilityBasedOnOtherCompetitors,
Waypoint w, double onCorrectSideOfWaypoint, Distance distance) {
super(oneBasedIndexOfWaypoint, p, probability, startProbabilityBasedOnOtherCompetitors, w, onCorrectSideOfWaypoint);
this.distance = distance;
}
@Override
public String toString() {
return super.toString()+", distance: "+distance;
}
}
@@ -4,7 +4,7 @@ import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.markpassingcalculation.Candidate;
/**
* Represent the passage between two {@link Candidate}s, <code>start</code> and <code>end</code>. {@link #getCost()}
* Represent the passage between two {@link Candidate}s, <code>start</code> and <code>end</code>. {@link #getProbability()}
* returns the probability of this passage being correct, consisting of the probability of each Candidate, the
* probability of the passage between them (an estimate whether the distance sailed between the candidates matches the
* distance between their {@link Waypoint}s), and a penalty for skipping waypoints, which might happen if a tracker
@@ -13,14 +13,30 @@ import com.sap.sailing.domain.markpassingcalculation.Candidate;
* @author Nicolas Klose
*
*/
public class Edge implements Comparable<Edge> {
private final Candidate start;
private final Candidate end;
// TODO what is the meaning of this constant?
private final static double penaltyForSkipped = 0.7;
// TODO what is the meaning of this constant?
private final static double penaltyForSkippedToEnd = 0.6;
/**
* The penalty for an edge's probability in case the edge skips one or more waypoints and does not
* end at the end proxy node. Skipping a waypoint that is not the last means that either the waypoint
* was passed in a very strange, unrecognized way or we got a tracker outage. This is both pretty
* unlikely and is penalized by this factor being multiplied to the edge's general probability.<p>
*
* This factor is raised to the n-th power for n waypoints skipped.
*/
private final static double PENALTY_FOR_SKIPPED = 0.1;
/**
* Similar to {@link #PENALTY_FOR_SKIPPED}, but applied to edges that skip to the end proxy node. Such skips
* are regular business while the race is still going on, and paths that are otherwise likely but don't lead
* up to the end shall be possible and therefore must not be penalized harshly, receiving less of a penalty
* (greater factor).<p>
*
* As with {@link #PENALTY_FOR_SKIPPED}, this factor is raised to the n-th power for n waypoints skipped.
*/
private final static double PENALTY_FOR_SKIPPED_TO_END = 0.5;
private final double estimatedDistanceAndStartTimingProbability;
private final int numberOfWaypoints;
@@ -32,20 +48,22 @@ public class Edge implements Comparable<Edge> {
}
public static double getPenaltyForSkipping() {
return penaltyForSkipped;
return PENALTY_FOR_SKIPPED;
}
/**
* The cost for skipping a waypoint is 2*{@link #penaltyForSkipped} but is reduced to {@link #penaltyForSkippedToEnd} when
* skipping to the end. The reason for preferring skips to the end proxy node is that in live situations where the course
* hasn't been completed yet it is required to skip to the end. Additional cost comes from the inverse probability of
* the product of the start node, end node and distance-based probabilities. If these probabilities multiply to zero,
* an additional cost of 1.0 is added to any skip penalty.
* The probability-reducing factor for skipping a waypoint is {@link #PENALTY_FOR_SKIPPED} but is reduced to
* {@link #PENALTY_FOR_SKIPPED_TO_END} when skipping to the end. The reason for preferring skips to the end proxy node
* is that in live situations where the course hasn't been completed yet it is required to skip to the end. The main
* probability comes from the probability of the product of the start node, end node and distance-based
* probabilities.
*/
public Double getCost() {
double penalty = end.getOneBasedIndexOfWaypoint() == numberOfWaypoints + 1 ? penaltyForSkippedToEnd : penaltyForSkipped;
return 1 - (start.getProbability() * end.getProbability() * estimatedDistanceAndStartTimingProbability) + 2 * penalty
* (end.getOneBasedIndexOfWaypoint() - start.getOneBasedIndexOfWaypoint() - 1);
public Double getProbability() {
final double penalty = end.getOneBasedIndexOfWaypoint() == numberOfWaypoints + 1 ? PENALTY_FOR_SKIPPED_TO_END : PENALTY_FOR_SKIPPED;
// See bug 3241 comment #38: only use the edge's end candidate's probability; the start candidate's probability is the
// previous edge's end probability. The only probability we'll miss this way is that of the start proxy node and that is always 1.
return end.getProbability() * estimatedDistanceAndStartTimingProbability *
Math.pow(penalty, (end.getOneBasedIndexOfWaypoint() - start.getOneBasedIndexOfWaypoint() - 1));
}
public Candidate getStart() {
@@ -57,11 +75,13 @@ public class Edge implements Comparable<Edge> {
}
public String toString() {
return "Edge from Waypoint " + start.getOneBasedIndexOfWaypoint() + " to " + end.getOneBasedIndexOfWaypoint() + ": " + getCost();
return "Edge from Waypoint " + start.getOneBasedIndexOfWaypoint() + " (" +
start.getTimePoint()+") to " + end.getOneBasedIndexOfWaypoint() + " (" +
end.getTimePoint()+"): " + getProbability();
}
@Override
public int compareTo(Edge o) {
return start != o.getStart() ? start.compareTo(o.getStart()) : end != o.getEnd() ? end.compareTo(o.getEnd()) : getCost().compareTo(o.getCost());
return start != o.getStart() ? start.compareTo(o.getStart()) : end != o.getEnd() ? end.compareTo(o.getEnd()) : getProbability().compareTo(o.getProbability());
}
}
@@ -0,0 +1,20 @@
package com.sap.sailing.domain.markpassingcalculation.impl;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sse.common.TimePoint;
public class XTECandidateImpl extends CandidateWithSide {
private final Double passesInTheRightDirectionProbability;
public XTECandidateImpl(int oneBasedIndexOfWaypoint, TimePoint p, double probability,
Double startProbabilityBasedOnOtherCompetitors, Waypoint w, double onCorrectSideOfWaypoint,
Double passesInTheRightDirectionProbability) {
super(oneBasedIndexOfWaypoint, p, probability, startProbabilityBasedOnOtherCompetitors, w, onCorrectSideOfWaypoint);
this.passesInTheRightDirectionProbability = passesInTheRightDirectionProbability;
}
@Override
public String toString() {
return super.toString()+", passesInTheRightDirection: "+passesInTheRightDirectionProbability;
}
}
@@ -4,7 +4,25 @@ import com.sap.sse.common.Timed;
public interface DynamicTrack<FixType extends Timed> extends Track<FixType> {
/**
* Tries to add {@code fix} to this track. If an {@link #equals(Object) equal} fix already exists
* in this track, the track remains unchanged and {@code false} will be returned.
*
* @return <code>true</code> if the element was added, <code>false</code> otherwise.
*/
boolean add(FixType fix);
/**
* Tries to add {@code fix} to this track. If an {@link #equals(Object) equal} fix already exists
* in this track and {@code replace} is {@code false}, the track remains unchanged and {@code false} will be returned.
* If {@code replace} is {@code true}, an equal (by definition of the comparator used for the fixes set) fix will
* replace the fix contained in this track.
*
* @param replace
* whether or not to replace an existing fix in the track that is equal to {@link #fix} as defined by the
* comparator used for the {@link #fixes} set. By default this is a comparator only comparing the fixes'
* time stamps. Subclasses may use different comparator implementations.
*
* @return <code>true</code> if the element was added, <code>false</code> otherwise.
*/
boolean add(FixType fix, boolean replace);
}
@@ -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
@@ -47,12 +47,17 @@ public class DynamicGPSFixMovingTrackImpl<ItemType> extends GPSFixTrackImpl<Item
*/
@Override
public void addGPSFix(GPSFixMoving gpsFix) {
add(gpsFix);
add(gpsFix, /* replace */ true);
}
@Override
public boolean add(GPSFixMoving fix) {
return super.add(new CompactGPSFixMovingImpl(fix));
return super.add(fix); // ends up calling this.add(fix, false) where conversion in CompactGPSFixMovingImpl will happen
}
@Override
public boolean add(GPSFixMoving fix, boolean replace) {
return super.add(new CompactGPSFixMovingImpl(fix), replace);
}
/**
@@ -12,12 +12,17 @@ public class DynamicGPSFixTrackImpl<ItemType> extends GPSFixTrackImpl<ItemType,
}
public void addGPSFix(GPSFix gpsFix) {
add(gpsFix);
add(gpsFix, /* replace */ true);
}
@Override
public boolean add(GPSFix fix) {
return super.add(new CompactGPSFixImpl(fix));
return super.add(fix); // ends up calling this.add(fix, false) where conversion in CompactGPSFixImpl will happen
}
@Override
public boolean add(GPSFix fix, boolean replace) {
return super.add(new CompactGPSFixImpl(fix), replace);
}
@Override
@@ -17,4 +17,9 @@ public class DynamicTrackImpl<FixType extends Timed> extends TrackImpl<FixType>
public boolean add(FixType fix) {
return super.add(fix);
}
@Override
public boolean add(FixType fix, boolean replace) {
return super.add(fix, replace);
}
}
@@ -1100,13 +1100,13 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
}
@Override
protected boolean add(FixType fix) {
protected boolean add(FixType fix, boolean replace) {
final boolean result;
final boolean firstFixInTrack;
lockForWrite();
try {
firstFixInTrack = getRawFixes().isEmpty();
result = addWithoutLocking(fix);
result = addWithoutLocking(fix, replace);
if (!validityCachingSuspended) {
invalidateValidityAndEstimatedSpeedAndDistanceCaches(fix);
}
@@ -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;
}
/**
@@ -1498,7 +1528,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
return getOrCreateTrack(mark, false);
}
private GPSFixTrack<Mark, GPSFix> getOrCreateTrack(Mark mark, boolean createIfNotExistent){
private GPSFixTrack<Mark, GPSFix> getOrCreateTrack(Mark mark, boolean createIfNotExistent) {
GPSFixTrack<Mark, GPSFix> result = markTracks.get(mark);
if (result == null) {
// try again, this time with more expensive synchronization
@@ -3248,22 +3278,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
logger.info("Finished loading competitor tracks for " + getRace().getName());
logger.info("Started loading mark tracks for " + getRace().getName());
for (Mark mark : getMarksFromRegattaLogs()) {
try {
gpsFixStore.loadMarkTrack((DynamicGPSFixTrack<Mark, GPSFix>) getOrCreateTrack(mark),
log, mark, startOfTimeWindowToLoad, endOfTimeWindowToLoad);
if (getOrCreateTrack(mark).getFirstRawFix() == null) {
logger.fine("Loading mark positions from outside of start/end of tracking interval ("+
startOfTimeWindowToLoad+".."+endOfTimeWindowToLoad+
") because no fixes were found in that interval");
// got an empty track for the mark; try again without constraining the mapping interval
// by start/end of tracking to at least attempt to get fixes at all in case there were any
// within the device mapping interval specified
gpsFixStore.loadMarkTrack((DynamicGPSFixTrack<Mark, GPSFix>) getOrCreateTrack(mark),
log, mark, /* startOfTimeWindowToLoad */ null, /* endOfTimeWindowToLoad */ null);
}
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load track for " + mark, e);
}
final DynamicGPSFixTrack<Mark, GPSFix> markTrack = (DynamicGPSFixTrack<Mark, GPSFix>) getOrCreateTrack(mark);
loadMarkTrack(log, mark, markTrack, startOfTimeWindowToLoad, endOfTimeWindowToLoad);
}
logger.info("Finished loading mark tracks for " + getRace().getName());
} finally {
@@ -3287,6 +3303,32 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
}
}
/**
* Loads the GPS fixes for a mark from the {@link #gpsFixStore} and adds them to {@code intoTrack}. The device
* mappings are expected to be found in {@code logWithDeviceMappings}. The fixes added have to be within
* {@code from} and {@code to}, inclusively, with one exception: if no fix is found within this interval at all, all
* fixes found for the mark are loaded to make sure that any position data that may give a hint for the interval
* requests is loaded.
*/
private void loadMarkTrack(final RegattaLog logWithDeviceMappings, Mark mark,
final DynamicGPSFixTrack<Mark, GPSFix> intoTrack, final TimePoint from, final TimePoint to) {
try {
gpsFixStore.loadMarkTrack(intoTrack, logWithDeviceMappings, mark, from, to);
if (intoTrack.getFirstRawFix() == null) {
logger.fine("Loading mark positions from outside of start/end of tracking interval ("+
from+".."+to+
") because no fixes were found in that interval");
// got an empty track for the mark; try again without constraining the mapping interval
// by start/end of tracking to at least attempt to get fixes at all in case there were any
// within the device mapping interval specified
gpsFixStore.loadMarkTrack(intoTrack,
logWithDeviceMappings, mark, /* startOfTimeWindowToLoad */ null, /* endOfTimeWindowToLoad */ null);
}
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load track for " + mark, e);
}
}
/**
* Tells if currently the race is loading GPS fixes from the {@link GPSFixStore}. Clients may {@link Object#wait()} on <code>this</code>
* object and will be notified whenever a change of this flag's value occurs.
@@ -108,9 +108,14 @@ public class WindTrackImpl extends TrackImpl<Wind> implements WindTrack {
}
@Override
public boolean add(Wind wind) {
public boolean add(Wind fix) {
return super.add(fix);
}
@Override
public boolean add(Wind wind, boolean replace) {
final CompactWindImpl compactWind = new CompactWindImpl(wind);
final boolean result = super.add(compactWind);
final boolean result = super.add(compactWind, replace);
notifyListenersAboutReceive(compactWind);
return result;
}
@@ -86,6 +86,11 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public TimePoint getStartOfRace(boolean inferred) {
return null;
}
@Override
public Iterable<TrackedLeg> getTrackedLegs() {
return null;
+67
View File
@@ -753,3 +753,70 @@ img.openColumn {
.Media-Select-Popup .Caption {
padding: 4px 10px;
}
.EditMarkPositionPopup table {
background-color: #fff;
border-spacing: 0;
border: 1px solid #aaa;
-webkit-box-shadow: 2px 2px 2px 0px rgba(0,0,0,0.5);
-moz-box-shadow: 2px 2px 2px 0px rgba(0,0,0,0.5);
box-shadow: 2px 2px 2px 0px rgba(0,0,0,0.5);
}
.EditMarkPositionPopup .gwt-MenuItem {
padding: 10px 15px;
}
.EditMarkPositionPopup .gwt-MenuItem-disabled {
background-color: #eee;
}
.EditMarkPositionConfirmCancelButtons .gwt-MenuItem {
color: #fff;
font-size: 15px;
font-weight: bold;
padding: 5px 5px;
background-color: #efab00;
border: 1px solid gray;
}
.EditMarkPositionConfirmCancelButtons .gwt-MenuItem:hover {
background-color: #bf8b00;
}
.EditMarkPositionConfirmCancelButtons table {
border-spacing: 10px;
border-collapse: seperate;
}
.EditMarkPositionNotification > div {
margin: 80px;
font-size: 15px;
padding: 20px;
max-width: 500px;
text-align: center;
background-color: #fff;
line-height: 130%;
}
.EditMarkPositionMarkTable {
margin: 10px;
min-width: 300px;
border-top: 1px solid #e5e5e5;
border-left: 1px solid #e5e5e5;
}
.EditMarkPositionMarkTable button {
width: 100%;
font-size: 12px;
font-weight: bold;
border: 1px solid gray;
background-color: #efab00;
padding: 7px;
color: #fff;
}
.EditMarkPositionMarkTable button:hover {
background-color: #bf8b00;
}
@@ -1,78 +1,78 @@
<?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="true"/>
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9876"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart"/>
<listEntry value="com.sap.sailing.gwt.ui.RaceBoard"/>
<listEntry value="com.sap.sailing.gwt.ui.DataMining"/>
<listEntry value="com.sap.sailing.gwt.ui.LeaderboardEditing"/>
<listEntry value="com.sap.sailing.gwt.ui.Spectator"/>
<listEntry value="com.sap.sailing.gwt.regattaoverview.RegattaOverview"/>
<listEntry value="com.sap.sailing.gwt.ui.Leaderboard"/>
<listEntry value="com.sap.sailing.gwt.ui.Simulator"/>
<listEntry value="com.sap.sailing.gwt.ui.YoutubePopup"/>
<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
<listEntry value="com.sap.sailing.gwt.ui.AdminConsole"/>
<listEntry value="com.sap.sailing.gwt.home.Home"/>
<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
</listAttribute>
<booleanAttribute key="com.google.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/Home.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.gwt.ui"/>
</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="false"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; javaProject=&quot;com.sap.sailing.gwt.ui&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.googlecode.java-diff-utils/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.json.simple/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.udpconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.mongodb/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.operationaltransformation/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.replication/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.geocoding/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.gwt.ui&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.polars.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining.annotations/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -workDir &quot;${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work&quot; -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9876 -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/EmbeddedMapAndWindChart com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/AutoPlay.html com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
<?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="true"/>
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9876"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart"/>
<listEntry value="com.sap.sailing.gwt.ui.RaceBoard"/>
<listEntry value="com.sap.sailing.gwt.ui.DataMining"/>
<listEntry value="com.sap.sailing.gwt.ui.LeaderboardEditing"/>
<listEntry value="com.sap.sailing.gwt.ui.Spectator"/>
<listEntry value="com.sap.sailing.gwt.regattaoverview.RegattaOverview"/>
<listEntry value="com.sap.sailing.gwt.ui.Leaderboard"/>
<listEntry value="com.sap.sailing.gwt.ui.Simulator"/>
<listEntry value="com.sap.sailing.gwt.ui.YoutubePopup"/>
<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
<listEntry value="com.sap.sailing.gwt.ui.AdminConsole"/>
<listEntry value="com.sap.sailing.gwt.home.Home"/>
<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
</listAttribute>
<booleanAttribute key="com.google.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/Home.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.gwt.ui"/>
</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="false"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; javaProject=&quot;com.sap.sailing.gwt.ui&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.googlecode.java-diff-utils/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.json.simple/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.udpconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.mongodb/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.operationaltransformation/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.replication/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.geocoding/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.gwt.ui&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.polars.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining.annotations/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -workDir &quot;${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work&quot; -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9876 -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/EmbeddedMapAndWindChart com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/AutoPlay.html com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xms1024m -Xmx3072m"/>
</launchConfiguration>
</launchConfiguration>
+74 -74
View File
@@ -1,75 +1,75 @@
<?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="true"/>
<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.gwt.ui.RaceBoard"/>
<listEntry value="com.sap.sailing.gwt.ui.DataMining"/>
<listEntry value="com.sap.sailing.gwt.ui.LeaderboardEditing"/>
<listEntry value="com.sap.sailing.gwt.ui.Spectator"/>
<listEntry value="com.sap.sailing.gwt.regattaoverview.RegattaOverview"/>
<listEntry value="com.sap.sailing.gwt.ui.Leaderboard"/>
<listEntry value="com.sap.sailing.gwt.ui.Simulator"/>
<listEntry value="com.sap.sailing.gwt.ui.YoutubePopup"/>
<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
<listEntry value="com.sap.sailing.gwt.ui.AdminConsole"/>
<listEntry value="com.sap.sailing.gwt.home.Home"/>
<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
</listAttribute>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/Home.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.gwt.ui"/>
</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="false"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; javaProject=&quot;com.sap.sailing.gwt.ui&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.googlecode.java-diff-utils/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.json.simple/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.udpconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.mongodb/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.operationaltransformation/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.replication/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.geocoding/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.gwt.ui&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.polars.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -incremental -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort auto -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/AutoPlay.html com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
<?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="true"/>
<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.gwt.ui.RaceBoard"/>
<listEntry value="com.sap.sailing.gwt.ui.DataMining"/>
<listEntry value="com.sap.sailing.gwt.ui.LeaderboardEditing"/>
<listEntry value="com.sap.sailing.gwt.ui.Spectator"/>
<listEntry value="com.sap.sailing.gwt.regattaoverview.RegattaOverview"/>
<listEntry value="com.sap.sailing.gwt.ui.Leaderboard"/>
<listEntry value="com.sap.sailing.gwt.ui.Simulator"/>
<listEntry value="com.sap.sailing.gwt.ui.YoutubePopup"/>
<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
<listEntry value="com.sap.sailing.gwt.ui.AdminConsole"/>
<listEntry value="com.sap.sailing.gwt.home.Home"/>
<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
</listAttribute>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/Home.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.gwt.ui"/>
</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="false"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; javaProject=&quot;com.sap.sailing.gwt.ui&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.googlecode.java-diff-utils/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.json.simple/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.server/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.expeditionconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.declination/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.udpconnector/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.mongodb/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.swisstimingadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.operationaltransformation/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.replication/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.tractracadapter.persistence/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.geocoding/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;com.sap.sailing.gwt.ui&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.security.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.polars.datamining.shared/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -incremental -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort auto -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/AutoPlay.html com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m -Dgwt-usearchives=false -Dgwt.persistentunitcache=false"/>
</launchConfiguration>
</launchConfiguration>
@@ -139,7 +139,6 @@ public interface TextMessages extends Messages {
String latestRaceResultsHeadline();
String latestRaceResultsIsNew();
String latestRaceResultsIsUpdated();
String latestRaceResultsRaceCountLabel();
String latestRaceResultsRaceCountUnit();
String latestRaceResultsRefresh();
String leaderboardQuickAccessLabel();
@@ -128,7 +128,6 @@ headerSponsoring=Sponsoring
latestRaceResultsHeadline=Latest Official Race Results
latestRaceResultsIsNew=New
latestRaceResultsIsUpdated=Updated
latestRaceResultsRaceCountLabel=Result after
latestRaceResultsRaceCountUnit=races
latestRaceResultsRefresh=refresh
leaderboardQuickAccessLabel=Leaderboard Quick Access
@@ -128,7 +128,6 @@ headerSponsoring=Sponsoren
latestRaceResultsHeadline=Letzten ofiziellen Ergebnisse
latestRaceResultsIsNew=Neu
latestRaceResultsIsUpdated=Aktualisiert
latestRaceResultsRaceCountLabel=Ergebnis nach
latestRaceResultsRaceCountUnit=Rennen
latestRaceResultsRefresh=aktualisieren
leaderboardQuickAccessLabel=Ergebnisliste Schnellzugang
@@ -128,7 +128,6 @@ headerSponsoring=Sponsoring
latestRaceResultsHeadline=Latest Official Race Results
latestRaceResultsIsNew=New
latestRaceResultsIsUpdated=Updated
latestRaceResultsRaceCountLabel=Result after
latestRaceResultsRaceCountUnit=races
latestRaceResultsRefresh=refresh
leaderboardQuickAccessLabel=Leaderboard Quick Access
@@ -159,7 +159,6 @@ headerSponsoring=Sponsoring
latestRaceResultsHeadline=Latest Official Race Results
latestRaceResultsIsNew=New
latestRaceResultsIsUpdated=Updated
latestRaceResultsRaceCountLabel=Result after
latestRaceResultsRaceCountUnit=races
latestRaceResultsRefresh=refresh
leaderboardQuickAccessLabel=Leaderboard Quick Access
@@ -922,6 +922,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:
@@ -69,6 +69,9 @@ interface AdminConsoleResources extends ClientBundle {
@Source("com/sap/sailing/gwt/ui/client/images/start_racelog_tracking.png")
ImageResource startRaceLogTracking();
@Source("com/sap/sailing/gwt/ui/client/images/stop_racelog_tracking.png")
ImageResource stopRaceLogTracking();
@Source("com/sap/sailing/gwt/ui/client/images/competitor_registrations.png")
ImageResource competitorRegistrations();
@@ -4,6 +4,7 @@ import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
@@ -23,13 +24,18 @@ public abstract class BaseQRIdentifierWidget implements IsWidget {
private final QRCodeComposite qrCodeComposite;
private final Widget baseWidget;
private final Label error;
private final Label url;
final Label url;
private RegExp urlValidator = null;
private RegExp urlPlusTldValidator = null;
StringMessages stringMessages;
public BaseQRIdentifierWidget(int qrCodeSize, StringMessages stringMessages) {
this(qrCodeSize, stringMessages, QRCodeComposite.ERROR_CORRECTION_LEVEL_H);
}
public BaseQRIdentifierWidget(int qrCodeSize, StringMessages stringMessages, int errorCorrectionLevel) {
this.stringMessages = stringMessages;
serverBox = new TextBox();
serverBox.setVisibleLength(40);
serverBox.setValue(Window.Location.getProtocol() + "//" + Window.Location.getHost());
@@ -93,6 +99,21 @@ public abstract class BaseQRIdentifierWidget implements IsWidget {
return serverUrl;
}
//from: https://stackoverflow.com/questions/4498225/gwt-java-url-validator#4499412 as java.net.URL not available in GWT
private boolean isValidUrl(String url, boolean topLevelDomainRequired) {
if (urlValidator == null || urlPlusTldValidator == null) {
urlValidator = RegExp.compile("^((ftp|http|https)://[\\w@.\\-\\_]+(:\\d{1,5})?(/[\\w#!:.?+=&%@!\\_\\-/]+)*){1}$");
urlPlusTldValidator = RegExp.compile("^((ftp|http|https)://[\\w@.\\-\\_]+\\.[a-zA-Z]{2,}(:\\d{1,5})?(/[\\w#!:.?+=&%@!\\_\\-/]+)*){1}$");
}
return (topLevelDomainRequired ? urlPlusTldValidator : urlValidator).exec(url) != null;
}
public boolean isServerUrlValid(){
String url = getServerUrlWithoutFinalSlash();
return isValidUrl(url, false);
}
protected void clear() {
qrCodeComposite.clearQRCode();
url.setText("");
@@ -38,12 +38,17 @@ public class DeviceMappingQRCodeWidget extends BaseQRIdentifierWidget {
@Override
protected String generateEncodedQRCodeContent() throws QRCodeURLCreationException {
if (!isServerUrlValid()){
super.url.setText("");
throw new QRCodeURLCreationException(stringMessages.serverURLInvalid());
}
String serverUrl = getServerUrlWithoutFinalSlash();
if (serverUrl.isEmpty()) {
throw new QRCodeURLCreationException("Server URL empty");
throw new QRCodeURLCreationException(stringMessages.serverURLEmpty());
}
if (mappedItemId == null) {
throw new QRCodeURLCreationException("No item selected for mapping");
throw new QRCodeURLCreationException(stringMessages.pleaseSelectAnItemToMapTo());
}
return urlFactory.createURL(serverUrl, mappedItemType, mappedItemId);
@@ -217,7 +217,7 @@ public class RegattaLogAddDeviceMappingDialog extends DataEntryDialogWithBootstr
}
private void loadCompetitorsAndMarks() {
sailingService.getCompetitorRegistrationsInRegattaLog(leaderboardName, itemSelectionPanel.getSetCompetitorsCallback());
sailingService.getCompetitorRegistrationsForLeaderboard(leaderboardName, itemSelectionPanel.getSetCompetitorsCallback());
sailingService.getMarksInRegattaLog(leaderboardName, itemSelectionPanel.getSetMarksCallback());
}
}
@@ -103,7 +103,7 @@ public class RegattaLogImportFixesAndAddMappingsDialog extends DataEntryDialog<C
}
void getCompetitorRegistrations(SailingServiceAsync sailingService, final ErrorReporter errorReporter) {
sailingService.getCompetitorRegistrationsInRegattaLog(leaderboardName,
sailingService.getCompetitorRegistrationsForLeaderboard(leaderboardName,
new AsyncCallback<Collection<CompetitorDTO>>() {
@Override
public void onSuccess(Collection<CompetitorDTO> result) {
@@ -40,6 +40,8 @@ import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
public class RegattaLogTrackingDeviceMappingsDialog extends DataEntryDialog<Void> {
private static final int HOURS_TO_EXPAND_FOR_OPEN_END = 2;
protected final String leaderboardName;
public static final double PERCENTAGE_OF_TIMESPAN_TO_EXTEND_OPEN_ENDS = 0.1;
@@ -165,16 +167,19 @@ public class RegattaLogTrackingDeviceMappingsDialog extends DataEntryDialog<Void
}
void updateChart() {
earliest = new Date(Long.MAX_VALUE);
latest = new Date(Long.MIN_VALUE);
earliest = null;
latest = null;
for (DeviceMappingDTO mapping : mappings) {
updateExtremes(mapping);
}
data = new Point[mappings.size()];
handleOpenEndedIntervalls();
long earliestMillis = earliest.getTime();
long latestMillis = latest.getTime();
long range = latestMillis - earliestMillis;
long extension = (long) (range * PERCENTAGE_OF_TIMESPAN_TO_EXTEND_OPEN_ENDS); // TODO bug 3426: open intervals dominate the chart too badly
long extension = (long) (range * PERCENTAGE_OF_TIMESPAN_TO_EXTEND_OPEN_ENDS);
long yMin = earliestMillis - extension;
long yMax = latestMillis + extension;
int i = 0;
@@ -192,20 +197,26 @@ public class RegattaLogTrackingDeviceMappingsDialog extends DataEntryDialog<Void
.setPoints(data));
chart.getYAxis().setExtremes(yMin, yMax);
}
private void handleOpenEndedIntervalls() {
if (earliest == null && latest == null){
//allMappingsAreOpenEnded in Both directions or no mappings present
earliest = new Date(0);
latest = new Date();
} else if (earliest == null && latest != null){
earliest = new Date(latest.getTime() - HOURS_TO_EXPAND_FOR_OPEN_END*1000*60*60);
} else if (latest == null && earliest != null){
latest = new Date(earliest.getTime() + HOURS_TO_EXPAND_FOR_OPEN_END*1000*60*60);
}
}
private void updateExtremes(DeviceMappingDTO mapping) {
if (mapping.from != null && earliest.after(mapping.from)) {
if (mapping.from != null && (earliest == null || earliest.after(mapping.from))) {
earliest = mapping.from;
}
if (mapping.to != null && latest.before(mapping.to)) {
if (mapping.to != null && (latest == null || latest.before(mapping.to))) {
latest = mapping.to;
}
if (mapping.to != null && earliest.after(mapping.to)) {
earliest = mapping.to;
}
if (mapping.from != null && latest.before(mapping.from)) {
latest = mapping.from;
}
}
private void addMapping() {
@@ -1,11 +1,14 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.Date;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CaptionPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
@@ -14,6 +17,7 @@ import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PushButton;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.ToggleButton;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.ui.client.DataEntryDialogWithBootstrap;
@@ -46,6 +50,7 @@ public class SetTrackingTimesDialog extends DataEntryDialogWithBootstrap<RaceLog
private BetterDateTimeBox startTimeBox;
private BetterDateTimeBox endTimeBox;
private ToggleButton startStopButton;
private TextBox authorNameBox;
private com.sap.sse.gwt.client.controls.IntegerBox authorPriorityBox;
@@ -85,6 +90,13 @@ public class SetTrackingTimesDialog extends DataEntryDialogWithBootstrap<RaceLog
currentEnd = result == null ? null : result.getB();
updateDateTimeLabelAndTimeBoxFromDate(currentStart, currentStartLabel, startTimeBox);
updateDateTimeLabelAndTimeBoxFromDate(currentEnd, currentEndLabel, endTimeBox);
if (currentStart == null){
setupStartStopButton(false);
} else if (currentStart != null && currentEnd == null){
setupStartStopButton(true);
} else {
setupStartStopButton(false);
}
}
});
}
@@ -92,24 +104,49 @@ public class SetTrackingTimesDialog extends DataEntryDialogWithBootstrap<RaceLog
private void updateDateTimeLabelAndTimeBoxFromDate(final TimePoint timePoint, final Label label, final BetterDateTimeBox dateTimeBox) {
if (timePoint == null) {
label.setText(stringMessages.notAvailable());
dateTimeBox.setValue(null);
} else {
label.setText(dateTimeFormat.format(timePoint == null ? null : timePoint.asDate()));
dateTimeBox.setValue(timePoint==null ? null : timePoint.asDate());
label.setText(dateTimeFormat.format(timePoint.asDate()));
dateTimeBox.setValue(timePoint.asDate());
}
}
private void setupStartStopButton(boolean startTimeSet) {
startStopButton.setDown(startTimeSet);
}
private Widget createInputPanel() {
Grid content = new Grid(4, 2);
Grid content = new Grid(4, 3);
Button startNow = new Button(stringMessages.now());
startNow.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
startTimeBox.setValue(new Date());
}
});
Button endNow = new Button(stringMessages.now());
endNow.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
endTimeBox.setValue(new Date());
}
});
startTimeBox = createDateTimeBox(null);
startTimeBox.setFormat("dd/mm/yyyy hh:ii:ss");
content.setWidget(0, 0, createLabel(stringMessages.startOfTracking()));
content.setWidget(0, 1, startTimeBox);
content.setWidget(0, 2, startNow);
endTimeBox = createDateTimeBox(null);
endTimeBox.setFormat("dd/mm/yyyy hh:ii:ss");
content.setWidget(1, 0, createLabel(stringMessages.endOfTracking()));
content.setWidget(1, 1, endTimeBox);
content.setWidget(1, 2, endNow);
authorNameBox = createTextBox("Shore");
content.setWidget(2, 0, createLabel(stringMessages.authorName()));
content.setWidget(2, 1, authorNameBox);
@@ -142,6 +179,35 @@ public class SetTrackingTimesDialog extends DataEntryDialogWithBootstrap<RaceLog
}
});
currentPanel.add(refreshButton);
Image startImage = new Image(resources.startRaceLogTracking());
Image stopImage = new Image(resources.stopRaceLogTracking());
startStopButton = new ToggleButton(startImage, stopImage, new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
RaceLogSetTrackingTimesDTO trackingTimesDTO;
if (startStopButton.isDown()){
trackingTimesDTO = generateRaceLogSetTrackingTimesDTOWith(new Date(), null);
} else {
trackingTimesDTO = generateRaceLogSetTrackingTimesDTOWith(startTimeBox.getValue(), new Date());
}
service.setTrackingTimes(trackingTimesDTO, new AsyncCallback<Void>(){
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Error while setting tracking times: " + caught.getMessage());
}
@Override
public void onSuccess(Void result) {
refreshTimes();
}
});
}
});
currentPanel.add(startStopButton);
current.add(currentPanel);
return current;
@@ -149,6 +215,10 @@ public class SetTrackingTimesDialog extends DataEntryDialogWithBootstrap<RaceLog
@Override
protected RaceLogSetTrackingTimesDTO getResult() {
return generateRaceLogSetTrackingTimesDTOWith(startTimeBox.getValue(), endTimeBox.getValue());
}
private RaceLogSetTrackingTimesDTO generateRaceLogSetTrackingTimesDTOWith(Date startTime, Date endTime) {
RaceLogSetTrackingTimesDTO dto = new RaceLogSetTrackingTimesDTO();
dto.leaderboardName = leaderboardName;
dto.raceColumnName = raceColumnName;
@@ -156,8 +226,8 @@ public class SetTrackingTimesDialog extends DataEntryDialogWithBootstrap<RaceLog
dto.authorName = authorNameBox.getValue();
dto.authorPriority = authorPriorityBox.getValue();
dto.logicalTimePoint = MillisecondsTimePoint.now();
dto.newStartOfTracking = startTimeBox.getValue() == null ? null : new MillisecondsTimePoint(startTimeBox.getValue());
dto.newEndOfTracking = endTimeBox.getValue() == null ? null : new MillisecondsTimePoint(endTimeBox.getValue());
dto.newStartOfTracking = startTime == null ? null : new MillisecondsTimePoint(startTime);
dto.newEndOfTracking = endTime == null ? null : new MillisecondsTimePoint(endTime);
dto.currentStartOfTracking = currentStart;
dto.currentEndOfTracking = currentEnd;
return dto;
@@ -46,6 +46,7 @@ import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.gwt.ui.adminconsole.RaceLogSetTrackingTimesDTO;
import com.sap.sailing.gwt.ui.client.shared.charts.MarkPositionService.MarkTracksDTO;
import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO;
import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO;
import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO;
@@ -59,6 +60,7 @@ import com.sap.sailing.gwt.ui.shared.DeviceMappingDTO;
import com.sap.sailing.gwt.ui.shared.EventBaseDTO;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTOWithSpeedWindTackAndLegType;
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
import com.sap.sailing.gwt.ui.shared.ManeuverDTO;
import com.sap.sailing.gwt.ui.shared.MarkDTO;
@@ -269,7 +271,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
String[] getCountryCodes();
Map<CompetitorDTO, List<GPSFixDTO>> getDouglasPoints(RegattaAndRaceIdentifier raceIdentifier,
Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> getDouglasPoints(RegattaAndRaceIdentifier raceIdentifier,
Map<CompetitorDTO, Date> from, Map<CompetitorDTO, Date> to, double meters) throws NoWindException;
Map<CompetitorDTO, List<ManeuverDTO>> getManeuvers(RegattaAndRaceIdentifier raceIdentifier,
@@ -520,13 +522,13 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
* pairs of zero-based waypoint numbers and times where <code>null</code> represents a suppressed mark
* passing and a valid {@link Date} objects represents a fixed mark passing.
*/
Map<Integer, Date> getCompetitorRaceLogMarkPassingData(String leaderboardName, RaceColumnDTO raceColumnDTO, FleetDTO fleet,
Map<Integer, Date> getCompetitorRaceLogMarkPassingData(String leaderboardName, String raceColumnName, String fleetName,
CompetitorDTO competitor);
void updateSuppressedMarkPassings(String leaderboardName, RaceColumnDTO raceColumnDTO, FleetDTO fleet,
void updateSuppressedMarkPassings(String leaderboardName, String raceColumnName, String fleetName,
Integer newZeroBasedIndexOfSuppressedMarkPassing, CompetitorDTO competitorDTO);
void updateFixedMarkPassing(String leaderboardName, RaceColumnDTO raceColumnDTO, FleetDTO fleet, Integer indexOfWaypoint,
void updateFixedMarkPassing(String leaderboardName, String raceColumnName, String fleetName, Integer indexOfWaypoint,
Date dateOfMarkPassing, CompetitorDTO competitorDTO);
void setCompetitorRegistrationsInRegattaLog(String leaderboardName, Set<CompetitorDTO> competitors)
@@ -619,4 +621,22 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
String fleetName) throws NotFoundException;
Collection<CompetitorDTO> getCompetitorRegistrationsForLeaderboard(String leaderboardName) throws NotFoundException;
MarkTracksDTO getMarkTracks(String leaderboardName, String raceColumnName, String fleetName);
/**
* The service may decide whether a mark fix can be removed. It may, for example, be impossible to
* cleanly remove a mark fix if a tracked race already exists and the mark fixes are already part of
* the GPS fix track which currently does not support a remove operation. However, when only the
* regatta log is the basis of the service and no tracked race exists yet, mark fixes may be removed
* by revoking the device mappings.
*/
boolean canRemoveMarkFix(String leaderboardName, String raceColumnName, String fleetName, String markIdAsString, GPSFixDTO fix);
void removeMarkFix(String leaderboardName, String raceColumnName, String fleetName, String markIdAsString, GPSFixDTO fix) throws NotRevokableException;
void addMarkFix(String leaderboardName, String raceColumnName, String fleetName, String markIdAsString, GPSFixDTO newFix);
void editMarkFix(String leaderboardName, String raceColumnName, String fleetName, String markIdAsString, GPSFixDTO oldFix, Position newPosition) throws NotRevokableException;
}
@@ -35,6 +35,7 @@ import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO;
import com.sap.sailing.domain.common.racelog.RacingProcedureType;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.gwt.ui.adminconsole.RaceLogSetTrackingTimesDTO;
import com.sap.sailing.gwt.ui.client.shared.charts.MarkPositionService.MarkTracksDTO;
import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO;
import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO;
import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO;
@@ -48,6 +49,7 @@ import com.sap.sailing.gwt.ui.shared.DeviceMappingDTO;
import com.sap.sailing.gwt.ui.shared.EventBaseDTO;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTOWithSpeedWindTackAndLegType;
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
import com.sap.sailing.gwt.ui.shared.ManeuverDTO;
import com.sap.sailing.gwt.ui.shared.MarkDTO;
@@ -309,7 +311,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan
void getCountryCodes(AsyncCallback<String[]> callback);
void getDouglasPoints(RegattaAndRaceIdentifier raceIdentifier, Map<CompetitorDTO, Date> from,
Map<CompetitorDTO, Date> to, double meters, AsyncCallback<Map<CompetitorDTO, List<GPSFixDTO>>> callback);
Map<CompetitorDTO, Date> to, double meters, AsyncCallback<Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>>> callback);
void getManeuvers(RegattaAndRaceIdentifier raceIdentifier, Map<CompetitorDTO, Date> from,
Map<CompetitorDTO, Date> to, AsyncCallback<Map<CompetitorDTO, List<ManeuverDTO>>> callback);
@@ -682,15 +684,15 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan
void getCompetitorMarkPassings(RegattaAndRaceIdentifier race, CompetitorDTO competitorDTO,
AsyncCallback<Map<Integer, Date>> callback);
void getCompetitorRaceLogMarkPassingData(String leaderboardName, RaceColumnDTO raceColumnDTO, FleetDTO fleet,
void getCompetitorRaceLogMarkPassingData(String leaderboardName, String raceColumnName, String fleetName,
CompetitorDTO competitor, AsyncCallback<Map<Integer, Date>> callback);
void updateSuppressedMarkPassings(String leaderboardName, RaceColumnDTO raceColumnDTO, FleetDTO fleet,
void updateSuppressedMarkPassings(String leaderboardName, String raceColumnName, String fleetName,
Integer newZeroBasedIndexOfSuppressedMarkPassing, CompetitorDTO competitorDTO, AsyncCallback<Void> callback);
void createRegattaStructure(Iterable<RegattaDTO> regattaNames, EventDTO newEvent, AsyncCallback<Void> asyncCallback);
void updateFixedMarkPassing(String leaderboardName, RaceColumnDTO raceColumnDTO, FleetDTO fleet,
void updateFixedMarkPassing(String leaderboardName, String raceColumnName, String fleetName,
Integer indexOfWaypoint, Date dateOfMarkPassing, CompetitorDTO competitorDTO, AsyncCallback<Void> callback);
void getActiveFileStorageServiceName(AsyncCallback<String> callback);
@@ -763,4 +765,19 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan
void getCompetitorRegistrationsForLeaderboard(String leaderboardName,
AsyncCallback<Collection<CompetitorDTO>> callback);
void addMarkFix(String leaderboardName, String raceColumnName, String fleetName, String markIdAsString,
GPSFixDTO newFix, AsyncCallback<Void> callback);
void editMarkFix(String leaderboardName, String raceColumnName, String fleetName, String markIdAsString,
GPSFixDTO oldFix, Position newPosition, AsyncCallback<Void> callback);
void removeMarkFix(String leaderboardName, String raceColumnName, String fleetName, String markIdAsString,
GPSFixDTO fix, AsyncCallback<Void> callback);
void canRemoveMarkFix(String leaderboardName, String raceColumnName, String fleetName, String markIdAsString,
GPSFixDTO fix, AsyncCallback<Boolean> callback);
void getMarkTracks(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback<MarkTracksDTO> callback);
}
@@ -1221,6 +1221,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c
String timeBetweenRaceStartAndCompetitorStartInSecondsTooltip();
String useInternalAlgorithm();
String editMarkPassings();
String editMarkPositions();
String removeFixedPassings();
String setFixedPassing();
String setSuppressedPassing();
@@ -1502,4 +1503,24 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c
String thereIsNoIdentifierSet();
String couldNotObtainAccessTokenForUser(String message);
String raceManagerDeviceName();
String serverURLInvalid();
String serverURLEmpty();
String marks();
String addNewFix();
String pleaseSelectOtherTimepoint();
String confirmMove();
String confirmNewFix();
String selectAFixPositionBy();
String pleaseSelectAMark();
String markFixes();
String distanceFromAveragePosition();
String useATouchOptimizedUI();
String deleteFix();
String theDeletionOfThisFix();
String loadingMarkFixes();
String fixSuccessfullyAdded();
String fixPositionSuccessfullyEdited();
String fixSuccessfullyRemoved();
String dragToChangePosition();
String moveFix();
}
@@ -1189,6 +1189,7 @@ timeBetweenRaceStartAndCompetitorStartInSeconds=Start delay
timeBetweenRaceStartAndCompetitorStartInSecondsTooltip=Time between the start of the race and the time the competitor passed the start waypoint. Negative time suggests OCS.
useInternalAlgorithm=Use internal mark passing algorithm
editMarkPassings=Edit Mark Passings
editMarkPositions=Edit Mark Positions
removeFixedPassings=Remove fixed mark passing
setFixedPassing=Set time as mark passing
setSuppressedPassing=Suppress mark passing starting with current
@@ -1469,4 +1470,24 @@ defaultSailorsInfoLinkInfoText=The default link is used unless a language specif
qrSync=QR-Sync
thereIsNoIdentifierSet=There is no identifier set.
couldNotObtainAccessTokenForUser=Could not obtain access token for user {0}
raceManagerDeviceName=Device Name
raceManagerDeviceName=Device Name
serverURLInvalid=Please enter a valid Server URL
serverURLEmpty=Server URL empty
marks=Marks
addNewFix=Add new fix
pleaseSelectOtherTimepoint=Please select another timepoint. There already is a fix at the timepoint of the timeslider.
confirmMove=Confirm move
confirmNewFix=Confirm new fix
selectAFixPositionBy=Select a fix position by dragging the map. Then confirm or cancel with the buttons below.
pleaseSelectAMark=Please select a mark from the list on the left.
markFixes=Mark fixes
distanceFromAveragePosition=Distance from average position
useATouchOptimizedUI=Use a touch optimized UI to move the fix
deleteFix=Delete fix
theDeletionOfThisFix=The deletion of this fix is currently not possible.
loadingMarkFixes=Loading mark fixes...
fixSuccessfullyAdded=Fix successfully added.
fixPositionSuccessfullyEdited=Fix position successfully edited.
fixSuccessfullyRemoved=Fix successfully removed.
dragToChangePosition=Drag to change position\nRight-click to get menu to delete
moveFix=Move fix
@@ -28,7 +28,7 @@ timeDelayHelp=Leer für Wiedergabe zu beliebigen Zeitpunkten; Verzögerung des D
speedInKnots=Geschwindigkeit (sm/h)
fromDeg=aus (Grad)
noRacesYet=Es sind keine Wettfahrten vorhanden.
total=Gesamt
total=\u03A3
carry=übertr.
competitor=Team
competitors=Teams
@@ -1176,6 +1176,7 @@ timeBetweenRaceStartAndCompetitorStartInSeconds=Startverzug
timeBetweenRaceStartAndCompetitorStartInSecondsTooltip=Zeit seit dem Start der Wettfahrt, nach der der Teilnehmer die Startmarke passiert hat. Negative Werte suggerieren einen Frühstart.
useInternalAlgorithmus=Internen Algorithmus für Tonnenumrundungen verwenden
editMarkPassings=Tonnenumrundungen editieren
editMarkPositions=Tonnenpositionen editieren
removeFixedPassings=Gesetze Umrundungszeit entfernen
setFixedPassing=Zeit als Umrundungszeit setzen
setSuppressedPassing=Umrundungen unterdrücken
@@ -1460,4 +1461,24 @@ defaultSailorsInfoLinkInfoText=Der Standardwert wird verwendet, sofern kein Spra
qrSync=QR-Sync
thereIsNoIdentifierSet=Es ist noch keine Kennung gesetzt.
couldNotObtainAccessTokenForUser=Zugangscode für Benutzer {0} konnte nicht bezogen werden
raceManagerDeviceName=Gerätename
raceManagerDeviceName=Gerätename
serverURLInvalid=Bitte gültige Server-URL eingeben
serverURLEmpty=Server URL darf nicht leer sein
marks=Bahnmarken
addNewFix=Neuen Fix hinzufügen
pleaseSelectOtherTimepoint=Bitte wählen Sie einen anderen Zeitpunkt. Am gewählten Zeitpunkt ist bereits ein Fix.
confirmMove=Bestätige Umpositionierung
confirmNewFix=Bestätige neuen Fix
selectAFixPositionBy=Wählen sie eine Fixposition, indem sie die Karte verschieben. Bestätigen oder Brechen sie danach mit den Knöpfen unten ab.
pleaseSelectAMark=Bitte wählen sie einen Marker aus der Liste auf der linken Seite aus.
markFixes=Marker Fixe
distanceFromAveragePosition=Entfernung vom Durchschnitt
useATouchOptimizedUI=Benutzen sie eine auf Touchbildschirme optimierte Eingabe.
deleteFix=Fix löschen
theDeletionOfThisFix=The Löschung des Fixes is aktuell nicht möglich.
loadingMarkFixes=Lade Marker Fixe...
fixSuccessfullyAdded=Fix erfolgreich hinzugefügt.
fixPositionSuccessfullyEdited=Fixposition erfolgreich editiert.
fixSuccessfullyRemoved=Fix erfolgreich bewegt.
dragToChangePosition=Ziehen um die Position zu verändern\nRechtsklick für Menü mit Löschfunktion
moveFix=Position durch Ziehen ändern
@@ -1119,6 +1119,7 @@ timeBetweenRaceStartAndCompetitorStartInSeconds=Start delay
timeBetweenRaceStartAndCompetitorStartInSecondsTooltip=Time between the start of the race and the time the competitor passed the start waypoint. Negative time suggests OCS.
useInternalAlgorithm=Use internal mark passing algorithm
editMarkPassings=Edit Mark Passings
editMarkPositions=Edit Mark Positions
removeFixedPassings=Remove fixed mark passing
setFixedPassing=Set time as mark passing
setSuppressedPassing=Suppress mark passing starting with current
@@ -1391,4 +1392,24 @@ defaultSailorsInfoLinkInfoText=The default link is used unless a language specif
qrSync=QR-Sync
thereIsNoIdentifierSet=There is no identifier set.
couldNotObtainAccessTokenForUser=Could not obtain access token for user {0}
raceManagerDeviceName=Device Name
raceManagerDeviceName=Device Name
serverURLInvalid=Please enter a valid Server URL
serverURLEmpty=Server URL empty
marks=Marks
addNewFix=Add new fix
pleaseSelectOtherTimepoint=Please select another timepoint. There already is a fix at the timepoint of the timeslider.
confirmMove=Confirm move
confirmNewFix=Confirm new fix
selectAFixPositionBy=Select a fix position by dragging the map. Then confirm or cancel with the buttons below.
pleaseSelectAMark=Please select a mark from the list on the left.
markFixes=Mark fixes
distanceFromAveragePosition=Distance from average position
useATouchOptimizedUI=Use a touch optimized UI to move the fix
deleteFix=Delete fix
theDeletionOfThisFix=The deletion of this fix is currently not possible.
loadingMarkFixes=Loading mark fixes...
fixSuccessfullyAdded=Fix successfully added.
fixPositionSuccessfullyEdited=Fix position successfully edited.
fixSuccessfullyRemoved=Fix successfully removed.
dragToChangePosition=Drag to change position\nRight-click to get menu to delete
moveFix=Move fix
@@ -157,7 +157,7 @@ addRegatta=Add Regatta...
hideToolbar=Hide toolbar
tracTracUpdateUrl=TracTrac Server Update URI
apply=Apply
total=u03A3
total=\u03A3
explainReplicationServletPort=Servlet port number that is used to connect to register this replica and receive initial load.
numberOfManeuvers=操作
successfullyAllowedCompetitorReset=Successfully allowed reload of competitor(s) {0}.
@@ -1158,6 +1158,7 @@ explainConnectionsToMaster=Connections to a master (this server is a replica):
errorStartingReplication=Error starting replication from host {0} / exchange {1}: {2}
rotatedFromTrueNorthClickToToggleWindUp=Rotated {0} degrees from true north.nTap/click to toggle between wind-up and north-up map display
editMarkPassings=Edit Mark Passings
editMarkPositions=Edit Mark Positions
stepSizeMustBeGreaterThanNull=Step size must be greater than 0
hideRaces=隐藏比赛轮次
speedOverGroundFiveSecondsBeforeStartTooltip=起航前5秒时的实际速度
@@ -1406,4 +1407,24 @@ defaultSailorsInfoLinkInfoText=The default link is used unless a language specif
qrSync=QR-Sync
thereIsNoIdentifierSet=There is no identifier set.
couldNotObtainAccessTokenForUser=Could not obtain access token for user {0}
raceManagerDeviceName=Device Name
raceManagerDeviceName=Device Name
serverURLInvalid=Please enter a valid Server URL
serverURLEmpty=Server URL empty
marks=Marks
addNewFix=Add new fix
pleaseSelectOtherTimepoint=Please select another timepoint. There already is a fix at the timepoint of the timeslider.
confirmMove=Confirm move
confirmNewFix=Confirm new fix
selectAFixPositionBy=Select a fix position by dragging the map. Then confirm or cancel with the buttons below.
pleaseSelectAMark=Please select a mark from the list on the left.
markFixes=Mark fixes
distanceFromAveragePosition=Distance from average position
useATouchOptimizedUI=Use a touch optimized UI to move the fix
deleteFix=Delete fix
theDeletionOfThisFix=The deletion of this fix is currently not possible.
loadingMarkFixes=Loading mark fixes...
fixSuccessfullyAdded=Fix successfully added.
fixPositionSuccessfullyEdited=Fix position successfully edited.
fixSuccessfullyRemoved=Fix successfully removed.
dragToChangePosition=Drag to change position\nRight-click to get menu to delete
moveFix=Move fix
@@ -427,10 +427,7 @@ public class TimePanel<T extends TimePanelSettings> extends SimplePanel implemen
assert min != null && max != null;
boolean changed = false;
if (!max.equals(timeRangeProvider.getToTime()) || !min.equals(timeRangeProvider.getFromTime())) {
changed = true;
timeSlider.setMinAndMaxValue(new Double(min.getTime()),new Double(max.getTime()), fireEvent);
}
changed = timeSlider.setMinAndMaxValue(new Double(min.getTime()), new Double(max.getTime()), fireEvent);
if (changed) {
if (!timeRangeProvider.isZoomed()) {
timeRangeProvider.setTimeRange(min, max, this);
@@ -528,6 +525,7 @@ public class TimePanel<T extends TimePanelSettings> extends SimplePanel implemen
@Override
public void onTimeRangeChanged(Date fromTime, Date toTime) {
setMinMax(fromTime, toTime, true);
}
protected boolean isLiveModeToBeMadePossible() {
@@ -108,7 +108,7 @@ public abstract class AbstractCompetitorRaceChart<SettingsType extends ChartSett
Timer timer, TimeRangeWithZoomProvider timeRangeWithZoomProvider, final StringMessages stringMessages,
ErrorReporter errorReporter, DetailType detailType, boolean compactChart, boolean allowTimeAdjust,
String leaderboardGroupName, String leaderboardName) {
super(sailingService, timer, timeRangeWithZoomProvider, stringMessages, asyncActionsExecutor, errorReporter);
super(sailingService, selectedRaceIdentifier, timer, timeRangeWithZoomProvider, stringMessages, asyncActionsExecutor, errorReporter);
this.competitorSelectionProvider = competitorSelectionProvider;
this.compactChart = compactChart;
this.allowTimeAdjust = allowTimeAdjust;
@@ -126,7 +126,6 @@ public abstract class AbstractCompetitorRaceChart<SettingsType extends ChartSett
createChart();
setSelectedDetailType(detailType);
competitorSelectionProvider.addCompetitorSelectionChangeListener(this);
this.selectedRaceIdentifier = selectedRaceIdentifier;
clearChart();
if (selectedRaceIdentifier != null) {
timeChanged(timer.getTime(), null);
@@ -505,7 +504,7 @@ public abstract class AbstractCompetitorRaceChart<SettingsType extends ChartSett
if (oldReversedYAxis != hasReversedYAxis(selectedDetailType)) {
chart = createChart();
if (isZoomed) {
com.sap.sse.common.Util.Pair<Date, Date> zoomRange = timeRangeWithZoomProvider.getTimeZoom();
com.sap.sse.common.Util.Pair<Date, Date> zoomRange = timeRangeWithZoomProvider.getTimeZoom();
onTimeZoomChanged(zoomRange.getA(), zoomRange.getB());
} else {
resetMinMaxAndExtremesInterval(/* redraw */ true);
@@ -632,12 +631,6 @@ public abstract class AbstractCompetitorRaceChart<SettingsType extends ChartSett
}
}
private void updateTimePlotLine(Date date) {
chart.getXAxis().removePlotLine(timePlotLine);
timePlotLine.setValue(date.getTime());
chart.getXAxis().addPlotLines(timePlotLine);
}
@Override
public void onResize() {
chart.setSizeToMatchContainer();
@@ -11,6 +11,8 @@ import org.moxieapps.gwt.highcharts.client.XAxis;
import org.moxieapps.gwt.highcharts.client.events.ChartClickEvent;
import org.moxieapps.gwt.highcharts.client.events.ChartSelectionEvent;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.ui.AbsolutePanel;
@@ -45,7 +47,7 @@ public abstract class AbstractRaceChart extends AbsolutePanel implements TimeLis
protected final Timer timer;
protected final TimeRangeWithZoomProvider timeRangeWithZoomProvider;
protected RegattaAndRaceIdentifier selectedRaceIdentifier;
protected final RegattaAndRaceIdentifier selectedRaceIdentifier;
protected final DateTimeFormat dateFormat = DateTimeFormat.getFormat("HH:mm:ss");
protected final DateTimeFormat dateFormatHoursMinutes = DateTimeFormat.getFormat("HH:mm");
@@ -69,9 +71,10 @@ public abstract class AbstractRaceChart extends AbsolutePanel implements TimeLis
private final Button settingsButton;
protected AbstractRaceChart(SailingServiceAsync sailingService, Timer timer, TimeRangeWithZoomProvider timeRangeWithZoomProvider, final StringMessages stringMessages,
AsyncActionsExecutor asyncActionsExecutor, ErrorReporter errorReporter) {
protected AbstractRaceChart(SailingServiceAsync sailingService, RegattaAndRaceIdentifier selectedRaceIdentifier, Timer timer, TimeRangeWithZoomProvider timeRangeWithZoomProvider,
final StringMessages stringMessages, AsyncActionsExecutor asyncActionsExecutor, ErrorReporter errorReporter) {
this.sailingService = sailingService;
this.selectedRaceIdentifier = selectedRaceIdentifier;
this.timer = timer;
this.timeRangeWithZoomProvider = timeRangeWithZoomProvider;
this.stringMessages = stringMessages;
@@ -155,13 +158,17 @@ public abstract class AbstractRaceChart extends AbsolutePanel implements TimeLis
}
timeRangeWithZoomProvider.setTimeZoom(new Date(xAxisMin), new Date(xAxisMax), this);
} catch (Exception e) {
// in case the user clicks the "reset zoom" button chartSelectionEvent.getXAxisMinAsLong() throws in exception
timeRangeWithZoomProvider.resetTimeZoom(this);
// Trigger the redrawing... otherwise chart wouldn't reset the zoom
chart.redraw();
isZoomed = false;
// after the selection change event, another click event is sent with the mouse position on the "Reset Zoom" button; ignore that
ignoreNextClickEvent = true;
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
// in case the user clicks the "reset zoom" button chartSelectionEvent.getXAxisMinAsLong() throws in exception
isZoomed = false;
timeRangeWithZoomProvider.resetTimeZoom();
// redraw is triggered by the call to onTimeZoomReset() and therefore not necessary again here
// after the selection change event, another click event is sent with the mouse position on the "Reset Zoom" button; ignore that
ignoreNextClickEvent = true;
}
});
}
return true;
}
@@ -179,20 +186,22 @@ public abstract class AbstractRaceChart extends AbsolutePanel implements TimeLis
}
protected void changeMinMaxAndExtremesInterval(Date minTimepoint, Date maxTimepoint, boolean redraw) {
XAxis xAxis = chart.getXAxis();
if (minTimepoint != null) {
xAxis.setMin(minTimepoint.getTime());
}
if (maxTimepoint != null) {
xAxis.setMax(maxTimepoint.getTime());
}
if (minTimepoint != null && maxTimepoint != null) {
xAxis.setExtremes(minTimepoint.getTime(), maxTimepoint.getTime(), false, false);
long tickInterval = (maxTimepoint.getTime() - minTimepoint.getTime()) / TICKCOUNT;
xAxis.setTickInterval(tickInterval);
}
if (redraw) {
chart.redraw();
if (chart != null) {
XAxis xAxis = chart.getXAxis();
if (minTimepoint != null) {
xAxis.setMin(minTimepoint.getTime());
}
if (maxTimepoint != null) {
xAxis.setMax(maxTimepoint.getTime());
}
if (minTimepoint != null && maxTimepoint != null) {
xAxis.setExtremes(minTimepoint.getTime(), maxTimepoint.getTime(), /* redraw */ false, false);
long tickInterval = (maxTimepoint.getTime() - minTimepoint.getTime()) / TICKCOUNT;
xAxis.setTickInterval(tickInterval);
}
if (redraw) {
chart.redraw();
}
}
}
@@ -229,4 +238,10 @@ public abstract class AbstractRaceChart extends AbsolutePanel implements TimeLis
public void onTimeZoomReset() {
resetMinMaxAndExtremesInterval(true);
}
protected void updateTimePlotLine(Date date) {
chart.getXAxis().removePlotLine(timePlotLine);
timePlotLine.setValue(date.getTime());
chart.getXAxis().addPlotLines(timePlotLine);
}
}
@@ -29,11 +29,11 @@ import com.google.gwt.view.client.SingleSelectionModel;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.charts.RaceIdentifierToLeaderboardRaceColumnAndFleetMapper.LeaderboardNameRaceColumnNameAndFleetName;
import com.sap.sailing.gwt.ui.shared.RaceCourseDTO;
import com.sap.sailing.gwt.ui.shared.WaypointDTO;
import com.sap.sse.common.Util;
@@ -60,7 +60,7 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
private final ErrorReporter errorReporter;
private final StringMessages stringMessages;
private final CompetitorSelectionProvider competitorSelectionModel;
private LeaderboardDTO leaderboard;
private final RaceIdentifierToLeaderboardRaceColumnAndFleetMapper raceIdentifierToLeaderboardRaceColumnAndFleetMapper;
private CompetitorDTO competitor;
@@ -88,6 +88,7 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
public EditMarkPassingsPanel(final SailingServiceAsync sailingService, final RegattaAndRaceIdentifier raceIdentifier,
final StringMessages stringMessages, final CompetitorSelectionProvider competitorSelectionModel,
final ErrorReporter errorReporter, final Timer timer) {
this.raceIdentifierToLeaderboardRaceColumnAndFleetMapper = new RaceIdentifierToLeaderboardRaceColumnAndFleetMapper();
this.sailingService = sailingService;
this.raceIdentifier = raceIdentifier;
this.errorReporter = errorReporter;
@@ -164,39 +165,49 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
removeFixedMarkPassingsButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
final RaceColumnDTO column = getColumn();
sailingService.updateFixedMarkPassing(getLeaderboardName(), column, column.getFleet(raceIdentifier),
final LeaderboardNameRaceColumnNameAndFleetName leaderboardNameRaceColumnNameAndFleetName =
raceIdentifierToLeaderboardRaceColumnAndFleetMapper.getLeaderboardNameAndRaceColumnNameAndFleetName(raceIdentifier);
if (leaderboardNameRaceColumnNameAndFleetName != null) {
sailingService.updateFixedMarkPassing(leaderboardNameRaceColumnNameAndFleetName.getLeaderboardName(),
leaderboardNameRaceColumnNameAndFleetName.getRaceColumnName(),
leaderboardNameRaceColumnNameAndFleetName.getFleetName(),
waypointSelectionModel.getSelectedObject().getA(), null, competitor, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorRemovingFixedPassing(caught.getMessage()));
}
@Override
public void onSuccess(Void result) {
refillList();
}
});
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorRemovingFixedPassing(caught.getMessage()));
}
@Override
public void onSuccess(Void result) {
refillList();
}
});
}
}
});
setTimeAsMarkPassingsButton = new Button(stringMessages.setFixedPassing());
setTimeAsMarkPassingsButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
final RaceColumnDTO column = getColumn();
sailingService.updateFixedMarkPassing(getLeaderboardName(), column, column.getFleet(raceIdentifier),
waypointSelectionModel.getSelectedObject().getA(), timer.getTime(), competitor,
new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorSettingFixedPassing(caught.getMessage()));
}
@Override
public void onSuccess(Void result) {
refillList();
}
});
final LeaderboardNameRaceColumnNameAndFleetName leaderboardNameRaceColumnNameAndFleetName =
raceIdentifierToLeaderboardRaceColumnAndFleetMapper.getLeaderboardNameAndRaceColumnNameAndFleetName(raceIdentifier);
if (leaderboardNameRaceColumnNameAndFleetName != null) {
sailingService.updateFixedMarkPassing(leaderboardNameRaceColumnNameAndFleetName.getLeaderboardName(),
leaderboardNameRaceColumnNameAndFleetName.getRaceColumnName(),
leaderboardNameRaceColumnNameAndFleetName.getFleetName(),
waypointSelectionModel.getSelectedObject().getA(), timer.getTime(), competitor,
new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorSettingFixedPassing(caught.getMessage()));
}
@Override
public void onSuccess(Void result) {
refillList();
}
});
}
}
});
@@ -205,20 +216,25 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
suppressPassingsButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
final RaceColumnDTO column = getColumn();
sailingService.updateSuppressedMarkPassings(getLeaderboardName(), column,
column.getFleet(raceIdentifier), waypointSelectionModel.getSelectedObject().getA(),
competitor, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorSuppressingPassing(caught.getMessage()));
}
@Override
public void onSuccess(Void result) {
refillList();
}
});
final LeaderboardNameRaceColumnNameAndFleetName leaderboardNameRaceColumnNameAndFleetName =
raceIdentifierToLeaderboardRaceColumnAndFleetMapper.getLeaderboardNameAndRaceColumnNameAndFleetName(raceIdentifier);
if (leaderboardNameRaceColumnNameAndFleetName != null) {
sailingService.updateSuppressedMarkPassings(leaderboardNameRaceColumnNameAndFleetName.getLeaderboardName(),
leaderboardNameRaceColumnNameAndFleetName.getRaceColumnName(),
leaderboardNameRaceColumnNameAndFleetName.getFleetName(),
waypointSelectionModel.getSelectedObject().getA(),
competitor, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorSuppressingPassing(caught.getMessage()));
}
@Override
public void onSuccess(Void result) {
refillList();
}
});
}
}
});
@@ -226,20 +242,25 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
removeSuppressedPassingButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
final RaceColumnDTO column = getColumn();
sailingService.updateSuppressedMarkPassings(getLeaderboardName(), column,
column.getFleet(raceIdentifier), null, competitor,
final LeaderboardNameRaceColumnNameAndFleetName leaderboardNameRaceColumnNameAndFleetName =
raceIdentifierToLeaderboardRaceColumnAndFleetMapper.getLeaderboardNameAndRaceColumnNameAndFleetName(raceIdentifier);
if (leaderboardNameRaceColumnNameAndFleetName != null) {
sailingService.updateSuppressedMarkPassings(leaderboardNameRaceColumnNameAndFleetName.getLeaderboardName(),
leaderboardNameRaceColumnNameAndFleetName.getRaceColumnName(),
leaderboardNameRaceColumnNameAndFleetName.getFleetName(),
null, competitor,
new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorRemovingSupressedPassing(caught.getMessage()));
}
@Override
public void onSuccess(Void result) {
refillList();
}
});
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorRemovingSupressedPassing(caught.getMessage()));
}
@Override
public void onSuccess(Void result) {
refillList();
}
});
}
}
});
selectCompetitorLabel.setText(stringMessages.selectCompetitor());
@@ -265,7 +286,6 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
public void setVisible(boolean visible) {
processCompetitorSelectionChange(visible);
super.setVisible(visible);
}
@Override
@@ -314,27 +334,32 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
waypointList.getList().clear();
waypointList.getList().addAll(newMarkPassings);
// Get current edits
final RaceColumnDTO column = getColumn();
sailingService.getCompetitorRaceLogMarkPassingData(getLeaderboardName(), column,
column.getFleet(raceIdentifier), competitor, new AsyncCallback<Map<Integer, Date>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorTryingToObtainRaceLogMarkPassingData(caught.getMessage()));
}
@Override
public void onSuccess(Map<Integer, Date> result) {
for (Entry<Integer, Date> data : result.entrySet()) {
if (data.getValue() == null) {
zeroBasedIndexOfFirstSuppressedWaypoint = data.getKey();
} else {
currentCompetitorEdits.put(data.getKey(), data.getValue());
}
final LeaderboardNameRaceColumnNameAndFleetName leaderboardNameRaceColumnNameAndFleetName =
raceIdentifierToLeaderboardRaceColumnAndFleetMapper.getLeaderboardNameAndRaceColumnNameAndFleetName(raceIdentifier);
if (leaderboardNameRaceColumnNameAndFleetName != null) {
sailingService.getCompetitorRaceLogMarkPassingData(leaderboardNameRaceColumnNameAndFleetName.getLeaderboardName(),
leaderboardNameRaceColumnNameAndFleetName.getRaceColumnName(),
leaderboardNameRaceColumnNameAndFleetName.getFleetName(), competitor, new AsyncCallback<Map<Integer, Date>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorTryingToObtainRaceLogMarkPassingData(caught.getMessage()));
}
enableButtons();
wayPointSelectionTable.redraw();
}
});
@Override
public void onSuccess(Map<Integer, Date> result) {
for (Entry<Integer, Date> data : result.entrySet()) {
if (data.getValue() == null) {
zeroBasedIndexOfFirstSuppressedWaypoint = data.getKey();
} else {
currentCompetitorEdits.put(data.getKey(), data.getValue());
}
}
enableButtons();
wayPointSelectionTable.redraw();
}
});
}
}
});
}
@@ -384,7 +409,7 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
}
public void setLeaderboard(LeaderboardDTO leaderboard) {
this.leaderboard = leaderboard;
this.raceIdentifierToLeaderboardRaceColumnAndFleetMapper.setLeaderboard(leaderboard);
}
@Override
@@ -429,23 +454,6 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component<Ab
public void filteredCompetitorsListChanged(Iterable<CompetitorDTO> filteredCompetitors) {
}
private String getLeaderboardName() {
return leaderboard.name;
}
private RaceColumnDTO getColumn() {
RaceColumnDTO result = null;
if (leaderboard != null) {
for (RaceColumnDTO columnDTO : leaderboard.getRaceList()) {
if (columnDTO.containsRace(raceIdentifier)) {
result = columnDTO;
break;
}
}
}
return result;
}
@Override
public AbstractSettings getSettings() {
return null;
@@ -0,0 +1,987 @@
package com.sap.sailing.gwt.ui.client.shared.charts;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;
import org.moxieapps.gwt.highcharts.client.Axis;
import org.moxieapps.gwt.highcharts.client.BaseChart;
import org.moxieapps.gwt.highcharts.client.Chart;
import org.moxieapps.gwt.highcharts.client.ChartSubtitle;
import org.moxieapps.gwt.highcharts.client.ChartTitle;
import org.moxieapps.gwt.highcharts.client.Color;
import org.moxieapps.gwt.highcharts.client.Credits;
import org.moxieapps.gwt.highcharts.client.PlotLine.DashStyle;
import org.moxieapps.gwt.highcharts.client.Point;
import org.moxieapps.gwt.highcharts.client.Series;
import org.moxieapps.gwt.highcharts.client.ToolTip;
import org.moxieapps.gwt.highcharts.client.ToolTipData;
import org.moxieapps.gwt.highcharts.client.ToolTipFormatter;
import org.moxieapps.gwt.highcharts.client.XAxis;
import org.moxieapps.gwt.highcharts.client.events.ChartClickEvent;
import org.moxieapps.gwt.highcharts.client.events.ChartClickEventHandler;
import org.moxieapps.gwt.highcharts.client.events.ChartSelectionEvent;
import org.moxieapps.gwt.highcharts.client.events.ChartSelectionEventHandler;
import org.moxieapps.gwt.highcharts.client.labels.AxisLabelsData;
import org.moxieapps.gwt.highcharts.client.labels.AxisLabelsFormatter;
import org.moxieapps.gwt.highcharts.client.labels.XAxisLabels;
import org.moxieapps.gwt.highcharts.client.plotOptions.LinePlotOptions;
import org.moxieapps.gwt.highcharts.client.plotOptions.Marker;
import com.google.gwt.ajaxloader.client.Properties.TypeException;
import com.google.gwt.core.client.Callback;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.maps.client.MapOptions;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.base.LatLng;
import com.google.gwt.maps.client.base.LatLngBounds;
import com.google.gwt.maps.client.controls.ControlPosition;
import com.google.gwt.maps.client.events.click.ClickMapEvent;
import com.google.gwt.maps.client.events.click.ClickMapHandler;
import com.google.gwt.maps.client.events.mousedown.MouseDownMapEvent;
import com.google.gwt.maps.client.events.mousedown.MouseDownMapHandler;
import com.google.gwt.maps.client.events.mousemove.MouseMoveMapEvent;
import com.google.gwt.maps.client.events.mousemove.MouseMoveMapHandler;
import com.google.gwt.maps.client.events.mouseout.MouseOutMapEvent;
import com.google.gwt.maps.client.events.mouseout.MouseOutMapHandler;
import com.google.gwt.maps.client.events.mouseup.MouseUpMapEvent;
import com.google.gwt.maps.client.events.mouseup.MouseUpMapHandler;
import com.google.gwt.maps.client.events.resize.ResizeMapEvent;
import com.google.gwt.maps.client.events.resize.ResizeMapHandler;
import com.google.gwt.maps.client.events.rightclick.RightClickMapEvent;
import com.google.gwt.maps.client.events.rightclick.RightClickMapHandler;
import com.google.gwt.maps.client.mvc.MVCArray;
import com.google.gwt.maps.client.overlays.Polyline;
import com.google.gwt.maps.client.overlays.PolylineOptions;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.MenuItem;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RequiresResize;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.sap.sailing.domain.common.FixType;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.charts.MarkPositionService.MarkTrackDTO;
import com.sap.sailing.gwt.ui.client.shared.charts.MarkPositionService.MarkTracksDTO;
import com.sap.sailing.gwt.ui.client.shared.racemap.BoundsUtil;
import com.sap.sailing.gwt.ui.client.shared.racemap.CourseMarkOverlay;
import com.sap.sailing.gwt.ui.client.shared.racemap.FixOverlay;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMap;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
import com.sap.sailing.gwt.ui.raceboard.SideBySideComponentViewer;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTOWithSpeedWindTackAndLegType;
import com.sap.sailing.gwt.ui.shared.MarkDTO;
import com.sap.sailing.gwt.ui.shared.WindDTO;
import com.sap.sailing.gwt.ui.shared.racemap.CanvasOverlayV3;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.settings.AbstractSettings;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
import com.sap.sse.gwt.client.player.TimeRangeWithZoomProvider;
import com.sap.sse.gwt.client.player.Timer;
import com.sap.sse.gwt.client.shared.components.Component;
import com.sap.sse.gwt.client.shared.components.SettingsDialog;
import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent;
public class EditMarkPositionPanel extends AbstractRaceChart implements Component<AbstractSettings>, RequiresResize, SelectionChangeEvent.Handler {
protected static final int FIX_OVERLAY_Z_ORDER = 230;
private final RaceMap raceMap;
private final LeaderboardPanel leaderboardPanel;
private final MarksPanel marksPanel;
private Series markSeries;
private LinePlotOptions markSeriesPlotOptions;
private final Label noMarkSelectedLabel;
private MapWidget map;
private boolean visible;
private List<HandlerRegistration> courseMarkClickHandlers;
private Map<MarkDTO, Pair<Date, Date>> marksFromToTimes;
private Date raceFromTime;
private Date raceToTime;
private Map<MarkDTO, SortedMap<GPSFixDTO, FixOverlay>> marks;
private MarkDTO selectedMark;
private Map<MarkDTO, Polyline> polylines;
private final ListDataProvider<MarkDTO> markDataProvider;
private SideBySideComponentViewer sideBySideComponentViewer;
private FixPositionChooser currentFixPositionChooser;
private List<OverlayClickHandler> overlayClickHandlers;
private final MarkPositionService markPositionService;
private final RaceIdentifierToLeaderboardRaceColumnAndFleetMapper raceIdentifierToLeaderboardRaceColumnAndFleetMapper;
public EditMarkPositionPanel(final RaceMap raceMap, final LeaderboardPanel leaderboardPanel,
RegattaAndRaceIdentifier selectedRaceIdentifier, String leaderboardName, final StringMessages stringMessages,
SailingServiceAsync sailingService, Timer timer, TimeRangeWithZoomProvider timeRangeWithZoomProvider,
AsyncActionsExecutor asyncActionsExecutor, ErrorReporter errorReporter) {
super(sailingService, selectedRaceIdentifier, timer, timeRangeWithZoomProvider, stringMessages, asyncActionsExecutor, errorReporter);
this.markPositionService = new MarkPositionServiceForSailingService(sailingService);
this.raceIdentifierToLeaderboardRaceColumnAndFleetMapper = new RaceIdentifierToLeaderboardRaceColumnAndFleetMapper();
this.raceMap = raceMap;
this.leaderboardPanel = leaderboardPanel;
this.polylines = new HashMap<>();
this.markDataProvider = new ListDataProvider<>();
this.marksPanel = new MarksPanel(this, markDataProvider, stringMessages);
this.noMarkSelectedLabel = new Label(stringMessages.pleaseSelectAMark());
this.noMarkSelectedLabel.setStyleName("abstractChartPanel-importantMessageOfChart");
this.courseMarkClickHandlers = new ArrayList<>();
this.marksFromToTimes = new HashMap<>();
this.getEntryWidget().setTitle(stringMessages.editMarkPositions());
this.setVisible(false);
this.overlayClickHandlers = new ArrayList<>();
chart = new Chart()
.setPersistent(true)
.setZoomType(BaseChart.ZoomType.X)
.setMarginLeft(65)
.setMarginRight(65)
.setWidth100()
.setHeight100()
.setBorderWidth(0)
.setBorderRadius(0)
.setBackgroundColor(new Color("#FFFFFF"))
.setPlotBackgroundColor("#f8f8f8")
.setPlotBorderWidth(0)
.setCredits(new Credits().setEnabled(false))
.setChartTitle(new ChartTitle().setText(stringMessages.markFixes()))
.setChartSubtitle(new ChartSubtitle().setText(stringMessages.clickAndDragToZoomIn()))
.setPersistent(true);
chart.setClickEventHandler(new ChartClickEventHandler() {
@Override
public boolean onClick(ChartClickEvent chartClickEvent) {
return EditMarkPositionPanel.this.onClick(chartClickEvent);
}
});
chart.setSelectionEventHandler(new ChartSelectionEventHandler() {
@Override
public boolean onSelection(ChartSelectionEvent chartSelectionEvent) {
return EditMarkPositionPanel.this.onXAxisSelectionChange(chartSelectionEvent);
}
});
chart.getXAxis().setType(Axis.Type.DATE_TIME)
.setMaxZoom(60 * 1000) // 1 minute
.setAxisTitleText(stringMessages.time());
chart.getXAxis().setLabels(new XAxisLabels().setFormatter(new AxisLabelsFormatter() {
@Override
public String format(AxisLabelsData axisLabelsData) {
return dateFormatHoursMinutes.format(new Date(axisLabelsData.getValueAsLong()));
}
}));
chart.getYAxis().setAxisTitleText(stringMessages.distanceFromAveragePosition() + " (" + stringMessages.metersUnit() + ")").setOption("labels/enabled", false);
timePlotLine = chart.getXAxis().createPlotLine().setColor("#656565").setWidth(1)
.setDashStyle(DashStyle.SOLID);
markSeriesPlotOptions = new LinePlotOptions().setSelected(true).setShowInLegend(false).setLineWidth(1).setColor("#000")
.setHoverStateLineWidth(1).setEnableMouseTracking(true);
markSeries = chart.createSeries().setType(Series.Type.LINE).setYAxis(0)
.setPlotOptions(markSeriesPlotOptions).setName(stringMessages.distanceFromAveragePosition());
chart.addSeries(markSeries, false, false);
chart.setToolTip(new ToolTip().setEnabled(true).setFormatter(new ToolTipFormatter() {
@Override
public String format(ToolTipData toolTipData) {
final String seriesName = toolTipData.getSeriesName();
if (seriesName.equals(stringMessages.time())) {
return "<b>" + seriesName + ":</b> " + dateFormat.format(new Date(toolTipData.getXAsLong()))
+ "<br/>(" + stringMessages.clickChartToSetTime() + ")";
} else {
return "<b>" + (dateFormat.format(new Date(toolTipData.getXAsLong()))) + "</b>: "
+ NumberFormat.getFormat("0.0").format(toolTipData.getYAsDouble()) + " m";
}
}
}));
}
private void canRemoveMarkFix(MarkDTO mark, GPSFixDTO fix, AsyncCallback<Boolean> callback) {
markPositionService.canRemoveMarkFix(
raceIdentifierToLeaderboardRaceColumnAndFleetMapper.getLeaderboardNameAndRaceColumnNameAndFleetName(selectedRaceIdentifier),
mark, fix, callback);
}
private static class Pixel extends JavaScriptObject {
protected Pixel() {
}
private native int getX() /*-{
return this.x;
}-*/;
private native int getY() /*-{
return this.y;
}-*/;
}
private class OverlayClickHandler {
private final PopupPanel popup;
private final MenuItem moveMenuItem;
private final FixOverlay overlay;
private FixOverlay moveOverlay;
private final MouseDownMapHandler mouseDownHandler;
private HandlerRegistration mouseDownHandlerRegistration;
private final RightClickMapHandler rightClickHandler;
private HandlerRegistration rightClickHandlerRegistration;
private int mouseDownX;
private int mouseDownY;
private boolean mouseDown;
private boolean dragging;
//Currently the big problem with the OverlayClickHandler is that you cannot reliably detect if users use a touch device
//and even if they don't at first they could switch from touch to mouse or the other way around. Thus, both input methods
//are always present. Touch users should not accidentally drag fixes, because there is a buffer.
public OverlayClickHandler(final MarkDTO mark, final GPSFixDTO fix, final FixOverlay overlay) {
this.overlay = overlay;
final Polyline polyline = polylines.get(mark);
map.addResizeHandler(new ResizeMapHandler() {
@Override
public void onEvent(ResizeMapEvent event) {
onResizeMap(event);
}
});
rightClickHandler = new RightClickMapHandler() {
@Override
public void onEvent(RightClickMapEvent event) {
onRightClick(event);
}
};
mouseDownHandler = new MouseDownMapHandler() {
@Override
public void onEvent(MouseDownMapEvent event) {
try {
if (event.getProperties().getNumber("which") == 1) {
mouseDownX = event.getProperties().getNumber("pageX").intValue() - map.getAbsoluteLeft();
mouseDownY = event.getProperties().getNumber("pageY").intValue() - map.getAbsoluteTop();
mouseDown = true;
dragging = false;
MapOptions options = MapOptions.newInstance(false);
options.setDraggable(false);
map.setOptions(options);
}
} catch (TypeException e) {
GWT.log("Exception trying to obtain pageX or pageY property on event "+event, e);
}
}
};
map.addMouseMoveHandler(new MouseMoveMapHandler() {
@Override
public void onEvent(MouseMoveMapEvent event) {
if (mouseDown) {
int currentX = 0;
int currentY = 0;
try {
Pixel pixel = event.getProperties().getObject("pixel").cast();
currentX = pixel.getX();
currentY = pixel.getY();
} catch (TypeException e) {
GWT.log("Exception trying to obtain pixel property on event "+event, e);
}
final int index = getIndexOfFixInPolyline(mark, fix);
if (!dragging && Math.sqrt(Math.pow(currentX - mouseDownX, 2) +
Math.pow(currentY - mouseDownY, 2)) > 15) {
dragging = true;
overlay.setVisible(false);
setRedPoint(index);
moveOverlay = new FixOverlay(map, overlay.getZIndex(), overlay.getGPSFixDTO(), overlay.getType(),
"#f00", raceMap.getCoordinateSystem(), stringMessages.dragToChangePosition());
moveOverlay.addMouseUpHandler(new MouseUpMapHandler() {
@Override
public void onEvent(MouseUpMapEvent event) {
editMarkFix(mark, fix, fix.position);
overlay.setVisible(true);
resetPointColor(index);
moveOverlay.removeFromMap();
moveOverlay = null;
MapOptions options = MapOptions.newInstance(false);
options.setDraggable(true);
map.setOptions(options);
mouseDown = false;
}
});
}
if (dragging) {
fix.position = raceMap.getCoordinateSystem().getPosition(event.getMouseEvent().getLatLng());
overlay.setGPSFixDTO(fix);
moveOverlay.setGPSFixDTO(fix);
updateRedPoint(index);
polyline.getPath().setAt(index, event.getMouseEvent().getLatLng());
}
}
}
});
overlay.addMouseUpHandler(new MouseUpMapHandler() {
@Override
public void onEvent(MouseUpMapEvent event) {
if (mouseDown) {
if (!dragging) {
onClick(event);
}
MapOptions options = MapOptions.newInstance(false);
options.setDraggable(true);
map.setOptions(options);
mouseDown = false;
}
}
});
map.addMouseOutMoveHandler(new MouseOutMapHandler() {
@Override
public void onEvent(MouseOutMapEvent event) {
if (mouseDown) {
if (dragging) {
editMarkFix(mark, fix, fix.position);
overlay.setVisible(true);
resetPointColor(getIndexOfFixInPolyline(mark, fix));
moveOverlay.removeFromMap();
moveOverlay = null;
}
MapOptions options = MapOptions.newInstance(false);
options.setDraggable(true);
map.setOptions(options);
mouseDown = false;
}
}
});
popup = new PopupPanel(true);
popup.setStyleName("EditMarkPositionPopup");
final MenuBar menu = new MenuBar(true);
moveMenuItem = new MenuItem(stringMessages.moveFix(), new ScheduledCommand() {
@Override
public void execute() {
overlay.setVisible(false);
if (currentFixPositionChooser == null) {
OverlayClickHandler.this.unregisterAll();
currentFixPositionChooser = new FixPositionChooser(EditMarkPositionPanel.this, stringMessages, map, getIndexOfFixInPolyline(mark, fix), polyline.getPath(), overlay, new Callback<Position, Exception>() {
@Override
public void onFailure(Exception reason) {
overlay.setVisible(true);
OverlayClickHandler.this.registerAll();
resetCurrentFixPositionChooser();
}
@Override
public void onSuccess(Position result) {
editMarkFix(mark, fix, result);
overlay.setVisible(true);
OverlayClickHandler.this.registerAll();
resetCurrentFixPositionChooser();
}
});
}
popup.hide();
}
});
moveMenuItem.setTitle(stringMessages.useATouchOptimizedUI());
menu.addItem(moveMenuItem);
canRemoveMarkFix(mark, fix, new AsyncCallback<Boolean>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorCommunicatingWithServer()+": "+caught.getMessage(), /* silent */ true);
addDeleteItemAndShowPopup(false);
}
@Override
public void onSuccess(Boolean result) {
addDeleteItemAndShowPopup(result);
}
private void addDeleteItemAndShowPopup(boolean enableDelete) {
final MenuItem delete;
if (enableDelete) {
delete = new MenuItem(stringMessages.deleteFix(), new ScheduledCommand() {
@Override
public void execute() {
removeMarkFix(mark, fix);
popup.hide();
}
});
} else {
delete = new MenuItem(stringMessages.deleteFix(), new ScheduledCommand() {
@Override
public void execute() {
}
});
delete.setEnabled(false);
delete.setTitle(stringMessages.theDeletionOfThisFix());
}
menu.addItem(delete);
popup.setWidget(menu);
}
});
}
private int getIndexOfFixInPolyline(MarkDTO mark, GPSFixDTO fix) {
int index;
if (marks == null) {
index = -1;
} else {
index = 0;
for (GPSFixDTO fixToCompare : marks.get(mark).keySet()) {
if (fixToCompare.timepoint.equals(fix.timepoint))
return index;
index++;
}
}
return index;
}
public void onClick(MouseUpMapEvent event) {
setPopupPosition();
moveMenuItem.setVisible(true);
popup.show();
}
public OverlayClickHandler register() {
if (mouseDownHandlerRegistration == null) {
mouseDownHandlerRegistration = overlay.addMouseDownHandler(mouseDownHandler);
}
if (rightClickHandlerRegistration == null) {
rightClickHandlerRegistration = overlay.addRightClickHandler(rightClickHandler);
}
return this;
}
public OverlayClickHandler unregister() {
if (mouseDownHandlerRegistration != null) {
mouseDownHandlerRegistration.removeHandler();
mouseDownHandlerRegistration = null;
}
if (rightClickHandlerRegistration != null) {
rightClickHandlerRegistration.removeHandler();
rightClickHandlerRegistration = null;
}
return this;
}
public void unregisterAll() {
for (OverlayClickHandler handler : overlayClickHandlers) {
handler.unregister();
}
}
public void registerAll() {
for (OverlayClickHandler handler : overlayClickHandlers) {
handler.register();
}
}
private void setPopupPosition() {
popup.setPopupPosition(overlay.getCanvas().getAbsoluteLeft() + (int) overlay.getFixScaleAndSize().getB().getWidth() / 2 + 5,
overlay.getCanvas().getAbsoluteTop() + (int) overlay.getFixScaleAndSize().getB().getHeight() / 2 + 5);
}
public void onResizeMap(ResizeMapEvent event) {
if (popup.isVisible()) {
popup.hide();
}
}
public void onRightClick(RightClickMapEvent event) {
setPopupPosition();
moveMenuItem.setVisible(false);
popup.show();
}
}
public void setRedPoint(Point[] points, int index) {
points[index].setMarker(new Marker().setFillColor("#f00"));
}
public void setRedPoint(int index) {
Point[] points = markSeries.getPoints();
if (points.length > index) {
setRedPoint(points, index);
setSeriesPoints(markSeries, points);
}
}
public void updateRedPoint(int index) {
if (selectedMark != null && marks != null) {
Point[] points = getSeriesPoints(marks.get(selectedMark).keySet());
if (points.length > index) {
setRedPoint(points, index);
setSeriesPoints(markSeries, points);
chart.redraw();
}
}
}
public void resetPointColor(int index) {
Point[] points = markSeries.getPoints();
if (points.length > index) {
points[index].setMarker(new Marker().setFillColor(selectedMark.color));
setSeriesPoints(markSeries, points);
}
}
private void finalizeLoadingData() {
hideLoading();
onSelectionChange(null);
onResize();
}
private void loadData(final Date from, final Date to) {
if (selectedRaceIdentifier != null && from != null && to != null && marks == null) {
setWidget(chart);
showLoading(stringMessages.loadingMarkFixes());
markPositionService.getMarkTracks(
raceIdentifierToLeaderboardRaceColumnAndFleetMapper.getLeaderboardNameAndRaceColumnNameAndFleetName(selectedRaceIdentifier),
new AsyncCallback<MarkTracksDTO>() {
@Override
public void onFailure(Throwable caught) {
finalizeLoadingData();
errorReporter.reportError(stringMessages.errorCommunicatingWithServer()+": "+caught.getMessage());
}
@Override
public void onSuccess(MarkTracksDTO markTracks) {
Map<MarkDTO, Iterable<GPSFixDTO>> result = new HashMap<>();
for (MarkTrackDTO track : markTracks.getTracks()) {
result.put(track.getMark(), track.getFixes());
marks = new HashMap<MarkDTO, SortedMap<GPSFixDTO, FixOverlay>>();
raceFromTime = timeRangeWithZoomProvider.getFromTime();
raceToTime = timeRangeWithZoomProvider.getToTime();
for (final Map.Entry<MarkDTO, Iterable<GPSFixDTO>> fixes : result.entrySet()) {
Date fromTime = raceFromTime;
Date toTime = raceToTime;
PolylineOptions options = PolylineOptions.newInstance();
if (map != null) {
options.setMap(map);
}
options.setStrokeWeight(1);
options.setVisible(false);
final Polyline polyline = Polyline.newInstance(options); // Line can not be dashed at the moment, because line symbols are not supported
polylines.put(fixes.getKey(), polyline);
SortedMap<GPSFixDTO, FixOverlay> fixOverlayMap = new TreeMap<>(new Comparator<GPSFixDTO>() {
@Override
public int compare(GPSFixDTO o1, GPSFixDTO o2) {
return o1.timepoint.compareTo(o2.timepoint);
}
});
for (final GPSFixDTO fix : fixes.getValue()) {
final FixOverlay overlay = new FixOverlay(map, FIX_OVERLAY_Z_ORDER, fix, FixType.BUOY, fixes.getKey().color, raceMap.getCoordinateSystem(), stringMessages.dragToChangePosition());
fixOverlayMap.put(fix, overlay);
overlay.setVisible(false);
overlayClickHandlers.add(new OverlayClickHandler(fixes.getKey(), fix, overlay).register());
if (fromTime.after(fix.timepoint)) {
fromTime = fix.timepoint;
} else if (toTime.before(fix.timepoint)) {
toTime = fix.timepoint;
}
}
marks.put(fixes.getKey(), fixOverlayMap);
updatePolylinePoints(fixes.getKey());
marksFromToTimes.put(fixes.getKey(), new Pair<Date, Date>(fromTime, toTime));
}
List<MarkDTO> markList = new ArrayList<MarkDTO>();
markList.addAll(marks.keySet());
markDataProvider.setList(markList);
}
finalizeLoadingData();
}
});
}
}
public void addMarkFix(final MarkDTO mark, final Date timepoint, final Position fixPosition) {
final GPSFixDTOWithSpeedWindTackAndLegType fix = new GPSFixDTOWithSpeedWindTackAndLegType(timepoint, fixPosition, null, new WindDTO(), null, null, false);
markPositionService.addMarkFix(
raceIdentifierToLeaderboardRaceColumnAndFleetMapper.getLeaderboardNameAndRaceColumnNameAndFleetName(selectedRaceIdentifier),
mark, fix, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorCommunicatingWithServer()+": "+caught.getMessage());
}
@Override
public void onSuccess(Void result) {
FixOverlay overlay = new FixOverlay(map, FIX_OVERLAY_Z_ORDER, fix, FixType.BUOY, mark.color, raceMap.getCoordinateSystem(), stringMessages.dragToChangePosition());
overlayClickHandlers.add(new OverlayClickHandler(mark, fix, overlay).register());
marks.get(mark).put(fix, overlay);
updatePolylinePoints(mark);
setSeriesPoints(mark);
onResize();
showNotification(stringMessages.fixSuccessfullyAdded(), NotificationType.SUCCESS);
}
});
}
private void editMarkFix(final MarkDTO mark, final GPSFixDTO fix, final Position newPosition) {
markPositionService.editMarkFix(
raceIdentifierToLeaderboardRaceColumnAndFleetMapper.getLeaderboardNameAndRaceColumnNameAndFleetName(selectedRaceIdentifier),
mark, fix, newPosition, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorCommunicatingWithServer()+": "+caught.getMessage());
}
@Override
public void onSuccess(Void result) {
fix.position = newPosition;
marks.get(mark).get(fix).setGPSFixDTO(fix);
updatePolylinePoints(mark);
setSeriesPoints(mark);
onResize();
showNotification(stringMessages.fixPositionSuccessfullyEdited(), NotificationType.SUCCESS);
timer.setTime(timer.getTime().getTime()+1);
}
});
}
private void removeMarkFix(final MarkDTO mark, final GPSFixDTO fix) {
markPositionService.removeMarkFix(
raceIdentifierToLeaderboardRaceColumnAndFleetMapper.getLeaderboardNameAndRaceColumnNameAndFleetName(selectedRaceIdentifier),
mark, fix, new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorCommunicatingWithServer()+": "+caught.getMessage());
}
@Override
public void onSuccess(Void result) {
CanvasOverlayV3 overlay = marks.get(mark).remove(fix);
overlay.removeFromMap();
updatePolylinePoints(mark);
setSeriesPoints(mark);
onResize();
showNotification(stringMessages.fixSuccessfullyRemoved(), NotificationType.SUCCESS);
}
});
}
private void updatePolylinePoints(MarkDTO mark) {
MVCArray<LatLng> path = polylines.get(mark).getPath();
path.clear();
for (GPSFixDTO fix : marks.get(mark).keySet()) {
path.push(LatLng.newInstance(fix.position.getLatDeg(), fix.position.getLngDeg()));
}
}
public Point[] getSeriesPoints(Collection<GPSFixDTO> fixes) {
double latAverage = 0;
double lngAverage = 0;
for (GPSFixDTO fix : fixes) {
latAverage += fix.position.getLatDeg();
lngAverage += fix.position.getLngDeg();
}
latAverage /= fixes.size();
lngAverage /= fixes.size();
Position averagePosition = new DegreePosition(latAverage, lngAverage);
Point[] points = new Point[fixes.size()];
int i = 0;
for (GPSFixDTO fix : fixes) {
final double metersFromAverage = fix.position.getDistance(averagePosition).getMeters();
points[i] = new Point(fix.timepoint.getTime(), metersFromAverage);
i++;
}
return points;
}
private void setSeriesPoints(MarkDTO mark) {
setSeriesPoints(markSeries, getSeriesPoints(marks.get(mark).keySet()));
}
public void setSeriesPoints(Point[] points) {
setSeriesPoints(markSeries, points);
}
public void setVisible(boolean visible) {
this.visible = visible;
if (map == null) {
map = raceMap.getMap();
if (map != null) {
setMap(map);
}
}
if (this.visible) {
if (sideBySideComponentViewer != null) {
sideBySideComponentViewer.setLeftComponent(marksPanel);
sideBySideComponentViewer.setLeftComponentToggleButtonVisible(false);
}
for (final CourseMarkOverlay overlay : raceMap.getCourseMarkOverlays().values()) {
courseMarkClickHandlers.add(overlay.addClickHandler(new ClickMapHandler() {
@Override
public void onEvent(ClickMapEvent event) {
marksPanel.select(overlay.getMark());
}
}));
}
raceMap.unregisterAllCourseMarkInfoWindowClickHandlers();
} else {
if (currentFixPositionChooser != null) {
currentFixPositionChooser.cancel();
currentFixPositionChooser = null;
}
marksPanel.deselectMark();
selectedMark = null;
if (sideBySideComponentViewer != null) {
sideBySideComponentViewer.setLeftComponent(leaderboardPanel);
sideBySideComponentViewer.setLeftComponentToggleButtonVisible(true);
}
for (HandlerRegistration registration : courseMarkClickHandlers) {
registration.removeHandler();
}
raceMap.unregisterAllCourseMarkInfoWindowClickHandlers();
raceMap.registerAllCourseMarkInfoWindowClickHandlers();
}
super.setVisible(this.visible);
}
@Override
public void onResize() {
if (selectedMark != null) {
LatLngBounds bounds = null;
if (marks != null) {
for (GPSFixDTO fix : marks.get(selectedMark).keySet()) {
if (bounds == null) {
bounds = BoundsUtil.getAsBounds(raceMap.getCoordinateSystem().toLatLng(fix.position));
} else {
bounds = bounds.extend(raceMap.getCoordinateSystem().toLatLng(fix.position));
}
}
}
}
chart.setSizeToMatchContainer();
chart.redraw();
}
public void redrawChart() {
chart.redraw();
}
@Override
public String getLocalizedShortName() {
return null;
}
@Override
public Widget getEntryWidget() {
return this;
}
@Override
public boolean hasSettings() {
return false;
}
@Override
public SettingsDialogComponent<AbstractSettings> getSettingsDialogComponent() {
return null;
}
@Override
public void updateSettings(AbstractSettings newSettings) {
}
@Override
public String getDependentCssClassName() {
return null;
}
public void setMap(final MapWidget map) {
this.map = map;
for (Map.Entry<MarkDTO, Polyline> polyline : polylines.entrySet()) {
polyline.getValue().setMap(map);
}
}
public Date getTimepoint() {
return timer.getTime();
}
@Override
protected Button createSettingsButton() {
Button settingsButton = SettingsDialog.createSettingsButton(this, stringMessages);
settingsButton.setVisible(false);
return settingsButton;
}
@Override
public void timeChanged(Date newTime, Date oldTime) {
if (isVisible()) {
loadData(timeRangeWithZoomProvider.getFromTime(), timeRangeWithZoomProvider.getToTime());
updateTimePlotLine(newTime);
}
}
public void setComponentViewer(SideBySideComponentViewer sideBySideComponentViewer) {
this.sideBySideComponentViewer = sideBySideComponentViewer;
}
@Override
public void onSelectionChange(SelectionChangeEvent event) {
selectedMark = marksPanel.getSelectedMark();
if (currentFixPositionChooser != null) {
currentFixPositionChooser.cancel();
currentFixPositionChooser = null;
}
if (selectedMark != null) {
if (marksFromToTimes.get(selectedMark) != null) {
// For some reason the time slider does not change with this method only if you comment out line 430 and 432 in TimePanel it works
timeRangeWithZoomProvider.setTimeRange(marksFromToTimes.get(selectedMark).getA(), marksFromToTimes.get(selectedMark).getB());
}
setWidget(chart);
markSeries.remove();
markSeries.setPlotOptions(markSeriesPlotOptions.setMarker(
new Marker().setFillColor(selectedMark.color != null ? selectedMark.color : "#efab00")
.setLineColor("#fff").setLineWidth(2)));
chart.addSeries(markSeries);
setSeriesPoints(selectedMark);
onResize(); // redraw chart
hideAllCourseMarkOverlaysExceptSelected();
raceMap.hideAllHelplines();
if (marks != null) {
for (final Entry<MarkDTO, SortedMap<GPSFixDTO, FixOverlay>> e : marks.entrySet()) {
for (CanvasOverlayV3 overlay : e.getValue().values()) {
// show only tail of selected mark, hide all others
overlay.setVisible(selectedMark.equals(e.getKey()));
}
}
}
for (final Entry<MarkDTO, Polyline> e : polylines.entrySet()) {
e.getValue().setVisible(selectedMark.equals(e.getKey()));
}
} else {
if (raceFromTime != null && raceToTime != null) {
timeRangeWithZoomProvider.setTimeRange(raceFromTime, raceToTime);
}
setWidget(noMarkSelectedLabel);
showAllCourseMarkOverlays();
raceMap.showAllHelplinesToShow();
hideAllFixOverlays();
hideAllPolylines();
}
}
public void showAllCourseMarkOverlays() {
for (Map.Entry<String, CourseMarkOverlay> overlay : raceMap.getCourseMarkOverlays().entrySet()) {
overlay.getValue().setVisible(true);
}
}
public void hideAllCourseMarkOverlaysExceptSelected() {
for (Map.Entry<String, CourseMarkOverlay> overlay : raceMap.getCourseMarkOverlays().entrySet()) {
overlay.getValue().setVisible(overlay.getKey().equals(selectedMark.getName()));
}
}
public void hideAllFixOverlays() {
if (marks != null) {
for (Map.Entry<MarkDTO, SortedMap<GPSFixDTO, FixOverlay>> mark : marks.entrySet()) {
for (CanvasOverlayV3 overlay : mark.getValue().values()) {
overlay.setVisible(false);
}
}
}
}
public void hideAllPolylines() {
for (Map.Entry<MarkDTO, Polyline> polyline : polylines.entrySet()) {
polyline.getValue().setVisible(false);
}
}
/**
* Call {@link #resetCurrentFixPositionChooser()} after the fix position chooser is done.
*
* @throws MultipleFixPositionChooserException
* Is thrown when another fix position chooser is still open
*/
public void createFixPositionChooserToAddFixToMark(MarkDTO mark, Callback<Position, Exception> callback) {
if (currentFixPositionChooser != null) {
return;
}
int index = 0;
for (GPSFixDTO fix : marks.get(mark).keySet()) {
if (fix.timepoint.after(timer.getTime()))
break;
index++;
}
Polyline polyline = polylines.get(mark);
currentFixPositionChooser = new FixPositionChooser(this, stringMessages, map, index, polyline != null ? polyline.getPath() : null, map.getCenter(), raceMap.getCoordinateSystem(), callback);
}
public void resetCurrentFixPositionChooser() {
currentFixPositionChooser = null;
}
private com.google.gwt.user.client.Timer notificationTimer;
public enum NotificationType {
INFO,
ERROR,
SUCCESS
}
public void showNotification(String message) {
showNotification(message, NotificationType.INFO);
}
public void showNotification(String message, NotificationType type) {
if (notificationTimer != null && notificationTimer.isRunning()) {
notificationTimer.run();
}
final HTMLPanel text = new HTMLPanel("<div style = \" color: " + (type == NotificationType.ERROR ? "#c00" : type == NotificationType.SUCCESS ? "#0c0" : "#000") + "\">" + message + "</div>");
text.setStyleName("EditMarkPositionNotification");
map.setControls(ControlPosition.TOP_CENTER, text);
notificationTimer = new com.google.gwt.user.client.Timer(){
@Override
public void run() {
text.removeFromParent();
}
};
notificationTimer.schedule(5000);
}
public boolean hasFixAtTimePoint(MarkDTO mark, Date timePoint) {
boolean result = false;
for (GPSFixDTO fix : marks.get(mark).keySet()) {
if (fix.timepoint.equals(timePoint)) {
result = true;
break;
}
}
return result;
}
public List<GPSFixDTO> getMarkFixes() {
if (selectedMark != null) {
List<GPSFixDTO> set = new ArrayList<>();
set.addAll(marks.get(selectedMark).keySet());
return set;
} else {
return null;
}
}
public XAxis getXAxis() {
return chart.getXAxis();
}
public void setLeaderboard(LeaderboardDTO leaderboard) {
raceIdentifierToLeaderboardRaceColumnAndFleetMapper.setLeaderboard(leaderboard);
}
}
@@ -0,0 +1,181 @@
package com.sap.sailing.gwt.ui.client.shared.charts;
import java.util.List;
import org.moxieapps.gwt.highcharts.client.Color;
import org.moxieapps.gwt.highcharts.client.PlotLine;
import org.moxieapps.gwt.highcharts.client.Point;
import org.moxieapps.gwt.highcharts.client.PlotLine.DashStyle;
import com.google.gwt.core.client.Callback;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.base.LatLng;
import com.google.gwt.maps.client.controls.ControlPosition;
import com.google.gwt.maps.client.events.center.CenterChangeMapEvent;
import com.google.gwt.maps.client.events.center.CenterChangeMapHandler;
import com.google.gwt.maps.client.mvc.MVCArray;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.MenuItem;
import com.sap.sailing.domain.common.FixType;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.racemap.CoordinateSystem;
import com.sap.sailing.gwt.ui.client.shared.racemap.FixOverlay;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTOWithSpeedWindTackAndLegType;
import com.sap.sailing.gwt.ui.shared.WindDTO;
public class FixPositionChooser {
private final Callback<Position, Exception> callback;
private final MapWidget map;
private final boolean newFix;
private final int polylineFixIndex;
private final MVCArray<LatLng> polylinePath;
private final LatLng startPos;
private final FixOverlay overlay;
private final CoordinateSystem coordinateSystem;
private FixOverlay moveOverlay;
private HandlerRegistration centerChangeHandlerRegistration;
private MenuBar menu;
private final EditMarkPositionPanel editMarkPositionPanel;
private final StringMessages stringMessages;
private final PlotLine redTimeLine;
/**
* Use this constructor when there is already a fix with an overlay.
* This constructor will automatically assume that an existing fix is moved.
* @param editMarkPositionPanel
* @param map
* @param polylineFixIndex Position of the fix in the polyline path
* @param polylinePath
* @param overlay
* @param callback
*/
public FixPositionChooser(final EditMarkPositionPanel editMarkPositionPanel, final StringMessages stringMessages, final MapWidget map, final int polylineFixIndex, final MVCArray<LatLng> polylinePath, final FixOverlay overlay,
final Callback<Position, Exception> callback) {
this(editMarkPositionPanel, stringMessages, false, map, polylineFixIndex, polylinePath, overlay, overlay.getLatLngPosition(), overlay.getCoordinateSystem(),
stringMessages.confirmMove(), callback);
}
/**
* Use this constructor when you want to add a fix and not move one.
* This constructor will automatically assume that there is no existing fix.
*/
public FixPositionChooser(final EditMarkPositionPanel editMarkPositionPanel, final StringMessages stringMessages, final MapWidget map, final int polylineFixIndex, final MVCArray<LatLng> polylinePath, final LatLng startPos,
final CoordinateSystem coordinateSystem, final Callback<Position, Exception> callback) {
this(editMarkPositionPanel, stringMessages, true, map, polylineFixIndex, polylinePath, null, startPos, coordinateSystem,
stringMessages.confirmNewFix(), callback);
}
// The problem of not knowing if it is a touchscreen or mouse the user is currently using, applies to this class too.
// The touch optimized input is currently implemented, because mouse users can use it more easily than the other way around.
private FixPositionChooser(final EditMarkPositionPanel editMarkPositionPanel, final StringMessages stringMessages, final boolean newFix, final MapWidget map, final int polylineFixIndex, final MVCArray<LatLng> polylinePath,
final FixOverlay overlay, final LatLng startPos, final CoordinateSystem coordinateSystem, final String confirmButtonText,
final Callback<Position, Exception> callback) {
this.callback = callback;
this.stringMessages = stringMessages;
this.map = map;
this.newFix = newFix;
this.polylineFixIndex = polylineFixIndex;
this.polylinePath = polylinePath;
this.overlay = overlay;
this.startPos = startPos;
this.coordinateSystem = coordinateSystem;
this.editMarkPositionPanel = editMarkPositionPanel;
this.editMarkPositionPanel.showNotification(stringMessages.selectAFixPositionBy());
this.redTimeLine = editMarkPositionPanel.getXAxis().createPlotLine().setColor(new Color(255, 0, 0)).setWidth(1.5).setDashStyle(DashStyle.SOLID);
setupUIOverlay(confirmButtonText);
}
private void setupUIOverlay(final String confirmButtonText) {
final GPSFixDTO fix;
if (overlay != null) {
final GPSFixDTO oldFix = overlay.getGPSFixDTO();
fix = new GPSFixDTO(oldFix.timepoint, oldFix.position);
this.moveOverlay = new FixOverlay(map, overlay.getZIndex(), fix, overlay.getType(), "#f00", coordinateSystem, stringMessages.dragToChangePosition());
} else {
fix = new GPSFixDTOWithSpeedWindTackAndLegType(editMarkPositionPanel.getTimepoint(), coordinateSystem.getPosition(startPos), null, new WindDTO(), null, null, false);
this.moveOverlay = new FixOverlay(map, EditMarkPositionPanel.FIX_OVERLAY_Z_ORDER+1, fix, FixType.BUOY, "#f00", coordinateSystem, stringMessages.dragToChangePosition());
}
redTimeLine.setValue(fix.timepoint.getTime());
editMarkPositionPanel.getXAxis().addPlotLines(redTimeLine);
map.panTo(startPos);
if (polylinePath != null && newFix) {
polylinePath.insertAt(polylineFixIndex, map.getCenter());
}
setRedPointInChart(fix);
centerChangeHandlerRegistration = map.addCenterChangeHandler(new CenterChangeMapHandler() {
@Override
public void onEvent(CenterChangeMapEvent event) {
fix.position = coordinateSystem.getPosition(map.getCenter());
moveOverlay.setGPSFixDTO(fix);
if (polylinePath != null) {
polylinePath.setAt(polylineFixIndex, map.getCenter());
}
setRedPointInChart(fix);
}
});
menu = new MenuBar(/* vertical */ false);
menu.setStyleName("EditMarkPositionConfirmCancelButtons");
MenuItem confirm = new MenuItem(confirmButtonText, new ScheduledCommand() {
@Override
public void execute() {
destroyUIOverlay();
cleanupChart();
callback.onSuccess(coordinateSystem.getPosition(map.getCenter()));
}
});
MenuItem cancel = new MenuItem(stringMessages.cancel(), new ScheduledCommand() {
@Override
public void execute() {
cancel();
}
});
menu.addItem(confirm);
menu.addItem(cancel);
map.setControls(ControlPosition.BOTTOM_LEFT, menu);
}
private void destroyUIOverlay() {
moveOverlay.removeFromMap();
centerChangeHandlerRegistration.removeHandler();
menu.removeFromParent();
}
private void cleanupChart() {
editMarkPositionPanel.getXAxis().removePlotLine(redTimeLine);
editMarkPositionPanel.updateRedPoint(polylineFixIndex);
editMarkPositionPanel.resetPointColor(polylineFixIndex);
editMarkPositionPanel.redrawChart();
}
private void setRedPointInChart(GPSFixDTO fix) {
List<GPSFixDTO> fixes = editMarkPositionPanel.getMarkFixes();
if (fixes != null) {
if (newFix) {
fixes.add(polylineFixIndex, fix);
} else {
fixes.set(polylineFixIndex, fix);
}
Point[] points = editMarkPositionPanel.getSeriesPoints(fixes);
editMarkPositionPanel.setRedPoint(points, polylineFixIndex);
editMarkPositionPanel.setSeriesPoints(points);
editMarkPositionPanel.redrawChart();
}
}
public void cancel() {
destroyUIOverlay();
cleanupChart();
if (polylinePath != null) {
if (newFix) {
polylinePath.removeAt(polylineFixIndex);
} else {
polylinePath.setAt(polylineFixIndex, overlay.getLatLngPosition());
}
}
callback.onFailure(null);
}
}
@@ -0,0 +1,71 @@
package com.sap.sailing.gwt.ui.client.shared.charts;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.gwt.ui.client.shared.charts.RaceIdentifierToLeaderboardRaceColumnAndFleetMapper.LeaderboardNameRaceColumnNameAndFleetName;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.MarkDTO;
public interface MarkPositionService {
public static class MarkTrackDTO implements IsSerializable {
private MarkDTO mark;
private Iterable<GPSFixDTO> fixes;
private boolean thinnedOut;
MarkTrackDTO() {} // for GWT serialization
public MarkTrackDTO(MarkDTO mark, Iterable<GPSFixDTO> fixes, boolean thinnedOut) {
this.mark = mark;
this.fixes = fixes;
this.thinnedOut = thinnedOut;
}
public MarkDTO getMark() {
return mark;
}
public Iterable<GPSFixDTO> getFixes() {
return fixes;
}
public boolean isThinnedOut() {
return thinnedOut;
}
}
public static class MarkTracksDTO implements IsSerializable {
private Iterable<MarkTrackDTO> tracks;
MarkTracksDTO() {} // for GWT serialization
public MarkTracksDTO(Iterable<MarkTrackDTO> tracks) {
this.tracks = tracks;
}
public Iterable<MarkTrackDTO> getTracks() {
return tracks;
}
}
// Jonas Dann: I had no great idea for handling of massive amount of fixes. An exception is no real option. I
// think the fixes have to be thinned out somehow and maybe shown in more detail when zoomed in. The aggregation of
// fixes is in my eyes no option, because then you have to define behaviour when moving them.
// I had no remaining time to incorporate the thinned out variable in my code.
void getMarkTracks(LeaderboardNameRaceColumnNameAndFleetName raceIdentifier, AsyncCallback<MarkTracksDTO> callback);
/**
* The service may decide whether a mark fix can be removed. It may, for example, be impossible to
* cleanly remove a mark fix if a tracked race already exists and the mark fixes are already part of
* the GPS fix track which currently does not support a remove operation. However, when only the
* regatta log is the basis of the service and no tracked race exists yet, mark fixes may be removed
* by revoking the device mappings.
*/
void canRemoveMarkFix(LeaderboardNameRaceColumnNameAndFleetName raceIdentifier, MarkDTO mark, GPSFixDTO fix, AsyncCallback<Boolean> callback);
void removeMarkFix(LeaderboardNameRaceColumnNameAndFleetName raceIdentifier, MarkDTO mark, GPSFixDTO fix, AsyncCallback<Void> callback);
void addMarkFix(LeaderboardNameRaceColumnNameAndFleetName raceIdentifier, MarkDTO mark, GPSFixDTO newFix, AsyncCallback<Void> callback);
void editMarkFix(LeaderboardNameRaceColumnNameAndFleetName raceIdentifier, MarkDTO mark, GPSFixDTO oldFix, Position newPosition, AsyncCallback<Void> callback);
}
@@ -0,0 +1,45 @@
package com.sap.sailing.gwt.ui.client.shared.charts;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.shared.charts.RaceIdentifierToLeaderboardRaceColumnAndFleetMapper.LeaderboardNameRaceColumnNameAndFleetName;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.MarkDTO;
public class MarkPositionServiceForSailingService implements MarkPositionService {
private final SailingServiceAsync sailingService;
public MarkPositionServiceForSailingService(SailingServiceAsync sailingService) {
super();
this.sailingService = sailingService;
}
@Override
public void getMarkTracks(LeaderboardNameRaceColumnNameAndFleetName raceIdentifier, AsyncCallback<MarkTracksDTO> callback) {
sailingService.getMarkTracks(raceIdentifier.getLeaderboardName(), raceIdentifier.getRaceColumnName(), raceIdentifier.getFleetName(), callback);
}
@Override
public void canRemoveMarkFix(LeaderboardNameRaceColumnNameAndFleetName raceIdentifier, MarkDTO mark,
GPSFixDTO fix, AsyncCallback<Boolean> callback) {
sailingService.canRemoveMarkFix(raceIdentifier.getLeaderboardName(), raceIdentifier.getRaceColumnName(), raceIdentifier.getFleetName(), mark.getIdAsString(), fix, callback);
}
@Override
public void removeMarkFix(LeaderboardNameRaceColumnNameAndFleetName raceIdentifier, MarkDTO mark, GPSFixDTO fix, AsyncCallback<Void> callback) {
sailingService.removeMarkFix(raceIdentifier.getLeaderboardName(), raceIdentifier.getRaceColumnName(), raceIdentifier.getFleetName(), mark.getIdAsString(), fix, callback);
}
@Override
public void addMarkFix(LeaderboardNameRaceColumnNameAndFleetName raceIdentifier, MarkDTO mark, GPSFixDTO newFix, AsyncCallback<Void> callback) {
sailingService.addMarkFix(raceIdentifier.getLeaderboardName(), raceIdentifier.getRaceColumnName(), raceIdentifier.getFleetName(), mark.getIdAsString(), newFix, callback);
}
@Override
public void editMarkFix(LeaderboardNameRaceColumnNameAndFleetName raceIdentifier, MarkDTO mark, GPSFixDTO oldFix,
Position newPosition, AsyncCallback<Void> callback) {
sailingService.editMarkFix(raceIdentifier.getLeaderboardName(), raceIdentifier.getRaceColumnName(), raceIdentifier.getFleetName(), mark.getIdAsString(), oldFix, newPosition, callback);
}
}
@@ -0,0 +1,164 @@
package com.sap.sailing.gwt.ui.client.shared.charts;
import java.util.Date;
import com.google.gwt.cell.client.ButtonCell;
import com.google.gwt.cell.client.FieldUpdater;
import com.google.gwt.cell.client.TextCell;
import com.google.gwt.core.client.Callback;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.cellview.client.Header;
import com.google.gwt.user.cellview.client.TextHeader;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.SingleSelectionModel;
import com.sap.sailing.domain.common.InvertibleComparator;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SortingOrder;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.charts.EditMarkPositionPanel.NotificationType;
import com.sap.sailing.gwt.ui.client.shared.controls.FlushableSortedCellTableWithStylableHeaders;
import com.sap.sailing.gwt.ui.client.shared.controls.SortableColumn;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardTableResources;
import com.sap.sailing.gwt.ui.shared.MarkDTO;
import com.sap.sse.common.settings.AbstractSettings;
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel;
import com.sap.sse.gwt.client.shared.components.Component;
import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent;
public class MarksPanel extends SimplePanel implements Component<AbstractSettings> {
private static final LeaderboardTableResources tableResources = GWT.create(LeaderboardTableResources.class);
private final ListDataProvider<MarkDTO> markDataProvider;
private final FlushableSortedCellTableWithStylableHeaders<MarkDTO> markTable;
public MarksPanel(final EditMarkPositionPanel parent, final ListDataProvider<MarkDTO> markDataProvider, final StringMessages stringMessages) {
this.markDataProvider = markDataProvider;
setTitle(stringMessages.marks());
markTable = new FlushableSortedCellTableWithStylableHeaders<MarkDTO>(10000, tableResources);
SortableColumn<MarkDTO, String> markNameColumn = new SortableColumn<MarkDTO, String>(new TextCell(), SortingOrder.NONE) {
@Override
public String getValue(MarkDTO object) {
return object.getName();
}
@Override
public InvertibleComparator<MarkDTO> getComparator() {
return null;
}
@Override
public Header<?> getHeader() {
return null;
}
};
markTable.addColumn(markNameColumn, new TextHeader(stringMessages.marks()));
SortableColumn<MarkDTO, String> addFixColumn = new SortableColumn<MarkDTO, String>(new ButtonCell(), SortingOrder.NONE) {
@Override
public String getValue(MarkDTO object) {
return stringMessages.addNewFix();
}
@Override
public InvertibleComparator<MarkDTO> getComparator() {
return null;
}
@Override
public Header<?> getHeader() {
return null;
}
};
addFixColumn.setFieldUpdater(new FieldUpdater<MarkDTO, String>() {
@Override
public void update(int index, final MarkDTO mark, String value) {
final Date timePoint = parent.timer.getTime();
if (parent.hasFixAtTimePoint(mark, timePoint)) {
parent.showNotification(stringMessages.pleaseSelectOtherTimepoint(), NotificationType.ERROR);
} else {
parent.createFixPositionChooserToAddFixToMark(mark, new Callback<Position, Exception>() {
@Override
public void onFailure(Exception reason) {
parent.resetCurrentFixPositionChooser();
}
@Override
public void onSuccess(Position result) {
parent.addMarkFix(mark, timePoint, result);
parent.resetCurrentFixPositionChooser();
}
});
}
}
});
markTable.addColumn(addFixColumn, new TextHeader(""));
SingleSelectionModel<MarkDTO> selectionModel = new RefreshableSingleSelectionModel<MarkDTO>(new EntityIdentityComparator<MarkDTO>() {
@Override
public boolean representSameEntity(MarkDTO dto1, MarkDTO dto2) {
return dto1.getIdAsString().equals(dto2.getIdAsString());
}
@Override
public int hashCode(MarkDTO t) {
return t.getIdAsString().hashCode();
}
},this.markDataProvider);
markTable.setSelectionModel(selectionModel);
markTable.getSelectionModel().addSelectionChangeHandler(parent);
markDataProvider.addDataDisplay(markTable);
setWidget(markTable);
}
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
}
@Override
public String getLocalizedShortName() {
return null;
}
@Override
public Widget getEntryWidget() {
return this;
}
@Override
public boolean hasSettings() {
return false;
}
@Override
public SettingsDialogComponent<AbstractSettings> getSettingsDialogComponent() {
return null;
}
@Override
public void updateSettings(AbstractSettings newSettings) {
}
@Override
public String getDependentCssClassName() {
return null;
}
public MarkDTO getSelectedMark() {
for (MarkDTO mark : markDataProvider.getList()) {
if (markTable.getSelectionModel().isSelected(mark)) {
return mark;
}
}
return null;
}
public void deselectMark() {//FIXME
markTable.getSelectionModel().setSelected(getSelectedMark(), false);
}
public void select(MarkDTO mark) {
markTable.getSelectionModel().setSelected(mark, true);
}
}
@@ -0,0 +1,79 @@
package com.sap.sailing.gwt.ui.client.shared.charts;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.FleetDTO;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
/**
* Given a {@link LeaderboardDTO}, maps a {@link RegattaAndRaceIdentifier} to the triple of
* leaderboard, race column and fleet which is useful to identify a race log and/or regatta
* log.<p>
*
* The {@link LeaderboardDTO} can be {@link #setLeaderboard updated} at any time which may affect the result of resolving
* the race identifier. A typical case would be changing a race's tracking status or attaching or
* detaching the tracked race from the race column.
*
* @author Axel Uhl (D043530)
*
*/
public class RaceIdentifierToLeaderboardRaceColumnAndFleetMapper {
private LeaderboardDTO leaderboard;
public static class LeaderboardNameRaceColumnNameAndFleetName {
private final String leaderboardName;
private final String raceColumnName;
private final String fleetName;
public LeaderboardNameRaceColumnNameAndFleetName(String leaderboardName, String raceColumnName, String fleetName) {
super();
this.leaderboardName = leaderboardName;
this.raceColumnName = raceColumnName;
this.fleetName = fleetName;
}
public String getLeaderboardName() {
return leaderboardName;
}
public String getRaceColumnName() {
return raceColumnName;
}
public String getFleetName() {
return fleetName;
}
}
public void setLeaderboard(LeaderboardDTO leaderboard) {
this.leaderboard = leaderboard;
}
public LeaderboardDTO getLeaderboard() {
return leaderboard;
}
public RaceColumnDTO getColumn(RegattaAndRaceIdentifier raceIdentifier) {
RaceColumnDTO result = null;
if (leaderboard != null) {
for (RaceColumnDTO columnDTO : leaderboard.getRaceList()) {
if (columnDTO.containsRace(raceIdentifier)) {
result = columnDTO;
break;
}
}
}
return result;
}
public LeaderboardNameRaceColumnNameAndFleetName getLeaderboardNameAndRaceColumnNameAndFleetName(RegattaAndRaceIdentifier raceIdentifier) {
final LeaderboardNameRaceColumnNameAndFleetName result;
final RaceColumnDTO raceColumn = getColumn(raceIdentifier);
if (raceColumn == null) {
result = null;
} else {
final FleetDTO fleet = raceColumn.getFleet(raceIdentifier);
if (fleet == null) {
result = null;
} else {
result = new LeaderboardNameRaceColumnNameAndFleetName(leaderboard.name, raceColumn.getName(), fleet.getName());
}
}
return result;
}
}
@@ -93,7 +93,7 @@ public class WindChart extends AbstractRaceChart implements Component<WindChartS
public WindChart(WindChartLifecycle windChartLifecycle, SailingServiceAsync sailingService, RegattaAndRaceIdentifier selectedRaceIdentifier, Timer timer,
TimeRangeWithZoomProvider timeRangeWithZoomProvider, WindChartSettings settings, final StringMessages stringMessages,
AsyncActionsExecutor asyncActionsExecutor, ErrorReporter errorReporter, boolean compactChart) {
super(sailingService, timer, timeRangeWithZoomProvider, stringMessages, asyncActionsExecutor, errorReporter);
super(sailingService, selectedRaceIdentifier, timer, timeRangeWithZoomProvider, stringMessages, asyncActionsExecutor, errorReporter);
this.windChartLifecycle = windChartLifecycle;
this.settings = settings;
windSourceDirectionSeries = new HashMap<WindSource, Series>();
@@ -190,7 +190,6 @@ public class WindChart extends AbstractRaceChart implements Component<WindChartS
.getXAxis().setAxisTitle(null);
}
setSize("100%", "100%");
this.selectedRaceIdentifier = selectedRaceIdentifier;
if (selectedRaceIdentifier != null) {
clearCacheAndReload();
if (isVisible()) {
@@ -543,12 +542,6 @@ public class WindChart extends AbstractRaceChart implements Component<WindChartS
}
}
private void updateTimePlotLine(Date date) {
chart.getXAxis().removePlotLine(timePlotLine);
timePlotLine.setValue(date.getTime());
chart.getXAxis().addPlotLines(timePlotLine);
}
@Override
public void onResize() {
chart.setSizeToMatchContainer();
@@ -11,7 +11,7 @@ import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.dto.BoatClassDTO;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTOWithSpeedWindTackAndLegType;
import com.sap.sailing.gwt.ui.shared.SpeedWithBearingDTO;
import com.sap.sailing.gwt.ui.shared.racemap.BoatClassVectorGraphics;
import com.sap.sailing.gwt.ui.shared.racemap.CanvasOverlayV3;
@@ -32,7 +32,7 @@ public class BoatOverlay extends CanvasOverlayV3 {
/**
* The current GPS fix used to draw the boat.
*/
private GPSFixDTO boatFix;
private GPSFixDTOWithSpeedWindTackAndLegType boatFix;
/**
* The rotation angle of the original boat image in degrees
@@ -142,7 +142,7 @@ public class BoatOverlay extends CanvasOverlayV3 {
}
}
public void setBoatFix(GPSFixDTO boatFix, long timeForPositionTransitionMillis) {
public void setBoatFix(GPSFixDTOWithSpeedWindTackAndLegType boatFix, long timeForPositionTransitionMillis) {
updateTransition(timeForPositionTransitionMillis);
this.boatFix = boatFix;
}
@@ -23,7 +23,7 @@ public class CourseMarkOverlay extends CanvasOverlayV3 {
/**
* The course mark to draw
*/
private MarkDTO mark;
private final MarkDTO mark;
private Position position;
@@ -171,6 +171,11 @@ public class CourseMarkOverlay extends CanvasOverlayV3 {
this.position = position;
}
public void setMarkPosition(LatLng positionLatLng) {
position = coordinateSystem.getPosition(positionLatLng);
draw();
}
/**
* The real-world position where the mark displayed by this overlay is located
*/
@@ -0,0 +1,114 @@
package com.sap.sailing.gwt.ui.client.shared.racemap;
import java.util.HashMap;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.base.Point;
import com.google.gwt.maps.client.base.Size;
import com.sap.sailing.domain.common.FixType;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.racemap.CanvasOverlayV3;
import com.sap.sailing.gwt.ui.shared.racemap.FixVectorGraphics;
import com.sap.sse.common.Util;
public class FixOverlay extends CanvasOverlayV3 {
private GPSFixDTO fix;
private final FixVectorGraphics fixVectorGraphics;
private final HashMap<Integer, Util.Pair<Double,Size>> fixScaleAndSizePerZoomCache;
private Double lastWidth;
private Double lastHeight;
private Double lastScaleFactor;
public FixOverlay(MapWidget map, int zIndex, GPSFixDTO fixDTO, FixType type, String color, CoordinateSystem coordinateSystem, String tooltip) {
super(map, zIndex, coordinateSystem);
fix = fixDTO;
fixVectorGraphics = new FixVectorGraphics(type, color);
fixScaleAndSizePerZoomCache = new HashMap<Integer, Util.Pair<Double,Size>>();
setCanvasSize(50, 50);
getCanvas().setTitle(tooltip);
}
@Override
protected void draw() {
if (mapProjection != null && fix != null) {
int zoom = map.getZoom();
Util.Pair<Double, Size> fixScaleAndSize = fixScaleAndSizePerZoomCache.get(zoom);
if (fixScaleAndSize == null) {
fixScaleAndSize = getFixScaleAndSize();
fixScaleAndSizePerZoomCache.put(zoom, fixScaleAndSize);
}
double fixSizeScaleFactor = fixScaleAndSize.getA();
// calculate canvas size
double canvasWidth = fixScaleAndSize.getB().getWidth();
double canvasHeight = fixScaleAndSize.getB().getHeight();
if (needToDraw(canvasWidth, canvasHeight, fixSizeScaleFactor)) {
setCanvasSize((int) canvasWidth, (int) canvasHeight);
Context2d context2d = getCanvas().getContext2d();
// draw the fix
fixVectorGraphics.drawMarkToCanvas(context2d, canvasWidth, canvasHeight, fixSizeScaleFactor);
lastScaleFactor = fixSizeScaleFactor;
lastWidth = canvasWidth;
lastHeight = canvasHeight;
}
setLatLngPosition(coordinateSystem.toLatLng(fix.position));
Point fixPositionInPx = mapProjection.fromLatLngToDivPixel(coordinateSystem.toLatLng(fix.position));
setCanvasPosition(fixPositionInPx.getX() - canvasWidth / 2.0, fixPositionInPx.getY() - canvasHeight / 2.0);
}
}
/**
* Compares the drawing parameters to the <code>last...</code>. If anything has
* changed, the result is <code>true</code>.
*/
private boolean needToDraw(double width, double height, double scaleFactor) {
return lastScaleFactor == null || lastScaleFactor != scaleFactor ||
lastWidth == null || lastWidth != width ||
lastHeight == null || lastHeight != height;
}
public Util.Pair<Double, Size> getFixScaleAndSize() {
double minFixHeight = 20;
// the original fix vector graphics is too small (2.1m x 1.5m) for higher zoom levels
// therefore we scale the fixes with factor 2 by default
double buoyScaleFactor = 2.0;
Size fixSizeInPixel = calculateBoundingBox(mapProjection, fix.position,
fixVectorGraphics.getFixWidthInMeters() * buoyScaleFactor, fixVectorGraphics.getFixHeightInMeters() * buoyScaleFactor);
double fixHeightInPixel = fixSizeInPixel.getHeight();
if(fixHeightInPixel < minFixHeight)
fixHeightInPixel = minFixHeight;
// The coordinates of the canvas drawing methods are based on the 'centimeter' unit (1px = 1cm).
// To calculate the display real fix size the scale factor from canvas units to the real
double fixSizeScaleFactor = fixHeightInPixel / (fixVectorGraphics.getFixHeightInMeters() * 100);
return new Util.Pair<Double, Size>(fixSizeScaleFactor, Size.newInstance(fixHeightInPixel * 2.0, fixHeightInPixel * 2.0));
}
public GPSFixDTO getGPSFixDTO() {
return fix;
}
public void setGPSFixDTO(GPSFixDTO fix) {
this.fix = fix;
draw();
}
public String getColor() {
return fixVectorGraphics.getColor();
}
public FixType getType() {
return fixVectorGraphics.getType();
}
public CoordinateSystem getCoordinateSystem() {
return coordinateSystem;
}
}
@@ -18,15 +18,15 @@ import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTOWithSpeedWindTackAndLegType;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Triple;
/**
* Manages the cache of {@link GPSFixDTO}s for the competitors and the polylines encoding the tails that visualize the
* Manages the cache of {@link GPSFixDTOWithSpeedWindTackAndLegType}s for the competitors and the polylines encoding the tails that visualize the
* course the boats took. The tails are based on the GPS fix data. This class offers methods to update the fixes and
* the tails, making sure that the data is always managed consistently. In particular, it keeps an eye on
* {@link GPSFixDTO#extrapolated extrapolated fixes}. Those are just a guess where a boat may have been and will need
* {@link GPSFixDTOWithSpeedWindTackAndLegType#extrapolated extrapolated fixes}. Those are just a guess where a boat may have been and will need
* to be removed once actual data for that time is available.
*
* @author Axel Uhl (d043530)
@@ -36,16 +36,16 @@ public class FixesAndTails {
/**
* Fixes of each competitors tail. If a list is contained for a competitor, the list contains a timely "contiguous"
* list of fixes for the competitor. This means the server has no more data for the time interval covered, unless
* the last fix was {@link GPSFixDTO#extrapolated obtained by extrapolation}.
* the last fix was {@link GPSFixDTOWithSpeedWindTackAndLegType#extrapolated obtained by extrapolation}.
* <p>
*
* If the fixes for a competitor contain an {@link GPSFixDTO#extrapolated extrapolated} fix, that fix is always
* If the fixes for a competitor contain an {@link GPSFixDTOWithSpeedWindTackAndLegType#extrapolated extrapolated} fix, that fix is always
* guaranteed to be the last element of the list when outside the execution of a method on this class. This in
* particular means that when more fixes are added, and there is now one fix later than the extrapolated fix, the
* extrapolated fix will be removed, re-establishing the invariant of an extrapolated fix always being the last
* in the list.
*/
private final Map<CompetitorDTO, List<GPSFixDTO>> fixes;
private final Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> fixes;
/**
* Tails of competitors currently displayed as overlays on the map. A tail may have an {@link MVCArray#getLength()
@@ -72,19 +72,19 @@ public class FixesAndTails {
public FixesAndTails(CoordinateSystem coordinateSystem) {
this.coordinateSystem = coordinateSystem;
fixes = new HashMap<CompetitorDTO, List<GPSFixDTO>>();
fixes = new HashMap<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>>();
tails = new HashMap<CompetitorDTO, Polyline>();
firstShownFix = new HashMap<CompetitorDTO, Integer>();
lastShownFix = new HashMap<CompetitorDTO, Integer>();
}
/**
* @return the list of fixes cached for the competitor; if a fix is {@link GPSFixDTO#extrapolated extrapolated}, it
* @return the list of fixes cached for the competitor; if a fix is {@link GPSFixDTOWithSpeedWindTackAndLegType#extrapolated extrapolated}, it
* must be the last fix in the list. <code>null</code> may be returned in no fixes are cached for
* <code>competitor</code>. The list returned is unmodifiable for the caller.
*/
public List<GPSFixDTO> getFixes(CompetitorDTO competitor) {
final List<GPSFixDTO> competitorFixes = fixes.get(competitor);
public List<GPSFixDTOWithSpeedWindTackAndLegType> getFixes(CompetitorDTO competitor) {
final List<GPSFixDTOWithSpeedWindTackAndLegType> competitorFixes = fixes.get(competitor);
return competitorFixes == null ? null : Collections.unmodifiableList(competitorFixes);
}
@@ -108,20 +108,20 @@ public class FixesAndTails {
}
/**
* When for a competitor the last fix obtained from the server is {@link GPSFixDTO#extrapolated extrapolated}, the quality
* When for a competitor the last fix obtained from the server is {@link GPSFixDTOWithSpeedWindTackAndLegType#extrapolated extrapolated}, the quality
* of this fix depends on the time difference between the extrapolated fix's time point and the last non-extrapolated
* fix's time point. This time difference in milliseconds is returned by this method, or <code>0</code> in case
* the last fix for <code>competitor</code> is not extrapolated.
*/
protected long getMillisecondsBetweenExtrapolatedAndLastNonExtrapolatedFix(CompetitorDTO competitor) {
List<GPSFixDTO> competitorFixes = getFixes(competitor);
List<GPSFixDTOWithSpeedWindTackAndLegType> competitorFixes = getFixes(competitor);
final long result;
if (competitorFixes == null || competitorFixes.size() < 2 || !competitorFixes.get(competitorFixes.size()-1).extrapolated) {
result = 0;
} else {
// last fix is extrapolated and another fix present; check time difference
GPSFixDTO extrapolatedFix = competitorFixes.get(competitorFixes.size()-1);
GPSFixDTO fixBeforeExtrapolated = competitorFixes.get(competitorFixes.size()-2);
GPSFixDTOWithSpeedWindTackAndLegType extrapolatedFix = competitorFixes.get(competitorFixes.size()-1);
GPSFixDTOWithSpeedWindTackAndLegType fixBeforeExtrapolated = competitorFixes.get(competitorFixes.size()-2);
result = extrapolatedFix.timepoint.getTime() - fixBeforeExtrapolated.timepoint.getTime();
}
return result;
@@ -137,13 +137,13 @@ public class FixesAndTails {
*/
protected Polyline createTailAndUpdateIndices(final CompetitorDTO competitorDTO, Date from, Date to, TailFactory tailFactory) {
List<LatLng> points = new ArrayList<LatLng>();
List<GPSFixDTO> fixesForCompetitor = getFixes(competitorDTO);
List<GPSFixDTOWithSpeedWindTackAndLegType> fixesForCompetitor = getFixes(competitorDTO);
int indexOfFirst = -1;
int indexOfLast = -1;
int i = 0;
// TODO consider binary search to find beginning of interesting segment faster
for (Iterator<GPSFixDTO> fixIter = fixesForCompetitor.iterator(); fixIter.hasNext() && indexOfLast == -1;) {
GPSFixDTO fix = fixIter.next();
for (Iterator<GPSFixDTOWithSpeedWindTackAndLegType> fixIter = fixesForCompetitor.iterator(); fixIter.hasNext() && indexOfLast == -1;) {
GPSFixDTOWithSpeedWindTackAndLegType fix = fixIter.next();
if (!fix.timepoint.before(to)) {
indexOfLast = i-1;
} else {
@@ -187,7 +187,7 @@ public class FixesAndTails {
* also {@link #updateTail}).
*
* @param fixesForCompetitors
* For each list the invariant must hold that an {@link GPSFixDTO#extrapolated extrapolated} fix must be
* For each list the invariant must hold that an {@link GPSFixDTOWithSpeedWindTackAndLegType#extrapolated extrapolated} fix must be
* the last one in the list
* @param overlapsWithKnownFixes
* if for a competitor whose fixes are provided in <code>fixesForCompetitors</code> this holds
@@ -211,15 +211,15 @@ public class FixesAndTails {
* we would be producing severe flicker. By delaying the command to the point when the update is applied we
* should avoid such flicker.
*/
protected Map<CompetitorDTO, Runnable> updateFixes(Map<CompetitorDTO, List<GPSFixDTO>> fixesForCompetitors,
protected Map<CompetitorDTO, Runnable> updateFixes(Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> fixesForCompetitors,
Map<CompetitorDTO, Boolean> overlapsWithKnownFixes, TailFactory tailFactory, long timeForPositionTransitionMillis) {
final Map<CompetitorDTO, Runnable> result = new HashMap<>();
final Runnable noOp = new Runnable() { @Override public void run() {} };
for (final Map.Entry<CompetitorDTO, List<GPSFixDTO>> e : fixesForCompetitors.entrySet()) {
for (final Map.Entry<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> e : fixesForCompetitors.entrySet()) {
if (e.getValue() != null && !e.getValue().isEmpty()) {
List<GPSFixDTO> fixesForCompetitor = fixes.get(e.getKey());
List<GPSFixDTOWithSpeedWindTackAndLegType> fixesForCompetitor = fixes.get(e.getKey());
if (fixesForCompetitor == null) {
fixesForCompetitor = new ArrayList<GPSFixDTO>();
fixesForCompetitor = new ArrayList<GPSFixDTOWithSpeedWindTackAndLegType>();
fixes.put(e.getKey(), fixesForCompetitor);
}
if (!overlapsWithKnownFixes.get(e.getKey())) {
@@ -255,26 +255,26 @@ public class FixesAndTails {
* competitor's tail, the tail is adjusted by inserting the corresponding fix.
* <p>
*
* If the last fix so far was an {@link GPSFixDTO#extrapolated extrapolated} fix, and the merge leads to a different
* If the last fix so far was an {@link GPSFixDTOWithSpeedWindTackAndLegType#extrapolated extrapolated} fix, and the merge leads to a different
* fix being the last one shown, the previously last fix that was extrapolated will be removed. This way, at most
* one extrapolated fix is shown, avoiding jitter on the map as actual fixes are received that obsolete the
* extrapolated ones.<p>
*
* Precondition: {@link #hasFixesFor(CompetitorDTO) hasFixesFor(competitorDTO)}<code>==true</code>
* @param mergeThis
* If this list contains an {@link GPSFixDTO#extrapolated extrapolated} fix, that fix must be the last in
* If this list contains an {@link GPSFixDTOWithSpeedWindTackAndLegType#extrapolated extrapolated} fix, that fix must be the last in
* the list
*/
private void mergeFixes(CompetitorDTO competitorDTO, List<GPSFixDTO> mergeThis, final long timeForPositionTransitionMillis) {
List<GPSFixDTO> intoThis = fixes.get(competitorDTO);
private void mergeFixes(CompetitorDTO competitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType> mergeThis, final long timeForPositionTransitionMillis) {
List<GPSFixDTOWithSpeedWindTackAndLegType> intoThis = fixes.get(competitorDTO);
int indexOfFirstShownFix = firstShownFix.get(competitorDTO) == null ? -1 : firstShownFix.get(competitorDTO);
int indexOfLastShownFix = lastShownFix.get(competitorDTO) == null ? -1 : lastShownFix.get(competitorDTO);
final Polyline tail = getTail(competitorDTO);
int intoThisIndex = 0;
for (GPSFixDTO mergeThisFix : mergeThis) {
intoThisIndex = Collections.binarySearch(intoThis, mergeThisFix, new Comparator<GPSFixDTO>() {
for (GPSFixDTOWithSpeedWindTackAndLegType mergeThisFix : mergeThis) {
intoThisIndex = Collections.binarySearch(intoThis, mergeThisFix, new Comparator<GPSFixDTOWithSpeedWindTackAndLegType>() {
@Override
public int compare(GPSFixDTO o1, GPSFixDTO o2) {
public int compare(GPSFixDTOWithSpeedWindTackAndLegType o1, GPSFixDTOWithSpeedWindTackAndLegType o2) {
return o1.timepoint.compareTo(o2.timepoint);
}
});
@@ -396,7 +396,7 @@ public class FixesAndTails {
tailPreparer.run();
}
int vertexCount = tail.getPath().getLength();
final List<GPSFixDTO> fixesForCompetitor = getFixes(competitorDTO);
final List<GPSFixDTOWithSpeedWindTackAndLegType> fixesForCompetitor = getFixes(competitorDTO);
int indexOfFirstShownFix = firstShownFix.get(competitorDTO) == null ? -1 : firstShownFix.get(competitorDTO);
// remove fixes before what is now to be the beginning of the polyline:
while (indexOfFirstShownFix != -1 && vertexCount > 0
@@ -411,7 +411,7 @@ public class FixesAndTails {
while (indexOfFirstShownFix > 0
&& !fixesForCompetitor.get(indexOfFirstShownFix - 1).timepoint.before(from)) {
indexOfFirstShownFix--;
GPSFixDTO fix = fixesForCompetitor.get(indexOfFirstShownFix);
GPSFixDTOWithSpeedWindTackAndLegType fix = fixesForCompetitor.get(indexOfFirstShownFix);
tail.getPath().insertAt(0, coordinateSystem.toLatLng(fix.position));
vertexCount++;
}
@@ -431,7 +431,7 @@ public class FixesAndTails {
while (indexOfLastShownFix < fixesForCompetitor.size() - 1
&& !fixesForCompetitor.get(indexOfLastShownFix + 1).timepoint.after(to)) {
indexOfLastShownFix++;
GPSFixDTO fix = fixesForCompetitor.get(indexOfLastShownFix);
GPSFixDTOWithSpeedWindTackAndLegType fix = fixesForCompetitor.get(indexOfLastShownFix);
tail.getPath().insertAt(vertexCount++, coordinateSystem.toLatLng(fix.position));
if (indexOfFirstShownFix < 0) { // empty tail before?
indexOfFirstShownFix = indexOfLastShownFix; // set to the first vertex inserted into tail
@@ -500,7 +500,7 @@ public class FixesAndTails {
Map<CompetitorDTO, Boolean> overlapWithKnownFixes = new HashMap<CompetitorDTO, Boolean>();
for (CompetitorDTO competitor : competitorsToShow) {
List<GPSFixDTO> fixesForCompetitor = getFixes(competitor);
List<GPSFixDTOWithSpeedWindTackAndLegType> fixesForCompetitor = getFixes(competitor);
Date fromDate;
Date toDate;
Date timepointOfLastKnownFix = fixesForCompetitor == null ? null : getTimepointOfLastNonExtrapolated(fixesForCompetitor);
@@ -535,8 +535,8 @@ public class FixesAndTails {
overlapWithKnownFixes);
}
private Date getTimepointOfFirstNonExtrapolated(List<GPSFixDTO> fixesForCompetitor) {
for (GPSFixDTO fix : fixesForCompetitor) {
private Date getTimepointOfFirstNonExtrapolated(List<GPSFixDTOWithSpeedWindTackAndLegType> fixesForCompetitor) {
for (GPSFixDTOWithSpeedWindTackAndLegType fix : fixesForCompetitor) {
if (!fix.extrapolated) {
return fix.timepoint;
}
@@ -544,11 +544,11 @@ public class FixesAndTails {
return null;
}
private Date getTimepointOfLastNonExtrapolated(List<GPSFixDTO> fixesForCompetitor) {
private Date getTimepointOfLastNonExtrapolated(List<GPSFixDTOWithSpeedWindTackAndLegType> fixesForCompetitor) {
if (!fixesForCompetitor.isEmpty()) {
for (ListIterator<GPSFixDTO> fixIter = fixesForCompetitor.listIterator(fixesForCompetitor.size() - 1); fixIter
for (ListIterator<GPSFixDTOWithSpeedWindTackAndLegType> fixIter = fixesForCompetitor.listIterator(fixesForCompetitor.size() - 1); fixIter
.hasPrevious();) {
GPSFixDTO fix = fixIter.previous();
GPSFixDTOWithSpeedWindTackAndLegType fix = fixIter.previous();
if (!fix.extrapolated) {
return fix.timepoint;
}
@@ -22,6 +22,7 @@ import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
import com.google.gwt.maps.client.LoadApi;
@@ -104,7 +105,7 @@ import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil;
import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO;
import com.sap.sailing.gwt.ui.shared.ControlPointDTO;
import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTOWithSpeedWindTackAndLegType;
import com.sap.sailing.gwt.ui.shared.LegInfoDTO;
import com.sap.sailing.gwt.ui.shared.ManeuverDTO;
import com.sap.sailing.gwt.ui.shared.MarkDTO;
@@ -248,7 +249,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
private WindInfoForRaceDTO lastCombinedWindTrackInfoDTO;
/**
* Manages the cached set of {@link GPSFixDTO}s for the boat positions as well as their graphical counterpart in the
* Manages the cached set of {@link GPSFixDTOWithSpeedWindTackAndLegType}s for the boat positions as well as their graphical counterpart in the
* form of {@link Polyline}s.
*/
private final FixesAndTails fixesAndTails;
@@ -274,6 +275,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
* Map overlays with html5 canvas used to display course marks including buoy zones
*/
private final Map<String, CourseMarkOverlay> courseMarkOverlays;
private final Map<String, HandlerRegistration> courseMarkClickHandlers;
private final Map<String, MarkDTO> markDTOs;
@@ -291,7 +294,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
private Map<CompetitorDTO, List<ManeuverDTO>> lastManeuverResult;
private Map<CompetitorDTO, List<GPSFixDTO>> lastDouglasPeuckerResult;
private Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> lastDouglasPeuckerResult;
private final CompetitorSelectionProvider competitorSelection;
@@ -427,6 +430,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
competitorInfoOverlays = new HashMap<CompetitorDTO, CompetitorInfoOverlay>();
windSensorOverlays = new HashMap<WindSource, WindSensorOverlay>();
courseMarkOverlays = new HashMap<String, CourseMarkOverlay>();
courseMarkClickHandlers = new HashMap<String, HandlerRegistration>();
this.competitorSelection = competitorSelection;
this.raceCompetitorSet = new RaceCompetitorSet(competitorSelection);
competitorSelection.addCompetitorSelectionChangeListener(this);
@@ -944,7 +948,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
simulationOverlay.updateLeg(Math.max(lastLegNumber, 1), /* clearCanvas */ false, raceMapDataDTO.simulationResultVersion);
}
// Do boat specific actions
Map<CompetitorDTO, List<GPSFixDTO>> boatData = raceMapDataDTO.boatPositions;
Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> boatData = raceMapDataDTO.boatPositions;
updateBoatPositions(newTime, transitionTimeInMillis, hasTailOverlapForCompetitor, competitorsToShow, boatData, /* updateTailsOnly */ false);
if (douglasMarkers != null) {
removeAllMarkDouglasPeuckerpoints();
@@ -1016,7 +1020,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
*/
private void updateBoatPositions(final Date newTime, final long transitionTimeInMillis,
final Map<CompetitorDTO, Boolean> hasTailOverlapForCompetitor,
final Iterable<CompetitorDTO> competitorsToShow, Map<CompetitorDTO, List<GPSFixDTO>> boatData, boolean updateTailsOnly) {
final Iterable<CompetitorDTO> competitorsToShow, Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> boatData, boolean updateTailsOnly) {
final Map<CompetitorDTO, Runnable> tailPreparersPerCompetitor =
fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis);
showBoatsOnMap(newTime, transitionTimeInMillis, competitorsToShow, updateTailsOnly, tailPreparersPerCompetitor);
@@ -1099,12 +1103,13 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
}
CourseMarkOverlay courseMarkOverlay = courseMarkOverlays.get(markDTO.getName());
if (courseMarkOverlay == null) {
courseMarkOverlay = createCourseMarkOverlay(RaceMapOverlaysZIndexes.COURSEMARK_ZINDEX, markDTO);
courseMarkOverlay = new CourseMarkOverlay(map, RaceMapOverlaysZIndexes.COURSEMARK_ZINDEX, markDTO, coordinateSystem);
courseMarkOverlay.setShowBuoyZone(settings.getHelpLinesSettings().isVisible(HelpLineTypes.BUOYZONE));
courseMarkOverlay.setBuoyZoneRadiusInMeter(settings.getBuoyZoneRadiusInMeters());
courseMarkOverlay.setSelected(isSelected);
courseMarkOverlays.put(markDTO.getName(), courseMarkOverlay);
markDTOs.put(markDTO.getName(), markDTO);
registerCourseMarkInfoWindowClickHandler(markDTO.getName());
courseMarkOverlay.addToMap();
} else {
courseMarkOverlay.setMarkPosition(markDTO.position, transitionTimeInMillis);
@@ -1185,7 +1190,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
competitorInfoOverlays.keySet());
for (CompetitorDTO competitorDTO : competitorsToShow) {
if (fixesAndTails.hasFixesFor(competitorDTO)) {
GPSFixDTO lastBoatFix = getBoatFix(competitorDTO, newTime);
GPSFixDTOWithSpeedWindTackAndLegType lastBoatFix = getBoatFix(competitorDTO, newTime);
if (lastBoatFix != null) {
CompetitorInfoOverlay competitorInfoOverlay = competitorInfoOverlays.get(competitorDTO);
if (competitorInfoOverlay == null) {
@@ -1332,7 +1337,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
com.sap.sse.common.Util.Pair<Integer, CompetitorDTO> visibleLeaderInfo = getFarthestAheadVisibleCompetitorWithOneBasedLegNumber(competitorsToShow);
// the boat fix may be null; may mean that no positions were loaded yet for the leading visible boat;
// don't show anything
GPSFixDTO lastBoatFix = null;
GPSFixDTOWithSpeedWindTackAndLegType lastBoatFix = null;
boolean isVisibleLeaderInfoComplete = false;
boolean isLegTypeKnown = false;
WindTrackInfoDTO windDataForLegMiddle = null;
@@ -1448,39 +1453,41 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
private void showStartLineToFirstMarkTriangle(final CoursePositionsDTO courseDTO){
final List<Position> startMarkPositions = courseDTO.getStartMarkPositions();
final Position windwardStartLinePosition = startMarkPositions.get(0);
final Position leewardStartLinePosition = startMarkPositions.get(1);
final Position firstMarkPosition = courseDTO.waypointPositions.get(1);
windwardStartLineMarkToFirstMarkLineText.replace(0, windwardStartLineMarkToFirstMarkLineText.length(),
stringMessages.startLineToFirstMarkTriangle(numberFormatOneDecimal
.format(windwardStartLinePosition.getDistance(firstMarkPosition)
.getMeters())));
leewardStartLineMarkToFirstMarkLineText.replace(0, leewardStartLineMarkToFirstMarkLineText.length(),
stringMessages.startLineToFirstMarkTriangle(numberFormatOneDecimal
.format(leewardStartLinePosition.getDistance(firstMarkPosition)
.getMeters())));
final LineInfoProvider windwardStartLineMarkToFirstMarkLineInfoProvider = new LineInfoProvider() {
@Override
public String getLineInfo() {
return windwardStartLineMarkToFirstMarkLineText.toString();
}
};
final LineInfoProvider leewardStartLineMarkToFirstMarkLineInfoProvider = new LineInfoProvider() {
@Override
public String getLineInfo() {
return leewardStartLineMarkToFirstMarkLineText.toString();
}
};
windwardStartLineMarkToFirstMarkLine = showOrRemoveOrUpdateLine(windwardStartLineMarkToFirstMarkLine, /* showLine */
(settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE))
&& startMarkPositions.size() > 1 && courseDTO.waypointPositions.size() > 1,
windwardStartLinePosition, firstMarkPosition, windwardStartLineMarkToFirstMarkLineInfoProvider,
"grey");
leewardStartLineMarkToFirstMarkLine = showOrRemoveOrUpdateLine(leewardStartLineMarkToFirstMarkLine, /* showLine */
(settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE))
&& startMarkPositions.size() > 1 && courseDTO.waypointPositions.size() > 1,
leewardStartLinePosition, firstMarkPosition, leewardStartLineMarkToFirstMarkLineInfoProvider,
"grey");
if (startMarkPositions.size() > 1 && courseDTO.waypointPositions.size() > 1) {
final Position windwardStartLinePosition = startMarkPositions.get(0);
final Position leewardStartLinePosition = startMarkPositions.get(1);
final Position firstMarkPosition = courseDTO.waypointPositions.get(1);
windwardStartLineMarkToFirstMarkLineText.replace(0, windwardStartLineMarkToFirstMarkLineText.length(),
stringMessages.startLineToFirstMarkTriangle(numberFormatOneDecimal
.format(windwardStartLinePosition.getDistance(firstMarkPosition)
.getMeters())));
leewardStartLineMarkToFirstMarkLineText.replace(0, leewardStartLineMarkToFirstMarkLineText.length(),
stringMessages.startLineToFirstMarkTriangle(numberFormatOneDecimal
.format(leewardStartLinePosition.getDistance(firstMarkPosition)
.getMeters())));
final LineInfoProvider windwardStartLineMarkToFirstMarkLineInfoProvider = new LineInfoProvider() {
@Override
public String getLineInfo() {
return windwardStartLineMarkToFirstMarkLineText.toString();
}
};
final LineInfoProvider leewardStartLineMarkToFirstMarkLineInfoProvider = new LineInfoProvider() {
@Override
public String getLineInfo() {
return leewardStartLineMarkToFirstMarkLineText.toString();
}
};
windwardStartLineMarkToFirstMarkLine = showOrRemoveOrUpdateLine(windwardStartLineMarkToFirstMarkLine, /* showLine */
(settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE))
&& startMarkPositions.size() > 1 && courseDTO.waypointPositions.size() > 1,
windwardStartLinePosition, firstMarkPosition, windwardStartLineMarkToFirstMarkLineInfoProvider,
"grey");
leewardStartLineMarkToFirstMarkLine = showOrRemoveOrUpdateLine(leewardStartLineMarkToFirstMarkLine, /* showLine */
(settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE))
&& startMarkPositions.size() > 1 && courseDTO.waypointPositions.size() > 1,
leewardStartLinePosition, firstMarkPosition, leewardStartLineMarkToFirstMarkLineInfoProvider,
"grey");
}
}
private final StringBuilder startLineAdvantageText = new StringBuilder();
@@ -1654,47 +1661,49 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
*/
private Polyline showOrRemoveOrUpdateLine(Polyline lineToShowOrRemoveOrUpdate, final boolean showLine,
final Position position1DTO, final Position position2DTO, final LineInfoProvider lineInfoProvider, String lineColorRGB) {
if (showLine) {
LatLng courseMiddleLinePoint1 = coordinateSystem.toLatLng(position1DTO);
LatLng courseMiddleLinePoint2 = coordinateSystem.toLatLng(position2DTO);
final MVCArray<LatLng> pointsAsArray;
if (lineToShowOrRemoveOrUpdate == null) {
PolylineOptions options = PolylineOptions.newInstance();
options.setClickable(true);
options.setGeodesic(true);
options.setStrokeColor(lineColorRGB);
options.setStrokeWeight(1);
options.setStrokeOpacity(1.0);
pointsAsArray = MVCArray.newInstance();
lineToShowOrRemoveOrUpdate = Polyline.newInstance(options);
lineToShowOrRemoveOrUpdate.setPath(pointsAsArray);
lineToShowOrRemoveOrUpdate.setMap(map);
Hoverline lineToShowOrRemoveOrUpdateHoverline = new Hoverline(lineToShowOrRemoveOrUpdate, options, this);
lineToShowOrRemoveOrUpdate.addMouseOverHandler(new MouseOverMapHandler() {
@Override
public void onEvent(MouseOverMapEvent event) {
map.setTitle(lineInfoProvider.getLineInfo());
}
});
lineToShowOrRemoveOrUpdateHoverline.addMouseOutMoveHandler(new MouseOutMapHandler() {
@Override
public void onEvent(MouseOutMapEvent event) {
map.setTitle("");
}
});
if (position1DTO != null && position2DTO != null) {
if (showLine) {
LatLng courseMiddleLinePoint1 = coordinateSystem.toLatLng(position1DTO);
LatLng courseMiddleLinePoint2 = coordinateSystem.toLatLng(position2DTO);
final MVCArray<LatLng> pointsAsArray;
if (lineToShowOrRemoveOrUpdate == null) {
PolylineOptions options = PolylineOptions.newInstance();
options.setClickable(true);
options.setGeodesic(true);
options.setStrokeColor(lineColorRGB);
options.setStrokeWeight(1);
options.setStrokeOpacity(1.0);
pointsAsArray = MVCArray.newInstance();
lineToShowOrRemoveOrUpdate = Polyline.newInstance(options);
lineToShowOrRemoveOrUpdate.setPath(pointsAsArray);
lineToShowOrRemoveOrUpdate.setMap(map);
Hoverline lineToShowOrRemoveOrUpdateHoverline = new Hoverline(lineToShowOrRemoveOrUpdate, options, this);
lineToShowOrRemoveOrUpdate.addMouseOverHandler(new MouseOverMapHandler() {
@Override
public void onEvent(MouseOverMapEvent event) {
map.setTitle(lineInfoProvider.getLineInfo());
}
});
lineToShowOrRemoveOrUpdateHoverline.addMouseOutMoveHandler(new MouseOutMapHandler() {
@Override
public void onEvent(MouseOutMapEvent event) {
map.setTitle("");
}
});
} else {
pointsAsArray = lineToShowOrRemoveOrUpdate.getPath();
pointsAsArray.removeAt(1);
pointsAsArray.removeAt(0);
}
adjustInfoOverlayForVisibleLine(lineToShowOrRemoveOrUpdate, position1DTO, position2DTO, lineInfoProvider);
pointsAsArray.insertAt(0, courseMiddleLinePoint1);
pointsAsArray.insertAt(1, courseMiddleLinePoint2);
} else {
pointsAsArray = lineToShowOrRemoveOrUpdate.getPath();
pointsAsArray.removeAt(1);
pointsAsArray.removeAt(0);
}
adjustInfoOverlayForVisibleLine(lineToShowOrRemoveOrUpdate, position1DTO, position2DTO, lineInfoProvider);
pointsAsArray.insertAt(0, courseMiddleLinePoint1);
pointsAsArray.insertAt(1, courseMiddleLinePoint2);
} else {
if (lineToShowOrRemoveOrUpdate != null) {
lineToShowOrRemoveOrUpdate.setMap(null);
adjustInfoOverlayForRemovedLine(lineToShowOrRemoveOrUpdate);
lineToShowOrRemoveOrUpdate = null;
if (lineToShowOrRemoveOrUpdate != null) {
lineToShowOrRemoveOrUpdate.setMap(null);
adjustInfoOverlayForRemovedLine(lineToShowOrRemoveOrUpdate);
lineToShowOrRemoveOrUpdate = null;
}
}
}
return lineToShowOrRemoveOrUpdate;
@@ -1766,7 +1775,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
return Math.log((1 + sine) / (1 - sine)) / 2;
}
private int getZoomLevel(LatLngBounds bounds) {
public int getZoomLevel(LatLngBounds bounds) {
int GLOBE_PXSIZE = 256; // a constant in Google's map projection
int MAX_ZOOM = 20; // maximum zoom-level that should be automatically selected
double LOG2 = Math.log(2.0);
@@ -1846,7 +1855,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
*/
private boolean updateBoatCanvasForCompetitor(CompetitorDTO competitorDTO, Date date, long timeForPositionTransitionMillis) {
boolean usedExistingCanvas = false;
GPSFixDTO lastBoatFix = getBoatFix(competitorDTO, date);
GPSFixDTOWithSpeedWindTackAndLegType lastBoatFix = getBoatFix(competitorDTO, date);
if (lastBoatFix != null) {
BoatOverlay boatOverlay = boatOverlays.get(competitorDTO);
if (boatOverlay == null) {
@@ -1869,17 +1878,42 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
private boolean displayHighlighted(CompetitorDTO competitorDTO) {
return !settings.isShowOnlySelectedCompetitors() && competitorSelection.isSelected(competitorDTO);
}
protected CourseMarkOverlay createCourseMarkOverlay(int zIndex, final MarkDTO markDTO) {
final CourseMarkOverlay courseMarkOverlay = new CourseMarkOverlay(map, zIndex, markDTO, coordinateSystem);
courseMarkOverlay.addClickHandler(new ClickMapHandler() {
@Override
public void onEvent(ClickMapEvent event) {
LatLng latlng = courseMarkOverlay.getMarkLatLngPosition();
showMarkInfoWindow(markDTO, latlng);
}
});
return courseMarkOverlay;
private class CourseMarkInfoWindowClickHandler implements ClickMapHandler {
private final MarkDTO markDTO;
private final CourseMarkOverlay courseMarkOverlay;
public CourseMarkInfoWindowClickHandler(MarkDTO markDTO, CourseMarkOverlay courseMarkOverlay) {
this.markDTO = markDTO;
this.courseMarkOverlay = courseMarkOverlay;
}
@Override
public void onEvent(ClickMapEvent event) {
LatLng latlng = courseMarkOverlay.getMarkLatLngPosition();
showMarkInfoWindow(markDTO, latlng);
}
}
private void registerCourseMarkInfoWindowClickHandler(final String markDTOName) {
final CourseMarkOverlay courseMarkOverlay = courseMarkOverlays.get(markDTOName);
courseMarkClickHandlers.put(markDTOName,
courseMarkOverlay.addClickHandler(new CourseMarkInfoWindowClickHandler(markDTOs.get(markDTOName), courseMarkOverlay)));
}
public void registerAllCourseMarkInfoWindowClickHandlers() {
for (String markDTOName : markDTOs.keySet()) {
registerCourseMarkInfoWindowClickHandler(markDTOName);
}
}
public void unregisterAllCourseMarkInfoWindowClickHandlers() {
Iterator<Entry<String, HandlerRegistration>> iterator = courseMarkClickHandlers.entrySet().iterator();
while(iterator.hasNext()) {
Entry<String, HandlerRegistration> handler = iterator.next();
handler.getValue().removeHandler();
iterator.remove();
}
}
private CompetitorInfoOverlay createCompetitorInfoOverlay(int zIndex, final CompetitorDTO competitorDTO) {
@@ -1896,7 +1930,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
if (lastInfoWindow != null) {
lastInfoWindow.close();
}
GPSFixDTO latestFixForCompetitor = getBoatFix(competitorDTO, timer.getTime());
GPSFixDTOWithSpeedWindTackAndLegType latestFixForCompetitor = getBoatFix(competitorDTO, timer.getTime());
LatLng where = coordinateSystem.toLatLng(latestFixForCompetitor.position);
InfoWindowOptions options = InfoWindowOptions.newInstance();
InfoWindow infoWindow = InfoWindow.newInstance(options);
@@ -1951,7 +1985,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
if(lastInfoWindow != null) {
lastInfoWindow.close();
}
GPSFixDTO latestFixForCompetitor = getBoatFix(competitorDTO, timer.getTime());
GPSFixDTOWithSpeedWindTackAndLegType latestFixForCompetitor = getBoatFix(competitorDTO, timer.getTime());
// TODO find close fix where the mouse was; see BUG 470
InfoWindowOptions options = InfoWindowOptions.newInstance();
InfoWindow infoWindow = InfoWindow.newInstance(options);
@@ -2045,7 +2079,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
return vPanel;
}
private Widget getInfoWindowContent(CompetitorDTO competitorDTO, GPSFixDTO lastFix) {
private Widget getInfoWindowContent(CompetitorDTO competitorDTO, GPSFixDTOWithSpeedWindTackAndLegType lastFix) {
final VerticalPanel vPanel = new VerticalPanel();
vPanel.setWidth("350px");
vPanel.add(createInfoWindowLabelAndValue(stringMessages.competitor(), competitorDTO.getName()));
@@ -2080,14 +2114,14 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
Map<CompetitorDTO, Date> to = new HashMap<CompetitorDTO, Date>();
to.put(competitorDTO, getBoatFix(competitorDTO, timer.getTime()).timepoint);
sailingService.getDouglasPoints(race, from, to, 3,
new AsyncCallback<Map<CompetitorDTO, List<GPSFixDTO>>>() {
new AsyncCallback<Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Error obtaining douglas positions: " + caught.getMessage(), true /*silentMode */);
}
@Override
public void onSuccess(Map<CompetitorDTO, List<GPSFixDTO>> result) {
public void onSuccess(Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> result) {
lastDouglasPeuckerResult = result;
if (douglasMarkers != null) {
removeAllMarkDouglasPeuckerpoints();
@@ -2169,15 +2203,15 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
}
}
private void showMarkDouglasPeuckerPoints(Map<CompetitorDTO, List<GPSFixDTO>> gpsFixPointMapForCompetitors) {
private void showMarkDouglasPeuckerPoints(Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> gpsFixPointMapForCompetitors) {
douglasMarkers = new HashSet<Marker>();
if (map != null && gpsFixPointMapForCompetitors != null) {
Set<CompetitorDTO> keySet = gpsFixPointMapForCompetitors.keySet();
Iterator<CompetitorDTO> iter = keySet.iterator();
while (iter.hasNext()) {
CompetitorDTO competitorDTO = iter.next();
List<GPSFixDTO> gpsFix = gpsFixPointMapForCompetitors.get(competitorDTO);
for (GPSFixDTO fix : gpsFix) {
List<GPSFixDTOWithSpeedWindTackAndLegType> gpsFix = gpsFixPointMapForCompetitors.get(competitorDTO);
for (GPSFixDTOWithSpeedWindTackAndLegType fix : gpsFix) {
LatLng latLng = coordinateSystem.toLatLng(fix.position);
MarkerOptions options = MarkerOptions.newInstance();
options.setTitle(fix.timepoint+": "+fix.position+", "+fix.speedWithBearing.toString());
@@ -2229,14 +2263,14 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
* @return The GPS fix for the given competitor from {@link #fixes} that is closest to <code>date</code>, or
* <code>null</code> if no fix is available
*/
private GPSFixDTO getBoatFix(CompetitorDTO competitorDTO, Date date) {
GPSFixDTO result = null;
List<GPSFixDTO> competitorFixes = fixesAndTails.getFixes(competitorDTO);
private GPSFixDTOWithSpeedWindTackAndLegType getBoatFix(CompetitorDTO competitorDTO, Date date) {
GPSFixDTOWithSpeedWindTackAndLegType result = null;
List<GPSFixDTOWithSpeedWindTackAndLegType> competitorFixes = fixesAndTails.getFixes(competitorDTO);
if (competitorFixes != null && !competitorFixes.isEmpty()) {
int i = Collections.binarySearch(competitorFixes, new GPSFixDTO(date, null, null, (WindDTO) null, null, null, false),
new Comparator<GPSFixDTO>() {
int i = Collections.binarySearch(competitorFixes, new GPSFixDTOWithSpeedWindTackAndLegType(date, null, null, (WindDTO) null, null, null, false),
new Comparator<GPSFixDTOWithSpeedWindTackAndLegType>() {
@Override
public int compare(GPSFixDTO o1, GPSFixDTO o2) {
public int compare(GPSFixDTOWithSpeedWindTackAndLegType o1, GPSFixDTOWithSpeedWindTackAndLegType o2) {
return o1.timepoint.compareTo(o2.timepoint);
}
});
@@ -2251,9 +2285,9 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
} else {
// competitorFixes must have at least two elements, and i points neither to the end nor the beginning;
// get the fix from i and i+1 whose timepoint is closer to date
final GPSFixDTO fixBefore = competitorFixes.get(i-1);
final GPSFixDTO fixAfter = competitorFixes.get(i);
final GPSFixDTO closer;
final GPSFixDTOWithSpeedWindTackAndLegType fixBefore = competitorFixes.get(i-1);
final GPSFixDTOWithSpeedWindTackAndLegType fixAfter = competitorFixes.get(i);
final GPSFixDTOWithSpeedWindTackAndLegType closer;
if (date.getTime() - fixBefore.timepoint.getTime() < fixAfter.timepoint.getTime() - date.getTime()) {
closer = fixBefore;
} else {
@@ -2282,12 +2316,12 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
factorForBefore*(fixBefore.speedWithBearing==null?0:fixBefore.speedWithBearing.speedInKnots) +
factorForAfter*(fixAfter.speedWithBearing==null?0:fixAfter.speedWithBearing.speedInKnots),
betweenBearing);
result = new GPSFixDTO(date, betweenPosition, betweenSpeed, closer.degreesBoatToTheWind,
result = new GPSFixDTOWithSpeedWindTackAndLegType(date, betweenPosition, betweenSpeed, closer.degreesBoatToTheWind,
closer.tack, closer.legType, fixBefore.extrapolated || fixAfter.extrapolated);
}
} else {
// perfect match
final GPSFixDTO fixAfter = competitorFixes.get(i);
final GPSFixDTOWithSpeedWindTackAndLegType fixAfter = competitorFixes.get(i);
result = fixAfter;
}
}
@@ -2507,7 +2541,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
}
for (CompetitorDTO competitor : competitors) {
try {
GPSFixDTO competitorFix = forMap.getBoatFix(competitor, forMap.timer.getTime());
GPSFixDTOWithSpeedWindTackAndLegType competitorFix = forMap.getBoatFix(competitor, forMap.timer.getTime());
Position competitorPosition = competitorFix != null ? competitorFix.position : null;
if (competitorPosition != null) {
if (newBounds == null) {
@@ -2787,10 +2821,66 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
return null;
}
DelegateCoordinateSystem getCoordinateSystem() {
public Map<String, CourseMarkOverlay> getCourseMarkOverlays() {
return courseMarkOverlays;
}
public CoordinateSystem getCoordinateSystem() {
return coordinateSystem;
}
public void hideAllHelplines() {
if (startLine != null) {
startLine.setVisible(false);
}
if (finishLine != null) {
finishLine.setVisible(false);
}
if (advantageLine != null) {
advantageLine.setVisible(false);
}
if (windwardStartLineMarkToFirstMarkLine != null && leewardStartLineMarkToFirstMarkLine != null) {
windwardStartLineMarkToFirstMarkLine.setVisible(false);
leewardStartLineMarkToFirstMarkLine.setVisible(false);
}
for (Polyline courseMiddleline : courseMiddleLines.values()) {
if (courseMiddleline != null) {
courseMiddleline.setVisible(false);
}
}
for (Polygon courseSideline : courseSidelines.values()) {
if (courseSideline != null) {
courseSideline.setVisible(false);
}
}
}
public void showAllHelplinesToShow() {
if (settings.getHelpLinesSettings().isShowAnyHelperLines()) {
if (startLine != null && settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINE))
startLine.setVisible(true);
if (finishLine != null && settings.getHelpLinesSettings().isVisible(HelpLineTypes.FINISHLINE))
finishLine.setVisible(true);
if (advantageLine != null && settings.getHelpLinesSettings().isVisible(HelpLineTypes.ADVANTAGELINE))
advantageLine.setVisible(true);
if (windwardStartLineMarkToFirstMarkLine != null && leewardStartLineMarkToFirstMarkLine != null &&
settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE)) {
windwardStartLineMarkToFirstMarkLine.setVisible(true);
leewardStartLineMarkToFirstMarkLine.setVisible(true);
}
if (settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEMIDDLELINE)) {
for (Polyline courseMiddleline : courseMiddleLines.values()) {
courseMiddleline.setVisible(true);
}
}
if (settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY)) {
for (Polygon courseSideline : courseSidelines.values()) {
courseSideline.setVisible(true);
}
}
}
}
void setLastInfoWindow(InfoWindow infoWindow) {
lastInfoWindow = infoWindow;
}
@@ -649,6 +649,9 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
protected SailIDColumn(CompetitorFetcher<T> competitorFetcher) {
super(new TextCell(), SortingOrder.ASCENDING, LeaderboardPanel.this);
this.competitorFetcher = competitorFetcher;
// This style is adding to avoid contained images CSS property "max-width: 100%", which could cause
// an overflow to the next column (see https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=3537)
setCellStyleNames(tableResources.cellTableStyle().cellTableSailIdColumn());
}
@Override
@@ -149,3 +149,7 @@
.cellTableTotalColumn {
background-color: #e0e0e0 !important;
}
.cellTableSailIdColumn img {
max-width: none;
}

Some files were not shown because too many files have changed in this diff Show More