diff --git a/configuration/RunFortifyBuild b/configuration/RunFortifyBuild new file mode 100755 index 00000000000..b63a0122a6b --- /dev/null +++ b/configuration/RunFortifyBuild @@ -0,0 +1,3 @@ +#!/bin/bash +sourceanalyzer -b com.sap.sailing.domain -Xmx4000m -debug -logfile FortifyScan.log -extdirs /home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins -cp "/home/sailing/code/java/com.google.gwt.osgi/lib/gwt-user.jar:/home/sailing/gwt-2.7.0/validation-api-1.0.0.GA.jar:/home/sailing/gwt-2.7.0/gwt-codeserver.jar:/home/sailing/gwt-2.7.0/validation-api-1.0.0.GA-sources.jar:/home/sailing/gwt-2.7.0/gwt-user.jar:/home/sailing/gwt-2.7.0/gwt-dev.jar:/home/sailing/code/java/com.google.gwt.dev/lib/gwt-dev.jar:/home/sailing/code/java/com.tractrac.clientmodule/lib/TracAPI.jar:/home/sailing/code/java/com.sap.sailing.dashboards.gwt/WEB-INF/lib/gwt-ajaxloader.jar:/home/sailing/code/java/com.sap.sailing.dashboards.gwt/WEB-INF/lib/gwt-maps-api-3.9.0-build-17.jar:/home/sailing/code/java/com.googlecode.mgwt/lib/mgwt-2.0.0.jar:/home/sailing/code/java/com.sap.sailing.gwt.ui/WEB-INF/lib/gwt-bootstrap-2.3.2.0.jar:/home/sailing/code/java/com.sap.sailing.gwt.ui/WEB-INF/lib/gwt-maps-api-3.9.0-build-17.jar:/home/sailing/code/java/com.sap.sailing.gwt.ui/WEB-INF/lib/gwt-ajaxloader.jar" -source 1.8 "/home/sailing/code/java/com.sap.sailing.ess40.resultimport" "/home/sailing/code/java/com.sap.sailing.manage2sail" "/home/sailing/code/java/com.sap.sailing.manage2sail.resultimport" "/home/sailing/code/java/com.tractrac.clientmodule" "/home/sailing/code/java/com.sap.sse.filestorage" "/home/sailing/code/java/com.sap.sailing.domain.swisstimingreplayadapter" "/home/sailing/code/java/com.sap.sse.mongodb" "/home/sailing/code/java/com.sap.sailing.udpconnector" "/home/sailing/code/java/com.sap.sailing.server.gateway" "/home/sailing/code/java/com.sap.sse" "/home/sailing/code/java/com.sap.sse.datamining" "/home/sailing/code/java/com.sap.sailing.domain.racelogtrackingadapter" "/home/sailing/code/java/com.sap.sailing.datamining.shared" "/home/sailing/code/java/com.sap.sailing.domain.igtimiadapter.persistence" "/home/sailing/code/java/com.sap.sailing.monitoring" "/home/sailing/code/java/com.sap.sailing.domain" "/home/sailing/code/java/com.sap.sse.datamining.annotations" "/home/sailing/code/java/com.sap.sailing.domain.swisstimingadapter.persistence" "/home/sailing/code/java/com.sap.sse.threadmanager" "/home/sailing/code/java/com.sap.sailing.xrr.structureimport" "/home/sailing/code/java/com.sap.sailing.freg.resultimport" "/home/sailing/code/java/com.sap.sailing.domain.igtimiadapter" "/home/sailing/code/java/com.sap.sailing.server.gateway.ess40" "/home/sailing/code/java/com.sap.sailing.xrr.schema" "/home/sailing/code/java/com.sap.sse.common" "/home/sailing/code/java/com.sap.sailing.expeditionconnector" "/home/sailing/code/java/com.sap.sailing.declination" "/home/sailing/code/java/com.sap.sailing.geocoding" "/home/sailing/code/java/com.sap.sailing.server.gateway.serialization" "/home/sailing/code/java/com.sap.sailing.kiworesultimport" "/home/sailing/code/java/com.sap.sailing.polars.datamining" "/home/sailing/code/java/com.sap.sailing.simulator" "/home/sailing/code/java/com.sap.sse.replication" "/home/sailing/code/java/com.sap.sailing.www" "/home/sailing/code/java/com.sap.sse.gwt.adminconsole" "/home/sailing/code/java/com.sap.sailing.dashboards.gwt" "/home/sailing/code/java/com.sap.sailing.news" "/home/sailing/code/java/com.sap.sailing.polars.datamining.shared" "/home/sailing/code/java/com.sap.sailing.barbados.resultimport" "/home/sailing/code/java/com.sap.sse.security.common" "/home/sailing/code/java/com.sap.sse.security.ui" "/home/sailing/code/java/com.sap.sse.shared.android" "/home/sailing/code/java/org.json.simple" "/home/sailing/code/java/com.sap.sailing.resultimport" "/home/sailing/code/java/com.sap.sse.security" "/home/sailing/code/java/com.sap.sailing.gwt.ui" "/home/sailing/code/java/com.sap.sailing.domain.tractracadapter" "/home/sailing/code/java/com.sap.sailing.server.trackfiles" "/home/sailing/code/java/com.googlecode.java-diff-utils" "/home/sailing/code/java/com.sap.sse.operationaltransformation" "/home/sailing/code/java/com.sap.sailing.domain.swisstimingadapter" "/home/sailing/code/java/com.sap.sse.gwt.theme" "/home/sailing/code/java/com.sap.sailing.domain.persistence" "/home/sailing/code/java/com.sap.sailing.sailwave.resultimport" "/home/sailing/code/java/com.sap.sailing.xmlexport" "/home/sailing/code/java/com.sap.sse.mail" "/home/sailing/code/java/com.sap.sailing.velum.resultimport" "/home/sailing/code/java/com.sap.sse.security.userstore.mongodb" "/home/sailing/code/java/com.sap.sailing.server.gateway.serialization.shared.android" "/home/sailing/code/java/com.sap.sse.datamining.shared" "/home/sailing/code/java/org.moxieapps.gwt.highcharts" "/home/sailing/code/java/com.sap.sailing.autoload" "/home/sailing/code/java/com.googlecode.mgwt" "/home/sailing/code/java/com.sap.sailing.datamining" "/home/sailing/code/java/com.sap.sailing.polars" "/home/sailing/code/java/com.sap.sailing.www.events" "/home/sailing/code/java/com.sap.sailing.domain.common" "/home/sailing/code/java/com.sap.sailing.domain.shared.android" "/home/sailing/code/java/com.sap.sailing.domain.deckmanadapter" "/home/sailing/code/java/com.sap.sailing.server" "/home/sailing/code/java/com.sap.sse.gwt" "/home/sailing/code/java/com.sap.sailing.xrr.resultimport" "/home/sailing/code/java/com.sap.sailing.domain.tractracadapter.persistence" +#sourceanalyzer -b com.sap.sailing -Xmx10g -debug -logfile FortifyScan.log "-show-build-warnings" "-machine-output" "-cp" "/home/sailing/code/java/com.sap.sse.gwt.theme/WEB-INF/classes:/home/sailing/code/java/com.sap.sailing.dashboards.gwt/WEB-INF/classes:/home/sailing/code/java/com.sap.sailing.gwt.ui/WEB-INF/classes:/home/sailing/code/java/com.sap.sse.security.ui/WEB-INF/classes:/home/sailing/code/java/com.sap.sailing.www.events/WEB-INF/classes:/home/sailing/code/java/com.google.gwt.osgi/lib/gwt-user.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.osgi_3.10.2.v20150203-1939.jar:/home/sailing/gwt-2.7.0/validation-api-1.0.0.GA.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.jetty.osgi.npn.fragment_9.2.2.v20140723.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/javax.servlet_3.1.0.v20140303-1611.jar:/home/sailing/gwt-2.7.0/gwt-codeserver.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.servicemix.bundles.zxing_3.1.0.1.jar:/home/sailing/gwt-2.7.0/validation-api-1.0.0.GA-sources.jar:/home/sailing/gwt-2.7.0/gwt-user.jar:/home/sailing/gwt-2.7.0/gwt-dev.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.commons.math_2.1.0.v201105210652.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/com.fasterxml.jackson.core.jackson-databind_2.5.0.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.hamcrest.core_1.1.0.v20090501071000.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.junit4_4.8.2.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/com.fasterxml.jackson.core.jackson-annotations_2.5.0.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/com.rabbitmq.client_2.8.4.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/lz4-java_1.3.0.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.servicemix.bundles.aws-java-sdk_1.9.8.1.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.commons.logging_1.1.3.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/javax.validation_1.0.0.GA_v201205091237.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.jetty.webapp_9.2.2.v20140723.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/com.fasterxml.jackson.core.jackson-core_2.5.0.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.jetty.server_9.2.2.v20140723.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.mongodb.mongo-java-driver_2.13.0.RELEASE.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.httpcomponents.httpclient_4.2.6.v201311072007.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.commons.io_2.2.0.v201405211200.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/com.sun.mail.javax.mail_1.4.5.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.jetty.io_9.2.2.v20140723.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/javax.ws.rs_1.1.1.v20130318-1750.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.jvnet.mimepull_1.9.4.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.shiro.ehcache_1.2.3.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.commons.lang_2.6.0.v201404270220.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.shiro.core_1.2.2.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.jetty.websocket.servlet_9.2.2.v20140723.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.commons.beanutils_1.9.0.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/com.sun.jersey.contribs.jersey-multipart_1.17.0.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.jetty.websocket.client_9.2.2.v20140723.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.objectweb.asm_5.0.1.v201404251740.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.jdom_1.1.1.v201101151400.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/routeconverter_2.12.2.201406032322.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/jackson-jaxrs_1.9.13.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.httpcomponents.httpcore_4.2.5.v201311072007.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.shiro.web_1.2.2.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.commons.fileupload_1.2.2.v20111214-1400.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.servicemix.bundles.ehcache_2.6.9.1.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.jetty.util_9.2.2.v20140723.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.servicemix.bundles.scribe_1.3.3.1.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.jetty.websocket.server_9.2.2.v20140723.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/slf4j.api_1.6.4.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/com.sun.jersey_1.17.0.v20130314-2020.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.jetty.websocket.api_9.2.2.v20140723.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.eclipse.jetty.websocket.common_9.2.2.v20140723.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.hyperic.sigar_1.6.4.1.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/jcl.over.slf4j_1.6.4.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.poi_3.9.0.v201405241750.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.dom4j_1.6.1.v201312101416.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.xmlbeans_2.3.0.v201405241900.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.poi.ooxml.schemas_3.9.0.v201405241750.jar:/home/sailing/workspace/.metadata/.plugins/org.eclipse.pde.core/.bundle_pool/plugins/org.apache.poi.ooxml_3.9.0.v201405241905.jar:/home/sailing/code/java/com.google.gwt.dev/lib/gwt-dev.jar:/home/sailing/code/java/com.tractrac.clientmodule/lib/TracAPI.jar:/home/sailing/code/java/com.sap.sailing.dashboards.gwt/WEB-INF/lib/gwt-ajaxloader.jar:/home/sailing/code/java/com.sap.sailing.dashboards.gwt/WEB-INF/lib/gwt-maps-api-3.9.0-build-17.jar:/home/sailing/code/java/com.googlecode.mgwt/lib/mgwt-2.0.0.jar:/home/sailing/code/java/com.sap.sailing.gwt.ui/WEB-INF/lib/gwt-bootstrap-2.3.2.0.jar:/home/sailing/code/java/com.sap.sailing.gwt.ui/WEB-INF/lib/gwt-maps-api-3.9.0-build-17.jar:/home/sailing/code/java/com.sap.sailing.gwt.ui/WEB-INF/lib/gwt-ajaxloader.jar" "-source" "1.8" "/home/sailing/code/java/com.sap.sailing.ess40.resultimport" "/home/sailing/code/java/com.sap.sailing.manage2sail" "/home/sailing/code/java/com.sap.sailing.manage2sail.resultimport" "/home/sailing/code/java/com.tractrac.clientmodule" "/home/sailing/code/java/com.sap.sse.filestorage" "/home/sailing/code/java/com.sap.sailing.domain.swisstimingreplayadapter" "/home/sailing/code/java/com.sap.sse.mongodb" "/home/sailing/code/java/com.sap.sailing.udpconnector" "/home/sailing/code/java/com.sap.sailing.server.gateway" "/home/sailing/code/java/com.sap.sse" "/home/sailing/code/java/com.sap.sse.datamining" "/home/sailing/code/java/com.sap.sailing.domain.racelogtrackingadapter" "/home/sailing/code/java/com.sap.sailing.datamining.shared" "/home/sailing/code/java/com.sap.sailing.domain.igtimiadapter.persistence" "/home/sailing/code/java/com.sap.sailing.monitoring" "/home/sailing/code/java/com.sap.sailing.domain" "/home/sailing/code/java/com.sap.sse.datamining.annotations" "/home/sailing/code/java/com.sap.sailing.domain.swisstimingadapter.persistence" "/home/sailing/code/java/com.sap.sse.threadmanager" "/home/sailing/code/java/com.sap.sailing.xrr.structureimport" "/home/sailing/code/java/com.sap.sailing.freg.resultimport" "/home/sailing/code/java/com.sap.sailing.domain.igtimiadapter" "/home/sailing/code/java/com.sap.sailing.server.gateway.ess40" "/home/sailing/code/java/com.sap.sailing.xrr.schema" "/home/sailing/code/java/com.sap.sse.common" "/home/sailing/code/java/com.sap.sailing.expeditionconnector" "/home/sailing/code/java/com.sap.sailing.declination" "/home/sailing/code/java/com.sap.sailing.geocoding" "/home/sailing/code/java/com.sap.sailing.server.gateway.serialization" "/home/sailing/code/java/com.sap.sailing.kiworesultimport" "/home/sailing/code/java/com.sap.sailing.polars.datamining" "/home/sailing/code/java/com.sap.sailing.simulator" "/home/sailing/code/java/com.sap.sse.replication" "/home/sailing/code/java/com.sap.sailing.www" "/home/sailing/code/java/com.sap.sse.gwt.adminconsole" "/home/sailing/code/java/com.sap.sailing.dashboards.gwt" "/home/sailing/code/java/com.sap.sailing.news" "/home/sailing/code/java/com.sap.sailing.polars.datamining.shared" "/home/sailing/code/java/com.sap.sailing.barbados.resultimport" "/home/sailing/code/java/com.sap.sse.security.common" "/home/sailing/code/java/com.sap.sse.security.ui" "/home/sailing/code/java/com.sap.sse.shared.android" "/home/sailing/code/java/org.json.simple" "/home/sailing/code/java/com.sap.sailing.resultimport" "/home/sailing/code/java/com.sap.sse.security" "/home/sailing/code/java/com.sap.sailing.gwt.ui" "/home/sailing/code/java/com.sap.sailing.domain.tractracadapter" "/home/sailing/code/java/com.sap.sailing.server.trackfiles" "/home/sailing/code/java/com.googlecode.java-diff-utils" "/home/sailing/code/java/com.sap.sse.operationaltransformation" "/home/sailing/code/java/com.sap.sailing.domain.swisstimingadapter" "/home/sailing/code/java/com.sap.sse.gwt.theme" "/home/sailing/code/java/com.sap.sailing.domain.persistence" "/home/sailing/code/java/com.sap.sailing.sailwave.resultimport" "/home/sailing/code/java/com.sap.sailing.xmlexport" "/home/sailing/code/java/com.sap.sse.mail" "/home/sailing/code/java/com.sap.sailing.velum.resultimport" "/home/sailing/code/java/com.sap.sse.security.userstore.mongodb" "/home/sailing/code/java/com.sap.sailing.server.gateway.serialization.shared.android" "/home/sailing/code/java/com.sap.sse.datamining.shared" "/home/sailing/code/java/org.moxieapps.gwt.highcharts" "/home/sailing/code/java/com.sap.sailing.autoload" "/home/sailing/code/java/com.googlecode.mgwt" "/home/sailing/code/java/com.sap.sailing.datamining" "/home/sailing/code/java/com.sap.sailing.polars" "/home/sailing/code/java/com.sap.sailing.www.events" "/home/sailing/code/java/com.sap.sailing.domain.common" "/home/sailing/code/java/com.sap.sailing.domain.shared.android" "/home/sailing/code/java/com.sap.sailing.domain.deckmanadapter" "/home/sailing/code/java/com.sap.sailing.server" "/home/sailing/code/java/com.sap.sse.gwt" "/home/sailing/code/java/com.sap.sailing.xrr.resultimport" "/home/sailing/code/java/com.sap.sailing.domain.tractracadapter.persistence" diff --git a/configuration/RunFortifyClean b/configuration/RunFortifyClean new file mode 100755 index 00000000000..e41a19501bd --- /dev/null +++ b/configuration/RunFortifyClean @@ -0,0 +1,2 @@ +#!/bin/bash +sourceanalyzer -b com.sap.sailing.domain -clean diff --git a/configuration/RunFortifyScan b/configuration/RunFortifyScan new file mode 100755 index 00000000000..cc09b144cbe --- /dev/null +++ b/configuration/RunFortifyScan @@ -0,0 +1,3 @@ +#!/bin/bash +#sourceanalyzer -b com.sap.sailing.domain -scan +sourceanalyzer -b com.sap.sailing.domain -Xmx6000m -scan "-f" "/home/sailing/code/java/com.sap.sailing_scan.fpr" diff --git a/configuration/convertELBLogToApacheFormat b/configuration/convertELBLogToApacheFormat index 4563874bf6e..2341923b4dd 100755 --- a/configuration/convertELBLogToApacheFormat +++ b/configuration/convertELBLogToApacheFormat @@ -6,7 +6,7 @@ for i in $*; do gzip -cd $i else cat $i - fi | iconv -f ISO-8859-1 -t UTF8 | + fi | recode ISO-8859-1..UTF-8 | sed -e 's/^\([^T]*\)T\([^.]*\)[^ ]* [^ ]* \([^:]*\):[^ ]* [^ ]* [^ ]* [^ ]* [^ ]* [^ ]* \([^ ]*\) \([^ ]*\) [^ ]* \(\"[^"]*\"\) \(\"[^"]*\"\).*$/'$PREFIX' \3 - - [\1:\2 +0000] \6 \4 \5 "http:\/\/'$PREFIX'" \7/' | sed -e 's/\[\([0-9]*\)-01-\([0-9]*\):/\[\2\/Jan\/\1:/' | sed -e 's/\[\([0-9]*\)-02-\([0-9]*\):/\[\2\/Feb\/\1:/' | diff --git a/configuration/unique_ips_per_referrer b/configuration/unique_ips_per_referrer index 518f7f27509..612d7b77447 100755 --- a/configuration/unique_ips_per_referrer +++ b/configuration/unique_ips_per_referrer @@ -28,7 +28,7 @@ for i in $*; do gzip -cd $i else cat $i - fi | iconv -f ISO-8859-1 -t UTF8 | sed -e 's/^\([^ ]*\) \([^ ]*\) \([^ ]*\) \([^ ]*\) \[\([^:]*\):\([^]]*\)\] \"[^"]*\" [^ ]* [^ ]* \"[^"]*\" \"\([^"]*\)\"/\1 \2 \5 \7/' | while read referrer hit; do + fi | recode ISO-8859-1..UTF-8 | sed -e 's/^\([^ ]*\) \([^ ]*\) \([^ ]*\) \([^ ]*\) \[\([^:]*\):\([^]]*\)\] \"[^"]*\" [^ ]* [^ ]* \"[^"]*\" \"\([^"]*\)\"/\1 \2 \5 \7/' | while read referrer hit; do echo "$hit" >>${STATS}/${referrer}.ips done echo "$i" >>$VISITED_FILES @@ -49,7 +49,7 @@ for month in `cat $MONTHS`; do echo -n "`basename $i .ips` " cat $i | grep "^[^ ]* [0-9]*/$month .*" | sort -u | wc | awk '{ print $1; }' # TODO consider producing an AWStats configuration file for each virtual host name and splitting all new logs into virtual host name-specific log files - done | tee $STATS/unique-ips-days-useragents-per-event-$month - echo "Wrote per-month results to $STATS/unique-ips-days-useragents-per-event-$month" + done | tee $STATS/unique-ips-days-useragents-per-event-`echo $month | tr / -` + echo "Wrote per-month results to $STATS/unique-ips-days-useragents-per-event-`echo $month | tr / -`" done echo "Done at `date`." diff --git a/configuration/unique_ips_per_referrer_generate_month_results_only b/configuration/unique_ips_per_referrer_generate_month_results_only index dcea8f54211..d12bddaa7ae 100755 --- a/configuration/unique_ips_per_referrer_generate_month_results_only +++ b/configuration/unique_ips_per_referrer_generate_month_results_only @@ -11,6 +11,6 @@ for month in `cat $MONTHS`; do cat $i | grep "^[^ ]* [0-9]*/$month .*" | sort -u | wc | awk '{ print $1; }' # TODO consider producing an AWStats configuration file for each virtual host name and splitting all new logs into virtual host name-specific log files done | tee $STATS/unique-ips-days-useragents-per-event-`echo $month | tr / -` - echo "Wrote per-month results to $STATS/unique-ips-days-useragents-per-event-$month" + echo "Wrote per-month results to $STATS/unique-ips-days-useragents-per-event-`echo $month | tr / -`" done echo "Done." diff --git a/home.md b/home.md index 51cc280a2bd..50e58978086 100644 --- a/home.md +++ b/home.md @@ -45,13 +45,14 @@ SAP is at the center of today’s technology revolution, developing innovations * [[Typical Data Mining Scenarios|wiki/typical-data-mining-scenarios]] * [[Load Tests|wiki/server-load-tests]] * [[Server Replication|wiki/server-replication]] - * [[Configure Races on Server|wiki/typical-development-scenarios]] + * [[Configure Races on Server|wiki/configure-races-on-server]] * [[Development Environment|wiki/development-environment]] * [[Working with GWT UI Binder|wiki/gwt-ui-binder]] * [[Working with GWT Locally|wiki/local-gwt]] * [[Working with GWT Super Dev Mode|wiki/super-dev-mode]] * [[Mobile Development|wiki/mobile-development]] * [[Security|wiki/security]] + * [[Fortify Security Scans|wiki/fortify]] * [[Java De(Serialization) and Circular Dependencies|wiki/java-de-serialization-and-circular-dependencies]] * Information for Webdesigners * [[Layout repository|wiki/webdesign]] diff --git a/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF b/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF index 15b3a813597..7af263e912b 100644 --- a/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF @@ -25,7 +25,11 @@ 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: org.osgi.framework;version="1.5.0", +Import-Package: com.sap.sse.util, + javax.servlet;version="3.1.0", + javax.servlet.descriptor;version="3.1.0", + javax.servlet.http;version="3.1.0", + org.osgi.framework;version="1.5.0", org.osgi.util.tracker Web-ContextPath: /dashboards Bundle-ClassPath: WEB-INF/classes/, diff --git a/java/com.sap.sailing.dashboards.gwt/WEB-INF/web.xml b/java/com.sap.sailing.dashboards.gwt/WEB-INF/web.xml index 46e9142f142..c6c14b60dbb 100644 --- a/java/com.sap.sailing.dashboards.gwt/WEB-INF/web.xml +++ b/java/com.sap.sailing.dashboards.gwt/WEB-INF/web.xml @@ -18,18 +18,6 @@ - - Rib Dashboard Service - RibDashboardService - com.sap.sailing.dashboards.gwt.server.RibDashboardServiceImpl - - - - RibDashboardService - /service/ribdashboard - - - Sailing Service SailingService @@ -39,6 +27,16 @@ SailingService /service/sailing + + + Dispatch Service + DispatchService + com.sap.sailing.dashboards.gwt.server.DashboardDispatchRPCServletImpl + + + DispatchService + /service/dispatch + diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/DashboardClientFactory.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/DashboardClientFactory.java new file mode 100644 index 00000000000..5475b7f583b --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/DashboardClientFactory.java @@ -0,0 +1,18 @@ +package com.sap.sailing.dashboards.gwt.client; + +import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardDispatchSystem; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sse.gwt.client.player.Timer; + +/** + * @author Alexander Ries (D062114) + * + */ +public interface DashboardClientFactory{ + + DashboardDispatchSystem getDispatch(); + + SailingServiceAsync getSailingService(); + + Timer getDashboardFiveSecondsTimer(); +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/DashboardClientFactoryImpl.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/DashboardClientFactoryImpl.java new file mode 100644 index 00000000000..9ca74a9fbdf --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/DashboardClientFactoryImpl.java @@ -0,0 +1,49 @@ +package com.sap.sailing.dashboards.gwt.client; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.rpc.ServiceDefTarget; +import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardDispatchSystem; +import com.sap.sailing.dashboards.gwt.shared.dispatch.impl.DashboardDispatchSystemImpl; +import com.sap.sailing.gwt.ui.client.SailingService; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sse.gwt.client.EntryPointHelper; +import com.sap.sse.gwt.client.player.Timer; +import com.sap.sse.gwt.client.player.Timer.PlayModes; + +/** + * @author Alexander Ries (D062114) + * + */ +public class DashboardClientFactoryImpl implements DashboardClientFactory{ + + private final DashboardDispatchSystem dispatch; + private final SailingServiceAsync sailingService; + private final Timer dashboardFiveSecondsTimer; + + private static final int DASHBOARD_RERFRESH_INTERVAL = 5000; + + public DashboardClientFactoryImpl() { + dispatch = new DashboardDispatchSystemImpl(); + sailingService = GWT.create(SailingService.class); + EntryPointHelper.registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.WEB_CONTEXT_PATH, RemoteServiceMappingConstants.sailingServiceRemotePath); + dashboardFiveSecondsTimer = new Timer(PlayModes.Live); + dashboardFiveSecondsTimer.setRefreshInterval(DASHBOARD_RERFRESH_INTERVAL); + + } + + @Override + public DashboardDispatchSystem getDispatch() { + return dispatch; + } + + @Override + public SailingServiceAsync getSailingService() { + return sailingService; + } + + @Override + public Timer getDashboardFiveSecondsTimer() { + return dashboardFiveSecondsTimer; + } + +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RemoteServiceMappingConstants.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RemoteServiceMappingConstants.java index 3e3f281bbc6..154243707d6 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RemoteServiceMappingConstants.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RemoteServiceMappingConstants.java @@ -17,5 +17,5 @@ public interface RemoteServiceMappingConstants { public static final String WEB_CONTEXT_PATH = "dashboards"; public static final String sailingServiceRemotePath = "service/sailing"; - public static final String ribdashboardServiceRemotePath = "service/ribdashboard"; + public static final String ribdashboardDispatchServiceRemotePath = "service/dispatch"; } diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardEntryPoint.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardEntryPoint.java index 8b143644190..d1ca55aa36a 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardEntryPoint.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardEntryPoint.java @@ -1,22 +1,15 @@ package com.sap.sailing.dashboards.gwt.client; -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.rpc.ServiceDefTarget; import com.google.gwt.user.client.ui.RootLayoutPanel; import com.googlecode.mgwt.ui.client.MGWT; import com.googlecode.mgwt.ui.client.MGWTSettings; import com.googlecode.mgwt.ui.client.MGWTSettings.StatusBarStyle; import com.googlecode.mgwt.ui.client.MGWTSettings.ViewPort; import com.googlecode.mgwt.ui.client.MGWTStyle; -import com.sap.sailing.dashboards.gwt.client.dataretriever.RibDashboardDataRetriever; +import com.sap.sailing.dashboards.gwt.client.dashboardpanel.DashboardPanel; import com.sap.sailing.dashboards.gwt.client.dataretriever.WindBotDataRetriever; -import com.sap.sailing.gwt.ui.client.SailingService; -import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sse.gwt.client.AbstractEntryPoint; -import com.sap.sse.gwt.client.EntryPointHelper; -import com.sap.sse.gwt.client.player.Timer; -import com.sap.sse.gwt.client.player.Timer.PlayModes; /** * @@ -25,28 +18,14 @@ import com.sap.sse.gwt.client.player.Timer.PlayModes; */ public class RibDashboardEntryPoint extends AbstractEntryPoint { - private static final int DASHBOARD_RERFRESH_INTERVAL = 5000; - @Override public void doOnModuleLoad() { - - SailingServiceAsync sailingService = GWT.create(SailingService.class); - EntryPointHelper.registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.WEB_CONTEXT_PATH, RemoteServiceMappingConstants.sailingServiceRemotePath); - - RibDashboardServiceAsync ribdashboardService = GWT.create(RibDashboardService.class); - EntryPointHelper.registerASyncService((ServiceDefTarget) ribdashboardService, RemoteServiceMappingConstants.WEB_CONTEXT_PATH, RemoteServiceMappingConstants.ribdashboardServiceRemotePath); - - RibDashboardDataRetriever dataRetriever = new RibDashboardDataRetriever(ribdashboardService); - applyMGWTSettings(); - RibDashboardPanel ribDashboardPanel = new RibDashboardPanel(ribdashboardService, sailingService, dataRetriever); - RootLayoutPanel.get().add(ribDashboardPanel); - WindBotDataRetriever windBotDataRetriever = new WindBotDataRetriever(ribdashboardService, sailingService); - windBotDataRetriever.addNumberOfWindBotsChangeListeners(ribDashboardPanel); - Timer dashboardTimer = new Timer(PlayModes.Live); - dashboardTimer.setRefreshInterval(DASHBOARD_RERFRESH_INTERVAL); - dashboardTimer.addTimeListener(windBotDataRetriever); - dashboardTimer.addTimeListener(dataRetriever); + DashboardClientFactory dashboardClientFactory = new DashboardClientFactoryImpl(); + DashboardPanel rootPanel = new DashboardPanel(dashboardClientFactory); + RootLayoutPanel.get().add(rootPanel); + WindBotDataRetriever windBotDataRetriever = new WindBotDataRetriever(dashboardClientFactory); + windBotDataRetriever.addNumberOfWindBotsChangeListeners(rootPanel); } private void applyMGWTSettings() { diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardImageResources.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardImageResources.java deleted file mode 100644 index 9962fb67cfb..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardImageResources.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * - */ -package com.sap.sailing.dashboards.gwt.client; - -import com.google.gwt.core.shared.GWT; -import com.google.gwt.resources.client.ClientBundle; -import com.google.gwt.resources.client.ImageResource; - -/** - * @author Alexander Ries - * - */ -public interface RibDashboardImageResources extends ClientBundle { - - public static final RibDashboardImageResources INSTANCE = GWT.create(RibDashboardImageResources.class); - - @Source("images/logo_sap.png") - ImageResource logo_sap(); - - @Source("images/logoess.png") - ImageResource logoess(); - - @Source("images/leftarrow.png") - ImageResource left(); - - @Source("images/rightarrow.png") - ImageResource right(); - - @Source("images/leftarrow_disabled.png") - ImageResource leftdisabled(); - - @Source("images/rightarrow_disabled.png") - ImageResource rightdisabled(); - - @Source("images/compass.png") - ImageResource compass(); - - @Source("images/watch.png") - ImageResource watch(); - - @Source("images/settings.png") - ImageResource settings(); - - @Source("images/rotatedevice.png") - ImageResource rotatedevice(); -} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardPanel.ui.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardPanel.ui.xml deleted file mode 100644 index 6b88deca4c0..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardPanel.ui.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - -
-
-
-
- -
-
-
-
- -
- - - - - - - - - - - - - -
-
- - - - -
- -
\ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardPanelStyle.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardPanelStyle.java deleted file mode 100644 index 8eb2b7760c4..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardPanelStyle.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.sap.sailing.dashboards.gwt.client; - -import com.google.gwt.resources.client.CssResource; - -public interface RibDashboardPanelStyle extends CssResource { - String logo(); - String start(); -} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardService.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardService.java deleted file mode 100644 index b7dc461ca98..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardService.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.sap.sailing.dashboards.gwt.client; - -import java.util.List; - -import com.google.gwt.user.client.rpc.RemoteService; -import com.sap.sailing.dashboards.gwt.shared.dto.RibDashboardRaceInfoDTO; -import com.sap.sailing.dashboards.gwt.shared.dto.StartlineAdvantagesWithMaxAndAverageDTO; -import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO; -import com.sap.sailing.domain.common.NoWindException; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.domain.common.dto.CompetitorDTO; - -public interface RibDashboardService extends RemoteService { - RibDashboardRaceInfoDTO getLiveRaceInfo(String leaderboardName) throws NoWindException; - - RegattaAndRaceIdentifier getIDFromRaceThatTakesWindFixesNow(String leaderboardName); - - List getStartAnalysisListForCompetitorIDAndLeaderboardName(String competitorIdAsString, String leaderboardName); - - List getCompetitorsInLeaderboard(String leaderboardName); - - StartlineAdvantagesWithMaxAndAverageDTO getAdvantagesOnStartline(String leaderboardName); -} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardServiceAsync.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardServiceAsync.java deleted file mode 100644 index 11dd9508bcf..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardServiceAsync.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.sap.sailing.dashboards.gwt.client; - -import java.util.List; - -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.sap.sailing.dashboards.gwt.shared.dto.RibDashboardRaceInfoDTO; -import com.sap.sailing.dashboards.gwt.shared.dto.StartlineAdvantagesWithMaxAndAverageDTO; -import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.domain.common.dto.CompetitorDTO; - -public interface RibDashboardServiceAsync { - - void getLiveRaceInfo(String leaderboardName, AsyncCallback callback); - - void getStartAnalysisListForCompetitorIDAndLeaderboardName(String competitorIdAsString, String leaderboardName, AsyncCallback> callback); - - void getCompetitorsInLeaderboard(String leaderboardName, AsyncCallback> callback); - - void getAdvantagesOnStartline(String leaderboardName, AsyncCallback callback); - - void getIDFromRaceThatTakesWindFixesNow(String leaderboardName, AsyncCallback callback); - -} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetCompetitorInLeaderboardAction.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetCompetitorInLeaderboardAction.java new file mode 100644 index 00000000000..433536bec7e --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetCompetitorInLeaderboardAction.java @@ -0,0 +1,35 @@ +package com.sap.sailing.dashboards.gwt.client.actions; + +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardAction; +import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardDispatchContext; +import com.sap.sailing.dashboards.gwt.shared.dto.LeaderboardCompetitorsDTO; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.gwt.dispatch.client.exceptions.DispatchException; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +/** + * @author Alexander Ries (D062114) + * + */ +public class GetCompetitorInLeaderboardAction implements DashboardAction { + + private String leaderboarName; + + @SuppressWarnings("unused") + private GetCompetitorInLeaderboardAction() { + } + + public GetCompetitorInLeaderboardAction(String leaderboardName) { + this.leaderboarName = leaderboardName; + } + + @Override + @GwtIncompatible + public LeaderboardCompetitorsDTO execute(DashboardDispatchContext dashboardDispatchContext) throws DispatchException { + LeaderboardCompetitorsDTO result = new LeaderboardCompetitorsDTO(); + Leaderboard lb = dashboardDispatchContext.getRacingEventService().getLeaderboardByName(this.leaderboarName); + result.setCompetitors(dashboardDispatchContext.getRacingEventService().getBaseDomainFactory().getCompetitorDTOList(lb.getCompetitorsFromBestToWorst(MillisecondsTimePoint.now()))); + return result; + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetIDFromRaceThatIsLiveAction.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetIDFromRaceThatIsLiveAction.java new file mode 100644 index 00000000000..c26ab8c6d0c --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetIDFromRaceThatIsLiveAction.java @@ -0,0 +1,32 @@ +package com.sap.sailing.dashboards.gwt.client.actions; + +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.dashboards.gwt.shared.dispatch.RequiresLiveRaceAction; +import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardDispatchContext; +import com.sap.sailing.dashboards.gwt.shared.dto.RaceIdDTO; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.gwt.dispatch.client.exceptions.DispatchException; + +/** + * @author Alexander Ries (D062114) + * + */ +public class GetIDFromRaceThatIsLiveAction extends RequiresLiveRaceAction { + + public GetIDFromRaceThatIsLiveAction() {} + + public GetIDFromRaceThatIsLiveAction(String leaderboardName) { + super(leaderboardName); + } + + @Override + @GwtIncompatible + public RaceIdDTO execute(DashboardDispatchContext dashboardDispatchContext) throws DispatchException { + RaceIdDTO result = new RaceIdDTO(); + TrackedRace liveRace = super.getLiveRace(dashboardDispatchContext); + if (liveRace != null) { + result.setRaceId(liveRace.getRaceIdentifier()); + } + return result; + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetIDFromRaceThatTakesWindFixesNowAction.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetIDFromRaceThatTakesWindFixesNowAction.java index 4405378671b..25491706bb6 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetIDFromRaceThatTakesWindFixesNowAction.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetIDFromRaceThatTakesWindFixesNowAction.java @@ -1,26 +1,47 @@ package com.sap.sailing.dashboards.gwt.client.actions; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.sap.sailing.dashboards.gwt.client.RibDashboardServiceAsync; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sse.gwt.client.async.AsyncAction; +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardAction; +import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardDispatchContext; +import com.sap.sailing.dashboards.gwt.shared.dto.RaceIdDTO; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.gwt.dispatch.client.exceptions.DispatchException; +import com.sap.sse.common.impl.MillisecondsTimePoint; /** * @author Alexander Ries (D062114) * */ -public class GetIDFromRaceThatTakesWindFixesNowAction implements AsyncAction { +public class GetIDFromRaceThatTakesWindFixesNowAction implements DashboardAction { - private final RibDashboardServiceAsync ribDashboardService; - private final String leaderboardName; + private String leaderboardName; - public GetIDFromRaceThatTakesWindFixesNowAction(RibDashboardServiceAsync ribDashboardService, String leaderboardName) { - this.ribDashboardService = ribDashboardService; + @SuppressWarnings("unused") + private GetIDFromRaceThatTakesWindFixesNowAction() { + } + + public GetIDFromRaceThatTakesWindFixesNowAction(String leaderboardName) { this.leaderboardName = leaderboardName; } @Override - public void execute(AsyncCallback callback) { - ribDashboardService.getIDFromRaceThatTakesWindFixesNow(leaderboardName, callback); + @GwtIncompatible + public RaceIdDTO execute(DashboardDispatchContext dashboardDispatchContext) throws DispatchException { + RaceIdDTO result = new RaceIdDTO(); + Leaderboard leaderboard = dashboardDispatchContext.getRacingEventService().getLeaderboardByName(this.leaderboardName); + if (leaderboard != null) { + for (RaceColumn column : leaderboard.getRaceColumns()) { + for (Fleet fleet : column.getFleets()) { + TrackedRace race = column.getTrackedRace(fleet); + if (race != null && race.takesWindFixWithTimePoint(MillisecondsTimePoint.now())) { + result.setRaceId(race.getRaceIdentifier()); + } + } + } + } + return result; } } \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetRibDashboardRaceInfoAction.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetRibDashboardRaceInfoAction.java deleted file mode 100644 index 78fda2421be..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetRibDashboardRaceInfoAction.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.sap.sailing.dashboards.gwt.client.actions; - -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.sap.sailing.dashboards.gwt.client.RibDashboardServiceAsync; -import com.sap.sailing.dashboards.gwt.shared.dto.RibDashboardRaceInfoDTO; -import com.sap.sse.gwt.client.async.AsyncAction; - -public class GetRibDashboardRaceInfoAction implements AsyncAction { - - private final RibDashboardServiceAsync ribDashboardService; - private final String leaderboardName; - - public GetRibDashboardRaceInfoAction(RibDashboardServiceAsync ribDashboardService, String leaderboardName) { - this.ribDashboardService = ribDashboardService; - this.leaderboardName = leaderboardName; - } - - @Override - public void execute(AsyncCallback callback) { - ribDashboardService.getLiveRaceInfo(leaderboardName, callback); - } -} \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetStartAnalysesAction.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetStartAnalysesAction.java new file mode 100644 index 00000000000..d0446002a28 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetStartAnalysesAction.java @@ -0,0 +1,68 @@ +package com.sap.sailing.dashboards.gwt.client.actions; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.dashboards.gwt.server.util.actions.startanalysis.StartAnalysisCreationController; +import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardAction; +import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardDispatchContext; +import com.sap.sailing.dashboards.gwt.shared.dto.StartAnalysesDTO; +import com.sap.sailing.dashboards.gwt.shared.dto.StartAnalysisDTO; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.gwt.dispatch.client.exceptions.DispatchException; + +/** + * @author Alexander Ries (D062114) + * + */ +public class GetStartAnalysesAction implements DashboardAction { + + private String leaderboarName; + private String competitorIdAsString; + + @SuppressWarnings("unused") + private GetStartAnalysesAction() { + } + + public GetStartAnalysesAction(String leaderboardName, String competitorIdAsString) { + this.leaderboarName = leaderboardName; + this.competitorIdAsString = competitorIdAsString; + } + + @Override + @GwtIncompatible + public StartAnalysesDTO execute(DashboardDispatchContext dashboardDispatchContext) throws DispatchException { + StartAnalysesDTO result = new StartAnalysesDTO(); + List startanalyses = new ArrayList(); + if (competitorIdAsString != null && leaderboarName != null) { + try { + Competitor competitor = dashboardDispatchContext.getRacingEventService().getBaseDomainFactory().getCompetitorStore().getExistingCompetitorByIdAsString( + competitorIdAsString); + Leaderboard leaderboard = dashboardDispatchContext.getRacingEventService().getLeaderboardByName(this.leaderboarName); + if (leaderboard != null) { + for (RaceColumn column : leaderboard.getRaceColumns()) { + for (Fleet fleet : column.getFleets()) { + TrackedRace race = column.getTrackedRace(fleet); + if (race != null) { + StartAnalysisDTO startAnalysisDTO = StartAnalysisCreationController.checkStartAnalysisForCompetitorInTrackedRace(dashboardDispatchContext, competitor, race); + if (startAnalysisDTO != null) { + startanalyses.add(startAnalysisDTO); + } + } + } + } + + } + } catch (NullPointerException e) { + // logger.log(Level.INFO, "", e); + } + } + result.setStartAnalyses(startanalyses); + return result; + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetStartlineAdvantageByGeometryAction.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetStartlineAdvantageByGeometryAction.java new file mode 100644 index 00000000000..7b79ef3205d --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetStartlineAdvantageByGeometryAction.java @@ -0,0 +1,48 @@ +package com.sap.sailing.dashboards.gwt.client.actions; + +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.StartlineAdvantageByGeometryCalculator; +import com.sap.sailing.dashboards.gwt.shared.StartlineAdvantageType; +import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardDispatchContext; +import com.sap.sailing.dashboards.gwt.shared.dispatch.RequiresLiveRaceAndCachesMovingAverageAction; +import com.sap.sailing.dashboards.gwt.shared.dto.StartLineAdvantageDTO; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.gwt.dispatch.client.exceptions.DispatchException; + +/** + * @author Alexander Ries (D062114) + * + */ +public class GetStartlineAdvantageByGeometryAction extends RequiresLiveRaceAndCachesMovingAverageAction { + + public GetStartlineAdvantageByGeometryAction() {} + + public GetStartlineAdvantageByGeometryAction(String leaderboardName) { + super(leaderboardName); + } + + @Override + @GwtIncompatible + public StartLineAdvantageDTO execute(DashboardDispatchContext dashboardDispatchContext) throws DispatchException { + StartLineAdvantageDTO result = new StartLineAdvantageDTO(); + Double startlineAdvantageByGeometry = null; + TrackedRace liveRace = super.getLiveRace(dashboardDispatchContext); + if (liveRace != null) { + startlineAdvantageByGeometry = StartlineAdvantageByGeometryCalculator.calculateStartlineAdvantageByGeometry(liveRace); + } + result.startLineAdvatageType = StartlineAdvantageType.GEOMETRIC; + result.startLineAdvantage = startlineAdvantageByGeometry; + Double average = null; + if (startlineAdvantageByGeometry != null) { + super.addValueToMovingAverage(startlineAdvantageByGeometry.doubleValue(), dashboardDispatchContext.getMovingAveragesCache()); + average = dashboardDispatchContext.getMovingAveragesCache().getValueForKey(getKeyForMovingAverage()); + } + result.average = average; + return result; + } + + @Override + protected String getKeyForMovingAverage() { + return super.getLeaderboardName(); + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetStartlineAdvantagesAction.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetStartlineAdvantagesAction.java deleted file mode 100644 index 1cf2be86c81..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetStartlineAdvantagesAction.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.sap.sailing.dashboards.gwt.client.actions; - -/** - * @author Alexander Ries (D062114) - * - */ - -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.sap.sailing.dashboards.gwt.client.RibDashboardServiceAsync; -import com.sap.sailing.dashboards.gwt.shared.dto.StartlineAdvantagesWithMaxAndAverageDTO; -import com.sap.sse.gwt.client.async.AsyncAction; - -public class GetStartlineAdvantagesAction implements AsyncAction { - - private final RibDashboardServiceAsync ribDashboardService; - private final String leaderboardName; - - public GetStartlineAdvantagesAction(RibDashboardServiceAsync ribDashboardService, String leaderboardName) { - this.ribDashboardService = ribDashboardService; - this.leaderboardName = leaderboardName; - } - - @Override - public void execute(AsyncCallback callback) { - ribDashboardService.getAdvantagesOnStartline(leaderboardName, callback); - }} \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetStartlineAdvantagesByWindAction.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetStartlineAdvantagesByWindAction.java new file mode 100644 index 00000000000..4d4d0ee8d67 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/actions/GetStartlineAdvantagesByWindAction.java @@ -0,0 +1,45 @@ +package com.sap.sailing.dashboards.gwt.client.actions; + +/** + * @author Alexander Ries (D062114) + * + */ + +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.StartlineAdvantagesByWindCalculator; +import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardDispatchContext; +import com.sap.sailing.dashboards.gwt.shared.dispatch.RequiresLiveRaceAndCachesMovingAverageAction; +import com.sap.sailing.dashboards.gwt.shared.dto.StartlineAdvantagesWithMaxAndAverageDTO; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.gwt.dispatch.client.exceptions.DispatchException; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public class GetStartlineAdvantagesByWindAction extends RequiresLiveRaceAndCachesMovingAverageAction { + + public GetStartlineAdvantagesByWindAction() {} + + public GetStartlineAdvantagesByWindAction(String leaderboardName) { + super(leaderboardName); + } + + @Override + @GwtIncompatible + public StartlineAdvantagesWithMaxAndAverageDTO execute(DashboardDispatchContext dashboardDispatchContext) throws DispatchException { + StartlineAdvantagesWithMaxAndAverageDTO result = new StartlineAdvantagesWithMaxAndAverageDTO(); + TrackedRace liveRace = super.getLiveRace(dashboardDispatchContext); + if (liveRace != null) { + StartlineAdvantagesByWindCalculator startlineAdvantagesByWindCalculator = new StartlineAdvantagesByWindCalculator(dashboardDispatchContext); + result = startlineAdvantagesByWindCalculator.getStartLineAdvantagesAccrossLineFromTrackedRaceAtTimePoint(liveRace, MillisecondsTimePoint.now()); + } + if(result != null && result.maximum != null) { + super.addValueToMovingAverage(result.maximum, dashboardDispatchContext.getMovingAveragesCache()); + result.average = dashboardDispatchContext.getMovingAveragesCache().getValueForKey(getKeyForMovingAverage()); + } + return result; + } + + @Override + protected String getKeyForMovingAverage() { + return super.getLeaderboardName(); + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dashboardpanel/DashboardPanel.gss b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dashboardpanel/DashboardPanel.gss new file mode 100644 index 00000000000..14b3eef2b2f --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dashboardpanel/DashboardPanel.gss @@ -0,0 +1,118 @@ +@require 'theme'; + +.bar { + height: 100%; + margin-top: 20pt; + line-height: 15pt; + vertical-align: middle; + font-size: 15pt; + font-family: DASHBOARD_FONT_FAMILY; + width: 100%; + text-align: center; + font-weight: bold; + overflow: visible; +} + +.page { + position: absolute; + border: none; + border-radius: 0; + left: 0; + right: 0; + top: 0; + bottom: 30pt; + margin: 5pt; +} + +.logo { + position: absolute; + width: 48pt; + height: 23.5pt; + margin-top: 17.5pt; + margin-left: 10pt; + z-index: 1; +} + +.collumn { + position: absolute; + top: 5pt; + bottom: 15pt; + margin: 5pt; +} + +.startanalysiscollumn { + right: 33.3%; + left: 0%; +} + +.collumnthree { + left: 66.8%; + right: 0pt; +} + +.left { + left: 0pt; + right: 50%; +} + +.right { + left: 50%; + right: 0pt; +} + +.startline_advantage_by_wind_container { + position: absolute; + background-color: white; + top: 27%; + right: 0pt; + left: 0pt; + bottom: 0%; +} + +.startline_advantage_by_geometry_container { + position: absolute; + background-color: white; + top: 0%; + right: 0pt; + left: 0pt; + bottom: 73%; + margin-bottom: 10pt; +} + +.windcharthint { + position: absolute; + bottom: 0pt; + height: 15pt; + line-height: 15pt; + margin-top: 0pt; + vertical-align: middle; + font-size: 12pt; + font-family: DASHBOARD_FONT_FAMILY; + width: 100%; + text-align: center; +} + +.windloadinghint { + position: absolute; + background-color: white; + opacity: 0.4; + width: 100%; + height: 100%; + vertical-align: middle; + text-align: center; + z-index: 0; +} + +.windloadinghinttext { + position: absolute; + bottom: 0pt; + height: 15pt; + line-height: 15pt; + margin-top: 0pt; + vertical-align: middle; + font-size: 12pt; + font-family: DASHBOARD_FONT_FAMILY; + width: 100%; + text-align: center; + top: 50%; +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardPanel.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dashboardpanel/DashboardPanel.java similarity index 59% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardPanel.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dashboardpanel/DashboardPanel.java index 6073c35555e..bde0b1d2608 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/RibDashboardPanel.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dashboardpanel/DashboardPanel.java @@ -1,6 +1,7 @@ -package com.sap.sailing.dashboards.gwt.client; +package com.sap.sailing.dashboards.gwt.client.dashboardpanel; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -10,24 +11,26 @@ import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Document; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.LayoutPanel; import com.google.gwt.user.client.ui.Widget; import com.googlecode.mgwt.ui.client.widget.carousel.Carousel; +import com.sap.sailing.dashboards.gwt.client.DashboardClientFactory; +import com.sap.sailing.dashboards.gwt.client.actions.GetIDFromRaceThatIsLiveAction; import com.sap.sailing.dashboards.gwt.client.dataretriever.NumberOfWindBotsChangeListener; -import com.sap.sailing.dashboards.gwt.client.dataretriever.RibDashboardDataRetriever; -import com.sap.sailing.dashboards.gwt.client.dataretriever.RibDashboardDataRetrieverListener; import com.sap.sailing.dashboards.gwt.client.dataretriever.WindBotDataRetrieverProvider; import com.sap.sailing.dashboards.gwt.client.eventlogo.EventLogo; -import com.sap.sailing.dashboards.gwt.client.notifications.WrongDeviceOrientationNotification; -import com.sap.sailing.dashboards.gwt.client.startanalysis.StartlineAnalysisComponent; -import com.sap.sailing.dashboards.gwt.client.startlineadvantage.StartLineAdvantageByGeometryComponent; -import com.sap.sailing.dashboards.gwt.client.startlineadvantage.StartlineAdvantagesByWindComponent; -import com.sap.sailing.dashboards.gwt.client.windchart.WindBotComponent; -import com.sap.sailing.dashboards.gwt.shared.dto.RibDashboardRaceInfoDTO; +import com.sap.sailing.dashboards.gwt.client.notifications.orientation.WrongDeviceOrientationNotification; +import com.sap.sailing.dashboards.gwt.client.widgets.PollsLiveDataEvery5Seconds; +import com.sap.sailing.dashboards.gwt.client.widgets.startanalysis.StartAnalysisWidget; +import com.sap.sailing.dashboards.gwt.client.widgets.startlineadvantage.course.StartLineAdvantageByGeometryWidget; +import com.sap.sailing.dashboards.gwt.client.widgets.startlineadvantage.wind.StartlineAdvantagesByWindWidget; +import com.sap.sailing.dashboards.gwt.client.widgets.windbot.WindBotWidget; +import com.sap.sailing.dashboards.gwt.shared.DashboardURLParameters; +import com.sap.sailing.dashboards.gwt.shared.dto.RaceIdDTO; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -38,17 +41,12 @@ import com.sap.sailing.gwt.ui.client.StringMessages; * @author Alexander Ries * */ -public class RibDashboardPanel extends Composite implements RibDashboardDataRetrieverListener, - NumberOfWindBotsChangeListener { +public class DashboardPanel extends Composite implements NumberOfWindBotsChangeListener, PollsLiveDataEvery5Seconds { - interface RootUiBinder extends UiBinder { - } + interface RootUiBinder extends UiBinder {} private static RootUiBinder uiBinder = GWT.create(RootUiBinder.class); - @UiField - RibDashboardPanelStyle style; - @UiField LayoutPanel root; @@ -65,7 +63,7 @@ public class RibDashboardPanel extends Composite implements RibDashboardDataRetr HTMLPanel rightwindbotcontainer; @UiField(provided = true) - StartlineAdvantagesByWindComponent startlineAdvantagesByWindComponent; + StartlineAdvantagesByWindWidget startlineAdvantagesByWindComponent; @UiField public DivElement windloadinghintleft; @@ -80,41 +78,43 @@ public class RibDashboardPanel extends Composite implements RibDashboardDataRetr public HTMLPanel windcharthint; @UiField(provided = true) - public StartlineAnalysisComponent startanalysisComponent; + public StartAnalysisWidget startanalysisComponent; @UiField(provided = true) - public StartLineAdvantageByGeometryComponent startlineAdvantageByGeometryComponent; + public StartLineAdvantageByGeometryWidget startlineAdvantageByGeometryComponent; - private List windBotComponents; + private List windBotComponents; private StringMessages stringConstants; - private final String EVENT_ID_PARAMETER = "eventId"; - private static final Logger logger = Logger.getLogger(RibDashboardPanel.class.getName()); + DashboardClientFactory dashboardClientFactory; + private static final Logger logger = Logger.getLogger(DashboardPanel.class.getName()); - public RibDashboardPanel(RibDashboardServiceAsync ribDashboardService, SailingServiceAsync sailingServiceAsync, RibDashboardDataRetriever ribDashboardDataRetriever) { - windBotComponents = new ArrayList(); - ribDashboardDataRetriever.addDataObserver(this); - startanalysisComponent = new StartlineAnalysisComponent(ribDashboardService, sailingServiceAsync); - startlineAdvantagesByWindComponent = new StartlineAdvantagesByWindComponent(ribDashboardService); - startlineAdvantageByGeometryComponent = new StartLineAdvantageByGeometryComponent(ribDashboardDataRetriever); + public DashboardPanel(DashboardClientFactory dashboardClientFactory) { + DashboardPanelResources.INSTANCE.style().ensureInjected(); + this.dashboardClientFactory = dashboardClientFactory; + windBotComponents = new ArrayList(); + startanalysisComponent = new StartAnalysisWidget(this.dashboardClientFactory); + startlineAdvantagesByWindComponent = new StartlineAdvantagesByWindWidget(this.dashboardClientFactory); + startlineAdvantageByGeometryComponent = new StartLineAdvantageByGeometryWidget(this.dashboardClientFactory); stringConstants = StringMessages.INSTANCE; initWidget(uiBinder.createAndBindUi(this)); initLogos(); windcharthint.getElement().setInnerText(stringConstants.dashboardWindChartHint()); windloadinghintleft.setInnerHTML(stringConstants.dashboardWindBotLoadingText()+"
"+stringConstants.dashboardWindBotLoadingMessage()); windloadinghintright.setInnerHTML(stringConstants.dashboardWindBotLoadingText()+"
"+stringConstants.dashboardWindBotLoadingMessage()); - loadAndAddEventLogo(sailingServiceAsync); + loadAndAddEventLogo(dashboardClientFactory.getSailingService()); initAndAddWrongOrientationNotification(); + registerForDashboardFiveSecondsTimer(dashboardClientFactory); } private void initLogos() { Image logo = new Image(); - logo.setResource(RibDashboardImageResources.INSTANCE.logo_sap()); - logo.getElement().addClassName(style.logo()); + logo.setResource(DashboardPanelResources.INSTANCE.logo_sap()); + logo.getElement().addClassName(DashboardPanelResources.INSTANCE.style().logo()); Document.get().getBody().appendChild(logo.getElement()); } private void loadAndAddEventLogo(SailingServiceAsync sailingServiceAsync){ - String eventId = Window.Location.getParameter(EVENT_ID_PARAMETER); + String eventId = DashboardURLParameters.EVENT_ID.getValue(); if (eventId != null) { EventLogo eventLogo = EventLogo.getEventLogoFromEventId(sailingServiceAsync, eventId); if (eventLogo != null) { @@ -136,12 +136,12 @@ public class RibDashboardPanel extends Composite implements RibDashboardDataRetr windBotComponents.clear(); for (String windBotID : windBotIDs) { if (windBotAddedCounterTOConsiderForPanels == 0) { - WindBotComponent leftwindBotComponent = new WindBotComponent(windBotID); + WindBotWidget leftwindBotComponent = new WindBotWidget(windBotID); leftwindbotcontainer.add(leftwindBotComponent); windBotComponents.add(leftwindBotComponent); windloadinghintleft.getStyle().setOpacity(0.0); } else if (windBotAddedCounterTOConsiderForPanels == 1) { - WindBotComponent rightwindBotComponent = new WindBotComponent(windBotID); + WindBotWidget rightwindBotComponent = new WindBotWidget(windBotID); rightwindbotcontainer.add(rightwindBotComponent); windBotComponents.add(rightwindBotComponent); windloadinghintright.getStyle().setOpacity(0.0); @@ -157,7 +157,7 @@ public class RibDashboardPanel extends Composite implements RibDashboardDataRetr } private void addWindBotComponentsAsDataRetrieverListener(WindBotDataRetrieverProvider windBotDataRetrieverProvider) { - for (WindBotComponent windBotComponent : windBotComponents) { + for (WindBotWidget windBotComponent : windBotComponents) { logger.log(Level.INFO, "Registering WindBotDataRetrieverListener"); windBotDataRetrieverProvider.addWindBotDataRetrieverListener(windBotComponent); } @@ -165,19 +165,47 @@ public class RibDashboardPanel extends Composite implements RibDashboardDataRetr private void removeWindBotComponentsAsDataRetrieverListener( WindBotDataRetrieverProvider windBotDataRetrieverProvider) { - for (WindBotComponent windBotComponent : windBotComponents) { + for (WindBotWidget windBotComponent : windBotComponents) { windBotDataRetrieverProvider.removeWindBotDataRetrieverListener(windBotComponent); } } + private void setHeaderText(String raceName) { + this.header.getElement().setInnerText(raceName); + } + @Override - public void updateUIWithNewLiveRaceInfo(RibDashboardRaceInfoDTO liveRaceInfoDTO) { - if (liveRaceInfoDTO != null && liveRaceInfoDTO.idOfLastTrackedRace != null) { - logger.log(Level.INFO, "Updating UI with RibDashboardRaceInfoDTO race name"); - this.header.getElement().setInnerText(liveRaceInfoDTO.idOfLastTrackedRace.getRaceName()); + public void timeChanged(Date newTime, Date oldTime) { + String leaderboardNameParameterValue = DashboardURLParameters.LEADERBOARD_NAME.getValue(); + if (leaderboardNameParameterValue != null) { + dashboardClientFactory.getDispatch().execute( + new GetIDFromRaceThatIsLiveAction(leaderboardNameParameterValue), new AsyncCallback() { + + @Override + public void onSuccess(RaceIdDTO result) { + if (result != null && result.getRaceId() != null) { + logger.log(Level.INFO, "Updating UI with live race id"); + setHeaderText(result.getRaceId().getRaceName()); + } else { + logger.log(Level.INFO, "Received null for live race id"); + } + } + + @Override + public void onFailure(Throwable caught) { + logger.log(Level.INFO, "Failed to received live race id" + caught.getMessage()); + } + }); } } + @Override + public void registerForDashboardFiveSecondsTimer(DashboardClientFactory dashboardClientFactory) { + if (dashboardClientFactory != null) { + dashboardClientFactory.getDashboardFiveSecondsTimer().addTimeListener(this); + } + } + @Override public void numberOfWindBotsReceivedChanged(List windBotIDs, WindBotDataRetrieverProvider windBotDataRetrieverProvider) { diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dashboardpanel/DashboardPanel.ui.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dashboardpanel/DashboardPanel.ui.xml new file mode 100644 index 00000000000..9016ead477b --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dashboardpanel/DashboardPanel.ui.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+ +
+ + + + + + + + + + + + + +
+
+ + + + +
+ +
\ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dashboardpanel/DashboardPanelResources.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dashboardpanel/DashboardPanelResources.java new file mode 100644 index 00000000000..e3b7a8dddc4 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dashboardpanel/DashboardPanelResources.java @@ -0,0 +1,40 @@ +package com.sap.sailing.dashboards.gwt.client.dashboardpanel; + +/** + * @author Alexander Ries (D062114) + * + */ +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.ImageResource; + +public interface DashboardPanelResources extends ClientBundle { + + public static final DashboardPanelResources INSTANCE = GWT.create(DashboardPanelResources.class); + + @Source("com/sap/sailing/dashboards/gwt/client/images/logo_sap.png") + ImageResource logo_sap(); + + @Source("com/sap/sailing/dashboards/gwt/client/images/settings.png") + ImageResource settings(); + + @Source({"com/sap/sailing/dashboards/gwt/client/theme/theme.gss", "DashboardPanel.gss"}) + DashboardGSS style(); + + public interface DashboardGSS extends CssResource { + String bar(); + String page(); + String collumn(); + String startanalysiscollumn(); + String collumnthree(); + String logo(); + String right(); + String left(); + String startline_advantage_by_wind_container(); + String startline_advantage_by_geometry_container(); + String windcharthint(); + String windloadinghint(); + String windloadinghinttext(); + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dataretriever/RibDashboardDataRetriever.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dataretriever/RibDashboardDataRetriever.java deleted file mode 100644 index 7ef3fbd6c27..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dataretriever/RibDashboardDataRetriever.java +++ /dev/null @@ -1,167 +0,0 @@ -package com.sap.sailing.dashboards.gwt.client.dataretriever; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.sap.sailing.dashboards.gwt.client.RibDashboardServiceAsync; -import com.sap.sailing.dashboards.gwt.client.actions.GetRibDashboardRaceInfoAction; -import com.sap.sailing.dashboards.gwt.shared.dto.RibDashboardRaceInfoDTO; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; -import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; -import com.sap.sse.gwt.client.async.AsyncActionsExecutor; -import com.sap.sse.gwt.client.player.TimeListener; - -/** - * Class calls method {@link #requestLiveRaceInfoFromRibDashboadService()) to retrieve new - * data for the dashboard widgets on a regularly basis. - * The request interval is 5 seconds. - * - * @author Alexander Ries - * - */ -public class RibDashboardDataRetriever implements TimeListener, RaceSelectionProvider { - - private String leaderboardName; - private ArrayList dataRetrieverListener; - private ArrayList raceSelectionChangeListener; - - private AsyncActionsExecutor asyncActionsExecutor; - - private final RibDashboardServiceAsync ribDashboardService; - - private final Object MUTEX; - - private static final String PARAM_LEADERBOARD_NAME = "leaderboardName"; - private static final Logger logger = Logger.getLogger(RibDashboardDataRetriever.class.getName()); - - public RibDashboardDataRetriever(RibDashboardServiceAsync ribDashboardService) { - initNonFinalMemberVariablesWithNoArgumentConstructor(); - MUTEX = new Object(); - this.ribDashboardService = ribDashboardService; - this.leaderboardName = Window.Location.getParameter(PARAM_LEADERBOARD_NAME); - asyncActionsExecutor = new AsyncActionsExecutor(); - } - - private void initNonFinalMemberVariablesWithNoArgumentConstructor() { - dataRetrieverListener = new ArrayList(); - raceSelectionChangeListener = new ArrayList(); - } - - private void loadLiveRaceInfoFromRibDashboadService() { - - GetRibDashboardRaceInfoAction getRibDashboardRaceInfoAction = new GetRibDashboardRaceInfoAction(ribDashboardService, leaderboardName); - logger.log(Level.INFO, "Executing GetRibDashboardRaceInfoAction"); - asyncActionsExecutor.execute(getRibDashboardRaceInfoAction, new AsyncCallback() { - @Override - public void onSuccess(RibDashboardRaceInfoDTO result) { - logger.log(Level.INFO, "Received RibDashboardRaceInfoDTO"); - if (result != null) { - switch (result.responseMessage) { - case RACE_LIVE: - logger.log(Level.INFO, "RibDashboardRaceInfoDTO.responseMessage is RACE_LIVE"); - if(result.idOfLastTrackedRace != null) { - logger.log(Level.INFO, "RibDashboardRaceInfoDTO idOfLastTrackedRace is "+result.idOfLastTrackedRace); - List singletonList = Collections.singletonList(result.idOfLastTrackedRace); - setSelection(singletonList); - } else { - logger.log(Level.INFO, "RibDashboardRaceInfoDTO.idOfLastTrackedRace is null"); - } - notifyDataObservers(result); - break; - case NO_RACE_LIVE: - logger.log(Level.INFO, "RibDashboardRaceInfoDTO.responseMessage is NO_RACE_LIVE"); - break; - default: - logger.log(Level.INFO, "RibDashboardRaceInfoDTO.responseMessage is null"); - break; - } - } else { - logger.log(Level.INFO, "RibDashboardRaceInfoDTO is null"); - } - } - - @Override - public void onFailure(Throwable caught) { - logger.log(Level.INFO, "Failed to received RibDashboardRaceInfoDTO, "+caught.getMessage()); - } - }); - } - - public void addDataObserver(RibDashboardDataRetrieverListener o) { - synchronized (MUTEX) { - if (o != null) { - dataRetrieverListener.add(o); - } - } - } - - public void removeDataObserver(RibDashboardDataRetrieverListener o) { - synchronized (MUTEX) { - this.dataRetrieverListener.remove(o); - } - } - - public void notifyDataObservers(RibDashboardRaceInfoDTO liveRaceInfoDTO) { - logger.log(Level.INFO, "Notifing RibDashboardDataRetrieverListener about new RibDashboardRaceInfoDTO"); - List dataObserverCopy; - synchronized (MUTEX) { - dataObserverCopy = new ArrayList(dataRetrieverListener); - } - for (RibDashboardDataRetrieverListener o : dataObserverCopy) { - o.updateUIWithNewLiveRaceInfo(liveRaceInfoDTO); - } - } - - @Override - public void timeChanged(Date newTime, Date oldTime) { - loadLiveRaceInfoFromRibDashboadService(); - } - - @Override - public void addRaceSelectionChangeListener(RaceSelectionChangeListener listener) { - synchronized (MUTEX) { - if (listener != null) { - raceSelectionChangeListener.add(listener); - } - } - } - - @Override - public void removeRaceSelectionChangeListener(RaceSelectionChangeListener listener) { - synchronized (MUTEX) { - raceSelectionChangeListener.remove(listener); - } - } - - @Override - public void setSelection(List newSelection, - RaceSelectionChangeListener... listenersNotToNotify) { - synchronized (MUTEX) { - for (RaceSelectionChangeListener currentRaceSelectionChangeListener : raceSelectionChangeListener) { - currentRaceSelectionChangeListener.onRaceSelectionChange(newSelection); - } - } - } - - @Override - public List getAllRaces() { - return null; - } - - @Override - public void setAllRaces(List newAllRaces, - RaceSelectionChangeListener... listenersNotToNotify) { - } - - @Override - public List getSelectedRaces() { - return null; - } -} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dataretriever/RibDashboardDataRetrieverListener.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dataretriever/RibDashboardDataRetrieverListener.java deleted file mode 100644 index ee2e95944c1..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dataretriever/RibDashboardDataRetrieverListener.java +++ /dev/null @@ -1,16 +0,0 @@ -/** - * - */ -package com.sap.sailing.dashboards.gwt.client.dataretriever; - -import com.sap.sailing.dashboards.gwt.shared.dto.RibDashboardRaceInfoDTO; - -/** - * - * @author Alexander Ries - * - */ -public interface RibDashboardDataRetrieverListener { - - public void updateUIWithNewLiveRaceInfo(RibDashboardRaceInfoDTO liveRaceInfoDTO); -} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dataretriever/WindBotDataRetriever.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dataretriever/WindBotDataRetriever.java index 6f69bbca8c7..440951f64b8 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dataretriever/WindBotDataRetriever.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/dataretriever/WindBotDataRetriever.java @@ -8,49 +8,46 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; -import com.sap.sailing.dashboards.gwt.client.RibDashboardServiceAsync; +import com.sap.sailing.dashboards.gwt.client.DashboardClientFactory; import com.sap.sailing.dashboards.gwt.client.actions.GetIDFromRaceThatTakesWindFixesNowAction; +import com.sap.sailing.dashboards.gwt.client.widgets.PollsLiveDataEvery5Seconds; +import com.sap.sailing.dashboards.gwt.shared.DashboardURLParameters; +import com.sap.sailing.dashboards.gwt.shared.dto.RaceIdDTO; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.gwt.ui.actions.GetWindInfoAction; -import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; -import com.sap.sse.gwt.client.player.TimeListener; -public class WindBotDataRetriever implements TimeListener, WindBotDataRetrieverProvider { +public class WindBotDataRetriever implements PollsLiveDataEvery5Seconds, WindBotDataRetrieverProvider { private AsyncActionsExecutor asyncActionsExecutor; - private final RibDashboardServiceAsync ribDashboardService; - private final SailingServiceAsync sailingService; private List numberOfWindBotsChangeListeners; private List windBotDataRetrieverListeners; private List windBotIDsInLiveRace; - private String leaderboardName; private final Set windSourceTypeNames; private final int WIND_CHART_RESOLUTION_IN_MILLISECONDS = 5000; private final int ONE_HOUR_IN_MILLISECONDS = 1000*60*60; private final String LODA_WIND_CHART_DATA_CATEGORY = "loadWindChartData"; - private static final String PARAM_LEADERBOARD_NAME = "leaderboardName"; private boolean didInitialLoading; + private DashboardClientFactory dashboardClientFactory; + private static final Logger logger = Logger.getLogger(WindBotDataRetriever.class.getName()); - public WindBotDataRetriever(RibDashboardServiceAsync ribDashboardService, SailingServiceAsync sailingService) { - this.ribDashboardService = ribDashboardService; - this.sailingService = sailingService; + public WindBotDataRetriever(DashboardClientFactory dashboardClientFactory) { + this.dashboardClientFactory = dashboardClientFactory; didInitialLoading = false; asyncActionsExecutor = new AsyncActionsExecutor(); numberOfWindBotsChangeListeners = new ArrayList(); windBotDataRetrieverListeners = new ArrayList(); windBotIDsInLiveRace = new ArrayList(); windSourceTypeNames = new HashSet<>(); - this.leaderboardName = Window.Location.getParameter(PARAM_LEADERBOARD_NAME); windSourceTypeNames.add(WindSourceType.EXPEDITION.name()); + registerForDashboardFiveSecondsTimer(dashboardClientFactory); } private void loadWindBotData(Date from, Date to, RegattaAndRaceIdentifier selectedRaceIdentifier) { @@ -59,7 +56,7 @@ public class WindBotDataRetriever implements TimeListener, WindBotDataRetrieverP from = new Date(from.getTime()-ONE_HOUR_IN_MILLISECONDS); } logger.log(Level.INFO, "Executing GetWindInfoAction with from "+from+" and to "+to); - GetWindInfoAction getWindInfoAction = new GetWindInfoAction(sailingService, selectedRaceIdentifier, from, to, + GetWindInfoAction getWindInfoAction = new GetWindInfoAction(dashboardClientFactory.getSailingService(), selectedRaceIdentifier, from, to, WIND_CHART_RESOLUTION_IN_MILLISECONDS, windSourceTypeNames, /* * onlyUpToNewestEvent==true because we don't want to * overshoot the evidence so far @@ -146,26 +143,38 @@ public class WindBotDataRetriever implements TimeListener, WindBotDataRetrieverP public void timeChanged(Date newTime, Date oldTime) { final Date finalNewTime = newTime; final Date finaloldTime = oldTime; - GetIDFromRaceThatTakesWindFixesNowAction getIDFromRaceThatTakesWindFixesNowAction = new GetIDFromRaceThatTakesWindFixesNowAction(ribDashboardService, leaderboardName); logger.log(Level.INFO, "Executing GetIDFromRaceThatTakesWindFixesNowAction"); - asyncActionsExecutor.execute(getIDFromRaceThatTakesWindFixesNowAction, new AsyncCallback() { - @Override - public void onSuccess(RegattaAndRaceIdentifier result) { - logger.log(Level.INFO, "Received RegattaAndRaceIdentifier from race that takes wind fixes now"); - if(result != null) { - loadWindBotData(finaloldTime, finalNewTime, result); - } else { - notifyListenersAboutNumberOfWindBotsReceivedIsZero(); - logger.log(Level.INFO, "Can´t load wind data because RegattaAndRaceIdentifier for race which takes wind is null"); - } - } + dashboardClientFactory.getDispatch().execute( + new GetIDFromRaceThatTakesWindFixesNowAction(DashboardURLParameters.LEADERBOARD_NAME.getValue()), + new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - notifyListenersAboutNumberOfWindBotsReceivedIsZero(); - logger.log(Level.INFO, " Failed to received RegattaAndRaceIdentifier from race that takes wind fixes now"); - logger.log(Level.INFO, caught.getMessage()); - } - }); + @Override + public void onSuccess(RaceIdDTO result) { + if (result != null) { + RegattaAndRaceIdentifier raceId = result.getRaceId(); + if (raceId != null) { + logger.log(Level.INFO, "Received RegattaAndRaceIdentifier from race that takes wind fixes now"); + loadWindBotData(finaloldTime, finalNewTime, raceId); + } else { + notifyListenersAboutNumberOfWindBotsReceivedIsZero(); + logger.log(Level.INFO, "Can´t load wind data because RegattaAndRaceIdentifier for race which takes wind is null"); + } + } + } + + @Override + public void onFailure(Throwable caught) { + notifyListenersAboutNumberOfWindBotsReceivedIsZero(); + logger.log(Level.INFO, "Failed to received RegattaAndRaceIdentifier from race that takes wind fixes now"); + logger.log(Level.INFO, caught.getMessage()); + } + }); + } + + @Override + public void registerForDashboardFiveSecondsTimer(DashboardClientFactory dashboardClientFactory) { + if (dashboardClientFactory != null) { + dashboardClientFactory.getDashboardFiveSecondsTimer().addTimeListener(this); + } } } diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/eventlogo/EventLogo.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/eventlogo/EventLogo.java index e9fe51f6920..bf051376afb 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/eventlogo/EventLogo.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/eventlogo/EventLogo.java @@ -5,6 +5,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import com.google.gwt.dom.client.Style.Visibility; +import com.google.gwt.safehtml.shared.UriUtils; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Image; @@ -33,7 +34,7 @@ public class EventLogo extends Image { if (event != null) { final ImageDTO logo = event.getLogoImage(); if (logo != null) { - eventLogo.getElement().setAttribute("src", logo.getSourceRef()); + eventLogo.getElement().setAttribute("src", UriUtils.fromString(logo.getSourceRef()).asString()); } else { eventLogo.getElement().getStyle().setVisibility(Visibility.HIDDEN); } diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/Notifications.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/Notifications.css deleted file mode 100644 index c8b15068faa..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/Notifications.css +++ /dev/null @@ -1,100 +0,0 @@ -@CHARSET "UTF-8"; - -.bottomnotification { - position: absolute !important; - opacity: 0.9; - background-color: #F0AB00; - height: 0pt; - line-height: 0pt; - width: 100%; - bottom: 0pt; - text-align: center; - font-size: 15pt; - line-height: 30pt; - color: black; - font-family: 'Open Sans', Arial, Verdana, sans-serif; - z-index: 2; -} - -.bottomnotification_shown { - -webkit-transition: all 700ms cubic-bezier(0.480, 0, 0.000, 1); - /* older webkit */ - -webkit-transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 2.650); - -moz-transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 2.650); - -o-transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 2.650); - transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 2.650); - /* custom */ - height: 30pt; - line-height: 30pt; -} - -.bottomnotification_hidden { - -webkit-transition: all 700ms cubic-bezier(0.480, 0, 0.000, 1); - /* older webkit */ - -webkit-transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 1.650); - -moz-transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 1.650); - -o-transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 1.650); - transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 1.650); - /* custom */ - height: 0pt; - line-height: 0pt; -} - -.wrongorientationnotification { - position: absolute !important; - top: 50%; - right: 50%; - background-color: white; - z-index: 2; -} - -.wrongorientationnotification_shown { - width: 200px; - height: 200px; - margin-right: -100px; - margin-top: -100px; - border-radius: 100px; - -webkit-transition: all 700ms cubic-bezier(0.480, 0, 0.000, 1); - /* older webkit */ - -webkit-transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 1.250); - -moz-transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 1.250); - -o-transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 1.250); - transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 1.250); - opacity: 1.0; -} - -.wrongorientationnotification_hidden { - width: 0px; - height: 0px; - marin-right: 0px; - margin-top: 0px; - border-radius: 0px; - overflow: hidden; - -webkit-transition: all 300ms; - /* older webkit */ - -webkit-transition: all 300ms; - -moz-transition: all 300ms; - -o-transition: all 300ms; - transition: all 300ms; - opacity: 0.0; -} - -.wrongorientationnotification_logo { - max-width: 40%; - max-height: 40%; - margin: 30%; - margin-top: 23% -} - -.wrongorientationnotification_text { - position: absolute; - width: 100%; - height: 14px; - top: 76%; - line-height: 10px; - text-align: center; - font-weight: bold; - font-size: 13px; - color: grey; - font-family: 'Open Sans', Arial, Verdana, sans-serif; -} \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/NotificationsResources.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/NotificationsResources.java deleted file mode 100644 index 2e8700466d3..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/NotificationsResources.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.sap.sailing.dashboards.gwt.client.notifications; - -import com.google.gwt.resources.client.ClientBundle; -import com.google.gwt.resources.client.CssResource; - -/** - * @author Alexander Ries (D062114) - * - */ -public interface NotificationsResources extends ClientBundle { - - @Source("Notifications.css") - NotificationsStyle notificationsStyle(); - - public interface NotificationsStyle extends CssResource { - public String bottomnotification(); - public String bottomnotification_shown(); - public String bottomnotification_hidden(); - public String wrongorientationnotification(); - public String wrongorientationnotification_shown(); - public String wrongorientationnotification_hidden(); - public String wrongorientationnotification_logo(); - public String wrongorientationnotification_text(); - } -} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/bottom/BottomNotification.gss b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/bottom/BottomNotification.gss new file mode 100644 index 00000000000..627f472867f --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/bottom/BottomNotification.gss @@ -0,0 +1,34 @@ +@require 'theme'; + +.notification { + position: absolute !important; + opacity: 0.9; + background-color: #F0AB00; + height: 0pt; + width: 100%; + bottom: 0pt; + text-align: center; + font-size: 15pt; + line-height: 30pt; + color: black; + font-family: DASHBOARD_FONT_FAMILY; + z-index: 2; +} + +.shown { + -webkit-transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 2.650); + -moz-transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 2.650); + -o-transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 2.650); + transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 2.650); + height: 30pt; + line-height: 30pt; +} + +.hidden { + -webkit-transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 1.650); + -moz-transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 1.650); + -o-transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 1.650); + transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 1.650); + height: 0pt; + line-height: 0pt; +} \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/BottomNotification.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/bottom/BottomNotification.java similarity index 79% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/BottomNotification.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/bottom/BottomNotification.java index 815a157afd0..3c301fa1d11 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/BottomNotification.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/bottom/BottomNotification.java @@ -1,9 +1,8 @@ -package com.sap.sailing.dashboards.gwt.client.notifications; +package com.sap.sailing.dashboards.gwt.client.notifications.bottom; import java.util.ArrayList; import java.util.List; -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.user.client.Timer; @@ -22,11 +21,9 @@ public class BottomNotification extends FocusPanel { private Timer timer; private List bottomNotificationClickListeners; - private static NotificationsResources resources = GWT.create(NotificationsResources.class); - public BottomNotification() { - resources.notificationsStyle().ensureInjected(); - this.addStyleName(resources.notificationsStyle().bottomnotification()); + BottomNotificationResources.INSTANCE.gss().ensureInjected(); + this.addStyleName(BottomNotificationResources.INSTANCE.gss().notification()); this.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { @@ -42,8 +39,8 @@ public class BottomNotification extends FocusPanel { public void show(BottomNotificationType bottomNotificationType) { if (shown == false) { - this.removeStyleName(resources.notificationsStyle().bottomnotification_hidden()); - this.addStyleName(resources.notificationsStyle().bottomnotification_shown()); + this.removeStyleName(BottomNotificationResources.INSTANCE.gss().hidden()); + this.addStyleName(BottomNotificationResources.INSTANCE.gss().shown()); this.getElement().setInnerHTML(bottomNotificationType.getMessage()); this.getElement().getStyle().setBackgroundColor(bottomNotificationType.getBackgroundColorAsHex()); this.getElement().getStyle().setColor(bottomNotificationType.getTextColorAsHex()); @@ -61,8 +58,8 @@ public class BottomNotification extends FocusPanel { if (timer != null) { timer.cancel(); } - this.removeStyleName(resources.notificationsStyle().bottomnotification_shown()); - this.addStyleName(resources.notificationsStyle().bottomnotification_hidden()); + this.removeStyleName(BottomNotificationResources.INSTANCE.gss().shown()); + this.addStyleName(BottomNotificationResources.INSTANCE.gss().hidden()); this.getElement().setInnerHTML(""); shown = false; } diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/BottomNotificationClickListener.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/bottom/BottomNotificationClickListener.java similarity index 60% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/BottomNotificationClickListener.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/bottom/BottomNotificationClickListener.java index bc63fd8d55b..772207ed02d 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/BottomNotificationClickListener.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/bottom/BottomNotificationClickListener.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.client.notifications; +package com.sap.sailing.dashboards.gwt.client.notifications.bottom; public interface BottomNotificationClickListener { diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/bottom/BottomNotificationResources.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/bottom/BottomNotificationResources.java new file mode 100644 index 00000000000..df2040f1687 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/bottom/BottomNotificationResources.java @@ -0,0 +1,23 @@ +package com.sap.sailing.dashboards.gwt.client.notifications.bottom; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +/** + * @author Alexander Ries (D062114) + * + */ +public interface BottomNotificationResources extends ClientBundle{ + + public static final BottomNotificationResources INSTANCE = GWT.create(BottomNotificationResources.class); + + @Source({"com/sap/sailing/dashboards/gwt/client/theme/theme.gss", "BottomNotification.gss"}) + NotificationsGSS gss(); + + public interface NotificationsGSS extends CssResource { + public String notification(); + public String shown(); + public String hidden(); + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/BottomNotificationType.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/bottom/BottomNotificationType.java similarity index 94% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/BottomNotificationType.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/bottom/BottomNotificationType.java index fe6bc5c31df..dd316175f34 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/BottomNotificationType.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/bottom/BottomNotificationType.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.client.notifications; +package com.sap.sailing.dashboards.gwt.client.notifications.bottom; import com.sap.sailing.gwt.ui.client.StringMessages; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/orientation/WrongDeviceOrientationNotification.gss b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/orientation/WrongDeviceOrientationNotification.gss new file mode 100644 index 00000000000..948368c90ee --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/orientation/WrongDeviceOrientationNotification.gss @@ -0,0 +1,56 @@ +@require 'theme'; + +.notification { + position: absolute !important; + top: 50%; + right: 50%; + background-color: white; + z-index: 2; +} + +.shown { + width: 200px; + height: 200px; + margin-right: -100px; + margin-top: -100px; + border-radius: 100px; + -webkit-transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 1.250); + -moz-transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 1.250); + -o-transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 1.250); + transition: all 700ms cubic-bezier(0.480, -0.035, 0.000, 1.250); + opacity: 1.0; +} + +.hidden { + width: 0px; + height: 0px; + marin-right: 0px; + margin-top: 0px; + border-radius: 0px; + overflow: hidden; + -webkit-transition: all 300ms; + -moz-transition: all 300ms; + -o-transition: all 300ms; + transition: all 300ms; + opacity: 0.0; +} + +.logo { + max-width: 40%; + max-height: 40%; + margin: 30%; + margin-top: 23% +} + +.text { + position: absolute; + width: 100%; + height: 14px; + top: 76%; + line-height: 10px; + text-align: center; + font-weight: bold; + font-size: 13px; + color: grey; + font-family: DASHBOARD_FONT_FAMILY; +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/WrongDeviceOrientationNotification.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/orientation/WrongDeviceOrientationNotification.java similarity index 58% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/WrongDeviceOrientationNotification.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/orientation/WrongDeviceOrientationNotification.java index 1c0ad96d9fb..6ae4e7f05d4 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/WrongDeviceOrientationNotification.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/orientation/WrongDeviceOrientationNotification.java @@ -1,11 +1,9 @@ -package com.sap.sailing.dashboards.gwt.client.notifications; +package com.sap.sailing.dashboards.gwt.client.notifications.orientation; -import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.ui.AbsolutePanel; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootLayoutPanel; -import com.sap.sailing.dashboards.gwt.client.RibDashboardImageResources; import com.sap.sailing.dashboards.gwt.client.device.Orientation; import com.sap.sailing.dashboards.gwt.client.device.OrientationListener; import com.sap.sailing.dashboards.gwt.client.device.OrientationType; @@ -16,21 +14,19 @@ public class WrongDeviceOrientationNotification extends AbsolutePanel implements private Label rotateDeviceLabel; - private static NotificationsResources resources = GWT.create(NotificationsResources.class); - public WrongDeviceOrientationNotification() { super(); - resources.notificationsStyle().ensureInjected(); - this.addStyleName(resources.notificationsStyle().wrongorientationnotification()); - this.addStyleName(resources.notificationsStyle().wrongorientationnotification_hidden()); + WrongDeviceOrientationNotificationResources.INSTANCE.gss().ensureInjected(); + this.addStyleName(WrongDeviceOrientationNotificationResources.INSTANCE.gss().notification()); + this.addStyleName(WrongDeviceOrientationNotificationResources.INSTANCE.gss().hidden()); Image rotateDeviceIcon = new Image(); - rotateDeviceIcon.setResource(RibDashboardImageResources.INSTANCE.rotatedevice()); - rotateDeviceIcon.getElement().addClassName(resources.notificationsStyle().wrongorientationnotification_logo()); + rotateDeviceIcon.setResource(WrongDeviceOrientationNotificationResources.INSTANCE.rotatedevice()); + rotateDeviceIcon.getElement().addClassName(WrongDeviceOrientationNotificationResources.INSTANCE.gss().logo()); add(rotateDeviceIcon); rotateDeviceLabel = new Label(); - rotateDeviceLabel.getElement().addClassName(resources.notificationsStyle().wrongorientationnotification_text()); + rotateDeviceLabel.getElement().addClassName(WrongDeviceOrientationNotificationResources.INSTANCE.gss().text()); rotateDeviceLabel.setText("Rotate Device"); add(rotateDeviceLabel); @@ -39,14 +35,14 @@ public class WrongDeviceOrientationNotification extends AbsolutePanel implements } public void show() { - this.removeStyleName(resources.notificationsStyle().wrongorientationnotification_hidden()); - this.addStyleName(resources.notificationsStyle().wrongorientationnotification_shown()); + this.removeStyleName(WrongDeviceOrientationNotificationResources.INSTANCE.gss().hidden()); + this.addStyleName(WrongDeviceOrientationNotificationResources.INSTANCE.gss().shown()); BlurEffect.getInstance().addToView(RootLayoutPanel.get()); } public void hide() { - this.removeStyleName(resources.notificationsStyle().wrongorientationnotification_shown()); - this.addStyleName(resources.notificationsStyle().wrongorientationnotification_hidden()); + this.removeStyleName(WrongDeviceOrientationNotificationResources.INSTANCE.gss().shown()); + this.addStyleName(WrongDeviceOrientationNotificationResources.INSTANCE.gss().hidden()); BlurEffect.getInstance().removeFromView(RootLayoutPanel.get()); } diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/orientation/WrongDeviceOrientationNotificationResources.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/orientation/WrongDeviceOrientationNotificationResources.java new file mode 100644 index 00000000000..43b88ebcfed --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/notifications/orientation/WrongDeviceOrientationNotificationResources.java @@ -0,0 +1,29 @@ +package com.sap.sailing.dashboards.gwt.client.notifications.orientation; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.ImageResource; + +/** + * @author Alexander Ries (D062114) + * + */ +public interface WrongDeviceOrientationNotificationResources extends ClientBundle { + + public static final WrongDeviceOrientationNotificationResources INSTANCE = GWT.create(WrongDeviceOrientationNotificationResources.class); + + @Source("com/sap/sailing/dashboards/gwt/client/images/rotatedevice.png") + ImageResource rotatedevice(); + + @Source({ "com/sap/sailing/dashboards/gwt/client/theme/theme.gss", "WrongDeviceOrientationNotification.gss"}) + WrongDeviceNotificationGSS gss(); + + public interface WrongDeviceNotificationGSS extends CssResource { + public String notification(); + public String shown(); + public String hidden(); + public String logo(); + public String text(); + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/PopupWithMessageAndImage.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/PopupWithMessageAndImage.css index a84dd94f1bf..ba08372c605 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/PopupWithMessageAndImage.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/PopupWithMessageAndImage.css @@ -61,7 +61,7 @@ padding: 0px; margin-top: 5pt; margin-left: 0px; - font-family: 'Open Sans', Arial, Verdana, sans-serif; + font-family: DASHBOARD_FONT_FAMILY; } .popup_button:active { @@ -77,7 +77,7 @@ font-weight: bold; font-size: 15px; color: grey; - font-family: 'Open Sans', Arial, Verdana, sans-serif; + font-family: DASHBOARD_FONT_FAMILY; } .blurred { diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/CompetitorSelectionPopup.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/CompetitorSelectionPopup.css index a2ba055f0f5..665596c0f8d 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/CompetitorSelectionPopup.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/CompetitorSelectionPopup.css @@ -1,5 +1,13 @@ @CHARSET "UTF-8"; +.competitorselectionbackground { + position: absolute; + background-color: rgba(0, 0, 0, 0.6); + width: 100%; + height: 100%; + z-index: 1; +} + .competitorselectionpopup { position: absolute; left: 50%; @@ -8,7 +16,7 @@ height: 200pt; margin-top: -600pt; margin-left: -100pt; - background-color: white; + background-color: rgba(255, 255, 255, 1); border-radius: 0px; z-index: 2; } @@ -23,7 +31,7 @@ vertical-align: middle; text-align: center; font-weight: bold; - text-shadow: 0 1px 1px rgba(255, 255, 255, .7); + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.7); width: 100%; z-index: 3; } diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/CompetitorSelectionPopup.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/CompetitorSelectionPopup.java index 0adc18b350c..a41d2825551 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/CompetitorSelectionPopup.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/CompetitorSelectionPopup.java @@ -15,24 +15,27 @@ import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.RootLayoutPanel; -import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.dashboards.gwt.client.visualeffects.BlurEffect; import com.sap.sailing.domain.common.dto.CompetitorDTO; public class CompetitorSelectionPopup extends Composite implements HasWidgets, CompetitorTableRowSelectionListener { interface CompetitorSelectionPopupStyle extends CssResource { String popupshow(); - String popuphide(); - String buttonshow(); } @UiField CompetitorSelectionPopupStyle style; + @UiField + HTMLPanel competitorselectionbackground; + + @UiField + VerticalPanel competitorselectionpopup; + @UiField HTMLPanel header; @@ -61,19 +64,17 @@ public class CompetitorSelectionPopup extends Composite implements HasWidgets, C public void show(List competitorList) { competitortable.setTableContent(competitorList); - RootPanel.get().add(this); + RootLayoutPanel.get().add(this); this.button.setText("OK"); - this.getElement().addClassName(style.popupshow()); - this.getElement().removeClassName(style.popuphide()); - BlurEffect.getInstance().addToView(RootLayoutPanel.get()); + competitorselectionpopup.addStyleName(style.popupshow()); + competitorselectionpopup.removeStyleName(style.popuphide()); isVisible = true; } public void hide() { - this.getElement().addClassName(style.popuphide()); - this.getElement().removeClassName(style.popupshow()); - BlurEffect.getInstance().removeFromView(RootLayoutPanel.get()); - RootPanel.get().remove(this); + competitorselectionpopup.addStyleName(style.popuphide()); + competitorselectionpopup.removeStyleName(style.popupshow()); + RootLayoutPanel.get().remove(this); isVisible = false; } diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/CompetitorSelectionPopup.ui.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/CompetitorSelectionPopup.ui.xml index bb2812d51ed..9c762ce1e0e 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/CompetitorSelectionPopup.ui.xml +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/CompetitorSelectionPopup.ui.xml @@ -1,22 +1,25 @@ - - - Select your Team - - - - - - - + + + Select your Team + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/CompetitorTableStyleResource.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/CompetitorTableStyleResource.java index 25c0d800909..e9cb0af6c7b 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/CompetitorTableStyleResource.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/CompetitorTableStyleResource.java @@ -3,7 +3,8 @@ package com.sap.sailing.dashboards.gwt.client.popups.competitorselection; import com.google.gwt.user.cellview.client.CellTable; public interface CompetitorTableStyleResource extends CellTable.Resources { - @Source({ CellTable.Style.DEFAULT_CSS, "CompetitorTableStyle.css"}) + + @Source({CellTable.Style.DEFAULT_CSS, "CompetitorTableStyle.css"}) TableStyle cellTableStyle(); diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/SettingsButtonWithSelectionIndicationLabel.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/SettingsButtonWithSelectionIndicationLabel.java index 3cf3294ccae..05a92598d1c 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/SettingsButtonWithSelectionIndicationLabel.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/popups/competitorselection/SettingsButtonWithSelectionIndicationLabel.java @@ -10,7 +10,7 @@ import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.dashboards.gwt.client.RibDashboardImageResources; +import com.sap.sailing.dashboards.gwt.client.dashboardpanel.DashboardPanelResources; public class SettingsButtonWithSelectionIndicationLabel extends Composite { @@ -38,7 +38,7 @@ public class SettingsButtonWithSelectionIndicationLabel extends Composite { public SettingsButtonWithSelectionIndicationLabel() { initWidget(uiBinder.createAndBindUi(this)); - settingsButtonImage.setResource(RibDashboardImageResources.INSTANCE.settings()); + settingsButtonImage.setResource(DashboardPanelResources.INSTANCE.settings()); } public void addClickHandlerToSettingsButton(ClickHandler clickHandler){ diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/resources/Colors.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/resources/Colors.java deleted file mode 100644 index d6fa0e42285..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/resources/Colors.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.sap.sailing.dashboards.gwt.client.resources; - -/** - * @author Alexander Ries (D062114) - * - */ -public class Colors { - /* -> App colors -- */ - public static final String C_PRIMARY = "#000000"; - public static final String C_SECONDARY = "#000000"; - /* -> Default colors -- */ - public static final String C_BACKGROUND = "#000000"; - public static final String C_TEXT = "#000000"; - public static final String C_TEXT_LIGHT = "#000000"; -} \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/resources/DashboardResources.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/resources/DashboardResources.java deleted file mode 100644 index fb1a18942ff..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/resources/DashboardResources.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.sap.sailing.dashboards.gwt.client.resources; - -/** - * @author Alexander Ries (D062114) - * - */ -import com.google.gwt.resources.client.ClientBundle; -import com.google.gwt.resources.client.CssResource; - -public interface DashboardResources extends ClientBundle { - - interface Style extends CssResource { - // Your classes here - } - - // We only need to source the theme file - @Source({"colors.gss", "Ribstyle.css"}) - Style style(); -} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/resources/Ribstyle.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/resources/Ribstyle.css deleted file mode 100644 index 33cb76ab06a..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/resources/Ribstyle.css +++ /dev/null @@ -1,295 +0,0 @@ -@require "colors"; - -.bar { - height: 100%; - margin-top: 20pt; - line-height: 15pt; - vertical-align: middle; - font-size: 15pt; - font-family: 'Open Sans', Arial, Verdana, sans-serif; - width: 100%; - text-align: center; - font-weight: bold; - overflow: visible; -} - -.page { - position: absolute; - border: none; - border-radius: 0; - background-color: C_PRIMARY; - left: 0; - right: 0; - top: 0; - bottom: 30pt; - margin: 5pt; -} - -.logo { - position: absolute; - width: 48pt; - height: 23.5pt; - margin-top: 17.5pt; - margin-left: 10pt; - z-index: 1; -} - -.collumn { - position: absolute; - top: 5pt; - bottom: 15pt; - margin: 5pt; -} - -.startanalysiscollumn { - right: 33.3%; - left: 0%; -} - -/*deprecated*/ -.collumn1 { - width: 33.3%; -} -/*deprecated*/ -.collumn2 { - width: 33.3%; - left: 33.3%; -} -/*deprecated*/ -.collumn3 { - left: 66.8%; - right: 0pt; -} - -.left { - left: 0pt; - right: 50%; -} - -.right { - left: 50%; - right: 0pt; -} - -/*+++++++++++++++++++++++++++++++++++++++++*/ -/* .content { */ -/* width: 100%; */ -/* height: 50pt; */ -/* margin: 0pt; */ -/* } */ -.stripe { - background-color: black; - maring: 0pt; - padding: 0pt; - width: 1%; - height: 90%; - float: left; -} - -.chart { - width: 100%; - height: 100%; -} - -.rootpanel { - background-color: C_PRIMARY; - width: 100%; - height: 200%; -} - -.startline_advantage_by_wind_container { - position: absolute; - background-color: white; - top: 27%; - right: 0pt; - left: 0pt; - bottom: 0%; -} - -.startline_advantage_by_geometry_container { - position: absolute; - background-color: white; - top: 0%; - right: 0pt; - left: 0pt; - bottom: 73%; - margin-bottom: 10pt; -} - -.hintpanel { - position: absolute; - background-color: white; - height: 25pt; - right: 0pt; - left: 0pt; - bottom: 0pt; -} - -.leaderboardpanel { - position: absolute; - top: 30%; - right: 0pt; - left: 0pt; - bottom: 0pt; - margin-top: 5pt; -} - -.hint { - height: 100%; - line-height: 25pt; - margin-top: 0pt; - vertical-align: middle; - font-size: 12pt; - font-family: 'Open Sans', Arial, Verdana, sans-serif; - width: 100%; - text-align: center; -} - -.windcharthint { - position: absolute; - bottom: 0pt; - height: 15pt; - line-height: 15pt; - margin-top: 0pt; - vertical-align: middle; - font-size: 12pt; - font-family: 'Open Sans', Arial, Verdana, sans-serif; - width: 100%; - text-align: center; -} - -.scrollLayoutPanel { - height: 100%; - width: 100%; -} - -windchartcontainer { - position: absolute; - left: 0pt; - right: 0pt; - top: 0pt; - bottom: 0pt; -} - -.windchartwrapper { - position: absolute; - top: 0pt; - bottom: 0pt; - left: 0pt; - right: 0pt; -} - -/*??????*/ -.windchart { - position: absolute; - left: 0pt; - right: 0pt; - top: 0pt; - bottom: 0pt; -} - -.startlineanalysis { - position: absolute; - top: 0pt; - right: 0pt; - left: 0pt; - bottom: 10pt; - background-color: #0DBFFF; -} - -.collumncontent { - height: 100%; - width: 100%; - background-color: white; -} - -/*Test*/ -.scrollHorizontal { - overflow-y: hidden !important; - overflow-x: hidden !important; - position: absolute !important; - top: 0pt; - right: 0pt; - left: 0pt; - bottom: 0pt; -} - -.scrollAbsPanel { - overflow-y: hidden !important; - overflow-x: hidden !important; - position: absolute !important; - top: 0pt; - right: 0pt; - left: 0pt; - bottom: 0pt; -} - -.scrollcontainer { - position: absolute; - top: 15pt; - right: 15pt; - width: 300%; - bottom: 15pt; - background-color: white; -} - -.start { - position: absolute; - top: 0pt; - right: 0pt; - left: 0pt; - bottom: 0pt; - height: 100%; - /* height: 300pt; */ /* width: 400pt; */ /* margin: 10pt; */ - background-color: #0DBFFF; -} - -.scrollPage1 { - position: absolute; - height: 80%; - width: 80%; - margin: 10%; - background-color: #0DBFFF; -} - -.scrollPage2 { - position: absolute; - height: 80%; - width: 80%; - left: 170%; - background-color: #0DBFFF; -} - -.scrollPage3 { - position: absolute; - height: 80%; - width: 80%; - left: 260%; - background-color: #0DBFFF; -} - -.windloadinghint { - position: absolute; - background-color: white; - opacity: 0.4; - width: 100%; - height: 100%; - vertical-align: middle; - text-align: center; - z-index: 0; -} - -.windloadinghinttext { - position: absolute; - bottom: 0pt; - height: 15pt; - line-height: 15pt; - margin-top: 0pt; - vertical-align: middle; - font-size: 12pt; - font-family: 'Open Sans', Arial, Verdana, sans-serif; - width: 100%; - text-align: center; - top: 50%; - margin-top: -7pt; -} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/resources/colors.gss b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/resources/colors.gss deleted file mode 100644 index e279b9c801c..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/resources/colors.gss +++ /dev/null @@ -1,7 +0,0 @@ -@provide 'colors'; - -@def C_PRIMARY eval("com.company.project.client.resources.Colors.C_PRIMARY"); -@def C_SECONDARY eval("com.company.project.client.resources.Colors.C_SECONDARY"); -@def C_BACKGROUND eval("com.company.project.client.resources.Colors.C_BACKGROUND"); -@def C_TEXT eval("com.company.project.client.resources.Colors.C_TEXT"); -@def C_TEXT_LIGHT eval("com.company.project.client.resources.Colors.C_TEXT_LIGHT"); \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartAnalysisResources.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartAnalysisResources.java deleted file mode 100644 index 6bca74985f4..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartAnalysisResources.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.sap.sailing.dashboards.gwt.client.startanalysis; - -import com.google.gwt.resources.client.ClientBundle; -import com.google.gwt.resources.client.ImageResource; -import com.sap.sailing.gwt.ui.client.shared.racemap.CombinedWindPanelStyle; - -interface StartAnalysisResources extends ClientBundle { - - @Source("com/sap/sailing/dashboards/gwt/client/images/windarrow.png") - ImageResource combinedWindIcon(); - - @Source("StartAnalysisCombinedWindPanel.css") - CombinedWindPanelStyle combinedWindPanelStyle(); -} \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysisCard.ui.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysisCard.ui.xml deleted file mode 100644 index 7133cdeb7d0..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysisCard.ui.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - -
-
-
-
Time: 00:05:00
- -
-
\ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysisComponent.ui.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysisComponent.ui.xml deleted file mode 100644 index 8368a8b2933..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysisComponent.ui.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - No Starts Available - - - - - - - - - - \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.ui.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.ui.xml deleted file mode 100644 index 49cb75c6be2..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.ui.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - -
-
- - -
- - -
-
- - - - - - -
- - -
-
- - -
-
- - -
-
- -
- -
\ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartLineAdvantageByGeometryComponent.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartLineAdvantageByGeometryComponent.java deleted file mode 100644 index fe16319c68e..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartLineAdvantageByGeometryComponent.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.sap.sailing.dashboards.gwt.client.startlineadvantage; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.google.gwt.i18n.client.NumberFormat; -import com.sap.sailing.dashboards.gwt.client.dataretriever.RibDashboardDataRetriever; -import com.sap.sailing.dashboards.gwt.client.dataretriever.RibDashboardDataRetrieverListener; -import com.sap.sailing.dashboards.gwt.shared.dto.RibDashboardRaceInfoDTO; -import com.sap.sailing.gwt.ui.client.StringMessages; - -/** - * The purpose of the class is to display the live and average start line advantage either by wind or by geometry. There - * is only one advantage display at once, so the user can switch between these following two - * {@link StartLineAdvantageComponentState}s {@link StartLineAdvantageComponentStateShowsAdvantageByWind} and - * {@link StartLineAdvantageComponentStateShowsAdvantageByGeometry} by tabing somewhere at the widget. The class extends - * from {@link LiveAverageComponent} to get necessary Ui Elements but customizes these with own CSS. - * - * @author Alexander Ries (D062114) - * - */ -public class StartLineAdvantageByGeometryComponent extends LiveAverageComponent implements RibDashboardDataRetrieverListener { - - private static StringMessages stringConstants = StringMessages.INSTANCE; - private static final Logger logger = Logger.getLogger(StartLineAdvantageByGeometryComponent.class.getName()); - - public StartLineAdvantageByGeometryComponent(RibDashboardDataRetriever ribDashboardDataRetriever) { - super(stringConstants.dashboardStartlineAdvantageByGeometry(), "m"); - StartLineAdvantageComponentRessources.INSTANCE.css().ensureInjected(); - this.header.setInnerText(stringConstants.dashboardStartlineAdvantageByGeometry()); - this.header.addClassName(StartLineAdvantageComponentRessources.INSTANCE.css().startLineAdvantageComponent_header()); - liveAveragePanel.getElement().addClassName(StartLineAdvantageComponentRessources.INSTANCE.css().startLineAdvantageComponent_liveAveragePanel()); - livePanel.getElement().addClassName(StartLineAdvantageComponentRessources.INSTANCE.css().startLineAdvantageComponent_livePanel()); - middleLine.getElement().addClassName(StartLineAdvantageComponentRessources.INSTANCE.css().startLineAdvantageComponent_middleLine()); - averagePanel.getElement().addClassName(StartLineAdvantageComponentRessources.INSTANCE.css().startLineAdvantageComponent_averagePanel()); - this.liveLabel.setInnerHTML(stringConstants.dashboardLiveWind()); - this.averageLabel.setInnerHTML(stringConstants.dashboardAverageWind()+"
"+stringConstants.dashboardAverageWindMinutes(15)); - ribDashboardDataRetriever.addDataObserver(this); - } - - public void setStartLineAdvantageValues(RibDashboardRaceInfoDTO liveRaceInfoDTO) { - if (liveRaceInfoDTO.startLineAdvantageDTO != null) { - logger.log(Level.INFO, "Updating UI with RibDashboardRaceInfoDTO.startLineAdvantageDTO"); - setLiveValue(NumberFormat.getFormat("#0.0").format(liveRaceInfoDTO.startLineAdvantageDTO.startLineAdvantage)); - setAverageValue(NumberFormat.getFormat("#0.0").format(liveRaceInfoDTO.startLineAdvantageDTO.average)); - } else { - logger.log(Level.INFO, "RibDashboardRaceInfoDTO.startLineAdvantageDTO is null"); - } - } - - @Override - public void updateUIWithNewLiveRaceInfo(RibDashboardRaceInfoDTO liveRaceInfoDTO) { - setStartLineAdvantageValues(liveRaceInfoDTO); - } -} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartlineAdvantagesByWindComponent.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartlineAdvantagesByWindComponent.java deleted file mode 100644 index d29bf87d175..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartlineAdvantagesByWindComponent.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.sap.sailing.dashboards.gwt.client.startlineadvantage; - -import java.util.Date; -import java.util.Iterator; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.resources.client.CssResource; -import com.google.gwt.uibinder.client.UiBinder; -import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.HasWidgets; -import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.dashboards.gwt.client.RibDashboardServiceAsync; -import com.sap.sailing.dashboards.gwt.client.actions.GetStartlineAdvantagesAction; -import com.sap.sailing.dashboards.gwt.client.dataretriever.WindBotDataRetriever; -import com.sap.sailing.dashboards.gwt.shared.dto.StartlineAdvantagesWithMaxAndAverageDTO; -import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sse.gwt.client.async.AsyncActionsExecutor; -import com.sap.sse.gwt.client.player.TimeListener; -import com.sap.sse.gwt.client.player.Timer; -import com.sap.sse.gwt.client.player.Timer.PlayModes; - -/** - * @author Alexander Ries (D062114) - * - */ -public class StartlineAdvantagesByWindComponent extends Composite implements HasWidgets, TimeListener { - - private static StartlineAdvantagesByWindComponentUiBinder uiBinder = GWT - .create(StartlineAdvantagesByWindComponentUiBinder.class); - - interface StartlineAdvantagesByWindComponentUiBinder extends UiBinder { - } - - interface StartlineAdvantagesByWindComponentStyle extends CssResource { - - } - - @UiField(provided = true) - LiveAverageComponent advantageMaximumLiveAverage; - - @UiField(provided = true) - StartlineAdvantagesOnLineChart startlineAdvantagesOnLineChart; - - @UiField - StartlineAdvantagesByWindComponentStyle style; - - private RibDashboardServiceAsync ribDashboardService; - private AsyncActionsExecutor asyncActionsExecutor; - private static final Logger logger = Logger.getLogger(WindBotDataRetriever.class.getName()); - - public StartlineAdvantagesByWindComponent(RibDashboardServiceAsync ribDashboardService) { - startlineAdvantagesOnLineChart = new StartlineAdvantagesOnLineChart(); - advantageMaximumLiveAverage = new LiveAverageComponent(StringMessages.INSTANCE.dashboardStartlineAdvantagesByWind(), "m"); - advantageMaximumLiveAverage.header.getStyle().setFontSize(14, Unit.PT); - advantageMaximumLiveAverage.liveLabel.setInnerHTML("advantage max."); - advantageMaximumLiveAverage.averageLabel.setInnerHTML("advantage max. average "+StringMessages.INSTANCE.dashboardAverageWindMinutes(15)); - initWidget(uiBinder.createAndBindUi(this)); - this.ribDashboardService = ribDashboardService; - this.asyncActionsExecutor = new AsyncActionsExecutor(); - initSampleTimer(); - } - - private void loadData() { - GetStartlineAdvantagesAction getRibDashboardRaceInfoAction = new GetStartlineAdvantagesAction(ribDashboardService, ""); - logger.log(Level.INFO, "Executing GetStartlineAdvantagesAction"); - asyncActionsExecutor.execute(getRibDashboardRaceInfoAction, - new AsyncCallback() { - - @Override - public void onSuccess(StartlineAdvantagesWithMaxAndAverageDTO result) { - logger.log(Level.INFO, "Received StartlineAdvantagesWithMaxAndAverageDTO"); - if (result != null) { - logger.log(Level.INFO, "Updating UI with StartlineAdvantagesWithMaxAndAverageDTO"); - startlineAdvantagesOnLineChart.setStartlineAdvantages(result.advantages); - advantageMaximumLiveAverage.setLiveValue("" + result.maximum); - advantageMaximumLiveAverage.setAverageValue("" + result.average); - } else { - logger.log(Level.INFO, "StartlineAdvantagesWithMaxAndAverageDTO is null"); - } - } - - @Override - public void onFailure(Throwable caught) { - logger.log(Level.INFO, "Failed to received StartlineAdvantagesWithMaxAndAverageDTO, "+caught.getMessage()); - } - }); - } - - private void initSampleTimer(){ - Timer timer = new Timer(PlayModes.Live); - timer.setRefreshInterval(5000); - timer.addTimeListener(this); - timer.play(); - } - - @Override - public void add(Widget w) { - throw new UnsupportedOperationException("The method add(Widget w) is not supported."); - } - - @Override - public void clear() { - throw new UnsupportedOperationException("The method clear() is not supported."); - } - - @Override - public Iterator iterator() { - return null; - } - - @Override - public boolean remove(Widget w) { - return false; - } - - @Override - public void timeChanged(Date newTime, Date oldTime) { - loadData(); - } - -} \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartlineAdvantagesByWindComponent.ui.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartlineAdvantagesByWindComponent.ui.xml deleted file mode 100644 index 6bd79c479e9..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartlineAdvantagesByWindComponent.ui.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartlineAdvantagesOnLineChart.ui.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartlineAdvantagesOnLineChart.ui.xml deleted file mode 100644 index 4ad17a938af..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartlineAdvantagesOnLineChart.ui.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - -
-
-
-
-
-
-
-
\ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/theme/Colors.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/theme/Colors.java new file mode 100644 index 00000000000..e74d80c2ed6 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/theme/Colors.java @@ -0,0 +1,16 @@ +package com.sap.sailing.dashboards.gwt.client.theme; + +/** + * @author Alexander Ries (D062114) + * + */ +public class Colors { + public static final String WHITE = "#FFFFFF"; + public static final String LIGHT_GREY = "#FFFFFF"; + public static final String DARK_GREY = "#FFFFFF"; + public static final String LIGHT_TEXT_COLOR = "#FFFFFF"; + public static final String DARK_TEXT_COLOR = "#FFFFFF"; + public static final String DARK_BLUE = "#FFFFFF"; + public static final String LIGHT_BLUE = "#FFFFFF"; + public static final String YELLOW = "#FFFFFF"; +} \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/theme/Fonts.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/theme/Fonts.java new file mode 100644 index 00000000000..1783e95bbc6 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/theme/Fonts.java @@ -0,0 +1,9 @@ +package com.sap.sailing.dashboards.gwt.client.theme; + +/** + * @author Alexander Ries (D062114) + * + */ +public class Fonts { + public static final String DASHBOARD_FONT_FAMILY = "'Open Sans', Arial, Verdana, sans-serif"; +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/theme/Sizes.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/theme/Sizes.java new file mode 100644 index 00000000000..a539e9021a4 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/theme/Sizes.java @@ -0,0 +1,13 @@ +package com.sap.sailing.dashboards.gwt.client.theme; + +/** + * @author Alexander Ries (D062114) + * + */ +public class Sizes { + public static final String DASHBOARD_FONT_SIZE_VERY_SMALL = "12px"; + public static final String DASHBOARD_FONT_SIZE_SMALL = "12px"; + public static final String DASHBOARD_FONT_SIZE_MEDIUM = "12px"; + public static final String DASHBOARD_FONT_SIZE_BIG = "12px"; + public static final String DASHBOARD_FONT_SIZE_VERY_BIG = "12px"; +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/theme/theme.gss b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/theme/theme.gss new file mode 100644 index 00000000000..168c9ab59aa --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/theme/theme.gss @@ -0,0 +1,7 @@ +@provide 'theme'; + +/*Colors*/ +@def WHITE eval("com.sap.sailing.dashboards.gwt.client.theme.Colors.WHITE"); + +/*Fonts*/ +@def DASHBOARD_FONT_FAMILY eval("com.sap.sailing.dashboards.gwt.client.theme.Fonts.DASHBOARD_FONT_FAMILY"); diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/util/HighchartsUtil.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/util/HighchartsUtil.java new file mode 100644 index 00000000000..d1bbd46a05c --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/util/HighchartsUtil.java @@ -0,0 +1,37 @@ +package com.sap.sailing.dashboards.gwt.client.util; + +import org.moxieapps.gwt.highcharts.client.Chart; + +import com.google.gwt.user.client.Timer; + +/** + * @author Alexander Ries (D062114) + * + */ +public class HighchartsUtil { + + /** + * The {@link Chart} spacing is not consistent + * so there is a need to correct the top, bottom and right spacing + * of the chart to make it look visually right. + * */ + public static void correctSpacingOfChart(Chart chart) { + chart.setSpacingTop(17) + .setSpacingBottom(13) + .setSpacingRight(35); + } + + /** + * In cases where it is necessary to initialize a {@link Chart} with dynamic size during the same browser loop, where it´s dynamic container is initialized, + * the {@link Chart.setSizeToMatchContainer()} method will not work. The solution to that problem is to call {@link Chart#setSizeToMatchContainer()} later, + * when the container of the chart is completely initialized and its size is available to the {@link Chart}. + * */ + public static void setSizeToMatchContainerDelayed(final Chart chart) { + Timer elapsedTimer = new Timer() { + public void run() { + chart.setSizeToMatchContainer(); + } + }; + elapsedTimer.schedule(500); + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/visualeffects/BlurEffect.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/visualeffects/BlurEffect.css index 6d533471683..7e1020ab331 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/visualeffects/BlurEffect.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/visualeffects/BlurEffect.css @@ -1,11 +1,11 @@ @CHARSET "UTF-8"; .blurred { - -webkit-filter: blur(10pt) brightness(0.92); - filter: blur(10pt) brightness(0.92); + -webkit-filter: brightness(0.7); + filter: brightness(0.7); } .not_blurred { - -webkit-filter: blur(0pt) brightness(1); - filter: blur(0pt) brightness(1); + -webkit-filter: brightness(1); + filter: brightness(1); } \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/PollsLiveDataEvery5Seconds.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/PollsLiveDataEvery5Seconds.java new file mode 100644 index 00000000000..c400402924c --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/PollsLiveDataEvery5Seconds.java @@ -0,0 +1,13 @@ +package com.sap.sailing.dashboards.gwt.client.widgets; +import com.sap.sailing.dashboards.gwt.client.DashboardClientFactory; +import com.sap.sse.gwt.client.player.TimeListener; + + +/** + * @author Alexander Ries (D062114) + * + */ +public interface PollsLiveDataEvery5Seconds extends TimeListener { + + public void registerForDashboardFiveSecondsTimer(DashboardClientFactory dashboardClientFactory); +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartAnalysisCombinedWindPanel.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisCombinedWindPanel.gss similarity index 88% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartAnalysisCombinedWindPanel.css rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisCombinedWindPanel.gss index dc195777bbf..d4843d25e1b 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartAnalysisCombinedWindPanel.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisCombinedWindPanel.gss @@ -1,4 +1,4 @@ -@CHARSET "UTF-8"; +@require 'theme'; .combinedWindPanel { margin-top: 10pt; @@ -18,7 +18,7 @@ line-height: 25pt; vertical-align: middle; font-size: 11pt; - font-family: "Arial"; + font-family: DASHBOARD_FONT_FAMILY; text-align: center; } diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartAnalysisPageChangeListener.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisPageChangeListener.java similarity index 56% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartAnalysisPageChangeListener.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisPageChangeListener.java index 98e4400e243..11cd4d3d0bd 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartAnalysisPageChangeListener.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisPageChangeListener.java @@ -1,6 +1,6 @@ -package com.sap.sailing.dashboards.gwt.client.startanalysis; +package com.sap.sailing.dashboards.gwt.client.widgets.startanalysis; -import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO; +import com.sap.sailing.dashboards.gwt.shared.dto.StartAnalysisDTO; public interface StartAnalysisPageChangeListener { diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysis.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisWidget.gss similarity index 75% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysis.css rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisWidget.gss index bb188a8b41b..4e60c3bae9d 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysis.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisWidget.gss @@ -1,4 +1,4 @@ -@CHARSET "UTF-8"; +@require 'theme'; .startanalysis { position: absolute; @@ -10,13 +10,13 @@ bottom: 0pt; } -.panelHeader { +.panel_header { height: 100%; margin-top: 0; line-height: 30pt; vertical-align: middle; font-size: 14pt; - font-family: 'Open Sans', Arial, Verdana, sans-serif; + font-family: DASHBOARD_FONT_FAMILY; width: 100%; text-align: center; font-weight: bold; @@ -24,22 +24,22 @@ background-color: white; } -.startanalysis_controll_header_button { +.controll_header_button { position: absolute; top: 10pt; height: 17.5pt; width: 17.5pt; } -.startanalysis_controll_header_button_left { +.controll_header_button_left { left: 10%; } -.startanalysis_controll_header_button_right { +.controll_header_button_right { right: 10%; } -.startanalysis_card_container { +.card_container { position: absolute; overflow: visible; top: 33.5pt; @@ -51,7 +51,7 @@ transition: left 0.7s ease-in-out; } -.startanalysis_card { +.card { position: absolute; display: inline; width: 80%; @@ -62,7 +62,7 @@ overflow: hidden; } -.startanalysis_card_table { +.card_table { position: absolute; bottom: 0pt; right: 0pt; @@ -74,7 +74,7 @@ z-index: 2; } -.startanalysis_card_wind_line_data_container { +.card_wind_line_data_container { position: absolute; top: 10pt; left: 10pt; @@ -83,7 +83,7 @@ z-index: 1; } -.startanalysis_card_data { +.card_data { position: absolute; right: 0pt; width: 45pt; @@ -91,11 +91,11 @@ line-height: 25pt; vertical-align: middle; font-size: 11pt; - font-family: 'Open Sans', Arial, Verdana, sans-serif; + font-family: DASHBOARD_FONT_FAMILY; text-align: center; } -.startanalysis_card_line_advantage { +.card_line_advantage { top: 0pt; left: 55pt; width: 215pt; @@ -104,7 +104,7 @@ background-color: white; } -.startanalysis_card_race_time{ +.card_race_time{ position: absolute; top: 10pt; right: 10pt; @@ -113,11 +113,11 @@ color: white; line-height: 25pt; font-size: 11pt; - font-family: 'Open Sans', Arial, Verdana, sans-serif; + font-family: DASHBOARD_FONT_FAMILY; z-index: 1; } -.startanalysis_card_line_data_geo { +.card_line_data_geo { top: 0pt; left: 220pt; width: 100pt; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysisComponent.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisWidget.java similarity index 65% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysisComponent.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisWidget.java index 9645a09aab4..29a7dc12220 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysisComponent.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisWidget.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.client.startanalysis; +package com.sap.sailing.dashboards.gwt.client.widgets.startanalysis; import java.util.ArrayList; import java.util.Date; @@ -12,10 +12,10 @@ 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.resources.client.CssResource; +import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.Cookies; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FocusPanel; @@ -24,44 +24,43 @@ import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.dashboards.gwt.client.RibDashboardImageResources; -import com.sap.sailing.dashboards.gwt.client.RibDashboardServiceAsync; -import com.sap.sailing.dashboards.gwt.client.notifications.BottomNotification; -import com.sap.sailing.dashboards.gwt.client.notifications.BottomNotificationClickListener; -import com.sap.sailing.dashboards.gwt.client.notifications.BottomNotificationType; +import com.sap.sailing.dashboards.gwt.client.DashboardClientFactory; +import com.sap.sailing.dashboards.gwt.client.actions.GetCompetitorInLeaderboardAction; +import com.sap.sailing.dashboards.gwt.client.actions.GetStartAnalysesAction; +import com.sap.sailing.dashboards.gwt.client.notifications.bottom.BottomNotification; +import com.sap.sailing.dashboards.gwt.client.notifications.bottom.BottomNotificationClickListener; +import com.sap.sailing.dashboards.gwt.client.notifications.bottom.BottomNotificationType; import com.sap.sailing.dashboards.gwt.client.popups.competitorselection.CompetitorSelectionListener; import com.sap.sailing.dashboards.gwt.client.popups.competitorselection.CompetitorSelectionPopup; import com.sap.sailing.dashboards.gwt.client.popups.competitorselection.SettingsButtonWithSelectionIndicationLabel; -import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO; +import com.sap.sailing.dashboards.gwt.client.widgets.PollsLiveDataEvery5Seconds; +import com.sap.sailing.dashboards.gwt.client.widgets.startanalysis.card.StartAnalysisCard; +import com.sap.sailing.dashboards.gwt.shared.DashboardURLParameters; +import com.sap.sailing.dashboards.gwt.shared.dto.LeaderboardCompetitorsDTO; +import com.sap.sailing.dashboards.gwt.shared.dto.StartAnalysesDTO; +import com.sap.sailing.dashboards.gwt.shared.dto.StartAnalysisDTO; import com.sap.sailing.domain.common.dto.CompetitorDTO; -import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sse.gwt.client.player.TimeListener; -import com.sap.sse.gwt.client.player.Timer; -import com.sap.sse.gwt.client.player.Timer.PlayModes; /** - * The class contains an collection of {@link StartlineAnalysisCard}s that are displayed in horizontal aligned pages. It - * shows only one {@link StartlineAnalysisCard} at a time and enables the user to switch between - * {@link StartlineAnalysisCard}s with left and right buttons represented by {@link #left_focus_panel} and - * {@link #right_focus_panel}. When a new {@link StartlineAnalysisCard} gets added to the component it shows a + * The class contains an collection of {@link StartAnalysisCard}s that are displayed in horizontal aligned pages. It + * shows only one {@link StartAnalysisCard} at a time and enables the user to switch between + * {@link StartAnalysisCard}s with left and right buttons represented by {@link #left_focus_panel} and + * {@link #right_focus_panel}. When a new {@link StartAnalysisCard} gets added to the component it shows a * {@link BottomNotification}. * * @author Alexander Ries (D062114) * */ -public class StartlineAnalysisComponent extends Composite implements HasWidgets { +public class StartAnalysisWidget extends Composite implements HasWidgets, PollsLiveDataEvery5Seconds { - private static StartlineAnalysisComponentUiBinder uiBinder = GWT.create(StartlineAnalysisComponentUiBinder.class); + private static StartAnalysisWidgetUiBinder uiBinder = GWT.create(StartAnalysisWidgetUiBinder.class); - interface StartlineAnalysisComponentUiBinder extends UiBinder { + interface StartAnalysisWidgetUiBinder extends UiBinder { } interface StartlineAnalysis extends CssResource { } - @UiField - StartlineAnalysis style; - @UiField HTMLPanel header; @@ -78,7 +77,7 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets FocusPanel right_focus_panel; /** - * Gets populated with new {@link StartlineAnalysisCard}s when the method + * Gets populated with new {@link StartAnalysisCard}s when the method * {@link #addStartAnalysisCard(StartAnalysisDTO)} gets called. * */ @UiField @@ -90,65 +89,57 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets private double currentScrollPosition = 0; /** - * The page or {@link StartlineAnalysisCard} the {@link #startanalysis_card_container} displays. Begins with 0. + * The page or {@link StartAnalysisCard} the {@link #startanalysis_card_container} displays. Begins with 0. * */ private int page = 0; /** - * Amount of {@link StartlineAnalysisCard}s in {@link #startanalysis_card_container}. + * Amount of {@link StartAnalysisCard}s in {@link #startanalysis_card_container}. * */ private int numberOfStartAnalysisCards = 0; private boolean displaysCards; private List starts; - private List pageChangeListener; + private List pageChangeListener; private BottomNotification bottomNotification; - private Timer timer; - private RibDashboardServiceAsync ribDashboardServiceAsync; - private SailingServiceAsync sailingServiceAsync; - - private static final int RERFRESH_INTERVAL = 10000; private CompetitorSelectionPopup competitorSelectionPopup; private SettingsButtonWithSelectionIndicationLabel settingsButtonWithSelectionIndicationLabel; + + private DashboardClientFactory dashboardClientFactory; - private String leaderboardName; - - private static final String PARAM_LEADERBOARD_NAME = "leaderboardName"; private static final String SELECTED_COMPETITOR_ID_COOKIE_KEY = "selectedCompetitorId"; private static final int SELECTED_COMPETITOR_ID_COOKIE_KEY_EXPIRE_TIME_IN_MILLIS = 60 * 1000 * 60 * 5; private static final int SCROLL_OFFSET_STARTANALYSIS_CARDS = 83; - private static final Logger logger = Logger.getLogger(StartlineAnalysisComponent.class.getName()); + private static final Logger logger = Logger.getLogger(StartAnalysisWidget.class.getName()); /** * Component that contains handles, displays and loads startanalysis cards. * */ - public StartlineAnalysisComponent(RibDashboardServiceAsync ribDashboardServiceAsync, SailingServiceAsync sailingServiceAsync) { - this.ribDashboardServiceAsync = ribDashboardServiceAsync; - this.sailingServiceAsync = sailingServiceAsync; - pageChangeListener = new ArrayList(); + public StartAnalysisWidget(DashboardClientFactory dashboardClientFactory) { + StartAnalysisWidgetResources.INSTANCE.gss().ensureInjected(); + StartAnalysisWidgetResources.INSTANCE.combinedWindPanelStyle().ensureInjected(); + this.dashboardClientFactory = dashboardClientFactory; + pageChangeListener = new ArrayList(); starts = new ArrayList(); - this.leaderboardName = Window.Location.getParameter(PARAM_LEADERBOARD_NAME); - initWidget(uiBinder.createAndBindUi(this)); - initCompetitorSelectionPopupAndAddCompetitorSelectionListener(); initLeftRightButtons(); - initTimer(); initAndAddBottomNotification(); getCachedSelectedCompetitorOrAskForWithPopup(); initAndAddSettingsButtonWithSelectionIndicationLabel(); + registerForDashboardFiveSecondsTimer(dashboardClientFactory); } private void initLeftRightButtons() { - leftButton.setResource(RibDashboardImageResources.INSTANCE.leftdisabled()); + leftButton.setResource(StartAnalysisWidgetResources.INSTANCE.leftdisabled()); left_focus_panel.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { clickedArrowButton(false); } }); - rightButton.setResource(RibDashboardImageResources.INSTANCE.rightdisabled()); + rightButton.setResource(StartAnalysisWidgetResources.INSTANCE.rightdisabled()); right_focus_panel.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { @@ -157,53 +148,33 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets }); } - private void initTimer() { - timer = new Timer(PlayModes.Live); - timer.setRefreshInterval(RERFRESH_INTERVAL); - timer.addTimeListener(new TimeListener() { - - @Override - public void timeChanged(Date newTime, Date oldTime) { - String selectedCompetitorId = Cookies.getCookie(SELECTED_COMPETITOR_ID_COOKIE_KEY); - if (selectedCompetitorId != null) { - loadStartAnalysisDTOsForCompetitorID(selectedCompetitorId); - } else { - loadCompetitorsAndShowCompetitorSelectionPopup(); - } - } - }); - } - private void loadStartAnalysisDTOsForCompetitorID(String competitorIdAsString){ logger.log(Level.INFO, "Loading startanalysis for competitor id " + competitorIdAsString); - ribDashboardServiceAsync.getStartAnalysisListForCompetitorIDAndLeaderboardName( - competitorIdAsString, leaderboardName, new AsyncCallback>() { - @Override - public void onSuccess(List result) { - logger.log(Level.INFO, "Received startanalysis list"); - if (result != null && !result.isEmpty()) { - - if (displayedStartAnalysisCompetitorDifferentToRequestedOne()) { - removeAllStartAnalysisCards(); - } - - if (result.size() != starts.size()) { - showNotificationForNewStartAnalysis(); - } - logger.log(Level.INFO, "Updating UI with startanalysis list"); - addNewStartAnalysisCards(result); - settingsButtonWithSelectionIndicationLabel - .setSelectionIndicationTextOnLabel(result.get(0).competitor.getName()); - } else { - logger.log(Level.INFO, "Received startanalysis list is null or empty"); - } - } + dashboardClientFactory.getDispatch().execute(new GetStartAnalysesAction(DashboardURLParameters.LEADERBOARD_NAME.getValue(), competitorIdAsString), new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - logger.log(Level.INFO, "Failed to received startanalysis list, "+caught.getMessage()); - } - }); + @Override + public void onFailure(Throwable caught) { + logger.log(Level.INFO, "Failed to received startanalysis list, "+caught.getMessage()); + } + + @Override + public void onSuccess(StartAnalysesDTO result) { + logger.log(Level.INFO, "Received startanalysis list"); + if (result != null && !result.getStartAnalyses().isEmpty()) { + if (displayedStartAnalysisCompetitorDifferentToRequestedOne()) { + removeAllStartAnalysisCards(); + } + if (result.getStartAnalyses().size() != starts.size()) { + showNotificationForNewStartAnalysis(); + } + logger.log(Level.INFO, "Updating UI with startanalysis list"); + addNewStartAnalysisCards(result.getStartAnalyses()); + settingsButtonWithSelectionIndicationLabel.setSelectionIndicationTextOnLabel(result.getStartAnalyses().get(0).competitor.getName()); + } else { + logger.log(Level.INFO, "Received startanalysis list is null or empty"); + } + } + }); } private void initAndAddBottomNotification() { @@ -247,27 +218,28 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets private void loadCompetitorsAndShowCompetitorSelectionPopup() { logger.log(Level.INFO, "Requesting Competitors in Leaderboard"); - ribDashboardServiceAsync.getCompetitorsInLeaderboard(leaderboardName, new AsyncCallback>() { - @Override - public void onSuccess(List result) { - logger.log(Level.INFO, "Received competitors for leaderboard name"); - if (result != null && result.size() > 0) { - if (!competitorSelectionPopup.isShown()) { - logger.log(Level.INFO, "Showing CompetitorSelectionPopup with competitors"); - competitorSelectionPopup.show(result); - } else { - logger.log(Level.INFO, "CompetitorSelectionPopup aleady shown"); - } - } else { - logger.log(Level.INFO, "Received competitors are null or empty"); - } - } + dashboardClientFactory.getDispatch().execute(new GetCompetitorInLeaderboardAction(DashboardURLParameters.LEADERBOARD_NAME.getValue()), new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - logger.log(Level.INFO, "Failed to received competitors for leaderboard name, "+caught.getMessage()); - } - }); + @Override + public void onSuccess(LeaderboardCompetitorsDTO result) { + logger.log(Level.INFO, "Received competitors for leaderboard name"); + if (result.getCompetitors() != null && result.getCompetitors().size() > 0) { + if (!competitorSelectionPopup.isShown()) { + logger.log(Level.INFO, "Showing CompetitorSelectionPopup with competitors"); + competitorSelectionPopup.show(result.getCompetitors()); + } else { + logger.log(Level.INFO, "CompetitorSelectionPopup aleady shown"); + } + } else { + logger.log(Level.INFO, "Received competitors are null or empty"); + } + } + + @Override + public void onFailure(Throwable caught) { + logger.log(Level.INFO, "Failed to received competitors for leaderboard name, "+caught.getMessage()); + } + }); } private void initAndAddSettingsButtonWithSelectionIndicationLabel() { @@ -298,11 +270,11 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets int displayPage = page + 1; header.getElement().setInnerHTML("Start " + starts.get(displayPage - 1).raceName); if (page != numberOfStartAnalysisCards - 1) { - rightButton.setResource(RibDashboardImageResources.INSTANCE.right()); + rightButton.setResource(StartAnalysisWidgetResources.INSTANCE.right()); rightButton.getElement().getStyle().setProperty("disabled", "false"); } if (page == 0) { - leftButton.setResource(RibDashboardImageResources.INSTANCE.leftdisabled()); + leftButton.setResource(StartAnalysisWidgetResources.INSTANCE.leftdisabled()); leftButton.getElement().getStyle().setProperty("disabled", "true"); } } @@ -315,11 +287,11 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets int displayPage = page + 1; header.getElement().setInnerHTML("Start " + starts.get(displayPage - 1).raceName); if (page >= 0) { - leftButton.setResource(RibDashboardImageResources.INSTANCE.left()); + leftButton.setResource(StartAnalysisWidgetResources.INSTANCE.left()); leftButton.getElement().getStyle().setProperty("disabled", "false"); } if (page == numberOfStartAnalysisCards - 1) { - rightButton.setResource(RibDashboardImageResources.INSTANCE.rightdisabled()); + rightButton.setResource(StartAnalysisWidgetResources.INSTANCE.rightdisabled()); rightButton.getElement().getStyle().setProperty("disabled", "true"); } } @@ -335,9 +307,9 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets startanalysis_card_container.getElement().getStyle().setLeft(currentScrollPosition, Unit.PCT); header.getElement().setInnerHTML("Start " + starts.get(page).raceName); notifyStartAnalysisPageChangeListener(page); - rightButton.setResource(RibDashboardImageResources.INSTANCE.rightdisabled()); + rightButton.setResource(StartAnalysisWidgetResources.INSTANCE.rightdisabled()); rightButton.getElement().getStyle().setProperty("disabled", "true"); - leftButton.setResource(RibDashboardImageResources.INSTANCE.left()); + leftButton.setResource(StartAnalysisWidgetResources.INSTANCE.left()); leftButton.getElement().getStyle().setProperty("disabled", "false"); } } @@ -346,14 +318,14 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets logger.log(Level.INFO, "Adding Startanalysis Card"); if (displaysCards == false) { displaysCards = true; - header.getElement().setInnerHTML("Start " + startAnalysisDTO.raceName); + header.getElement().setInnerHTML("Start " + SafeHtmlUtils.fromString(startAnalysisDTO.raceName)); } if (numberOfStartAnalysisCards > 0) { - rightButton.setResource(RibDashboardImageResources.INSTANCE.right()); + rightButton.setResource(StartAnalysisWidgetResources.INSTANCE.right()); } - final StartlineAnalysisCard startlineAnalysisCard = new StartlineAnalysisCard(numberOfStartAnalysisCards + final StartAnalysisCard startlineAnalysisCard = new StartAnalysisCard(numberOfStartAnalysisCards * SCROLL_OFFSET_STARTANALYSIS_CARDS + 10, numberOfStartAnalysisCards, startAnalysisDTO, - sailingServiceAsync); + dashboardClientFactory.getSailingService()); startanalysis_card_container.add(startlineAnalysisCard); startlineAnalysisCard.startAnalysisComponentPageChangedToIndexAndStartAnalysis(page, startAnalysisDTO); registerPageChangeListener(startlineAnalysisCard); @@ -370,7 +342,7 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets this.bottomNotification.show(BottomNotificationType.NEW_STARTANALYSIS_AVAILABLE); } - public void registerPageChangeListener(StartlineAnalysisCard s) { + public void registerPageChangeListener(StartAnalysisCard s) { pageChangeListener.add(s); } @@ -412,6 +384,23 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets return false; } } + + @Override + public void timeChanged(Date newTime, Date oldTime) { + String selectedCompetitorId = Cookies.getCookie(SELECTED_COMPETITOR_ID_COOKIE_KEY); + if (selectedCompetitorId != null) { + loadStartAnalysisDTOsForCompetitorID(selectedCompetitorId); + } else { + loadCompetitorsAndShowCompetitorSelectionPopup(); + } + } + + @Override + public void registerForDashboardFiveSecondsTimer(DashboardClientFactory dashboardClientFactory) { + if (dashboardClientFactory != null) { + dashboardClientFactory.getDashboardFiveSecondsTimer().addTimeListener(this); + } + } @Override public void add(Widget w) { diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisWidget.ui.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisWidget.ui.xml new file mode 100644 index 00000000000..3ede34ebdf6 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisWidget.ui.xml @@ -0,0 +1,22 @@ + + + + + + + + No Starts Available + + + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisWidgetResources.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisWidgetResources.java new file mode 100644 index 00000000000..7a0aab98c25 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/StartAnalysisWidgetResources.java @@ -0,0 +1,51 @@ +package com.sap.sailing.dashboards.gwt.client.widgets.startanalysis; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.ImageResource; +import com.sap.sailing.gwt.ui.client.shared.racemap.CombinedWindPanelStyle; + +public interface StartAnalysisWidgetResources extends ClientBundle { + + public static final StartAnalysisWidgetResources INSTANCE = GWT.create(StartAnalysisWidgetResources.class); + + @Source("com/sap/sailing/dashboards/gwt/client/images/leftarrow.png") + ImageResource left(); + + @Source("com/sap/sailing/dashboards/gwt/client/images/rightarrow.png") + ImageResource right(); + + @Source("com/sap/sailing/dashboards/gwt/client/images/leftarrow_disabled.png") + ImageResource leftdisabled(); + + @Source("com/sap/sailing/dashboards/gwt/client/images/rightarrow_disabled.png") + ImageResource rightdisabled(); + + @Source("com/sap/sailing/dashboards/gwt/client/images/windarrow.png") + ImageResource combinedWindIcon(); + + @Source({"com/sap/sailing/dashboards/gwt/client/theme/theme.gss", "StartAnalysisWidget.gss"}) + StartAnalysisGSS gss(); + + @Source({"com/sap/sailing/dashboards/gwt/client/theme/theme.gss", "StartAnalysisCombinedWindPanel.gss"}) + CombinedWindPanelStyle combinedWindPanelStyle(); + + public interface StartAnalysisGSS extends CssResource { + String startanalysis(); + String panel_header(); + String controll_header_button(); + String controll_header_button_left(); + String controll_header_button_right(); + String card_container(); + String card(); + String card_table(); + String card_wind_line_data_container(); + String card_data(); + String card_line_advantage(); + String card_race_time(); + String card_line_data_geo(); + String left_focus_panel(); + String right_focus_panel(); + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysisCard.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/card/StartAnalysisCard.java similarity index 89% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysisCard.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/card/StartAnalysisCard.java index 60ebd904b53..8d997ca6d63 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAnalysisCard.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/card/StartAnalysisCard.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.client.startanalysis; +package com.sap.sailing.dashboards.gwt.client.widgets.startanalysis.card; import java.util.ArrayList; import java.util.Collections; @@ -12,14 +12,18 @@ import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.i18n.client.NumberFormat; import com.google.gwt.resources.client.CssResource; +import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.dashboards.gwt.client.startanalysis.rankingtable.StartAnalysisStartRankTable; -import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO; +import com.sap.sailing.dashboards.gwt.client.widgets.startanalysis.StartAnalysisPageChangeListener; +import com.sap.sailing.dashboards.gwt.client.widgets.startanalysis.StartAnalysisWidgetResources; +import com.sap.sailing.dashboards.gwt.client.widgets.startanalysis.rankingtable.StartAnalysisStartRankTable; +import com.sap.sailing.dashboards.gwt.shared.StartlineAdvantageType; +import com.sap.sailing.dashboards.gwt.shared.dto.StartAnalysisDTO; import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.gwt.ui.client.CompetitorColorProvider; import com.sap.sailing.gwt.ui.client.CompetitorColorProviderImpl; @@ -35,19 +39,16 @@ import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomType import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.player.Timer.PlayModes; -public class StartlineAnalysisCard extends Composite implements HasWidgets, StartAnalysisPageChangeListener { +public class StartAnalysisCard extends Composite implements HasWidgets, StartAnalysisPageChangeListener { private static StartlineAnalysisCardUiBinder uiBinder = GWT.create(StartlineAnalysisCardUiBinder.class); - interface StartlineAnalysisCardUiBinder extends UiBinder { + interface StartlineAnalysisCardUiBinder extends UiBinder { } interface StartlineAnalysis extends CssResource { } - @UiField - StartlineAnalysis style; - @UiField HTMLPanel startanalysis_card; @@ -74,12 +75,8 @@ public class StartlineAnalysisCard extends Composite implements HasWidgets, Star private final int TABLE_MARGIN_IN_PT = 10; private final String RACE_TIME_START = "00:00:00"; - - private static StartAnalysisResources resources = GWT.create(StartAnalysisResources.class); - public StartlineAnalysisCard(double leftCSSProperty, int cardId, StartAnalysisDTO startAnalysisDTO, - SailingServiceAsync sailingServiceAsync) { + public StartAnalysisCard(double leftCSSProperty, int cardId, StartAnalysisDTO startAnalysisDTO, SailingServiceAsync sailingServiceAsync) { stringMessages = StringMessages.INSTANCE; - resources.combinedWindPanelStyle().ensureInjected(); this.sailingServiceAsync = sailingServiceAsync; colorProvider = new CompetitorColorProviderImpl(); competitorSelectionModel = new CompetitorSelectionModel(/* hasMultiSelection */true, colorProvider); @@ -96,20 +93,20 @@ public class StartlineAnalysisCard extends Composite implements HasWidgets, Star private void fillWindAndStartLineData(StartAnalysisDTO startAnalysisDTO) { if (startAnalysisDTO.startAnalysisWindLineInfoDTO != null) { setLineAdvantageDivWidth(startAnalysisDTO.startAnalysisWindLineInfoDTO.startLineAdvantage.startLineAdvatageType); - String startLineAdvantageType; + final String startLineAdvantageType; if (startAnalysisDTO.startAnalysisWindLineInfoDTO.startLineAdvantage.startLineAdvatageType .equals(StartlineAdvantageType.GEOMETRIC)) { startLineAdvantageType = stringMessages.dashboardStartlineAdvantageByGeometry(); - }else{ + } else { startLineAdvantageType = stringMessages.dashboardStartlineAdvantagesByWind(); } startanalysis_card_line_advantage - .setInnerHTML(startLineAdvantageType + .setInnerHTML(SafeHtmlUtils.fromString(startLineAdvantageType + ": " + NumberFormat .getFormat("#0.0") .format(startAnalysisDTO.startAnalysisWindLineInfoDTO.startLineAdvantage.startLineAdvantage) - + " m"); + + " m").asString()); String formattedTimeSinceStart; if (startAnalysisDTO.racingProcedureType.equals(RacingProcedureType.GateStart)) { formattedTimeSinceStart = getRaceTimeStringFromMilliseconds(startAnalysisDTO.tailLenghtInMilliseconds); @@ -152,8 +149,7 @@ public class StartlineAnalysisCard extends Composite implements HasWidgets, Star asyncActionsExecutor, null, Collections.singletonList(startAnalysisDTO.regattaAndRaceIdentifier), 5000l /* requestInterval */); raceMap = new RaceMap(sailingServiceAsync, asyncActionsExecutor, null, timer, competitorSelectionModel, StringMessages.INSTANCE, false, false, false, false, startAnalysisDTO.regattaAndRaceIdentifier, - resources.combinedWindPanelStyle(), /* showHeaderPanel */ true); - raceMap.onRaceSelectionChange(Collections.singletonList(startAnalysisDTO.regattaAndRaceIdentifier)); + StartAnalysisWidgetResources.INSTANCE.combinedWindPanelStyle(), /* showHeaderPanel */ true); raceMap.getSettings().setZoomSettings(new RaceMapZoomSettings(zoomTypes, false)); raceMap.getSettings().setHelpLinesSettings(getHelpLineSettings()); raceMap.getSettings().setTailLengthInMilliseconds(startAnalysisDTO.tailLenghtInMilliseconds); diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/card/StartAnalysisCard.ui.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/card/StartAnalysisCard.ui.xml new file mode 100644 index 00000000000..fbb57deff27 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/card/StartAnalysisCard.ui.xml @@ -0,0 +1,16 @@ + + + + + + +
+
+
+
Time: 00:05:00
+ +
+
\ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTable.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTable.java similarity index 96% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTable.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTable.java index 6cbde17f6a2..34ac687aae1 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTable.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTable.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.client.startanalysis.rankingtable; +package com.sap.sailing.dashboards.gwt.client.widgets.startanalysis.rankingtable; import java.util.List; @@ -9,7 +9,7 @@ import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy; import com.google.gwt.user.cellview.client.TextColumn; import com.google.gwt.user.client.ui.AbsolutePanel; -import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisCompetitorDTO; +import com.sap.sailing.dashboards.gwt.shared.dto.StartAnalysisCompetitorDTO; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sse.common.Util.Pair; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTableRankAtFirstMarkCell.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTableRankAtFirstMarkCell.java similarity index 94% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTableRankAtFirstMarkCell.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTableRankAtFirstMarkCell.java index fd64139ae9f..3169789147f 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTableRankAtFirstMarkCell.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTableRankAtFirstMarkCell.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.client.startanalysis.rankingtable; +package com.sap.sailing.dashboards.gwt.client.widgets.startanalysis.rankingtable; import com.google.gwt.cell.client.AbstractCell; import com.google.gwt.cell.client.Cell; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTableRankAtFirstMarkColumn.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTableRankAtFirstMarkColumn.java similarity index 84% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTableRankAtFirstMarkColumn.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTableRankAtFirstMarkColumn.java index f1caccad827..cc0291e1b60 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTableRankAtFirstMarkColumn.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTableRankAtFirstMarkColumn.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.client.startanalysis.rankingtable; +package com.sap.sailing.dashboards.gwt.client.widgets.startanalysis.rankingtable; import com.google.gwt.user.cellview.client.Column; import com.sap.sse.common.Util.Pair; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTableStyle.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTableStyle.css similarity index 100% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTableStyle.css rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTableStyle.css diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTableStyleResource.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTableStyleResource.java similarity index 79% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTableStyleResource.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTableStyleResource.java index dc0c35c590e..d648ee97b44 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTableStyleResource.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTableStyleResource.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.client.startanalysis.rankingtable; +package com.sap.sailing.dashboards.gwt.client.widgets.startanalysis.rankingtable; import com.google.gwt.user.cellview.client.CellTable; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTableTeamCell.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTableTeamCell.java similarity index 91% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTableTeamCell.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTableTeamCell.java index 7b2f32a3dda..20baef7dfe9 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTableTeamCell.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTableTeamCell.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.client.startanalysis.rankingtable; +package com.sap.sailing.dashboards.gwt.client.widgets.startanalysis.rankingtable; import com.google.gwt.cell.client.AbstractCell; import com.google.gwt.cell.client.Cell; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTableTeamCollumn.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTableTeamCollumn.java similarity index 83% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTableTeamCollumn.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTableTeamCollumn.java index b3fc3121acf..9ad2d1c776f 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/rankingtable/StartAnalysisStartRankTableTeamCollumn.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/rankingtable/StartAnalysisStartRankTableTeamCollumn.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.client.startanalysis.rankingtable; +package com.sap.sailing.dashboards.gwt.client.widgets.startanalysis.rankingtable; import com.google.gwt.user.cellview.client.Column; import com.sap.sse.common.Util.Pair; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartLineAdvantageComponent.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/course/StartLineAdvantageByGeometryWidget.gss similarity index 76% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartLineAdvantageComponent.css rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/course/StartLineAdvantageByGeometryWidget.gss index 93008d74441..77918068ae3 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartLineAdvantageComponent.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/course/StartLineAdvantageByGeometryWidget.gss @@ -1,4 +1,4 @@ -@CHARSET "UTF-8"; +@require 'theme'; .startLineAdvantageComponent_liveAveragePanel { bottom: 5pt; @@ -19,12 +19,12 @@ bottom: 20%; } -.startLineAdvantageComponent_middleLine{ - top: 35% +.startLineAdvantageComponent_middleLine { + top: 35%; bottom: 20%; } -.startLineAdvantageComponent_wrapper{ +.startLineAdvantageComponent_wrapper { position: absolute; width: 100%; height: 100%; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/course/StartLineAdvantageByGeometryWidget.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/course/StartLineAdvantageByGeometryWidget.java new file mode 100644 index 00000000000..e930a70fa76 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/course/StartLineAdvantageByGeometryWidget.java @@ -0,0 +1,82 @@ +package com.sap.sailing.dashboards.gwt.client.widgets.startlineadvantage.course; + +import java.util.Date; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.gwt.i18n.client.NumberFormat; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sailing.dashboards.gwt.client.DashboardClientFactory; +import com.sap.sailing.dashboards.gwt.client.actions.GetStartlineAdvantageByGeometryAction; +import com.sap.sailing.dashboards.gwt.client.widgets.PollsLiveDataEvery5Seconds; +import com.sap.sailing.dashboards.gwt.client.widgets.startlineadvantage.util.LiveAverageComponent; +import com.sap.sailing.dashboards.gwt.shared.DashboardURLParameters; +import com.sap.sailing.dashboards.gwt.shared.dto.StartLineAdvantageDTO; +import com.sap.sailing.gwt.ui.client.StringMessages; + +/** + * The purpose of the class is to display the live and average start line advantage either by wind or by geometry. There + * is only one advantage display at once, so the user can switch between these following two + * {@link StartLineAdvantageComponentState}s {@link StartLineAdvantageComponentStateShowsAdvantageByWind} and + * {@link StartLineAdvantageComponentStateShowsAdvantageByGeometry} by tabing somewhere at the widget. The class extends + * from {@link LiveAverageComponent} to get necessary Ui Elements but customizes these with own CSS. + * + * @author Alexander Ries (D062114) + * + */ +public class StartLineAdvantageByGeometryWidget extends LiveAverageComponent implements PollsLiveDataEvery5Seconds { + + private DashboardClientFactory dashboardClientFactory; + + private static StringMessages stringConstants = StringMessages.INSTANCE; + private static final Logger logger = Logger.getLogger(StartLineAdvantageByGeometryWidget.class.getName()); + + public StartLineAdvantageByGeometryWidget(DashboardClientFactory dashboardClientFactory) { + super(stringConstants.dashboardStartlineAdvantageByGeometry(), "m"); + this.dashboardClientFactory = dashboardClientFactory; + StartLineAdvantageByGeometryWidgetRessources.INSTANCE.gss().ensureInjected(); + this.header.setInnerText(stringConstants.dashboardStartlineAdvantageByGeometry()); + this.header.addClassName(StartLineAdvantageByGeometryWidgetRessources.INSTANCE.gss().startLineAdvantageComponent_header()); + liveAveragePanel.getElement().addClassName(StartLineAdvantageByGeometryWidgetRessources.INSTANCE.gss().startLineAdvantageComponent_liveAveragePanel()); + livePanel.getElement().addClassName(StartLineAdvantageByGeometryWidgetRessources.INSTANCE.gss().startLineAdvantageComponent_livePanel()); + middleLine.getElement().addClassName(StartLineAdvantageByGeometryWidgetRessources.INSTANCE.gss().startLineAdvantageComponent_middleLine()); + averagePanel.getElement().addClassName(StartLineAdvantageByGeometryWidgetRessources.INSTANCE.gss().startLineAdvantageComponent_averagePanel()); + this.liveLabel.setInnerHTML(stringConstants.dashboardLiveWind()); + this.averageLabel.setInnerHTML(stringConstants.dashboardAverageWind()+"
"+stringConstants.dashboardAverageWindMinutes(15)); + registerForDashboardFiveSecondsTimer(dashboardClientFactory); + } + + @Override + public void timeChanged(Date newTime, Date oldTime) { + String leaderboardNameParameterValue = DashboardURLParameters.LEADERBOARD_NAME.getValue(); + if (leaderboardNameParameterValue != null) { + dashboardClientFactory.getDispatch().execute( + new GetStartlineAdvantageByGeometryAction(leaderboardNameParameterValue), + new AsyncCallback() { + + @Override + public void onFailure(Throwable caught) { + logger.log(Level.INFO, "Failed to received startline advantage by geometry"); + } + + @Override + public void onSuccess(StartLineAdvantageDTO result) { + if (result.startLineAdvantage != null) { + logger.log(Level.INFO, "Updating UI with RibDashboardRaceInfoDTO.startLineAdvantageDTO"); + setLiveValue(NumberFormat.getFormat("#0.0").format(result.startLineAdvantage.doubleValue())); + setAverageValue(NumberFormat.getFormat("#0.0").format(result.average.doubleValue())); + } else { + logger.log(Level.INFO, "RibDashboardRaceInfoDTO.startLineAdvantageDTO is 0"); + } + } + }); + } + } + + @Override + public void registerForDashboardFiveSecondsTimer(DashboardClientFactory dashboardClientFactory) { + if (dashboardClientFactory != null) { + dashboardClientFactory.getDashboardFiveSecondsTimer().addTimeListener(this); + } + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartLineAdvantageComponentRessources.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/course/StartLineAdvantageByGeometryWidgetRessources.java similarity index 53% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartLineAdvantageComponentRessources.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/course/StartLineAdvantageByGeometryWidgetRessources.java index 0633f3c9dc0..ccbc0fe1578 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartLineAdvantageComponentRessources.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/course/StartLineAdvantageByGeometryWidgetRessources.java @@ -1,18 +1,19 @@ -package com.sap.sailing.dashboards.gwt.client.startlineadvantage; +package com.sap.sailing.dashboards.gwt.client.widgets.startlineadvantage.course; import com.google.gwt.core.client.GWT; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.CssResource; import com.google.gwt.resources.client.CssResource.Shared; -public interface StartLineAdvantageComponentRessources extends ClientBundle { - public static final StartLineAdvantageComponentRessources INSTANCE = GWT.create(StartLineAdvantageComponentRessources.class); +public interface StartLineAdvantageByGeometryWidgetRessources extends ClientBundle { + + public static final StartLineAdvantageByGeometryWidgetRessources INSTANCE = GWT.create(StartLineAdvantageByGeometryWidgetRessources.class); - @Source("StartLineAdvantageComponent.css") - LocalCss css(); + @Source({"com/sap/sailing/dashboards/gwt/client/theme/theme.gss", "StartLineAdvantageByGeometryWidget.gss"}) + StartLineAdvantageByGeometryGss gss(); @Shared - public interface LocalCss extends CssResource { + public interface StartLineAdvantageByGeometryGss extends CssResource { public String startLineAdvantageComponent_liveAveragePanel(); public String startLineAdvantageComponent_header(); diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/util/LiveAverageComponent.gss similarity index 56% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.css rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/util/LiveAverageComponent.gss index 62094360316..cdeb1373791 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/util/LiveAverageComponent.gss @@ -1,5 +1,6 @@ +@require 'theme'; -.liveAverageCompoment_panel { +.live_average_component { position: absolute; left: 0px; right: 0px; @@ -7,7 +8,7 @@ bottom: 0px; } -.liveAverageCompoment_header_container { +.header_container { position: absolute; left: 0px; right: 0px; @@ -15,20 +16,20 @@ height: 35%; } -.liveAverageCompoment_header { +.header { height: 100%; margin-top: 5pt; line-height: 15pt; vertical-align: middle; font-size: 13pt; - font-family: 'Open Sans', Arial, Verdana, sans-serif; + font-family: DASHBOARD_FONT_FAMILY; width: 100%; text-align: center; overflow: visible; font-weight: bold; } -.liveAverageCompoment_live_container { +.live_container { position: absolute; top: 35%; bottom: 30%; @@ -36,7 +37,7 @@ left: 0%; } -.liveAverageCompoment_average_container { +.average_container { position: absolute; top: 35%; bottom: 30%; @@ -44,7 +45,7 @@ left: 52%; } -.liveAverageCompoment_middle_line { +.middle_line { position: absolute; top: 35%; height: 30pt; @@ -54,42 +55,42 @@ z-index: 100; } -.liveAverageCompoment_panel_value_number { - font-family: 'Open Sans', Arial, Verdana, sans-serif; +.panel_value_number { + font-family: DASHBOARD_FONT_FAMILY; font-size: 26pt; } -.liveAverageCompoment_panel_value_unit { - font-family: 'Open Sans', Arial, Verdana, sans-serif; +.panel_value_unit { + font-family: DASHBOARD_FONT_FAMILY; font-size: 15pt; } -.liveAverageCompoment_panel_value_unit_degrees { +.panel_value_unit_degrees { position: absolute; font-size: 20pt; font-weight: lighter; } -.liveAverageCompoment_value_panel { +.value_panel { margin: 0px; width: 100%; height: 90%; text-align: center; - font-family: 'Open Sans', Arial, Verdana, sans-serif; + font-family: DASHBOARD_FONT_FAMILY; font-weight: bolder; } -.liveAverageCompoment_label_text { +.label_text { margin: 0px; width: 100%; height: 100%; text-align: center; font-size: 11pt; color: grey; - font-family: 'Open Sans', Arial, Verdana, sans-serif; + font-family: DASHBOARD_FONT_FAMILY; } -.liveAverageCompoment_liveLabelContainer { +.live_label_container { position: absolute; bottom: 0px; left: 0px; @@ -97,7 +98,7 @@ height: 35%; } -.liveAverageCompoment_averageLabelContainer { +.average_label_container { position: absolute; bottom: 0px; right: 0px; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/util/LiveAverageComponent.java similarity index 63% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/util/LiveAverageComponent.java index 9afcaf508d3..dd976dc9f0a 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/LiveAverageComponent.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/util/LiveAverageComponent.java @@ -1,25 +1,24 @@ -package com.sap.sailing.dashboards.gwt.client.startlineadvantage; +package com.sap.sailing.dashboards.gwt.client.widgets.startlineadvantage.util; import java.util.Iterator; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.SpanElement; -import com.google.gwt.resources.client.CssResource; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.dashboards.gwt.client.visualeffects.NumberTickingAnimation; -import com.sap.sailing.dashboards.gwt.client.windchart.VerticalWindChartClickListener; -import com.sap.sailing.dashboards.gwt.client.windchart.WindBotComponent; +import com.sap.sailing.dashboards.gwt.client.widgets.startlineadvantage.course.StartLineAdvantageByGeometryWidget; +import com.sap.sailing.dashboards.gwt.client.widgets.windbot.WindBotWidget; +import com.sap.sailing.dashboards.gwt.client.widgets.windbot.charts.VerticalWindChartClickListener; import com.sap.sailing.gwt.ui.client.StringMessages; /** * The purpose of the class is to display a live and an average value of a continuously updating data source. - * {@link StartLineAdvantageByGeometryComponent} extends from it and the class {@link WindBotComponent} uses it to display wind + * {@link StartLineAdvantageByGeometryWidget} extends from it and the class {@link WindBotWidget} uses it to display wind * data. * * @author Alexander Ries (D062114) @@ -31,17 +30,12 @@ public class LiveAverageComponent extends Composite implements HasWidgets, Verti interface LiveAverageComponentUiBinder extends UiBinder { } - - interface LiveAverageComponentStyle extends CssResource { - - String liveAverageCompoment_panel_value_unit_degrees(); - } @UiField - HTMLPanel liveAveragePanel; + public HTMLPanel liveAveragePanel; @UiField - DivElement header; + public DivElement header; @UiField SpanElement liveNumber; @@ -56,28 +50,24 @@ public class LiveAverageComponent extends Composite implements HasWidgets, Verti SpanElement averageUnit; @UiField - DivElement liveLabel; + public DivElement liveLabel; @UiField - DivElement averageLabel; + public DivElement averageLabel; @UiField - HTMLPanel livePanel; + public HTMLPanel livePanel; @UiField - HTMLPanel middleLine; + public HTMLPanel middleLine; @UiField - HTMLPanel averagePanel; - - @UiField - LiveAverageComponentStyle style; + public HTMLPanel averagePanel; private StringMessages stringConstants; - private NumberTickingAnimation liveTickingAnimation; - private NumberTickingAnimation averageTickingAnimation; public LiveAverageComponent() { + LiveAverageComponentResources.INSTANCE.gss().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); } @@ -89,8 +79,8 @@ public class LiveAverageComponent extends Composite implements HasWidgets, Verti * * */ public LiveAverageComponent(String header, String unit) { + LiveAverageComponentResources.INSTANCE.gss().ensureInjected(); stringConstants = StringMessages.INSTANCE; - initTickingAnimations(); initWidget(uiBinder.createAndBindUi(this)); liveAveragePanel.getElement().getStyle().setProperty("backgroundColor", "white"); this.header.setInnerText(header); @@ -98,37 +88,19 @@ public class LiveAverageComponent extends Composite implements HasWidgets, Verti this.averageUnit.setInnerText(unit); // Lifts the degrees unit string "°". Otherwise it would be too small. if (unit.equals("°")) { - this.liveUnit.addClassName(style.liveAverageCompoment_panel_value_unit_degrees()); - this.averageUnit.setClassName(style.liveAverageCompoment_panel_value_unit_degrees()); + this.liveUnit.addClassName(LiveAverageComponentResources.INSTANCE.gss().panel_value_unit_degrees()); + this.averageUnit.setClassName(LiveAverageComponentResources.INSTANCE.gss().panel_value_unit_degrees()); } this.liveLabel.setInnerHTML(stringConstants.dashboardLiveWind()); this.averageLabel.setInnerHTML(stringConstants.dashboardAverageWind()+"
"+stringConstants.dashboardAverageWindMinutes(15)); } - - private void initTickingAnimations() { - liveTickingAnimation = new NumberTickingAnimation() { - - @Override - public void setValueInUI(String value) { - liveNumber.setInnerText(value); - } - }; - - averageTickingAnimation = new NumberTickingAnimation() { - - @Override - public void setValueInUI(String value) { - averageNumber.setInnerText(value); - } - }; - } public void setLiveValue(String liveValue) { - liveTickingAnimation.execute(liveValue); + liveNumber.setInnerText(liveValue); } public void setAverageValue(String averageValue) { - averageTickingAnimation.execute(averageValue); + averageNumber.setInnerText(averageValue); } @Override diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/util/LiveAverageComponent.ui.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/util/LiveAverageComponent.ui.xml new file mode 100644 index 00000000000..907458fd311 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/util/LiveAverageComponent.ui.xml @@ -0,0 +1,41 @@ + + + + + + + + +
+
+ + +
+ + +
+
+ + + + + + +
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
\ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/util/LiveAverageComponentResources.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/util/LiveAverageComponentResources.java new file mode 100644 index 00000000000..2809a676b98 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/util/LiveAverageComponentResources.java @@ -0,0 +1,33 @@ +package com.sap.sailing.dashboards.gwt.client.widgets.startlineadvantage.util; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +/** + * @author Alexander Ries (D062114) + * + */ +public interface LiveAverageComponentResources extends ClientBundle { + + public static final LiveAverageComponentResources INSTANCE = GWT.create(LiveAverageComponentResources.class); + + @Source({"com/sap/sailing/dashboards/gwt/client/theme/theme.gss", "LiveAverageComponent.gss"}) + LiveAverageComponentGss gss(); + + interface LiveAverageComponentGss extends CssResource { + String live_average_component(); + String header(); + String header_container(); + String live_container(); + String average_container(); + String middle_line(); + String panel_value_number(); + String panel_value_unit(); + String panel_value_unit_degrees(); + String value_panel(); + String label_text(); + String live_label_container(); + String average_label_container(); + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartlineAdvantagesByWindComponentStyle.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/StartlineAdvantagesByWindWidget.gss similarity index 78% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartlineAdvantagesByWindComponentStyle.css rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/StartlineAdvantagesByWindWidget.gss index d6d6da58539..3d15fb8ff12 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartlineAdvantagesByWindComponentStyle.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/StartlineAdvantagesByWindWidget.gss @@ -1,6 +1,6 @@ -@CHARSET "UTF-8"; +@require 'theme'; -.startline_advantages_by_wind_component_container{ +.start_line_advantages_by_wind_component{ position: absolute; height: 100%; width: 100%; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/StartlineAdvantagesByWindWidget.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/StartlineAdvantagesByWindWidget.java new file mode 100644 index 00000000000..c54b0cc2482 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/StartlineAdvantagesByWindWidget.java @@ -0,0 +1,127 @@ +package com.sap.sailing.dashboards.gwt.client.widgets.startlineadvantage.wind; + +import java.util.Date; +import java.util.Iterator; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.i18n.client.NumberFormat; +import com.google.gwt.resources.client.CssResource; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.HasWidgets; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.dashboards.gwt.client.DashboardClientFactory; +import com.sap.sailing.dashboards.gwt.client.actions.GetStartlineAdvantagesByWindAction; +import com.sap.sailing.dashboards.gwt.client.widgets.PollsLiveDataEvery5Seconds; +import com.sap.sailing.dashboards.gwt.client.widgets.startlineadvantage.util.LiveAverageComponent; +import com.sap.sailing.dashboards.gwt.client.widgets.startlineadvantage.wind.charts.StartlineAdvantagesOnLineChart; +import com.sap.sailing.dashboards.gwt.shared.DashboardURLParameters; +import com.sap.sailing.dashboards.gwt.shared.dto.StartlineAdvantagesWithMaxAndAverageDTO; +import com.sap.sailing.gwt.ui.client.StringMessages; + +/** + * @author Alexander Ries (D062114) + * + */ +public class StartlineAdvantagesByWindWidget extends Composite implements HasWidgets, PollsLiveDataEvery5Seconds { + + private static StartlineAdvantagesByWindComponentUiBinder uiBinder = GWT + .create(StartlineAdvantagesByWindComponentUiBinder.class); + + interface StartlineAdvantagesByWindComponentUiBinder extends UiBinder { + } + + interface StartlineAdvantagesByWindComponentStyle extends CssResource { + + } + + @UiField(provided = true) + LiveAverageComponent advantageMaximumLiveAverage; + + @UiField(provided = true) + StartlineAdvantagesOnLineChart startlineAdvantagesOnLineChart; + + public DashboardClientFactory dashboardClientFactory; + private static final Logger logger = Logger.getLogger(StartlineAdvantagesByWindWidget.class.getName()); + + public StartlineAdvantagesByWindWidget(DashboardClientFactory dashboardClientFactory) { + StartlineAdvantagesByWindWidgetResources.INSTANCE.gss().ensureInjected(); + this.dashboardClientFactory = dashboardClientFactory; + startlineAdvantagesOnLineChart = new StartlineAdvantagesOnLineChart(); + advantageMaximumLiveAverage = new LiveAverageComponent(StringMessages.INSTANCE.dashboardStartlineAdvantagesByWind(), "m"); + advantageMaximumLiveAverage.header.getStyle().setFontSize(14, Unit.PT); + advantageMaximumLiveAverage.liveLabel.setInnerHTML("advantage max."); + advantageMaximumLiveAverage.averageLabel.setInnerHTML("advantage max. average "+StringMessages.INSTANCE.dashboardAverageWindMinutes(15)); + initWidget(uiBinder.createAndBindUi(this)); + registerForDashboardFiveSecondsTimer(dashboardClientFactory); + } + + private void loadData() { + logger.log(Level.INFO, "Executing GetStartlineAdvantagesAction"); + String leaderboardNameParameterValue = DashboardURLParameters.LEADERBOARD_NAME.getValue(); + if (leaderboardNameParameterValue != null) { + dashboardClientFactory.getDispatch().execute( + new GetStartlineAdvantagesByWindAction(leaderboardNameParameterValue), + new AsyncCallback() { + + @Override + public void onSuccess(StartlineAdvantagesWithMaxAndAverageDTO result) { + logger.log(Level.INFO, "Received StartlineAdvantagesWithMaxAndAverageDTO"); + if (result != null && result.maximum != null && result.average != null) { + logger.log(Level.INFO, "Updating UI with StartlineAdvantagesWithMaxAndAverageDTO"); + startlineAdvantagesOnLineChart.setStartlineAdvantagesAndConfidences(result.distanceToRCBoatToStartlineAdvantage, result.distanceToRCBoatToConfidence); + advantageMaximumLiveAverage.setLiveValue(NumberFormat.getFormat("#0.0").format( + result.maximum.doubleValue())); + advantageMaximumLiveAverage.setAverageValue(NumberFormat.getFormat("#0.0").format( + result.average.doubleValue())); + } else { + logger.log(Level.INFO, "StartlineAdvantagesWithMaxAndAverageDTO is null"); + } + } + + @Override + public void onFailure(Throwable caught) { + logger.log(Level.INFO, "Failed to received StartlineAdvantagesWithMaxAndAverageDTO, " + + caught.getMessage()); + } + }); + } + } + + @Override + public void timeChanged(Date newTime, Date oldTime) { + loadData(); + } + + @Override + public void registerForDashboardFiveSecondsTimer(DashboardClientFactory dashboardClientFactory) { + if (dashboardClientFactory != null) { + dashboardClientFactory.getDashboardFiveSecondsTimer().addTimeListener(this); + } + } + + @Override + public void add(Widget w) { + throw new UnsupportedOperationException("The method add(Widget w) is not supported."); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("The method clear() is not supported."); + } + + @Override + public Iterator iterator() { + return null; + } + + @Override + public boolean remove(Widget w) { + return false; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/StartlineAdvantagesByWindWidget.ui.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/StartlineAdvantagesByWindWidget.ui.xml new file mode 100644 index 00000000000..4b8cd97fb6b --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/StartlineAdvantagesByWindWidget.ui.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/StartlineAdvantagesByWindWidgetResources.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/StartlineAdvantagesByWindWidgetResources.java new file mode 100644 index 00000000000..7b59113b6b6 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/StartlineAdvantagesByWindWidgetResources.java @@ -0,0 +1,23 @@ +package com.sap.sailing.dashboards.gwt.client.widgets.startlineadvantage.wind; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +/** + * @author Alexander Ries (D062114) + * + */ +public interface StartlineAdvantagesByWindWidgetResources extends ClientBundle { + + public static final StartlineAdvantagesByWindWidgetResources INSTANCE = GWT.create(StartlineAdvantagesByWindWidgetResources.class); + + @Source({"com/sap/sailing/dashboards/gwt/client/theme/theme.gss", "StartlineAdvantagesByWindWidget.gss"}) + StartlineAdvantagesOnLineChartGSS gss(); + + public interface StartlineAdvantagesOnLineChartGSS extends CssResource { + String start_line_advantages_by_wind_component(); + String advantage_maximum_live_average(); + String advantages_on_line_chart_container(); + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartlineAdvantagesOnLineChartStyle.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/charts/StartlineAdvantagesOnLineChart.gss similarity index 50% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartlineAdvantagesOnLineChartStyle.css rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/charts/StartlineAdvantagesOnLineChart.gss index aacb90d9078..f6f89b4bb09 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartlineAdvantagesOnLineChartStyle.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/charts/StartlineAdvantagesOnLineChart.gss @@ -1,4 +1,4 @@ -@CHARSET "UTF-8"; +@require 'theme'; .advantages_on_line_chart_container { position: absolute !important; @@ -6,7 +6,7 @@ bottom: 50px; left: 0px; right: 0px; - font-family: 'Open Sans', Arial, Verdana, sans-serif !important; + font-family: DASHBOARD_FONT_FAMILY !important; } .confidence_bar_container { @@ -23,12 +23,12 @@ left: 15px; right: 15px; height: 15px; - background: -moz-linear-gradient(left, #FFDD00 0%, #008FFF 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left bottom, right bottom, color-stop(0%,#FFDD00), color-stop(100%,#008FFF)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(left, #FFDD00 0%,#008FFF 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(left, #FFDD00 0%,#008FFF 100%); /* Opera 11.10+ */ - background: -ms-linear-gradient(left, #FFDD00 0%,#008FFF 100%); /* IE10+ */ - background: linear-gradient(to right, #FFDD00 0%,#008FFF 100%); /* W3C */ + /* @alternate */ background: -moz-linear-gradient(left, #FFDD00 0%, #008FFF 100%); /* FF3.6+ */ + /* @alternate */ background: -webkit-gradient(linear, left bottom, right bottom, color-stop(0%,#FFDD00), color-stop(100%,#008FFF)); /* Chrome,Safari4+ */ + /* @alternate */ background: -webkit-linear-gradient(left, #FFDD00 0%,#008FFF 100%); /* Chrome10+,Safari5.1+ */ + /* @alternate */ background: -o-linear-gradient(left, #FFDD00 0%,#008FFF 100%); /* Opera 11.10+ */ + /* @alternate */ background: -ms-linear-gradient(left, #FFDD00 0%,#008FFF 100%); /* IE10+ */ + /* @alternate */ background: linear-gradient(to right, #FFDD00 0%,#008FFF 100%); /* W3C */ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#FFDD00', endColorstr='#008FFF',GradientType=0 ); /* IE6*/ } @@ -42,7 +42,7 @@ font-size: 11px; text-align: center; vertical-align: center; - font-family: 'Open Sans', Arial, Verdana, sans-serif !important; + font-family: DASHBOARD_FONT_FAMILY !important; } .confidence_bar_label { @@ -50,7 +50,7 @@ top: 20px; font-size: 12px; color: grey; - font-family: 'Open Sans', Arial, Verdana, sans-serif !important; + font-family: DASHBOARD_FONT_FAMILY !important; } .confidence_bar_min_label { diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartlineAdvantagesOnLineChart.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/charts/StartlineAdvantagesOnLineChart.java similarity index 55% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartlineAdvantagesOnLineChart.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/charts/StartlineAdvantagesOnLineChart.java index 6c68e0f9e05..6a6ed9ac540 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startlineadvantage/StartlineAdvantagesOnLineChart.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/charts/StartlineAdvantagesOnLineChart.java @@ -1,17 +1,15 @@ -package com.sap.sailing.dashboards.gwt.client.startlineadvantage; +package com.sap.sailing.dashboards.gwt.client.widgets.startlineadvantage.wind.charts; import java.util.Iterator; -import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; -import org.moxieapps.gwt.highcharts.client.Animation; -import org.moxieapps.gwt.highcharts.client.Animation.Easing; import org.moxieapps.gwt.highcharts.client.AxisTitle; import org.moxieapps.gwt.highcharts.client.Chart; import org.moxieapps.gwt.highcharts.client.Color; import org.moxieapps.gwt.highcharts.client.Credits; import org.moxieapps.gwt.highcharts.client.Legend; 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.Style; import org.moxieapps.gwt.highcharts.client.ToolTip; @@ -31,9 +29,8 @@ import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.dashboards.gwt.shared.dto.StartLineAdvantageDTO; +import com.sap.sailing.dashboards.gwt.client.util.HighchartsUtil; import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sse.common.Util.Pair; /** * @author Alexander Ries (D062114) @@ -63,28 +60,32 @@ public class StartlineAdvantagesOnLineChart extends Composite implements HasWidg private Chart chart; private Series series; - - private static final int POINT_ADDING_ANIMATION_DURATION_IN_MILLIS = 2000; + private static final Logger logger = Logger.getLogger(StartlineAdvantagesOnLineChart.class.getName()); public StartlineAdvantagesOnLineChart() { + StartlineAdvantagesOnLineChartResources.INSTANCE.gss().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); initAndAddChart(); setConfidenceColorBarLabelValues(); - //TODO Remove and centralize data retrieving + } + + public void setStartlineAdvantagesAndConfidences(Number [][] distanceToRCBoatToStartlineAdvantages, Number [][] distanceToRCBoatToConfidences) { + if (distanceToRCBoatToStartlineAdvantages != null) { + series.setPoints(distanceToRCBoatToStartlineAdvantages, true); + } + setConfidenceGradientColorToSeries(series, distanceToRCBoatToConfidences); + chart.redraw(); } private void initAndAddChart() { chart = initChart(); - /** - * Highcharts default chart element spacing is not consistent - * so there is a need to correct the right spacing - * of the chart to make it look visually right. - * */ - setCorrectChartSpacing(chart); + HighchartsUtil.correctSpacingOfChart(chart); setXAxisOfChart(chart); setYAxisOfChart(chart); series = createSeries(chart); + chart.addSeries(series); chartContainer.add(chart); + HighchartsUtil.setSizeToMatchContainerDelayed(chart); } private Chart initChart() { @@ -97,22 +98,16 @@ public class StartlineAdvantagesOnLineChart extends Composite implements HasWidg .setBackgroundColor("#FFFFFF") .setBorderColor("#FFFFFF") .setPlotShadow(false) + .setWidth100() + .setHeight100() .setPlotBorderColor("#FFFFFF") .setOption("/exporting/enabled", false) .setShadow(false) .setStyle(new Style().setPosition("absolute").setTop("0px").setBottom("0px").setLeft("0px").setRight("0px")) .setAnimation(true); - chart.setSize("100%", "100%"); - chart.setSizeToMatchContainer(); return chart; } - private void setCorrectChartSpacing(Chart chart){ - chart.setSpacingTop(17) - .setSpacingBottom(13) - .setSpacingRight(35); - } - private void setXAxisOfChart(Chart chart){ chart.getXAxis() .setAxisTitle(new AxisTitle().setText(StringMessages.INSTANCE.dashboardDistanceToRCBoat()).setStyle(new Style().setFontFamily("Open Sans") @@ -156,10 +151,9 @@ public class StartlineAdvantagesOnLineChart extends Composite implements HasWidg Series series = chart .createSeries() .setName(null) - .setPlotOptions( - new AreaPlotOptions().setDashStyle(DashStyle.SOLID).setLineWidth(1) + .setPlotOptions(new AreaPlotOptions().setDashStyle(DashStyle.SOLID).setLineWidth(1) .setMarker(new Marker().setEnabled(false)).setShadow(false).setHoverStateEnabled(false) - .setLineColor("#FFFFFF").setFillColor("#FFFFFF")); + .setLineColor("white").setFillColor("white")); return series; } @@ -170,71 +164,18 @@ public class StartlineAdvantagesOnLineChart extends Composite implements HasWidg } private String getAxisLabelValueForLabelData(double labelData) { - String result = String.valueOf(labelData); - com.sap.sse.common.Util.Pair firstAndLastXValue = getFirstnAndLastXValueOfSeries(); - if (firstAndLastXValue != null && firstAndLastXValue.getA() != null && firstAndLastXValue.getB() != null) { - chart.getXAxis().setTickInterval(firstAndLastXValue.getB()/2); - if (labelData == firstAndLastXValue.getA().doubleValue()) { - result = StringMessages.INSTANCE.dashboardRCBoat(); - } else if (labelData == firstAndLastXValue.getB().doubleValue()) { - result = StringMessages.INSTANCE.dashboardPinEnd(); - } - } - return result; - } - - private com.sap.sse.common.Util.Pair getFirstnAndLastXValueOfSeries(){ - if(series != null && - series.getPoints() != null && - series.getPoints().length > 1){ - Double firstValue = new Double(series.getPoints()[0].getX().doubleValue()); - Double lastValue = new Double(series.getPoints()[series.getPoints().length-1].getX().doubleValue()); - com.sap.sse.common.Util.Pair firstAndLastXValue = new Pair(firstValue, lastValue); - return firstAndLastXValue; - }else{ - return null; + if(labelData != 0) { + return labelData+""; + }else { + return "RC Boat"; } } - public void setStartlineAdvantages(List startlineAdvantages) { - if (startlineAdvantages != null) { - if (series.getPoints() != null && series.getPoints().length > 0) { - updateStartlineAdvantages(startlineAdvantages, series); - } else { - addStartlineAdvantages(startlineAdvantages, series); - chart.addSeries(series, true, new Animation().setDuration(POINT_ADDING_ANIMATION_DURATION_IN_MILLIS).setEasing(Easing.SWING)); - } - } - chart.setSizeToMatchContainer(); - } - - private void updateStartlineAdvantages(List startlineAdvantages, Series series) { - removeAllSeriesPoints(chart.getSeries()[0]); - addStartlineAdvantages(startlineAdvantages, series); - } - - private void removeAllSeriesPoints(Series series) { - for (Point point : series.getPoints()) { - series.removePoint(point); - } - } - - private void addStartlineAdvantages(List startlineAdvantages, Series series){ - //TODO Build this with forEach consumer as soon as the bundle uses GWT 2.8 with Java 8 Support. - for (StartLineAdvantageDTO startlineAdvantage : startlineAdvantages) { - Point point = new Point(startlineAdvantage.distanceToRCBoatInMeters, startlineAdvantage.startLineAdvantage); - series.addPoint(point, false, false, false); - } - setConfidenceGradientColorToSeries(series, startlineAdvantages); - chart.redraw(); - } - - private void setConfidenceGradientColorToSeries(Series series, List startlineAdvantages) { - Color confidenceGradientColor = ConfidenceColorCalculator.getColorWithConfidenceGradients(startlineAdvantages); - series.setPlotOptions( - new AreaPlotOptions().setDashStyle(DashStyle.SOLID).setLineWidth(1) - .setMarker(new Marker().setEnabled(false)).setShadow(false).setHoverStateEnabled(false) - .setLineColor("#FFFFFF").setFillColor(confidenceGradientColor)); + private void setConfidenceGradientColorToSeries(Series series, Number [][] distanceToRCBoatToConfidences) { + Color confidenceGradientColor = ConfidenceColorCalculator.getColorWithConfidenceGradients(distanceToRCBoatToConfidences); + String fillColor = confidenceGradientColor.getOptions().toString(); + logger.log(Level.INFO, fillColor); + series.updateSeriesFillColor(fillColor); } @Override @@ -261,26 +202,21 @@ public class StartlineAdvantagesOnLineChart extends Composite implements HasWidg private static final String LOW_CONFIDENCE_COLOR_AS_HEX = "#43cea2"; private static final String HIGHT_CONFIDENCE_COLOR_AS_HEX = "#008FFF"; - public static Color getColorWithConfidenceGradients(List startlineAdvantages) { + public static Color getColorWithConfidenceGradients(Number [][] distanceToRCBoatToConfidences) { Color color = new Color().setLinearGradient(200, 0, 0, 0); - double lineLenght = getBiggestDistanceToRCBoat(startlineAdvantages); - //TODO Need to find a prettier way of at adding color stops - double lastConfidence = 2; - for (StartLineAdvantageDTO startlineAdvantageDTO : startlineAdvantages) { - if (lastConfidence != startlineAdvantageDTO.confidence) { - lastConfidence = startlineAdvantageDTO.confidence; - double gradientPosition = getGradientStopPositionFromStartlineLenghtAndDistanceToRCBoat(lineLenght, - startlineAdvantageDTO.distanceToRCBoatInMeters); - color.addColorStop(gradientPosition, getColorAsHexStringFromConfidence(startlineAdvantageDTO.confidence)); + int previousIndex = 0; + for (int i = 0; i < distanceToRCBoatToConfidences.length; i++) { + double previousConfidence = distanceToRCBoatToConfidences[previousIndex][1].doubleValue(); + double currentConfidence = distanceToRCBoatToConfidences[i][1].doubleValue(); + if (previousConfidence != currentConfidence || i == 0) { + logger.log(Level.INFO, previousConfidence+" "+currentConfidence); + color.addColorStop(i, getColorAsHexStringFromConfidence(distanceToRCBoatToConfidences[i][0].doubleValue())); } + previousIndex++; } return color; } - private static double getGradientStopPositionFromStartlineLenghtAndDistanceToRCBoat(double startlineLenght, double distanceToRCBoat) { - return distanceToRCBoat/startlineLenght; - } - private static String getColorAsHexStringFromConfidence(double position) { if(position > 0.6) { return HIGHT_CONFIDENCE_COLOR_AS_HEX; @@ -288,10 +224,5 @@ public class StartlineAdvantagesOnLineChart extends Composite implements HasWidg return LOW_CONFIDENCE_COLOR_AS_HEX; } } - - private static double getBiggestDistanceToRCBoat(List startlineAdvantages) { - StartLineAdvantageDTO startlineAdvantageDTO = startlineAdvantages.get(startlineAdvantages.size()-1); - return startlineAdvantageDTO.distanceToRCBoatInMeters; - } } } diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/charts/StartlineAdvantagesOnLineChart.ui.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/charts/StartlineAdvantagesOnLineChart.ui.xml new file mode 100644 index 00000000000..ce8b9fcc78b --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/charts/StartlineAdvantagesOnLineChart.ui.xml @@ -0,0 +1,16 @@ + + + + + + + +
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/charts/StartlineAdvantagesOnLineChartResources.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/charts/StartlineAdvantagesOnLineChartResources.java new file mode 100644 index 00000000000..aee914f6eef --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startlineadvantage/wind/charts/StartlineAdvantagesOnLineChartResources.java @@ -0,0 +1,27 @@ +package com.sap.sailing.dashboards.gwt.client.widgets.startlineadvantage.wind.charts; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +/** + * @author Alexander Ries (D062114) + * + */ +public interface StartlineAdvantagesOnLineChartResources extends ClientBundle { + + public static final StartlineAdvantagesOnLineChartResources INSTANCE = GWT.create(StartlineAdvantagesOnLineChartResources.class); + + @Source({"com/sap/sailing/dashboards/gwt/client/theme/theme.gss", "StartlineAdvantagesOnLineChart.gss"}) + StartlineAdvantagesOnLineChartGSS gss(); + + public interface StartlineAdvantagesOnLineChartGSS extends CssResource { + String advantages_on_line_chart_container(); + String confidence_bar_container(); + String confidence_bar(); + String confidence_bar_header(); + String confidence_bar_label(); + String confidence_bar_min_label(); + String confidence_bar_max_label(); + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/WindBotComponentStyle.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/WindBotWidget.gss similarity index 67% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/WindBotComponentStyle.css rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/WindBotWidget.gss index 799c6df9a76..3443715463a 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/WindBotComponentStyle.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/WindBotWidget.gss @@ -1,18 +1,18 @@ -@CHARSET "UTF-8"; +@require 'theme'; -.windBotComponent{ +.wind_bot_component{ position: absolute; height: 100%; width: 100%; z-index: 5; } -.windBotNamePanel { +.name_panel { height: 45pt; line-height: 45pt; vertical-align: middle; font-size: 14pt; - font-family: 'Open Sans', Arial, Verdana, sans-serif; + font-family: DASHBOARD_FONT_FAMILY; width: 100%; text-align: center; font-weight: bold; @@ -20,7 +20,7 @@ background-color: white; } -.wind_bot_component_live_average_container { +.live_average_container { position: absolute; border: none; border-radius: 0pt; @@ -29,17 +29,17 @@ bottom: 65%; } -.wind_bot_component_left { +.left { left: 0pt; right: 50%; } -.wind_bot_component_right { +.right { left: 50%; right: 0pt; } -.wind_bot_component_windchart_container { +.windchart_container { position: absolute; border: none; border-radius: 0pt; @@ -48,7 +48,7 @@ bottom: 0pt; } -.wind_bot_component_windchart{ +.windchart{ position: absolute; left: 0pt; right: 0pt; @@ -56,7 +56,7 @@ bottom: 0pt; } -.wind_bot_component_compass_container{ +.compass_container{ position: absolute; height: 45pt; width: 45pt; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/WindBotComponent.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/WindBotWidget.java similarity index 54% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/WindBotComponent.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/WindBotWidget.java index acba0c39e09..5794821f6ff 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/WindBotComponent.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/WindBotWidget.java @@ -1,33 +1,30 @@ -package com.sap.sailing.dashboards.gwt.client.windchart; +package com.sap.sailing.dashboards.gwt.client.widgets.windbot; import java.util.Iterator; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; -import org.moxieapps.gwt.highcharts.client.Point; - import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.i18n.client.NumberFormat; import com.google.gwt.resources.client.CssResource; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.dashboards.gwt.client.dataretriever.RibDashboardDataRetrieverListener; import com.sap.sailing.dashboards.gwt.client.dataretriever.WindBotDataRetrieverListener; -import com.sap.sailing.dashboards.gwt.client.startlineadvantage.LiveAverageComponent; -import com.sap.sailing.dashboards.gwt.client.windchart.compass.LocationPointerCompass; +import com.sap.sailing.dashboards.gwt.client.widgets.startlineadvantage.util.LiveAverageComponent; +import com.sap.sailing.dashboards.gwt.client.widgets.windbot.charts.VerticalWindChart; +import com.sap.sailing.dashboards.gwt.client.widgets.windbot.compass.LocationPointerCompass; import com.sap.sailing.dashboards.gwt.shared.MovingAverage; -import com.sap.sailing.dashboards.gwt.shared.SinWave; import com.sap.sailing.dashboards.gwt.shared.WindType; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; +import com.sap.sailing.gwt.dispatch.client.batching.SplitScheduler; import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.client.shared.charts.ChartPointRecalculator; import com.sap.sailing.gwt.ui.shared.WindDTO; import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO; @@ -42,20 +39,15 @@ import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO; * @author Alexander Ries (D062114) * */ -public class WindBotComponent extends Composite implements HasWidgets, WindBotDataRetrieverListener { - - private SinWave speedSinWave; - private SinWave directionSinWave; - private boolean inSimulationMode; +public class WindBotWidget extends Composite implements HasWidgets, WindBotDataRetrieverListener { private MovingAverage movingAverageSpeed; private MovingAverage movingAverageDirection; - private static WindBotComponentUiBinder uiBinder = GWT.create(WindBotComponentUiBinder.class); - private static final String IN_SIMULATION_MODE = "simulation"; - private static final Logger logger = Logger.getLogger(WindBotComponent.class.getName()); + private static WindBotWidgetUiBinder uiBinder = GWT.create(WindBotWidgetUiBinder.class); + private static final Logger logger = Logger.getLogger(WindBotWidget.class.getName()); - interface WindBotComponentUiBinder extends UiBinder { + interface WindBotWidgetUiBinder extends UiBinder { } interface WindBotComponentStyle extends CssResource { @@ -109,17 +101,10 @@ public class WindBotComponent extends Composite implements HasWidgets, WindBotDa private StringMessages stringConstants; private final static String UNIT_WIND_DIRECTION = "°"; - public WindBotComponent(String windBotId) { + public WindBotWidget(String windBotId) { + WindBotWidgetResources.INSTANCE.gss().ensureInjected(); this.windBotId = windBotId; stringConstants = StringMessages.INSTANCE; - String inSimulationModeParameter = Window.Location.getParameter(IN_SIMULATION_MODE); - if (inSimulationModeParameter != null && inSimulationModeParameter.equals("true")) { - inSimulationMode = true; - this.speedSinWave = new SinWave(50, 8); - this.directionSinWave = new SinWave(100, 8); - } else { - inSimulationMode = false; - } movingAverageSpeed = new MovingAverage(500); movingAverageDirection = new MovingAverage(500); trueWindSpeedLiveAverageComponent = new LiveAverageComponent(stringConstants.dashboardTrueWindSpeed(), stringConstants.dashboardTrueWindSpeedUnit()); @@ -169,30 +154,42 @@ public class WindBotComponent extends Composite implements HasWidgets, WindBotDa * */ @Override public void updateWindBotUI(WindInfoForRaceDTO windInfoForRaceDTO) { - logger.log(Level.INFO, "WindBotComponent with id "+windBotId+" got notified about new WindInfoForRaceDTO"); + logger.log(Level.INFO, "WindBotComponent with id " + windBotId + " got notified about new WindInfoForRaceDTO"); if (windInfoForRaceDTO != null) { - WindTrackInfoDTO windTrackInfoDTO = getWindTrackInfoDTOFromAndWindBotID(windInfoForRaceDTO, windBotId); + final WindTrackInfoDTO windTrackInfoDTO = getWindTrackInfoDTOFromAndWindBotID(windInfoForRaceDTO, windBotId); if (windTrackInfoDTO != null) { - logger.log(Level.INFO, "WindInfoForRaceDTO contains WindTrackInfoDTO for Windbot id "+ windBotId); + logger.log(Level.INFO, "WindInfoForRaceDTO contains WindTrackInfoDTO for Windbot id " + windBotId); if (windTrackInfoDTO.windFixes != null) { if (windTrackInfoDTO.windFixes.size() > 0) { - logger.log(Level.INFO, "Upating UI with Wind Fixes for WindBot id "+ windBotId); - Point[] speedPoints = convertWindFixListIntoPointsArray(windTrackInfoDTO.windFixes, WindType.SPEED); - Point[] directionPoints = convertWindFixListIntoPointsArray(windTrackInfoDTO.windFixes, - WindType.DIRECTION); + logger.log(Level.INFO, "Upating UI with Wind Fixes for WindBot id " + windBotId); + SplitScheduler.get().schedule(new ScheduledCommand() { - trueWindSpeedLiveAverageComponent.setLiveValue(""+NumberFormat.getFormat("#0.0").format(speedPoints[speedPoints.length - 1].getY().doubleValue())); - trueWindSpeedLiveAverageComponent.setAverageValue(""+NumberFormat.getFormat("#0.0").format(movingAverageSpeed.getAverage())); - - trueWindDirectionLiveAverageComponent.setLiveValue(""+NumberFormat.getFormat("#0.0").format(directionPoints[directionPoints.length - 1].getY().doubleValue())); - trueWindDirectionLiveAverageComponent.setAverageValue(""+NumberFormat.getFormat("#0.0").format(movingAverageDirection.getAverage())); + @Override + public void execute() { + updateMovingAverages(windTrackInfoDTO.windFixes); + } + }); + SplitScheduler.get().schedule(new ScheduledCommand() { - trueWindSpeedVerticalWindChart.addPointsToSeriesWithAverage(speedPoints, - movingAverageSpeed.getAverage()); - trueWindDirectionVerticalWindChart.addPointsToSeriesWithAverage(directionPoints, - movingAverageDirection.getAverage()); - locationPointerCompass.windBotPositionChanged(windTrackInfoDTO.windFixes - .get(windTrackInfoDTO.windFixes.size() - 1).position); + @Override + public void execute() { + updateWindSpeedLabelsAndChart(windTrackInfoDTO); + } + }); + SplitScheduler.get().schedule(new ScheduledCommand() { + + @Override + public void execute() { + updateWindDirectionLabelsAndChart(windTrackInfoDTO); + } + }); + SplitScheduler.get().schedule(new ScheduledCommand() { + + @Override + public void execute() { + updateLocationPointerCompass(windTrackInfoDTO); + } + }); } else { logger.log(Level.INFO, "WindTrackInfoDTO.windFixes is empty"); } @@ -200,52 +197,37 @@ public class WindBotComponent extends Composite implements HasWidgets, WindBotDa logger.log(Level.INFO, "WindTrackInfoDTO.windFixes is null"); } } else { - logger.log(Level.INFO, "WindInfoForRaceDTO does not contains WindTrackInfoDTO for Windbot id "+ windBotId); + logger.log(Level.INFO, "WindInfoForRaceDTO does not contains WindTrackInfoDTO for Windbot id " + + windBotId); } } else { logger.log(Level.INFO, "WindInfoForRaceDTO is null"); } } - - private Point[] convertWindFixListIntoPointsArray(List windFixes, WindType windtype) { - if (windFixes != null && windFixes.size() > 0) { - Point[] points = new Point[windFixes.size()]; - int counter = 0; - if (windtype.equals(WindType.DIRECTION)) { - Point previouspoint = new Point(windFixes.get(0).requestTimepoint, windFixes.get(0).trueWindFromDeg); - for (WindDTO windDTO : windFixes) { - double nextSinusValue = 0; - if (inSimulationMode == true) { - nextSinusValue = directionSinWave.getNexNumber(); - } - points[counter] = new Point(windDTO.requestTimepoint, windDTO.trueWindFromDeg + nextSinusValue); - points[counter] = adaptWindDirectionPointToStayCloseToLastPoint(previouspoint, points[counter]); - previouspoint = points[counter]; - movingAverageDirection.add(windDTO.trueWindFromDeg + nextSinusValue); - counter++; - } - } else if (windtype.equals(WindType.SPEED)) { - Point previouspoint = new Point(windFixes.get(0).requestTimepoint, - windFixes.get(0).trueWindSpeedInKnots); - for (WindDTO windDTO : windFixes) { - double nextSinusValue = 0; - if (inSimulationMode == true) { - nextSinusValue = speedSinWave.getNexNumber(); - } - points[counter] = new Point(windDTO.requestTimepoint, windDTO.trueWindSpeedInKnots + nextSinusValue); - points[counter] = adaptWindDirectionPointToStayCloseToLastPoint(previouspoint, points[counter]); - previouspoint = points[counter]; - movingAverageSpeed.add(windDTO.trueWindSpeedInKnots + nextSinusValue); - counter++; - } - } - return points; - } else { - return null; + + private void updateWindSpeedLabelsAndChart(final WindTrackInfoDTO windTrackInfoDTO) { + int size = windTrackInfoDTO.windFixes.size(); + trueWindSpeedLiveAverageComponent.setLiveValue(""+NumberFormat.getFormat("#0.0").format(windTrackInfoDTO.windFixes.get(size-1).trueWindSpeedInKnots)); + trueWindSpeedVerticalWindChart.addPointsToSeriesWithAverageAndWindType(windTrackInfoDTO.windFixes, movingAverageSpeed.getAverage(), WindType.SPEED); + trueWindSpeedLiveAverageComponent.setAverageValue(""+NumberFormat.getFormat("#0.0").format(movingAverageSpeed.getAverage())); + } + + private void updateWindDirectionLabelsAndChart(final WindTrackInfoDTO windTrackInfoDTO) { + int size = windTrackInfoDTO.windFixes.size(); + trueWindDirectionLiveAverageComponent.setLiveValue(""+NumberFormat.getFormat("#0.0").format(windTrackInfoDTO.windFixes.get(size-1).trueWindFromDeg)); + trueWindDirectionVerticalWindChart.addPointsToSeriesWithAverageAndWindType(windTrackInfoDTO.windFixes, movingAverageDirection.getAverage(), WindType.DIRECTION); + trueWindDirectionLiveAverageComponent.setAverageValue(""+NumberFormat.getFormat("#0.0").format(movingAverageDirection.getAverage())); + } + + private void updateLocationPointerCompass(final WindTrackInfoDTO windTrackInfoDTO) { + locationPointerCompass.windBotPositionChanged(windTrackInfoDTO.windFixes + .get(windTrackInfoDTO.windFixes.size() - 1).position); + } + + private void updateMovingAverages(List windFixes) { + for(WindDTO windDTO : windFixes) { + movingAverageSpeed.add(windDTO.trueWindSpeedInKnots); + movingAverageDirection.add(windDTO.trueWindFromDeg); } } - - private Point adaptWindDirectionPointToStayCloseToLastPoint(Point previousPoint, Point point) { - return ChartPointRecalculator.stayClosestToPreviousPoint(previousPoint, point); - } } \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/WindBotWidget.ui.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/WindBotWidget.ui.xml new file mode 100644 index 00000000000..7a9d59ee719 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/WindBotWidget.ui.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/WindBotWidgetResources.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/WindBotWidgetResources.java new file mode 100644 index 00000000000..c4e600796ba --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/WindBotWidgetResources.java @@ -0,0 +1,28 @@ +package com.sap.sailing.dashboards.gwt.client.widgets.windbot; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +/** + * @author Alexander Ries (D062114) + * + */ +public interface WindBotWidgetResources extends ClientBundle { + + public static final WindBotWidgetResources INSTANCE = GWT.create(WindBotWidgetResources.class); + + @Source({"com/sap/sailing/dashboards/gwt/client/theme/theme.gss", "WindBotWidget.gss"}) + WindBotComponentGss gss(); + + public interface WindBotComponentGss extends CssResource { + String wind_bot_component(); + String name_panel(); + String live_average_container(); + String left(); + String right(); + String windchart_container(); + String windchart(); + String compass_container(); + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/VerticalWindChartStyle.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/charts/VerticalWindChart.gss similarity index 69% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/VerticalWindChartStyle.css rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/charts/VerticalWindChart.gss index 5f192987e9c..382fc2773a8 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/VerticalWindChartStyle.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/charts/VerticalWindChart.gss @@ -1,18 +1,12 @@ -@CHARSET "UTF-8"; +@require 'theme'; -.flowPanelContainer { +.verical_wind_chart { position: absolute; height: 100%; width: 100%; } -.verticalWindChartClickArea { - position: absolute; - height: 100%; - width: 100%; -} - -.verticalWindChart { +.chart { position: absolute; top: 0px; bottom: 5px; @@ -20,7 +14,13 @@ right: 0px; } -.verticalWindChartClickHint { +.click_area { + position: absolute; + height: 100%; + width: 100%; +} + +.click_hint { position: absolute; height: 35px; bottom: 0px; @@ -29,12 +29,12 @@ line-height: 35px; vertical-align: middle; font-size: 12px; - font-family: 'Open Sans', Arial, Verdana, sans-serif; + font-family: DASHBOARD_FONT_FAMILY; text-align: center; color: black; } -.windloadinghint { +.loading_hint { position: absolute; background-color: white; opacity: 0.4; @@ -45,21 +45,20 @@ z-index: 0; } -.windloadinghinttext { +.loading_hint_text { position: absolute; bottom: 0pt; height: 15pt; line-height: 15pt; - margin-top: 0pt; vertical-align: middle; font-size: 12pt; - font-family: 'Open Sans', Arial, Verdana, sans-serif; + font-family: DASHBOARD_FONT_FAMILY; width: 100%; text-align: center; top: 50%; margin-top: -7pt; } -.clickHintMinutesSpan{ +.click_hint_minutes { font-weight: bolder; } \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/VerticalWindChart.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/charts/VerticalWindChart.java similarity index 79% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/VerticalWindChart.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/charts/VerticalWindChart.java index 58eb388f314..01ea5d824aa 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/VerticalWindChart.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/charts/VerticalWindChart.java @@ -1,9 +1,8 @@ -package com.sap.sailing.dashboards.gwt.client.windchart; +package com.sap.sailing.dashboards.gwt.client.widgets.windbot.charts; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import java.util.logging.Level; import java.util.logging.Logger; import org.moxieapps.gwt.highcharts.client.Axis.Type; @@ -28,8 +27,6 @@ import org.moxieapps.gwt.highcharts.client.plotOptions.BarPlotOptions; import org.moxieapps.gwt.highcharts.client.plotOptions.Marker; import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.resources.client.CssResource; import com.google.gwt.uibinder.client.UiBinder; @@ -40,6 +37,10 @@ import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.dashboards.gwt.client.util.HighchartsUtil; +import com.sap.sailing.dashboards.gwt.shared.WindType; +import com.sap.sailing.gwt.ui.client.shared.charts.ChartPointRecalculator; +import com.sap.sailing.gwt.ui.shared.WindDTO; /** * The class represents a wind chart that is displayed vertically. Because it is meant to be used to display wind fixes, @@ -53,8 +54,7 @@ import com.google.gwt.user.client.ui.Widget; public class VerticalWindChart extends Composite implements HasWidgets { private Series verticalWindChartSeries; - private String positiveSeriesColorAsHex; - private String negativeSeriesColorAsHex; + private Point lastPoint; private List verticalWindChartClickListeners; /** @@ -74,6 +74,7 @@ public class VerticalWindChart extends Composite implements HasWidgets { private static VerticalWindChartUiBinder uiBinder = GWT.create(VerticalWindChartUiBinder.class); + @SuppressWarnings("unused") private static final Logger logger = Logger.getLogger(VerticalWindChart.class.getName()); interface VerticalWindChartUiBinder extends UiBinder { @@ -97,6 +98,7 @@ public class VerticalWindChart extends Composite implements HasWidgets { } public @UiConstructor VerticalWindChart(String positiveFillColorAsHex, String negativeFillColorAsHex) { + VerticalWindChartResources.INSTANCE.gss().ensureInjected(); initVerticalWindChartWithColors(positiveFillColorAsHex, negativeFillColorAsHex); initWidget(uiBinder.createAndBindUi(this)); } @@ -111,9 +113,9 @@ public class VerticalWindChart extends Composite implements HasWidgets { setChartOptions(); setXAxisOptions(); setYAxisOptions(); - positiveSeriesColorAsHex = positiveFillColor; - negativeSeriesColorAsHex = negativeFillColor; + initVerticalWindChartSeries(positiveFillColor, negativeFillColor); verticalWindChartClickListeners = new ArrayList(); + HighchartsUtil.setSizeToMatchContainerDelayed(verticalWindChart); } private void setChartOptions() { @@ -171,13 +173,13 @@ public class VerticalWindChart extends Composite implements HasWidgets { })); } - private void initVerticalWindChartSeries() { + private void initVerticalWindChartSeries(String positivSeriesColorAsHex, String negativeSeriesColorAsHex) { verticalWindChartSeries = verticalWindChart.createSeries(); AreaPlotOptions areaPlotOptions = new AreaPlotOptions(); verticalWindChart.addSeries(verticalWindChartSeries.setName(null).setPlotOptions( areaPlotOptions.setDashStyle(DashStyle.SOLID).setLineWidth(0.1) .setMarker(new Marker().setEnabled(false)).setShadow(false).setHoverStateEnabled(false) - .setLineColor("#FFFFFF").setFillColor(positiveSeriesColorAsHex) + .setLineColor("#FFFFFF").setFillColor(positivSeriesColorAsHex) .setOption("negativeFillColor", negativeSeriesColorAsHex)).setOption("turboThreshold", MAX_SERIES_POINTS)); } @@ -190,34 +192,52 @@ public class VerticalWindChart extends Composite implements HasWidgets { * {@link #setXAxisExtremesForSeriesPointRangeIsSmallerThanChartDisplayIntervall()} and * {@link #setXAxisExtremesForSeriesPointRangeIsBiggerThanChartDisplayIntervall()}. * */ - public void addPointsToSeriesWithAverage(final Point[] points, final double average) { - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - - @Override - public void execute() { - if (verticalWindChartSeries == null) { - initVerticalWindChartSeries(); - verticalWindChartSeries.setPoints(points, true); - logger.log(Level.INFO, "Set Points"); - } else { - for (Point point : points) { - verticalWindChartSeries.addPoint(point, true, false, false); - logger.log(Level.INFO, "Add Point"); - } - } - verticalWindChartSeries.updateThreshold("" + average); - adaptVerticalWindChartExtemes(); - verticalWindChart.setSizeToMatchContainer(); - } - }); + public void addPointsToSeriesWithAverageAndWindType(List windFixes, final double average, WindType windType) { + if(windType.equals(WindType.SPEED)) { + addPointsToSeriesOfTypeSpeed(windFixes); + } else { + addPointsToSeriesOfTypeDirection(windFixes); + } + verticalWindChartSeries.updateThreshold(""+average); + adaptVerticalWindChartExtemes(lastPoint.getX().longValue()); + } + + private Point createSpeedPoint(WindDTO windDTO) { + Point result = new Point(windDTO.measureTimepoint, windDTO.trueWindSpeedInKnots); + if (lastPoint != null) { + adaptWindDirectionPointToStayCloseToLastPoint(lastPoint, result); + } + lastPoint = result; + return result; + } + + private Point createDirectionPoint(WindDTO windDTO) { + Point result = new Point(windDTO.measureTimepoint, windDTO.trueWindFromDeg); + if (lastPoint != null) { + adaptWindDirectionPointToStayCloseToLastPoint(lastPoint, result); + } + lastPoint = result; + return result; + } + + private void addPointsToSeriesOfTypeSpeed(List windFixes) { + for (WindDTO windDTO : windFixes) { + verticalWindChartSeries.addPoint(createSpeedPoint(windDTO), false, false, false); + } + } + + private void addPointsToSeriesOfTypeDirection(List windFixes) { + for (WindDTO windDTO : windFixes) { + verticalWindChartSeries.addPoint(createDirectionPoint(windDTO), false, false, false); + } } /** * Depending on the series points time range and the selected display interval, the series fits into the display * interval or not. So there are two cases where the extreme points of the x-axis get calculated differently. * */ - private void adaptVerticalWindChartExtemes() { - setXAxisExtremesForSeriesPointRangeIsBiggerThanChartDisplayIntervall(); + private void adaptVerticalWindChartExtemes(Long lastTimePoint) { + setXAxisExtremesForSeriesPointRangeIsBiggerThanChartDisplayIntervall(lastTimePoint.longValue()); } /** @@ -226,23 +246,10 @@ public class VerticalWindChart extends Composite implements HasWidgets { * the the max value minus the selected display interval and the max value the last points time value. The chart * gets other extremes, because the values do not fit anymore into the selected display interval. * */ - private void setXAxisExtremesForSeriesPointRangeIsBiggerThanChartDisplayIntervall() { - long maximumExtremeValueInMillis = getLastPointOfVerticalWindChartSeries().getX().longValue(); + private void setXAxisExtremesForSeriesPointRangeIsBiggerThanChartDisplayIntervall(long lastTimePoint) { + long maximumExtremeValueInMillis = lastTimePoint; long minimumExtremeValueInMillis = maximumExtremeValueInMillis - chartIntervallinMinutes * 60 * 1000; - verticalWindChart.getXAxis().setExtremes(minimumExtremeValueInMillis, maximumExtremeValueInMillis, true, true); - } - - - private Point getLastPointOfVerticalWindChartSeries() { - Point lastPointOfVerticalWindChartSeries = null; - if (verticalWindChartSeries != null) { - Point[] seriesPoints = verticalWindChartSeries.getPoints(); - int seriesPointsLenght = seriesPoints.length; - if (seriesPointsLenght > 0) { - lastPointOfVerticalWindChartSeries = seriesPoints[seriesPointsLenght - 1]; - } - } - return lastPointOfVerticalWindChartSeries; + verticalWindChart.getXAxis().setExtremes(minimumExtremeValueInMillis, maximumExtremeValueInMillis, true, false); } public void addVerticalWindChartClickListener(VerticalWindChartClickListener verticalWindChartClickListener) { @@ -258,6 +265,10 @@ public class VerticalWindChart extends Composite implements HasWidgets { verticalWindChartClickListener.clickedWindChartWithNewIntervalChangeInMillis(intervalInMillis); } } + + private Point adaptWindDirectionPointToStayCloseToLastPoint(Point previousPoint, Point point) { + return ChartPointRecalculator.stayClosestToPreviousPoint(previousPoint, point); + } /** * The method handles a click on the vertical wind charts focus panel. It changes the display interval of the x-axis @@ -270,13 +281,8 @@ public class VerticalWindChart extends Composite implements HasWidgets { } else { chartIntervallinMinutes = SMALL_DISPLAY_INTERVALL_IN_MINUTES; } - Scheduler.get().scheduleFinally(new ScheduledCommand() { - - @Override - public void execute() { - adaptVerticalWindChartExtemes(); - notifyVerticalWindChartClickListeners(chartIntervallinMinutes); - }}); + adaptVerticalWindChartExtemes(lastPoint.getX().longValue()); + notifyVerticalWindChartClickListeners(chartIntervallinMinutes); } @Override diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/charts/VerticalWindChart.ui.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/charts/VerticalWindChart.ui.xml new file mode 100644 index 00000000000..4d3182113d3 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/charts/VerticalWindChart.ui.xml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/VerticalWindChartClickListener.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/charts/VerticalWindChartClickListener.java similarity index 67% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/VerticalWindChartClickListener.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/charts/VerticalWindChartClickListener.java index 26c03f15def..4d02fd8ad74 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/VerticalWindChartClickListener.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/charts/VerticalWindChartClickListener.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.client.windchart; +package com.sap.sailing.dashboards.gwt.client.widgets.windbot.charts; public interface VerticalWindChartClickListener { public void clickedWindChartWithNewIntervalChangeInMillis(int windChartIntervallInMillis); diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/charts/VerticalWindChartResources.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/charts/VerticalWindChartResources.java new file mode 100644 index 00000000000..f1f82307d93 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/charts/VerticalWindChartResources.java @@ -0,0 +1,27 @@ +package com.sap.sailing.dashboards.gwt.client.widgets.windbot.charts; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +/** + * @author Alexander Ries (D062114) + * + */ +public interface VerticalWindChartResources extends ClientBundle { + + public static final VerticalWindChartResources INSTANCE = GWT.create(VerticalWindChartResources.class); + + @Source({"com/sap/sailing/dashboards/gwt/client/theme/theme.gss", "VerticalWindChart.gss"}) + VerticalWindChartGSS gss(); + + public interface VerticalWindChartGSS extends CssResource { + String verical_wind_chart(); + String chart(); + String click_area(); + String click_hint(); + String loading_hint(); + String loading_hint_text(); + String click_hint_minutes(); + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompass.css b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/compass/LocationPointerCompass.gss similarity index 64% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompass.css rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/compass/LocationPointerCompass.gss index 4db1b4107b7..adbcad59e81 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompass.css +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/compass/LocationPointerCompass.gss @@ -1,12 +1,12 @@ -@CHARSET "UTF-8"; +@require 'theme'; -.locationPointerCompass{ +.compass{ height: 100%; width: 100%; overflow: visible !important; } -.locationPointerCompass_needle{ +.needle{ position: absolute; height: 100%; width: 100%; @@ -14,7 +14,7 @@ left: 0%; } -.locationPointerCompass_angleToLocationLabel{ +.angleToLocationLabel{ position: absolute; height: 10pt; width: 100%; @@ -22,20 +22,20 @@ top: -5pt; text-align: center; vertical-align: middle; - font-family: 'Open Sans', Arial, Verdana, sans-serif; + font-family: DASHBOARD_FONT_FAMILY; font-size: 9pt; font-weight: bold; color: white; } -.locationPointerCompass_distanceToLocationLabel{ +.distanceToLocationLabel{ position: absolute; height: 15pt; width: 100%; bottom: -17pt; text-align: center; vertical-align: middle; - font-family: 'Open Sans', Arial, Verdana, sans-serif; + font-family: DASHBOARD_FONT_FAMILY; line-height: 10pt; font-weight: bold; font-size: 9pt; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompass.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/compass/LocationPointerCompass.java similarity index 84% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompass.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/compass/LocationPointerCompass.java index f51ed0e3238..f92b6b3a1b9 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompass.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/compass/LocationPointerCompass.java @@ -1,11 +1,10 @@ -package com.sap.sailing.dashboards.gwt.client.windchart.compass; +package com.sap.sailing.dashboards.gwt.client.widgets.windbot.compass; import com.google.gwt.i18n.client.NumberFormat; import com.google.gwt.user.client.ui.AbsolutePanel; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.Label; -import com.sap.sailing.dashboards.gwt.client.RibDashboardImageResources; import com.sap.sailing.domain.common.Position; /** @@ -41,8 +40,8 @@ public class LocationPointerCompass extends AbsolutePanel implements HasWidgets, private LocationPointerCompassAngleDistance locationPointerCompassAngleDistance; public LocationPointerCompass() { - LocationPointerCompassRessources.INSTANCE.css().ensureInjected(); - this.getElement().addClassName(LocationPointerCompassRessources.INSTANCE.css().locationPointerCompass()); + LocationPointerCompassRessources.INSTANCE.gss().ensureInjected(); + this.getElement().addClassName(LocationPointerCompassRessources.INSTANCE.gss().compass()); initCompassNeedle(); initAngleToPointetLocationLabel(); initDistanceToPointedLocationLabel(); @@ -53,20 +52,20 @@ public class LocationPointerCompass extends AbsolutePanel implements HasWidgets, private void initCompassNeedle() { compassNeedle = new Image(); - compassNeedle.setResource(RibDashboardImageResources.INSTANCE.compass()); - compassNeedle.getElement().addClassName(LocationPointerCompassRessources.INSTANCE.css().locationPointerCompass_needle()); + compassNeedle.setResource(LocationPointerCompassRessources.INSTANCE.compass()); + compassNeedle.getElement().addClassName(LocationPointerCompassRessources.INSTANCE.gss().needle()); LocationPointerCompass.this.add(compassNeedle); } private void initAngleToPointetLocationLabel() { angleToPointetLocationLabel = new Label(); - angleToPointetLocationLabel.getElement().addClassName(LocationPointerCompassRessources.INSTANCE.css().locationPointerCompass_angleToLocationLabel()); + angleToPointetLocationLabel.getElement().addClassName(LocationPointerCompassRessources.INSTANCE.gss().angleToLocationLabel()); this.add(angleToPointetLocationLabel); } private void initDistanceToPointedLocationLabel() { distanceToPointetLocationLabel = new Label(); - distanceToPointetLocationLabel.getElement().addClassName(LocationPointerCompassRessources.INSTANCE.css().locationPointerCompass_distanceToLocationLabel()); + distanceToPointetLocationLabel.getElement().addClassName(LocationPointerCompassRessources.INSTANCE.gss().distanceToLocationLabel()); distanceToPointetLocationLabel.setText("m"); this.add(distanceToPointetLocationLabel); } @@ -74,11 +73,13 @@ public class LocationPointerCompass extends AbsolutePanel implements HasWidgets, private void updateNeedleAngle(double newDirection) { // Adapt degrees to image angle synchronized (this) { - String winddirectionformatted = NumberFormat.getFormat("#0").format(newDirection-angleOffset); + double newAngle = newDirection-angleOffset; + String winddirectionformatted = NumberFormat.getFormat("#0").format(newAngle); compassNeedle.getElement().getStyle().setProperty("transform", "rotate(" + winddirectionformatted + "deg)"); compassNeedle.getElement().getStyle() .setProperty("webkitTransform", "rotate(" + winddirectionformatted + "deg)"); - angleToPointetLocationLabel.setText(winddirectionformatted + "°"); + String winddirectionFormattedForLabel = NumberFormat.getFormat("#0").format((newAngle < 0) ? 360+newAngle : newAngle); + angleToPointetLocationLabel.setText(winddirectionFormattedForLabel+"°"); } } diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompassAngleDistance.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/compass/LocationPointerCompassAngleDistance.java similarity index 98% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompassAngleDistance.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/compass/LocationPointerCompassAngleDistance.java index f195355744e..11d2d0020c7 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompassAngleDistance.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/compass/LocationPointerCompassAngleDistance.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.client.windchart.compass; +package com.sap.sailing.dashboards.gwt.client.widgets.windbot.compass; import java.util.ArrayList; import java.util.List; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompassAngleDistanceListener.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/compass/LocationPointerCompassAngleDistanceListener.java similarity index 76% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompassAngleDistanceListener.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/compass/LocationPointerCompassAngleDistanceListener.java index 98b2852adbf..6c914e5fe61 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompassAngleDistanceListener.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/compass/LocationPointerCompassAngleDistanceListener.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.client.windchart.compass; +package com.sap.sailing.dashboards.gwt.client.widgets.windbot.compass; public interface LocationPointerCompassAngleDistanceListener { diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/compass/LocationPointerCompassRessources.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/compass/LocationPointerCompassRessources.java new file mode 100644 index 00000000000..b38b91e3b57 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/windbot/compass/LocationPointerCompassRessources.java @@ -0,0 +1,26 @@ +package com.sap.sailing.dashboards.gwt.client.widgets.windbot.compass; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.CssResource.Shared; +import com.google.gwt.resources.client.ImageResource; + +public interface LocationPointerCompassRessources extends ClientBundle { + + public static final LocationPointerCompassRessources INSTANCE = GWT.create(LocationPointerCompassRessources.class); + + @Source("com/sap/sailing/dashboards/gwt/client/images/compass.png") + ImageResource compass(); + + @Source({"com/sap/sailing/dashboards/gwt/client/theme/theme.gss", "LocationPointerCompass.gss"}) + LocationPointerCompassGSS gss(); + + @Shared + public interface LocationPointerCompassGSS extends CssResource { + public String compass(); + public String needle(); + public String angleToLocationLabel(); + public String distanceToLocationLabel(); + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/VerticalWindChart.ui.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/VerticalWindChart.ui.xml deleted file mode 100644 index d07585ece67..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/VerticalWindChart.ui.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/WindBotComponent.ui.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/WindBotComponent.ui.xml deleted file mode 100644 index ef3a9e37080..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/WindBotComponent.ui.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompassRessources.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompassRessources.java deleted file mode 100644 index c5d33105fa0..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/windchart/compass/LocationPointerCompassRessources.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.sap.sailing.dashboards.gwt.client.windchart.compass; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.resources.client.ClientBundle; -import com.google.gwt.resources.client.CssResource; -import com.google.gwt.resources.client.CssResource.Shared; - -public interface LocationPointerCompassRessources extends ClientBundle { - public static final LocationPointerCompassRessources INSTANCE = GWT.create(LocationPointerCompassRessources.class); - - @Source("LocationPointerCompass.css") - LocalCss css(); - - @Shared - public interface LocalCss extends CssResource { - - public String locationPointerCompass(); - - public String locationPointerCompass_needle(); - - public String locationPointerCompass_angleToLocationLabel(); - - public String locationPointerCompass_distanceToLocationLabel(); - } -} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/DashboardDispatchRPCServletImpl.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/DashboardDispatchRPCServletImpl.java new file mode 100644 index 00000000000..7f7533190d9 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/DashboardDispatchRPCServletImpl.java @@ -0,0 +1,87 @@ +package com.sap.sailing.dashboards.gwt.server; + +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTracker; + +import com.sap.sailing.dashboards.gwt.shared.DashboardLiveRaceProvider; +import com.sap.sailing.dashboards.gwt.shared.MovingAveragesCache; +import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardDispatchContext; +import com.sap.sailing.dashboards.gwt.shared.dispatch.impl.DashboardDispatchContextImpl; +import com.sap.sailing.domain.polars.PolarDataService; +import com.sap.sailing.gwt.dispatch.client.Action; +import com.sap.sailing.gwt.dispatch.client.Result; +import com.sap.sailing.gwt.dispatch.client.ResultWrapper; +import com.sap.sailing.gwt.dispatch.client.exceptions.DispatchException; +import com.sap.sailing.gwt.dispatch.client.exceptions.ServerDispatchException; +import com.sap.sailing.gwt.dispatch.client.rpcimpl.DispatchRPC; +import com.sap.sailing.gwt.dispatch.client.rpcimpl.RequestWrapper; +import com.sap.sailing.gwt.ui.server.Activator; +import com.sap.sailing.gwt.ui.server.ProxiedRemoteServiceServlet; +import com.sap.sailing.server.RacingEventService; +import com.sap.sse.util.ServiceTrackerFactory; + +public class DashboardDispatchRPCServletImpl extends ProxiedRemoteServiceServlet implements DispatchRPC { + private static final long serialVersionUID = -245230476512348999L; + private static final Logger logger = Logger.getLogger(DashboardDispatchRPCServletImpl.class.getName()); + + private final ServiceTracker racingEventServiceTracker; + private final PolarDataService polarDataService; + private final DashboardLiveRaceProvider dashboardLiveRaceProvider; + private final MovingAveragesCache movingAveragesCache; + + public DashboardDispatchRPCServletImpl() { + final BundleContext context = Activator.getDefault(); + racingEventServiceTracker = ServiceTrackerFactory.createAndOpen(context, RacingEventService.class); + polarDataService = getPolarDataService(); + dashboardLiveRaceProvider = new DashboardLiveRaceProvider(racingEventServiceTracker.getService()); + movingAveragesCache = new MovingAveragesCache(); + } + + @Override + public > ResultWrapper execute( + RequestWrapper request) throws DispatchException { + A action = request.getAction(); + long start = System.currentTimeMillis(); + try { + R executionResult = action.execute(new DashboardDispatchContextImpl(request.getCurrentClientTime(), + racingEventServiceTracker.getService(), polarDataService, dashboardLiveRaceProvider, + movingAveragesCache, request.getClientLocaleName(), getThreadLocalRequest())); + return new ResultWrapper(executionResult); + } catch (DispatchException d) { + logger.log(Level.WARNING, "Server exception", d); + throw d; + } catch (Throwable t) { + String serverExceptionUUID = UUID.randomUUID().toString(); + logger.log(Level.SEVERE, "Uncaught server exception id: " + serverExceptionUUID, t); + throw new ServerDispatchException(serverExceptionUUID, t); + } finally { + long duration = System.currentTimeMillis() - start; + final Level logLevel; + if (duration < 100) { + logLevel = Level.FINEST; + } else if (duration < 500) { + logLevel = Level.INFO; + } else { + logLevel = Level.WARNING; + } + logger.log(logLevel, "Dispatch took " + duration + "ms for " + action.getClass().getSimpleName()); + } + } + + @Override + protected void doUnexpectedFailure(Throwable e) { + logger.log(Level.WARNING, "GWT RPC Exception: " + e.getMessage(), e); + super.doUnexpectedFailure(e); + } + + private PolarDataService getPolarDataService() { + BundleContext context = Activator.getDefault(); + ServiceReference polarServiceReference = context.getServiceReference(PolarDataService.class); + return context.getService(polarServiceReference); + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/LiveTrackedRaceListener.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/LiveTrackedRaceListener.java deleted file mode 100644 index c67872c5d89..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/LiveTrackedRaceListener.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.sap.sailing.dashboards.gwt.server; - -import com.sap.sailing.domain.tracking.TrackedRace; - -/** - * Informs the listener about changes in a certain environment, where a {@link TrackedRace} with - * {@link TrackedRace#getStatus()} live is continuously required. - * - * @author Alexander Ries (D062114) - * - */ -public interface LiveTrackedRaceListener { - - public void liveTrackedRaceDidChange(TrackedRace trackedRace); -} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/LiveTrackedRaceProvider.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/LiveTrackedRaceProvider.java deleted file mode 100644 index 71dc43a4589..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/LiveTrackedRaceProvider.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.sap.sailing.dashboards.gwt.server; - -import java.util.ArrayList; -import java.util.List; - -import com.sap.sailing.domain.tracking.TrackedRace; - -/** - * Is continuously looking for {@link TrackedRace} with {@link TrackedRace#getStatus()} live and - * notifies {@link LiveTrackedRaceListener} if the current live {@link TrackedRace} has changed. - * - * - * @author Alexander Ries (D062114) - * - */ -public interface LiveTrackedRaceProvider { - - List listener = new ArrayList(); - - public void notifyLiveTrackedRaceListenerAboutLiveTrackedRaceChange(TrackedRace trackedRace); - - default void addLiveTrackedRaceListener(LiveTrackedRaceListener liveTrackedRaceListener){ - listener.add(liveTrackedRaceListener); - } - - default void removeLiveTrackedRaceListener(LiveTrackedRaceListener liveTrackedRaceListener){ - listener.remove(liveTrackedRaceListener); - } -} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/RibDashboardServiceImpl.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/RibDashboardServiceImpl.java deleted file mode 100644 index 59452db071e..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/RibDashboardServiceImpl.java +++ /dev/null @@ -1,323 +0,0 @@ -package com.sap.sailing.dashboards.gwt.server; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; - -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceReference; -import org.osgi.util.tracker.ServiceTracker; - -import com.google.gwt.user.server.rpc.RemoteServiceServlet; -import com.sap.sailing.dashboards.gwt.client.RibDashboardService; -import com.sap.sailing.dashboards.gwt.client.startanalysis.StartlineAdvantageType; -import com.sap.sailing.dashboards.gwt.server.startanalysis.StartAnalysisCreationController; -import com.sap.sailing.dashboards.gwt.server.startlineadvantages.StartlineAdvantagesCalculator; -import com.sap.sailing.dashboards.gwt.shared.MovingAverage; -import com.sap.sailing.dashboards.gwt.shared.ResponseMessage; -import com.sap.sailing.dashboards.gwt.shared.dto.RibDashboardRaceInfoDTO; -import com.sap.sailing.dashboards.gwt.shared.dto.StartLineAdvantageDTO; -import com.sap.sailing.dashboards.gwt.shared.dto.StartlineAdvantagesWithMaxAndAverageDTO; -import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.Course; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.base.RaceColumn; -import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.NoWindException; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.domain.common.dto.CompetitorDTO; -import com.sap.sailing.domain.common.tracking.GPSFix; -import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.domain.polars.PolarDataService; -import com.sap.sailing.domain.tracking.GPSFixTrack; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.server.RacingEventService; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util.Pair; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -/** - * The server side implementation of the RPC {@link RibDashboardService}. - * - * @author Alexander Ries (D062114) - * - */ -public class RibDashboardServiceImpl extends RemoteServiceServlet implements RibDashboardService, LiveTrackedRaceProvider { - - /** - * - */ - private static final long serialVersionUID = 1L; - - private StartAnalysisCreationController startAnalysisCreationController; - - private StartlineAdvantagesCalculator startlineAdvantagesCalculator; - - /** - * Variable contains last {@link TrackedRace} that is or was live - * */ - private TrackedRace runningRace; - - /** - * {@link MovingAverage} of last 400 start line advantage values during the last races. See initialization at - * {@link #RibDashboardServiceImpl()}) Advantage by geometry is the delta in meters of the distance from race - * committee boat to first mark and pin end mark to first mark. Wind has no influence on the value. - * */ - private MovingAverage averageStartLineAdvantageByGeometry; - - private final ServiceTracker racingEventServiceTracker; - private final com.sap.sailing.domain.base.DomainFactory baseDomainFactory; - private final BundleContext context; - - private static final Logger logger = Logger.getLogger(RibDashboardServiceImpl.class.getName()); - - public RibDashboardServiceImpl() { - this.context = Activator.getDefault(); - this.racingEventServiceTracker = createAndOpenRacingEventServiceTracker(context); - this.baseDomainFactory = getRacingEventService().getBaseDomainFactory(); - this.startAnalysisCreationController = new StartAnalysisCreationController(getRacingEventService()); - this.startlineAdvantagesCalculator = new StartlineAdvantagesCalculator(getPolarService()); - addLiveTrackedRaceListener(startlineAdvantagesCalculator); - this.averageStartLineAdvantageByGeometry = new MovingAverage(400); - } - - protected RacingEventService getRacingEventService() { - return racingEventServiceTracker.getService(); - } - - private PolarDataService getPolarService() { - ServiceReference polarServiceReference = context.getServiceReference(PolarDataService.class); - PolarDataService polarDataService = context.getService(polarServiceReference); - return polarDataService; - } - - protected ServiceTracker createAndOpenRacingEventServiceTracker( - BundleContext context) { - ServiceTracker result = new ServiceTracker( - context, RacingEventService.class.getName(), null); - result.open(); - return result; - } - - /** - * leaderboardName is used to retrieve the live running race with the method - * {@link #getLiveRaceFromLeaderboardName(String)}. - * - * @throws NoWindException - * */ - @Override - public RibDashboardRaceInfoDTO getLiveRaceInfo(String leaderboardName) throws NoWindException { - RibDashboardRaceInfoDTO lRInfo = new RibDashboardRaceInfoDTO(); - if (leaderboardName != null) { - TimePoint timePointOfRequest = MillisecondsTimePoint.now(); - if (checkIfRaceIsStillRunning(timePointOfRequest, leaderboardName)) { - fillLiveRaceInfoDTOWithRaceData(lRInfo, timePointOfRequest); - fillLiveRaceInfoDTOWithStartLineAdavantageByGeometryData(lRInfo, timePointOfRequest); - lRInfo.responseMessage = ResponseMessage.RACE_LIVE; - } else { - lRInfo.responseMessage = ResponseMessage.NO_RACE_LIVE; - } - } else { - lRInfo.responseMessage = ResponseMessage.NO_RACE_LIVE; - } - return lRInfo; - } - - @Override - public RegattaAndRaceIdentifier getIDFromRaceThatTakesWindFixesNow(String leaderboardName) { - RegattaAndRaceIdentifier result = null; - if(leaderboardName != null) { - List trackedRaces = getTrackedRacesFromLeaderboard(leaderboardName); - List filteredTrackedRaces = trackedRaces.stream().filter(trackedRace -> trackedRace.takesWindFixWithTimePoint(MillisecondsTimePoint.now())).collect(Collectors.toList()); - if(filteredTrackedRaces != null && filteredTrackedRaces.size() > 0) { - TrackedRace raceThatTakesWindNow = filteredTrackedRaces.get(0); - if(raceThatTakesWindNow != null) { - result = raceThatTakesWindNow.getRaceIdentifier(); - } - } - } - return result; - } - - private void fillLiveRaceInfoDTOWithRaceData(RibDashboardRaceInfoDTO lRInfo, TimePoint now) { - if (runningRace != null) { - lRInfo.idOfLastTrackedRace = runningRace.getRaceIdentifier(); - List competitors = runningRace.getCompetitorsFromBestToWorst(now); - List competitorNames = new ArrayList(); - for (Competitor competitor : competitors) { - competitorNames.add(competitor.getName()); - } - Collections.sort(competitorNames); - lRInfo.competitorNamesFromLastTrackedRace = competitorNames; - } - } - - // returns true if race is still live - private boolean checkIfRaceIsStillRunning(TimePoint now, String leaderboardName) { - if (runningRace == null || !(runningRace.isLive(now))) { - runningRace = getLiveRaceFromLeaderboardName(leaderboardName); - if (runningRace == null) { - return false; - } else { - return true; - } - } else { - return true; - } - } - - private TrackedRace getLiveRaceFromLeaderboardName(String leaderboardName) { - TrackedRace result = null; - Leaderboard lb = getRacingEventService().getLeaderboardByName(leaderboardName); - if (lb != null) { - for (RaceColumn column : lb.getRaceColumns()) { - for (Fleet fleet : column.getFleets()) { - TrackedRace race = column.getTrackedRace(fleet); - if (race != null && race.isLive(MillisecondsTimePoint.now())) { - result = race; - notifyLiveTrackedRaceListenerAboutLiveTrackedRaceChange(race); - } - } - } - } - return result; - } - - private List getTrackedRacesFromLeaderboard(String leaderboardName) { - List result = new ArrayList(); - Leaderboard lb = getRacingEventService().getLeaderboardByName(leaderboardName); - if (lb != null) { - for (RaceColumn column : lb.getRaceColumns()) { - for (Fleet fleet : column.getFleets()) { - TrackedRace race = column.getTrackedRace(fleet); - if (race != null) { - result.add(race); - } - } - } - } - return result; - } - - private void fillLiveRaceInfoDTOWithStartLineAdavantageByGeometryData(RibDashboardRaceInfoDTO lRInfo, - TimePoint timePoint) throws NoWindException { - if (runningRace != null && timePoint != null) { - StartLineAdvantageDTO startLineAdvantageDTO = new StartLineAdvantageDTO(); - startLineAdvantageDTO.startLineAdvatageType = StartlineAdvantageType.GEOMETRIC; - double startlineAdvantage = calculateStartlineAdvantageByGeometry(); - startLineAdvantageDTO.startLineAdvantage = startlineAdvantage; - averageStartLineAdvantageByGeometry.add(startlineAdvantage); - startLineAdvantageDTO.average = averageStartLineAdvantageByGeometry.getAverage(); - lRInfo.startLineAdvantageDTO = startLineAdvantageDTO; - } - } - - private double calculateStartlineAdvantageByGeometry() { - double result = 0.0; - Course course = runningRace.getRace().getCourse(); - if (course != null) { - Waypoint startlineWayPoint = course.getFirstLeg().getFrom(); - Waypoint firstmarkWayPoint = course.getFirstLeg().getTo(); - if (startlineWayPoint != null && firstmarkWayPoint != null) { - Pair startlineMarkPositions = retrieveStartlineMarkPositionsFromStartLineWayPoint(startlineWayPoint); - Position firstMarkPosition = retrieveFirstMarkPositionFromFirstMarkWayPoint(firstmarkWayPoint); - if (startlineMarkPositions != null && firstMarkPosition != null) { - Distance rcToMark = firstMarkPosition.getDistance(startlineMarkPositions.getA()); - Distance pinToMark = firstMarkPosition.getDistance(startlineMarkPositions.getB()); - return rcToMark.getMeters() - pinToMark.getMeters(); - } - } - } - return result; - } - - private Pair retrieveStartlineMarkPositionsFromStartLineWayPoint(Waypoint startLineWayPoint) { - Pair result = null; - Iterator markIterator = startLineWayPoint.getMarks().iterator(); - if (markIterator.hasNext()) { - Mark startboat = (Mark) markIterator.next(); - if (markIterator.hasNext()) { - Mark pinEnd = (Mark) markIterator.next(); - TimePoint now = MillisecondsTimePoint.now(); - Position startBoatPosition = getPositionFromMarkAtTimePoint(runningRace, startboat, now); - Position pinEndPosition = getPositionFromMarkAtTimePoint(runningRace, pinEnd, now); - if(startBoatPosition != null && pinEndPosition != null) { - result = new Pair(startBoatPosition, pinEndPosition); - } - } - } - return result; - } - - private Position getPositionFromMarkAtTimePoint(TrackedRace trackedRace, Mark mark, TimePoint timePoint) { - GPSFixTrack fixTrack = trackedRace.getTrack(mark); - return fixTrack.getEstimatedPosition(timePoint, true); - } - - private Position retrieveFirstMarkPositionFromFirstMarkWayPoint(Waypoint firstMarkWayPoint) { - Position firstMarkPosition = null; - if (firstMarkWayPoint.getMarks().iterator().hasNext()) { - Mark firstMark = firstMarkWayPoint.getMarks().iterator().next(); - TimePoint now = MillisecondsTimePoint.now(); - firstMarkPosition = getPositionFromMarkAtTimePoint(runningRace, firstMark, now); - logger.log(Level.INFO, "Firstmark: " + firstMarkPosition); - } - return firstMarkPosition; - } - - protected com.sap.sailing.domain.base.DomainFactory getBaseDomainFactory() { - return baseDomainFactory; - } - - @Override - public List getStartAnalysisListForCompetitorIDAndLeaderboardName(String competitorIdAsString, - String leaderboardName) { - List result = null; - if (competitorIdAsString != null && leaderboardName != null) { - result = new ArrayList(); - try { - Competitor competitor = baseDomainFactory.getCompetitorStore().getExistingCompetitorByIdAsString(competitorIdAsString); - List trackedRacesForLeaderBoardName = getTrackedRacesFromLeaderboard(leaderboardName); - for (TrackedRace trackedRace : trackedRacesForLeaderBoardName) { - StartAnalysisDTO startAnalysisDTO = startAnalysisCreationController.checkStartAnalysisForCompetitorInTrackedRace(competitor, trackedRace); - if (startAnalysisDTO != null) { - result.add(startAnalysisDTO); - } - } - } catch (NullPointerException e) { - logger.log(Level.INFO, "", e); - } - } - return result; - } - - @Override - public List getCompetitorsInLeaderboard(String leaderboardName) { - logger.log(Level.INFO, "getCompetitorsInLeaderboard(...) Request."); - if (leaderboardName != null) { - Leaderboard lb = getRacingEventService().getLeaderboardByName(leaderboardName); - return baseDomainFactory.getCompetitorDTOList(lb.getCompetitorsFromBestToWorst(MillisecondsTimePoint.now())); - } else { - return null; - } - } - - @Override - public StartlineAdvantagesWithMaxAndAverageDTO getAdvantagesOnStartline(String leaderboardName) { - return startlineAdvantagesCalculator.getStartLineAdvantagesAccrossLineAtTimePoint(MillisecondsTimePoint.now()); - } - - - @Override - public void notifyLiveTrackedRaceListenerAboutLiveTrackedRaceChange(TrackedRace trackedRace) { - listener.forEach(listener -> listener.liveTrackedRaceDidChange(trackedRace)); - } -} \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startanalysis/AbstractStartAnalysisCreationValidator.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startanalysis/AbstractStartAnalysisCreationValidator.java similarity index 79% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startanalysis/AbstractStartAnalysisCreationValidator.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startanalysis/AbstractStartAnalysisCreationValidator.java index 8747a387975..1839f4b31ef 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startanalysis/AbstractStartAnalysisCreationValidator.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startanalysis/AbstractStartAnalysisCreationValidator.java @@ -1,19 +1,21 @@ -package com.sap.sailing.dashboards.gwt.server.startanalysis; +package com.sap.sailing.dashboards.gwt.server.util.actions.startanalysis; import java.util.Iterator; +import com.google.gwt.core.shared.GwtIncompatible; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sse.common.TimePoint; +@GwtIncompatible public abstract class AbstractStartAnalysisCreationValidator { protected static int MINIMUM_MARKPASSIINGS_AT_FIRST_MARK = 3; protected static long MINIMUM_RACE_PROGRESSION_IN_MILLISECONDS = 6*60*1000; - protected boolean threeCompetitorsPassedSecondWayPoint(TrackedRace trackedRace){ + protected static boolean threeCompetitorsPassedSecondWayPoint(TrackedRace trackedRace){ Waypoint secondWaypoint = trackedRace.getRace().getCourse().getFirstLeg().getTo(); Iterator markPassingsInOrder = trackedRace.getMarkPassingsInOrder(secondWaypoint).iterator(); int markPassingsCounter = 0; @@ -24,12 +26,12 @@ public abstract class AbstractStartAnalysisCreationValidator { return markPassingsCounter >= MINIMUM_MARKPASSIINGS_AT_FIRST_MARK ? true : false; } - private boolean competitorPassedSecondWayPoint(Competitor competitor, TrackedRace trackedRace){ + private static boolean competitorPassedSecondWayPoint(Competitor competitor, TrackedRace trackedRace){ Waypoint secondWaypoint = trackedRace.getRace().getCourse().getFirstLeg().getTo(); return trackedRace.getMarkPassing(competitor, secondWaypoint) == null ? false : true; } - private boolean gateStartGolfFlagIsDown(TrackedRace trackedRace){ + private static boolean gateStartGolfFlagIsDown(TrackedRace trackedRace){ if(trackedRace.getGateStartGolfDownTime() != 0){ return true; }else{ @@ -44,7 +46,7 @@ public abstract class AbstractStartAnalysisCreationValidator { return false; } - protected boolean raceProgressedFarEnough(Competitor competitor, TrackedRace trackedRace){ + protected static boolean raceProgressedFarEnough(Competitor competitor, TrackedRace trackedRace){ final Boolean isGateStart = trackedRace.isGateStart(); if(isGateStart == Boolean.TRUE){ return gateStartGolfFlagIsDown(trackedRace); diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startanalysis/StartAnalysisCreationController.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startanalysis/StartAnalysisCreationController.java similarity index 52% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startanalysis/StartAnalysisCreationController.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startanalysis/StartAnalysisCreationController.java index 35ff0fc276f..c424e122404 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startanalysis/StartAnalysisCreationController.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startanalysis/StartAnalysisCreationController.java @@ -1,30 +1,29 @@ -package com.sap.sailing.dashboards.gwt.server.startanalysis; +package com.sap.sailing.dashboards.gwt.server.util.actions.startanalysis; import java.util.logging.Level; import java.util.logging.Logger; -import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO; +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardDispatchContext; +import com.sap.sailing.dashboards.gwt.shared.dto.StartAnalysisDTO; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.server.RacingEventService; -public class StartAnalysisCreationController extends AbstractStartAnalysisCreationValidator { +@GwtIncompatible +public final class StartAnalysisCreationController extends AbstractStartAnalysisCreationValidator { - private StartAnalysisDTOFactory startAnalysisDTOFactory; - private static final Logger logger = Logger.getLogger(StartAnalysisCreationController.class.getName()); - public StartAnalysisCreationController(RacingEventService racingEventService) { - startAnalysisDTOFactory = new StartAnalysisDTOFactory(racingEventService); + private StartAnalysisCreationController(DashboardDispatchContext ctx) { + } - public StartAnalysisDTO checkStartAnalysisForCompetitorInTrackedRace(Competitor competitor, TrackedRace trackedRace) { + public static StartAnalysisDTO checkStartAnalysisForCompetitorInTrackedRace(DashboardDispatchContext dashboardDispatchContext, Competitor competitor, TrackedRace trackedRace) { StartAnalysisDTO result = null; if (competitor != null && trackedRace != null) { if (threeCompetitorsPassedSecondWayPoint(trackedRace) && raceProgressedFarEnough(competitor, trackedRace)) { logger.log(Level.INFO, "Creating startanalysis for race " + trackedRace.getRace().getName() + " and competitor: " + competitor.getName()); - result = startAnalysisDTOFactory.createStartAnalysisForCompetitorAndTrackedRace(competitor, trackedRace); - + result = StartAnalysisDTOFactory.createStartAnalysisForCompetitorAndTrackedRace(dashboardDispatchContext, competitor, trackedRace); } else { logger.log(Level.INFO, "Waiting to create startanalysis for race " + trackedRace.getRace().getName() + " and competitor: " + competitor.getName()); } diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startanalysis/StartAnalysisDTOFactory.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startanalysis/StartAnalysisDTOFactory.java similarity index 76% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startanalysis/StartAnalysisDTOFactory.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startanalysis/StartAnalysisDTOFactory.java index 582ba70c694..a2de29ccd6d 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startanalysis/StartAnalysisDTOFactory.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startanalysis/StartAnalysisDTOFactory.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.server.startanalysis; +package com.sap.sailing.dashboards.gwt.server.util.actions.startanalysis; import java.util.ArrayList; import java.util.Date; @@ -7,12 +7,14 @@ import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; -import com.sap.sailing.dashboards.gwt.client.startanalysis.StartlineAdvantageType; +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.dashboards.gwt.shared.StartlineAdvantageType; +import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardDispatchContext; +import com.sap.sailing.dashboards.gwt.shared.dto.StartAnalysisCompetitorDTO; +import com.sap.sailing.dashboards.gwt.shared.dto.StartAnalysisDTO; +import com.sap.sailing.dashboards.gwt.shared.dto.StartAnalysisRankingTableEntryDTO; import com.sap.sailing.dashboards.gwt.shared.dto.StartLineAdvantageDTO; -import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisCompetitorDTO; -import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO; -import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisRankingTableEntryDTO; -import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.WindAndAdvantagesInfoForStartLineDTO; +import com.sap.sailing.dashboards.gwt.shared.dto.WindAndAdvantagesInfoForStartLineDTO; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.common.LegType; @@ -24,24 +26,20 @@ import com.sap.sailing.domain.tracking.LineDetails; 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.server.RacingEventService; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; -public class StartAnalysisDTOFactory extends AbstractStartAnalysisCreationValidator { - - private final com.sap.sailing.domain.base.DomainFactory baseDomainFactory; +@GwtIncompatible +public final class StartAnalysisDTOFactory extends AbstractStartAnalysisCreationValidator { private static int DEFAULT_GATE_START_INTERVALL_IN_MILLISECONDS = 5*60*1000; private static int ONE_MINUTE_INTERVALL_IN_MILLISECONDS = 60*1000; private static final Logger logger = Logger.getLogger(StartAnalysisDTOFactory.class.getName()); - public StartAnalysisDTOFactory(RacingEventService racingEventService) { - baseDomainFactory = racingEventService.getBaseDomainFactory(); - } + private StartAnalysisDTOFactory() {} - public StartAnalysisDTO createStartAnalysisForCompetitorAndTrackedRace(Competitor competitor, TrackedRace trackedRace) { + public static StartAnalysisDTO createStartAnalysisForCompetitorAndTrackedRace(DashboardDispatchContext dashboardDispatchContext, Competitor competitor, TrackedRace trackedRace) { StartAnalysisDTO startAnalysisDTO = new StartAnalysisDTO(); addStaticDataToStartAnalysisDTOFrom(startAnalysisDTO, trackedRace); List competitors = new ArrayList(); @@ -49,7 +47,7 @@ public class StartAnalysisDTOFactory extends AbstractStartAnalysisCreationValida List markPassingsInOrder = convertMarkpPassingsIteratorToList(trackedRace.getMarkPassingsInOrder(secondWaypoint).iterator()); boolean isCompetitorOneOfFirstThree = false; for (int i = 0; i < MINIMUM_MARKPASSIINGS_AT_FIRST_MARK; i++) { - competitors.add(createStartAnalysisCompetitorDTO(trackedRace, i + 1, markPassingsInOrder.get(i).getCompetitor())); + competitors.add(createStartAnalysisCompetitorDTO(dashboardDispatchContext, trackedRace, i + 1, markPassingsInOrder.get(i).getCompetitor())); if (markPassingsInOrder.get(i).getCompetitor().equals(competitor)) { isCompetitorOneOfFirstThree = true; } @@ -57,9 +55,9 @@ public class StartAnalysisDTOFactory extends AbstractStartAnalysisCreationValida if (!isCompetitorOneOfFirstThree) { int rankOfCompetitorWhilePassingSecondWaypoint = getRankOfCompetitorWhilePassingSecondWaypoint(competitor, trackedRace); - competitors.add(createStartAnalysisCompetitorDTO(trackedRace, rankOfCompetitorWhilePassingSecondWaypoint, competitor)); + competitors.add(createStartAnalysisCompetitorDTO(dashboardDispatchContext, trackedRace, rankOfCompetitorWhilePassingSecondWaypoint, competitor)); } - startAnalysisDTO.competitor = baseDomainFactory.getCompetitorStore().convertToCompetitorDTO(competitor); + startAnalysisDTO.competitor = dashboardDispatchContext.getRacingEventService().getBaseDomainFactory().getCompetitorStore().convertToCompetitorDTO(competitor); startAnalysisDTO.startAnalysisCompetitorDTOs = competitors; final Boolean isGateStart = trackedRace.isGateStart(); if(isGateStart == Boolean.TRUE){ @@ -83,7 +81,7 @@ public class StartAnalysisDTOFactory extends AbstractStartAnalysisCreationValida return startAnalysisDTO; } - private List convertMarkpPassingsIteratorToList(Iterator iterator) { + private static List convertMarkpPassingsIteratorToList(Iterator iterator) { List list = new ArrayList(); while (iterator.hasNext()) { list.add(iterator.next()); @@ -91,7 +89,7 @@ public class StartAnalysisDTOFactory extends AbstractStartAnalysisCreationValida return list; } - private int getRankOfCompetitorWhilePassingSecondWaypoint(Competitor competitor, TrackedRace trackedRace) { + private static int getRankOfCompetitorWhilePassingSecondWaypoint(Competitor competitor, TrackedRace trackedRace) { Waypoint secondWaypoint = trackedRace.getRace().getCourse().getFirstLeg().getTo(); Iterator markPassings = trackedRace.getMarkPassingsInOrder(secondWaypoint).iterator(); int counter = 0; @@ -103,20 +101,20 @@ public class StartAnalysisDTOFactory extends AbstractStartAnalysisCreationValida return 0; } - private StartAnalysisDTO addStaticDataToStartAnalysisDTOFrom(StartAnalysisDTO startAnalysisDTO, TrackedRace trackedRace) { + private static StartAnalysisDTO addStaticDataToStartAnalysisDTOFrom(StartAnalysisDTO startAnalysisDTO, TrackedRace trackedRace) { startAnalysisDTO.raceName = trackedRace.getRace().getName(); startAnalysisDTO.startAnalysisWindLineInfoDTO = createStartAnalysisWindAndLineData(trackedRace); return startAnalysisDTO; } - private StartAnalysisRankingTableEntryDTO createRankTableEntry(TrackedRace trackedRace, int rank, + private static StartAnalysisRankingTableEntryDTO createRankTableEntry(TrackedRace trackedRace, int rank, Competitor competitor) { StartAnalysisRankingTableEntryDTO startAnalysisRankTableEntryDTO = createStartAnalysisRankTableEntryDTOWithRankAndStartTimepoint( competitor, rank, trackedRace); return startAnalysisRankTableEntryDTO; } - private StartAnalysisRankingTableEntryDTO createStartAnalysisRankTableEntryDTOWithRankAndStartTimepoint( + private static StartAnalysisRankingTableEntryDTO createStartAnalysisRankTableEntryDTOWithRankAndStartTimepoint( Competitor competitor, int rank, TrackedRace trackedRace) { StartAnalysisRankingTableEntryDTO tableentry = new StartAnalysisRankingTableEntryDTO(); tableentry.rankAtFirstMark = rank; @@ -133,7 +131,7 @@ public class StartAnalysisDTOFactory extends AbstractStartAnalysisCreationValida return tableentry; } - private WindAndAdvantagesInfoForStartLineDTO createStartAnalysisWindAndLineData(TrackedRace trackedRace) { + private static WindAndAdvantagesInfoForStartLineDTO createStartAnalysisWindAndLineData(TrackedRace trackedRace) { WindAndAdvantagesInfoForStartLineDTO startAnalysisWindLineInfoDTO = new WindAndAdvantagesInfoForStartLineDTO(); final TimePoint startOfRace = trackedRace.getStartOfRace(); final TimePoint timePoint = startOfRace == null ? MillisecondsTimePoint.now() : startOfRace; @@ -158,7 +156,7 @@ public class StartAnalysisDTOFactory extends AbstractStartAnalysisCreationValida return startAnalysisWindLineInfoDTO; } - private StartlineAdvantageType getStartlineAdvantageType(TrackedRace trackedRace, TimePoint timePoint) { + private static StartlineAdvantageType getStartlineAdvantageType(TrackedRace trackedRace, TimePoint timePoint) { try { LegType typeOfFirstLeg; typeOfFirstLeg = getFirstLegTypeOfTrackedRaceAtTimePoint(trackedRace, timePoint); @@ -176,7 +174,7 @@ public class StartAnalysisDTOFactory extends AbstractStartAnalysisCreationValida } } - private LegType getFirstLegTypeOfTrackedRaceAtTimePoint(TrackedRace trackedRace, TimePoint timePoint) + private static LegType getFirstLegTypeOfTrackedRaceAtTimePoint(TrackedRace trackedRace, TimePoint timePoint) throws NoWindException { Iterable trackedLegs = trackedRace.getTrackedLegs(); if (trackedLegs != null && trackedLegs.iterator().hasNext()) { @@ -187,10 +185,10 @@ public class StartAnalysisDTOFactory extends AbstractStartAnalysisCreationValida } } - private StartAnalysisCompetitorDTO createStartAnalysisCompetitorDTO(TrackedRace trackedRace, int rank, + private static StartAnalysisCompetitorDTO createStartAnalysisCompetitorDTO(DashboardDispatchContext dashboardDispatchContext, TrackedRace trackedRace, int rank, Competitor competitor) { StartAnalysisCompetitorDTO startAnalysisCompetitorDTOsForRace = new StartAnalysisCompetitorDTO(); - startAnalysisCompetitorDTOsForRace.competitorDTO = baseDomainFactory.getCompetitorStore() + startAnalysisCompetitorDTOsForRace.competitorDTO = dashboardDispatchContext.getRacingEventService().getBaseDomainFactory().getCompetitorStore() .convertToCompetitorDTO(competitor); startAnalysisCompetitorDTOsForRace.rankingTableEntryDTO = createRankTableEntry(trackedRace, rank, competitor); return startAnalysisCompetitorDTOsForRace; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/DefaultPolarValues.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/DefaultPolarValues.java similarity index 87% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/DefaultPolarValues.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/DefaultPolarValues.java index 721524ae6ae..955f215fef8 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/DefaultPolarValues.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/DefaultPolarValues.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.server.startlineadvantages; +package com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage; import com.sap.sailing.domain.common.ManeuverType; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/DefaultPolarWindAngleBoatSpeedFunction.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/DefaultPolarWindAngleBoatSpeedFunction.java similarity index 96% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/DefaultPolarWindAngleBoatSpeedFunction.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/DefaultPolarWindAngleBoatSpeedFunction.java index 8c7082fdb43..7b16e26be3d 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/DefaultPolarWindAngleBoatSpeedFunction.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/DefaultPolarWindAngleBoatSpeedFunction.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.server.startlineadvantages; +package com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage; import org.apache.commons.math.ArgumentOutsideDomainException; import org.apache.commons.math.analysis.interpolation.SplineInterpolator; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/StartlineAdvantageByGeometryCalculator.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/StartlineAdvantageByGeometryCalculator.java new file mode 100644 index 00000000000..3c0dfd8e3e9 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/StartlineAdvantageByGeometryCalculator.java @@ -0,0 +1,77 @@ +package com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage; + +import java.util.Iterator; + +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.domain.base.Course; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +/** + * @author Alexander Ries (D062114) + * + */ +@GwtIncompatible +public final class StartlineAdvantageByGeometryCalculator { + + public static Double calculateStartlineAdvantageByGeometry(TrackedRace trackedRace) { + Double result = null; + Course course = trackedRace.getRace().getCourse(); + if (course != null) { + Waypoint startlineWayPoint = course.getFirstLeg().getFrom(); + Waypoint firstmarkWayPoint = course.getFirstLeg().getTo(); + if (startlineWayPoint != null && firstmarkWayPoint != null) { + Pair startlineMarkPositions = retrieveStartlineMarkPositionsFromStartLineWayPoint(trackedRace, startlineWayPoint); + Position firstMarkPosition = retrieveFirstMarkPositionFromFirstMarkWayPoint(trackedRace, firstmarkWayPoint); + if (startlineMarkPositions != null && firstMarkPosition != null) { + Distance rcToMark = firstMarkPosition.getDistance(startlineMarkPositions.getA()); + Distance pinToMark = firstMarkPosition.getDistance(startlineMarkPositions.getB()); + double startlineadvantage = rcToMark.getMeters() - pinToMark.getMeters(); + result = new Double(startlineadvantage); + } + } + } + return result; + } + + private static Pair retrieveStartlineMarkPositionsFromStartLineWayPoint(TrackedRace trackedRace, Waypoint startLineWayPoint) { + Pair result = null; + Iterator markIterator = startLineWayPoint.getMarks().iterator(); + if (markIterator.hasNext()) { + Mark startboat = (Mark) markIterator.next(); + if (markIterator.hasNext()) { + Mark pinEnd = (Mark) markIterator.next(); + TimePoint now = MillisecondsTimePoint.now(); + Position startBoatPosition = getPositionFromMarkAtTimePoint(trackedRace, startboat, now); + Position pinEndPosition = getPositionFromMarkAtTimePoint(trackedRace, pinEnd, now); + if(startBoatPosition != null && pinEndPosition != null) { + result = new Pair(startBoatPosition, pinEndPosition); + } + } + } + return result; + } + + private static Position getPositionFromMarkAtTimePoint(TrackedRace trackedRace, Mark mark, TimePoint timePoint) { + GPSFixTrack fixTrack = trackedRace.getTrack(mark); + return fixTrack.getEstimatedPosition(timePoint, true); + } + + private static Position retrieveFirstMarkPositionFromFirstMarkWayPoint(TrackedRace trackedRace, Waypoint firstMarkWayPoint) { + Position firstMarkPosition = null; + if (firstMarkWayPoint.getMarks().iterator().hasNext()) { + Mark firstMark = firstMarkWayPoint.getMarks().iterator().next(); + TimePoint now = MillisecondsTimePoint.now(); + firstMarkPosition = getPositionFromMarkAtTimePoint(trackedRace, firstMark, now); + } + return firstMarkPosition; + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/StartlineAdvantagesCalculator.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/StartlineAdvantagesByWindCalculator.java similarity index 80% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/StartlineAdvantagesCalculator.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/StartlineAdvantagesByWindCalculator.java index a42c039702a..806bd4bbf3c 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/StartlineAdvantagesCalculator.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/StartlineAdvantagesByWindCalculator.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.server.startlineadvantages; +package com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage; import java.util.ArrayList; import java.util.Collections; @@ -6,9 +6,8 @@ import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; -import com.sap.sailing.dashboards.gwt.server.LiveTrackedRaceListener; -import com.sap.sailing.dashboards.gwt.server.startlineadvantages.precalculation.AbstracPreCalculationDataRetriever; -import com.sap.sailing.dashboards.gwt.shared.MovingAverage; +import com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.precalculation.AbstracPreCalculationDataRetriever; +import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardDispatchContext; import com.sap.sailing.dashboards.gwt.shared.dto.StartLineAdvantageDTO; import com.sap.sailing.dashboards.gwt.shared.dto.StartlineAdvantagesWithMaxAndAverageDTO; import com.sap.sailing.domain.common.Bearing; @@ -17,7 +16,6 @@ import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.MeterDistance; -import com.sap.sailing.domain.polars.PolarDataService; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util.Pair; @@ -26,54 +24,40 @@ import com.sap.sse.common.Util.Pair; * @author Alexander Ries (D062114) * */ -public class StartlineAdvantagesCalculator extends AbstracPreCalculationDataRetriever implements LiveTrackedRaceListener{ +public class StartlineAdvantagesByWindCalculator extends AbstracPreCalculationDataRetriever { - private TrackedRace currentLiveTrackedRace; - private MovingAverage advantageMaximumAverage; - private PolarDataService polarDataService; + private DashboardDispatchContext dashboardDispatchContext; private DefaultPolarWindAngleBoatSpeedFunction defaultPolarSpeedWindAngleFunction; - private static final Logger logger = Logger.getLogger(StartlineAdvantagesCalculator.class.getName()); + private final Logger logger = Logger.getLogger(StartlineAdvantagesByWindCalculator.class.getName()); - public StartlineAdvantagesCalculator(PolarDataService polarDataService){ - this.advantageMaximumAverage = new MovingAverage(500); - this.polarDataService = polarDataService; + public StartlineAdvantagesByWindCalculator(DashboardDispatchContext dashboardDispatchContext){ + this.dashboardDispatchContext = dashboardDispatchContext; this.defaultPolarSpeedWindAngleFunction = new DefaultPolarWindAngleBoatSpeedFunction(); } - // Get Course Buoys - // Get Wind - // Get Maneuver Angle and Boat Speed at Angle - // Get Positions on start line with normal upwind and overlaying lay line upwind - // Calculate intersection point(s) between start line and lay lines - // Calculate duration from start line to first mark for overlaying positions - // Calculate intersection points from normal upwind start positions to first mark - // Calculate duration from two new distances for normal upwind starts - // Put all durations in one set - // Get the smallest of the durations and subtract number from every other number - public StartlineAdvantagesWithMaxAndAverageDTO getStartLineAdvantagesAccrossLineAtTimePoint(TimePoint timepoint) { + public StartlineAdvantagesWithMaxAndAverageDTO getStartLineAdvantagesAccrossLineFromTrackedRaceAtTimePoint(TrackedRace trackedRace,TimePoint timepoint) { StartlineAdvantagesWithMaxAndAverageDTO result = new StartlineAdvantagesWithMaxAndAverageDTO(); - if (currentLiveTrackedRace != null) { - retrieveDataForCalculation(currentLiveTrackedRace); + if (trackedRace != null) { + retrieveDataForCalculation(trackedRace, dashboardDispatchContext.getPolarDataService()); if (wind != null) { + Pair startlineAdvantagesAndConfidencesAsArray = null; if (isStartlineCompletelyUnderneathLaylines()) { logger.log(Level.INFO, "Startline is completely underneath laylines"); Pair advantagesRange = new Pair(0.0, startlineLenghtInMeters); - result.advantages = calculateStartlineAdvantagesUnderneathLaylinesInRange(advantagesRange); - double maximum = getMaximumAdvantageOfStartlineAdvantageDTOs(result.advantages); + List startlineAdvantages = calculateStartlineAdvantagesUnderneathLaylinesInRange(advantagesRange); + double maximum = getMaximumAdvantageOfStartlineAdvantageDTOs(startlineAdvantages); result.maximum = maximum; - advantageMaximumAverage.add(maximum); - result.average = advantageMaximumAverage.getAverage(); + startlineAdvantagesAndConfidencesAsArray = convertStartLineAdvantageDTOListToPointAndConfidenceArrays(startlineAdvantages); } else if (isStartlineCompletelyAboveLaylines()) { logger.log(Level.INFO, "Startline is completely above laylines"); Pair advantagesRange = new Pair(0.0, startlineLenghtInMeters); - result.advantages = calculatePolarBasedStartlineAdvantagesInRange(advantagesRange); - subtractMinimumOfAllStartlineAdvantages(result.advantages); - double maximum = getMaximumAdvantageOfStartlineAdvantageDTOs(result.advantages); + List startlineAdvantages = calculatePolarBasedStartlineAdvantagesInRange(advantagesRange); + subtractMinimumOfAllStartlineAdvantages(startlineAdvantages); + double maximum = getMaximumAdvantageOfStartlineAdvantageDTOs(startlineAdvantages); result.maximum = maximum; - advantageMaximumAverage.add(maximum); - result.average = advantageMaximumAverage.getAverage(); - subtractAgainstMaximumOfAllStartlineAdvantages(result.advantages, maximum); + subtractAgainstMaximumOfAllStartlineAdvantages(startlineAdvantages, maximum); + startlineAdvantagesAndConfidencesAsArray = convertStartLineAdvantageDTOListToPointAndConfidenceArrays(startlineAdvantages); } else { logger.log(Level.INFO, "Layline(s) cross startline"); Position intersectionOfRightLaylineAndStartline = getIntersectionOfRightLaylineAndStartline(); @@ -81,14 +65,17 @@ public class StartlineAdvantagesCalculator extends AbstracPreCalculationDataRet Pair polarBasedStartlineAdvatagesRange = getStartAndEndPointOfPolarBasedStartlineAdvatagesInDistancesToRCBoat( intersectionOfRightLaylineAndStartline, intersectionOfleftLaylineAndStartline); Pair pinEndStartlineAdvatagesRange = getPinEndStartlineAdvantagesRangeFromPolarAdvantagesRange(polarBasedStartlineAdvatagesRange); - result.advantages = calculatePolarBasedStartlineAdvantagesInRange(polarBasedStartlineAdvatagesRange); - subtractMinimumOfAllStartlineAdvantages(result.advantages); - double maximum = getMaximumAdvantageOfStartlineAdvantageDTOs(result.advantages); + List startlineAdvantages = calculatePolarBasedStartlineAdvantagesInRange(polarBasedStartlineAdvatagesRange); + subtractMinimumOfAllStartlineAdvantages(startlineAdvantages); + double maximum = getMaximumAdvantageOfStartlineAdvantageDTOs(startlineAdvantages); result.maximum = maximum; - advantageMaximumAverage.add(maximum); - result.average = advantageMaximumAverage.getAverage(); - subtractAgainstMaximumOfAllStartlineAdvantages(result.advantages, maximum); - addClosingZeroPointToMixedAdvantages(result.advantages, pinEndStartlineAdvatagesRange); + subtractAgainstMaximumOfAllStartlineAdvantages(startlineAdvantages, maximum); + addClosingZeroPointToMixedAdvantages(startlineAdvantages, pinEndStartlineAdvatagesRange); + startlineAdvantagesAndConfidencesAsArray = convertStartLineAdvantageDTOListToPointAndConfidenceArrays(startlineAdvantages); + } + if (startlineAdvantagesAndConfidencesAsArray != null) { + result.distanceToRCBoatToStartlineAdvantage = startlineAdvantagesAndConfidencesAsArray.getA(); + result.distanceToRCBoatToConfidence = startlineAdvantagesAndConfidencesAsArray.getB(); } } } else { @@ -108,7 +95,7 @@ public class StartlineAdvantagesCalculator extends AbstracPreCalculationDataRet return result; } - private boolean isBearingUnderneathAdvantageLines(Bearing bearing){ + private boolean isBearingUnderneathAdvantageLines(Bearing bearing){ boolean result = false; Bearing bearingOfRightLaylineInDeg = new DegreeBearingImpl(wind.getBearing().getDegrees() - meouvreAngle / 2); Bearing bearingOfLeftLaylineInDeg = new DegreeBearingImpl(wind.getBearing().getDegrees() + meouvreAngle / 2); @@ -269,6 +256,23 @@ public class StartlineAdvantagesCalculator extends AbstracPreCalculationDataRet return result; } + private Pair convertStartLineAdvantageDTOListToPointAndConfidenceArrays(List startLineAdvantageDTOList) { + Pair result = null; + if(startLineAdvantageDTOList != null && startLineAdvantageDTOList.size() > 0) { + Number[][] distanceToRCBoatToStartlineAdvantage = new Number[startLineAdvantageDTOList.size()][2]; + Number[][] distanceToRCBoatToConfidence = new Number[startLineAdvantageDTOList.size()][2]; + for(int i = 0; i < startLineAdvantageDTOList.size(); i++) { + StartLineAdvantageDTO startLineAdvantageDTO = startLineAdvantageDTOList.get(i); + distanceToRCBoatToStartlineAdvantage[i][0] = startLineAdvantageDTO.distanceToRCBoatInMeters; + distanceToRCBoatToStartlineAdvantage[i][1] = startLineAdvantageDTO.startLineAdvantage; + distanceToRCBoatToConfidence[i][0] = startLineAdvantageDTO.distanceToRCBoatInMeters; + distanceToRCBoatToConfidence[i][1] = startLineAdvantageDTO.confidence; + } + result = new Pair(distanceToRCBoatToStartlineAdvantage, distanceToRCBoatToConfidence); + } + return result; + } + private List calculatePolarBasedStartlineAdvantagesInRange(Pair rangePinEndStartlineAdvantage) { List result = new ArrayList(); if (rangePinEndStartlineAdvantage != null) { @@ -314,13 +318,13 @@ public class StartlineAdvantagesCalculator extends AbstracPreCalculationDataRet return advantages; } - private double getMaximumAdvantageOfStartlineAdvantageDTOs(List advantages) { - double result = 0; + private Double getMaximumAdvantageOfStartlineAdvantageDTOs(List advantages) { + Double result = null; List sortedAdvantages = new ArrayList(); sortedAdvantages.addAll(advantages); - if(sortedAdvantages != null && sortedAdvantages.size() > 0) { - Collections.sort(sortedAdvantages, StartLineAdvantageDTO.startlineAdvantageComparatorByAdvantageDesc); - result = sortedAdvantages.get(0).startLineAdvantage; + if (sortedAdvantages != null && sortedAdvantages.size() > 0) { + Collections.sort(sortedAdvantages, StartLineAdvantageDTO.startlineAdvantageComparatorByAdvantageDesc); + result = new Double(sortedAdvantages.get(0).startLineAdvantage); } return result; } @@ -335,14 +339,4 @@ public class StartlineAdvantagesCalculator extends AbstracPreCalculationDataRet } return result; } - - @Override - public void liveTrackedRaceDidChange(TrackedRace trackedRace) { - this.currentLiveTrackedRace = trackedRace; - } - - @Override - public PolarDataService getPolarDataService() { - return this.polarDataService; - } } diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/precalculation/AbstracPreCalculationDataRetriever.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/precalculation/AbstracPreCalculationDataRetriever.java similarity index 86% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/precalculation/AbstracPreCalculationDataRetriever.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/precalculation/AbstracPreCalculationDataRetriever.java index 4353242c43f..1cf1ab8d0e9 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/precalculation/AbstracPreCalculationDataRetriever.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/precalculation/AbstracPreCalculationDataRetriever.java @@ -1,9 +1,10 @@ -package com.sap.sailing.dashboards.gwt.server.startlineadvantages.precalculation; +package com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.precalculation; import com.sap.sailing.domain.base.impl.BoatClassImpl; import com.sap.sailing.domain.common.BoatClassMasterdata; import com.sap.sailing.domain.common.ManeuverType; import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.polars.PolarDataService; import com.sap.sailing.domain.tracking.TrackedRace; /** @@ -20,11 +21,11 @@ public abstract class AbstracPreCalculationDataRetriever implements PreCalculati public Wind wind; public double meouvreAngle; - protected void retrieveDataForCalculation(TrackedRace trackedRace) { + protected void retrieveDataForCalculation(TrackedRace trackedRace, PolarDataService polarDataService) { startlineAndFirstMarkPositions = retrieveMarkPositions(trackedRace); startlineAdvantageAtPinEndInMeters = retrieveStartlineAdvantage(trackedRace); startlineLenghtInMeters = retrieveStartlineLenght(trackedRace); wind = retrieveWindAtPosition(startlineAndFirstMarkPositions.startBoatPosition, trackedRace); - meouvreAngle = retrieveManouvreAngleAtWindSpeedAndBoatClass(new BoatClassImpl("Extreme40", BoatClassMasterdata.EXTREME_40), ManeuverType.TACK, wind); + meouvreAngle = retrieveManouvreAngleAtWindSpeedAndBoatClass(new BoatClassImpl("Extreme40", BoatClassMasterdata.EXTREME_40), ManeuverType.TACK, wind, polarDataService); } } diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/precalculation/PreCalculationMarkRetriever.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/precalculation/PreCalculationMarkRetriever.java similarity index 97% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/precalculation/PreCalculationMarkRetriever.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/precalculation/PreCalculationMarkRetriever.java index 112195ba379..ae909348bae 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/precalculation/PreCalculationMarkRetriever.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/precalculation/PreCalculationMarkRetriever.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.server.startlineadvantages.precalculation; +package com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.precalculation; import java.util.Iterator; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/precalculation/PreCalculationPolarDataRetriever.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/precalculation/PreCalculationPolarDataRetriever.java similarity index 73% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/precalculation/PreCalculationPolarDataRetriever.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/precalculation/PreCalculationPolarDataRetriever.java index 2f5998945ad..c7068933782 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/precalculation/PreCalculationPolarDataRetriever.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/precalculation/PreCalculationPolarDataRetriever.java @@ -1,6 +1,6 @@ -package com.sap.sailing.dashboards.gwt.server.startlineadvantages.precalculation; +package com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.precalculation; -import com.sap.sailing.dashboards.gwt.server.startlineadvantages.DefaultPolarValues; +import com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.DefaultPolarValues; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.common.ManeuverType; import com.sap.sailing.domain.common.Speed; @@ -14,11 +14,11 @@ import com.sap.sailing.domain.polars.PolarDataService; */ public interface PreCalculationPolarDataRetriever { - default double retrieveManouvreAngleAtWindSpeedAndBoatClass(BoatClass boatClass, ManeuverType maneuverType, Speed windSpeed) { + default double retrieveManouvreAngleAtWindSpeedAndBoatClass(BoatClass boatClass, ManeuverType maneuverType, Speed windSpeed, PolarDataService polarDataService) { double result = 0; try { if (boatClass != null && maneuverType != null && windSpeed != null) { - BearingWithConfidence bearingWithConfidence = getPolarDataService().getManeuverAngle(boatClass, maneuverType, windSpeed); + BearingWithConfidence bearingWithConfidence = polarDataService.getManeuverAngle(boatClass, maneuverType, windSpeed); result = bearingWithConfidence.getObject().getDegrees(); } else { result = DefaultPolarValues.getManouvreAngle(ManeuverType.TACK); @@ -28,6 +28,4 @@ public interface PreCalculationPolarDataRetriever { } return result; } - - PolarDataService getPolarDataService(); } diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/precalculation/PreCalculationStartlineAdvantageRetriever.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/precalculation/PreCalculationStartlineAdvantageRetriever.java similarity index 90% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/precalculation/PreCalculationStartlineAdvantageRetriever.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/precalculation/PreCalculationStartlineAdvantageRetriever.java index 9da4e2f2850..c02595e475d 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/precalculation/PreCalculationStartlineAdvantageRetriever.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/precalculation/PreCalculationStartlineAdvantageRetriever.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.server.startlineadvantages.precalculation; +package com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.precalculation; import com.sap.sailing.domain.tracking.LineDetails; import com.sap.sailing.domain.tracking.TrackedRace; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/precalculation/PreCalculationWindRetriever.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/precalculation/PreCalculationWindRetriever.java similarity index 84% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/precalculation/PreCalculationWindRetriever.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/precalculation/PreCalculationWindRetriever.java index e52ee193d0e..f1ff3539a4f 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/startlineadvantages/precalculation/PreCalculationWindRetriever.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/server/util/actions/startlineadvantage/precalculation/PreCalculationWindRetriever.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.server.startlineadvantages.precalculation; +package com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.precalculation; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.Wind; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/DashboardLiveRaceProvider.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/DashboardLiveRaceProvider.java new file mode 100644 index 00000000000..a13477f7a50 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/DashboardLiveRaceProvider.java @@ -0,0 +1,55 @@ +package com.sap.sailing.dashboards.gwt.shared; + +import java.util.HashMap; +import java.util.Map; + +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.server.RacingEventService; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +/** + * @author Alexander Ries (D062114) + * + */ +public class DashboardLiveRaceProvider { + + private Map liveRaceForLeaderboardNames; + private RacingEventService racingEventService; + + public DashboardLiveRaceProvider(RacingEventService racingEventService) { + liveRaceForLeaderboardNames = new HashMap(); + this.racingEventService = racingEventService; + } + + public void validateLiveRaceForLeaderboardName(String leaderboardName) { + Leaderboard leaderboard = racingEventService.getLeaderboardByName(leaderboardName); + TrackedRace liveRace = null; + if (leaderboard != null) { + for (RaceColumn column : leaderboard.getRaceColumns()) { + for (Fleet fleet : column.getFleets()) { + TrackedRace race = column.getTrackedRace(fleet); + if (race != null && race.isLive(MillisecondsTimePoint.now())) { + liveRace = race; + } + } + } + } + if (liveRace != null) + setLiveRaceForLeaderboardName(leaderboardName, liveRace); + } + + public TrackedRace getLiveRaceForLeaderboardName(String leaderboardName) { + return liveRaceForLeaderboardNames.get(leaderboardName); + } + + private void setLiveRaceForLeaderboardName(String leaderboardName, TrackedRace liveRace) { + if (liveRaceForLeaderboardNames.containsKey(leaderboardName)) { + liveRaceForLeaderboardNames.replace(leaderboardName, liveRace); + } else { + liveRaceForLeaderboardNames.put(leaderboardName, liveRace); + } + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/DashboardURLParameters.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/DashboardURLParameters.java new file mode 100644 index 00000000000..80de5e507f7 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/DashboardURLParameters.java @@ -0,0 +1,28 @@ +package com.sap.sailing.dashboards.gwt.shared; + +import com.google.gwt.user.client.Window; + + +/** + * @author Alexander Ries (D062114) + * + */ +public enum DashboardURLParameters { + + EVENT_ID("eventId"), + LEADERBOARD_NAME("leaderboardName"); + + private String key; + + private DashboardURLParameters(String key) { + this.key = key; + } + + public String getKey() { + return key; + } + + public String getValue() { + return Window.Location.getParameter(getKey()); + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/MovingAverage.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/MovingAverage.java index 84bbc5e637a..54f803eb96d 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/MovingAverage.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/MovingAverage.java @@ -5,10 +5,10 @@ package com.sap.sailing.dashboards.gwt.shared; /** - * Used to calculate the average value for a certain amount of renewing values. - * The amount of values is defined by {@link #NUMBER_OF_VALUES_AVERAGE_CALCULATED}. + * Used to calculate the average value for a defined amount of renewing values. + * The amount of values saved is defined by {@link #NUMBER_OF_VALUES_AVERAGE_CALCULATED}. * It uses a doubleEnededQueue to delete old values, when the queue reached the size - * of {@link #NUMBER_OF_FIEXES_AVERAGE_CALCULATED} and populate it with new ones. + * of {@link #NUMBER_OF_FIEXES_AVERAGE_CALCULATED} and populates it with new ones. * * @author Alexander Ries * diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/MovingAveragesCache.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/MovingAveragesCache.java new file mode 100644 index 00000000000..d04c0e8185d --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/MovingAveragesCache.java @@ -0,0 +1,44 @@ +package com.sap.sailing.dashboards.gwt.shared; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Alexander Ries (D062114) + * + */ +public class MovingAveragesCache { + + private Map averagesForStringKeys; + + public MovingAveragesCache() { + averagesForStringKeys = new HashMap(); + } + + public boolean containesCacheWithKey(String key) { + return averagesForStringKeys.containsKey(key); + } + + public void createMovingAverageWithKeyAndSize(String key, int size) { + if (!averagesForStringKeys.containsKey(key)) { + averagesForStringKeys.put(key, new MovingAverage(size)); + } + } + + public void addValueToAverageWithKey(String key, double value) { + if (averagesForStringKeys.containsKey(key)) { + MovingAverage movingAverage = averagesForStringKeys.get(key); + movingAverage.add(value); + averagesForStringKeys.replace(key, movingAverage); + } + } + + public Double getValueForKey(String key) { + Double result = null; + MovingAverage movingAverage = averagesForStringKeys.get(key); + if(movingAverage != null) { + result = new Double(movingAverage.getAverage()); + } + return result; + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/ResponseMessage.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/ResponseMessage.java deleted file mode 100644 index d7166e925c3..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/ResponseMessage.java +++ /dev/null @@ -1,15 +0,0 @@ -/** - * - */ -package com.sap.sailing.dashboards.gwt.shared; - -/** - * @author Alexander Ries - * - */ -public enum ResponseMessage -{ - RACE_LIVE, - NO_RACE_LIVE -} - diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/SinWave.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/SinWave.java deleted file mode 100644 index ad0d951226d..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/SinWave.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.sap.sailing.dashboards.gwt.shared; - -public class SinWave { - private double period = 100; // loop every 8 calls to updateNumber - private double scale = 5; // go between 0 and this - - private int _pos = 0; - private double Number1 = 0; - - public SinWave(double period, double scale) { - this.period = period; - this.scale = scale; - } - - - - public double getNexNumber() { - _pos++; - Number1 = (double) (Math.sin(_pos * 2 * Math.PI / period) * (scale / 2) + (scale / 2)); - return Number1; - } -} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/StartlineAdvantageCalculationBasis.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/StartlineAdvantageCalculationBasis.java deleted file mode 100644 index c9178e8bd74..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/StartlineAdvantageCalculationBasis.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.sap.sailing.dashboards.gwt.shared; - -import com.google.gwt.user.client.rpc.IsSerializable; -import com.sap.sailing.dashboards.gwt.shared.dto.StartLineAdvantageDTO; - -/** - * @author Alexander Ries (D062114) - * - */ -public enum StartlineAdvantageCalculationBasis implements IsSerializable { - MEASURED, - - /** - * When the {@link StartLineAdvantageDTO} is calculated with the Polar API and there are not values returned - * there are used manually entered polar values to provide still a {@link StartLineAdvantageDTO}. - */ - DEFAULT_VALUES -} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAdvantageType.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/StartlineAdvantageType.java similarity index 90% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAdvantageType.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/StartlineAdvantageType.java index 3e0f4a50ea2..bf6b953b85f 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/startanalysis/StartlineAdvantageType.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/StartlineAdvantageType.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.client.startanalysis; +package com.sap.sailing.dashboards.gwt.shared; import com.sap.sailing.domain.common.LegType; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/DashboardAction.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/DashboardAction.java new file mode 100644 index 00000000000..b9cc7b6ea50 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/DashboardAction.java @@ -0,0 +1,17 @@ +package com.sap.sailing.dashboards.gwt.shared.dispatch; + +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.gwt.dispatch.client.Action; +import com.sap.sailing.gwt.dispatch.client.Result; +import com.sap.sailing.gwt.dispatch.client.exceptions.DispatchException; + +/** + * @author Alexander Ries (D062114) + * + */ +public interface DashboardAction extends Action { + + @GwtIncompatible + R execute(DashboardDispatchContext dashboardDispatchContext) throws DispatchException; + +} \ No newline at end of file diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/DashboardDispatchContext.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/DashboardDispatchContext.java new file mode 100644 index 00000000000..ef159975ee2 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/DashboardDispatchContext.java @@ -0,0 +1,39 @@ +package com.sap.sailing.dashboards.gwt.shared.dispatch; + +import java.util.Date; +import java.util.Locale; + +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.dashboards.gwt.shared.DashboardLiveRaceProvider; +import com.sap.sailing.dashboards.gwt.shared.MovingAveragesCache; +import com.sap.sailing.domain.polars.PolarDataService; +import com.sap.sailing.gwt.dispatch.client.DispatchContext; +import com.sap.sailing.server.RacingEventService; + +/** + * @author Alexander Ries (D062114) + * + */ +public interface DashboardDispatchContext extends DispatchContext { + + @GwtIncompatible + RacingEventService getRacingEventService(); + + @GwtIncompatible + PolarDataService getPolarDataService(); + + @GwtIncompatible + DashboardLiveRaceProvider getDashboardLiveRaceProvider(); + + @GwtIncompatible + MovingAveragesCache getMovingAveragesCache(); + + @GwtIncompatible + String getClientLocaleName(); + + @GwtIncompatible + Locale getClientLocale(); + + @GwtIncompatible + Date getCurrentClientTime(); +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/DashboardDispatchSystem.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/DashboardDispatchSystem.java new file mode 100644 index 00000000000..4902a45e242 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/DashboardDispatchSystem.java @@ -0,0 +1,11 @@ +package com.sap.sailing.dashboards.gwt.shared.dispatch; + +import com.sap.sailing.gwt.dispatch.client.impl.DispatchSystem; + +/** + * @author Alexander Ries (D062114) + * + */ +public interface DashboardDispatchSystem extends DispatchSystem{ + +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/RequiresLiveRaceAction.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/RequiresLiveRaceAction.java new file mode 100644 index 00000000000..1c84c01a309 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/RequiresLiveRaceAction.java @@ -0,0 +1,30 @@ +package com.sap.sailing.dashboards.gwt.shared.dispatch; + +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.gwt.dispatch.client.Result; + +/** + * @author Alexander Ries (D062114) + * + */ +public abstract class RequiresLiveRaceAction implements DashboardAction{ + + private String leaderboardName; + + public RequiresLiveRaceAction() {} + + public RequiresLiveRaceAction(String leaderboardName) { + this.leaderboardName = leaderboardName; + } + + @GwtIncompatible + protected TrackedRace getLiveRace(DashboardDispatchContext dashboardDispatchContext) { + dashboardDispatchContext.getDashboardLiveRaceProvider().validateLiveRaceForLeaderboardName(this.leaderboardName); + return dashboardDispatchContext.getDashboardLiveRaceProvider().getLiveRaceForLeaderboardName(this.leaderboardName); + } + + protected String getLeaderboardName() { + return this.leaderboardName; + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/RequiresLiveRaceAndCachesMovingAverageAction.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/RequiresLiveRaceAndCachesMovingAverageAction.java new file mode 100644 index 00000000000..2c9907ccf49 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/RequiresLiveRaceAndCachesMovingAverageAction.java @@ -0,0 +1,30 @@ +package com.sap.sailing.dashboards.gwt.shared.dispatch; + +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.dashboards.gwt.shared.MovingAveragesCache; +import com.sap.sailing.gwt.dispatch.client.Result; + +/** + * @author Alexander Ries (D062114) + * + */ +public abstract class RequiresLiveRaceAndCachesMovingAverageAction extends RequiresLiveRaceAction{ + + private static final int MOVING_AVERAGE_CACHE_SIZE = (60/5)*15;/*15 minutes in seconds divided by request intervall*/ + + public RequiresLiveRaceAndCachesMovingAverageAction() {} + + public RequiresLiveRaceAndCachesMovingAverageAction(String leaderboardName) { + super(leaderboardName); + } + + @GwtIncompatible + protected void addValueToMovingAverage(double value, MovingAveragesCache movingAveragesCache) { + if(!movingAveragesCache.containesCacheWithKey(getKeyForMovingAverage())) { + movingAveragesCache.createMovingAverageWithKeyAndSize(getKeyForMovingAverage(), MOVING_AVERAGE_CACHE_SIZE); + } + movingAveragesCache.addValueToAverageWithKey(getKeyForMovingAverage(), value); + } + + protected abstract String getKeyForMovingAverage(); +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/impl/DashboardDispatchContextImpl.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/impl/DashboardDispatchContextImpl.java new file mode 100644 index 00000000000..706f0133c3a --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/impl/DashboardDispatchContextImpl.java @@ -0,0 +1,86 @@ +package com.sap.sailing.dashboards.gwt.shared.dispatch.impl; + +import java.net.URL; +import java.util.Date; +import java.util.Locale; + +import javax.servlet.http.HttpServletRequest; + +import com.sap.sailing.dashboards.gwt.shared.DashboardLiveRaceProvider; +import com.sap.sailing.dashboards.gwt.shared.MovingAveragesCache; +import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardDispatchContext; +import com.sap.sailing.domain.polars.PolarDataService; +import com.sap.sailing.gwt.dispatch.client.exceptions.DispatchException; +import com.sap.sailing.gwt.server.HomeServiceUtil; +import com.sap.sailing.server.RacingEventService; + +/** + * @author Alexander Ries (D062114) + * + */ +public class DashboardDispatchContextImpl implements DashboardDispatchContext{ + private final RacingEventService racingEventService; + private final PolarDataService polarDataService; + private final DashboardLiveRaceProvider dashboardLiveRaceProvider; + private final MovingAveragesCache movingAveragesCache; + private final Date currentClientTime; + private String clientLocaleName; + private final HttpServletRequest request; + + public DashboardDispatchContextImpl(Date currentClientTime, RacingEventService racingEventService, + PolarDataService polarDataService, DashboardLiveRaceProvider dashboardLiveRaceProvider, + MovingAveragesCache movingAveragesCache, String clientLocaleName, HttpServletRequest request) { + this.currentClientTime = currentClientTime; + this.racingEventService = racingEventService; + this.polarDataService = polarDataService; + this.dashboardLiveRaceProvider = dashboardLiveRaceProvider; + this.movingAveragesCache = movingAveragesCache; + this.clientLocaleName = clientLocaleName; + this.request = request; + } + + @Override + public RacingEventService getRacingEventService() { + return racingEventService; + } + + @Override + public PolarDataService getPolarDataService() { + return polarDataService; + } + + @Override + public DashboardLiveRaceProvider getDashboardLiveRaceProvider() { + return dashboardLiveRaceProvider; + } + + @Override + public Date getCurrentClientTime() { + return currentClientTime; + } + + @Override + public String getClientLocaleName() { + return clientLocaleName; + } + + @Override + public Locale getClientLocale() { + return Locale.forLanguageTag(clientLocaleName); + } + + @Override + public HttpServletRequest getRequest() { + return request; + } + + @Override + public URL getRequestBaseURL() throws DispatchException { + return HomeServiceUtil.getRequestBaseURL(request); + } + + @Override + public MovingAveragesCache getMovingAveragesCache() { + return movingAveragesCache; + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/impl/DashboardDispatchSystemImpl.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/impl/DashboardDispatchSystemImpl.java new file mode 100644 index 00000000000..67bdb57fdb3 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dispatch/impl/DashboardDispatchSystemImpl.java @@ -0,0 +1,13 @@ +package com.sap.sailing.dashboards.gwt.shared.dispatch.impl; + +import com.sap.sailing.dashboards.gwt.client.RemoteServiceMappingConstants; +import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardDispatchContext; +import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardDispatchSystem; +import com.sap.sailing.gwt.dispatch.client.impl.DispatchSystemImpl; + +public class DashboardDispatchSystemImpl extends DispatchSystemImpl implements DashboardDispatchSystem { + + public DashboardDispatchSystemImpl() { + super(RemoteServiceMappingConstants.ribdashboardDispatchServiceRemotePath, true); + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/AverageDTO.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/AverageDTO.java index 540ccffa8a1..df20cc4a3a5 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/AverageDTO.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/AverageDTO.java @@ -1,15 +1,15 @@ package com.sap.sailing.dashboards.gwt.shared.dto; -import com.google.gwt.user.client.rpc.IsSerializable; +import com.sap.sailing.gwt.dispatch.client.Result; /** * @author Alexander Ries (D062114) * */ -public class AverageDTO implements IsSerializable { +public class AverageDTO implements Result { - public double average; - public double collectingTimeFrameInMilliseconds; + public Double average; + public Double collectingTimeFrameInMilliseconds; public AverageDTO(){} } diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/LeaderboardCompetitorsDTO.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/LeaderboardCompetitorsDTO.java new file mode 100644 index 00000000000..3becb6c12e5 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/LeaderboardCompetitorsDTO.java @@ -0,0 +1,25 @@ +package com.sap.sailing.dashboards.gwt.shared.dto; + +import java.util.List; + +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.gwt.dispatch.client.Result; + +/** + * @author Alexander Ries (D062114) + * + */ +public class LeaderboardCompetitorsDTO implements Result { + + private List competitors; + + public LeaderboardCompetitorsDTO() {} + + public List getCompetitors() { + return competitors; + } + + public void setCompetitors(List competitors) { + this.competitors = competitors; + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/RaceIdDTO.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/RaceIdDTO.java new file mode 100644 index 00000000000..ee100009561 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/RaceIdDTO.java @@ -0,0 +1,23 @@ +package com.sap.sailing.dashboards.gwt.shared.dto; + +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.gwt.dispatch.client.Result; + +/** + * @author Alexander Ries (D062114) + * + */ +public class RaceIdDTO implements Result { + + private RegattaAndRaceIdentifier raceId; + + public RaceIdDTO() {} + + public RegattaAndRaceIdentifier getRaceId() { + return raceId; + } + + public void setRaceId(RegattaAndRaceIdentifier raceId) { + this.raceId = raceId; + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/RibDashboardRaceInfoDTO.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/RibDashboardRaceInfoDTO.java deleted file mode 100644 index bbc4c4039d7..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/RibDashboardRaceInfoDTO.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * - */ -package com.sap.sailing.dashboards.gwt.shared.dto; - -import java.io.Serializable; -import java.util.List; -import java.util.Map; - -import com.sap.sailing.dashboards.gwt.shared.ResponseMessage; -import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; - -/** - * @author Alexander Ries - * - */ -public class RibDashboardRaceInfoDTO implements Serializable{ - - private static final long serialVersionUID = -1325150193180234561L; - - public Map windBotDTOForID; - public StartLineAdvantageDTO startLineAdvantageDTO; - public List startAnalysisDTOList; - public List competitorNamesFromLastTrackedRace; - public RegattaAndRaceIdentifier idOfLastTrackedRace; - public ResponseMessage responseMessage; - - public RibDashboardRaceInfoDTO() {} - -} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartAnalysesDTO.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartAnalysesDTO.java new file mode 100644 index 00000000000..467b7463b36 --- /dev/null +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartAnalysesDTO.java @@ -0,0 +1,24 @@ +package com.sap.sailing.dashboards.gwt.shared.dto; + +import java.util.List; + +import com.sap.sailing.gwt.dispatch.client.Result; + +/** + * @author Alexander Ries (D062114) + * + */ +public class StartAnalysesDTO implements Result { + + private List startAnalyses; + + public StartAnalysesDTO() {} + + public List getStartAnalyses() { + return startAnalyses; + } + + public void setStartAnalyses(List startAnalyses) { + this.startAnalyses = startAnalyses; + } +} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/startanalysis/StartAnalysisCompetitorDTO.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartAnalysisCompetitorDTO.java similarity index 84% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/startanalysis/StartAnalysisCompetitorDTO.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartAnalysisCompetitorDTO.java index 2f56496ee13..42c2acd9715 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/startanalysis/StartAnalysisCompetitorDTO.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartAnalysisCompetitorDTO.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.shared.dto.startanalysis; +package com.sap.sailing.dashboards.gwt.shared.dto; import java.io.Serializable; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/startanalysis/StartAnalysisDTO.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartAnalysisDTO.java similarity index 95% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/startanalysis/StartAnalysisDTO.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartAnalysisDTO.java index a58757c4a94..40f25217439 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/startanalysis/StartAnalysisDTO.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartAnalysisDTO.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.shared.dto.startanalysis; +package com.sap.sailing.dashboards.gwt.shared.dto; import java.io.Serializable; import java.util.ArrayList; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/startanalysis/StartAnalysisRankingTableEntryDTO.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartAnalysisRankingTableEntryDTO.java similarity index 84% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/startanalysis/StartAnalysisRankingTableEntryDTO.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartAnalysisRankingTableEntryDTO.java index b29deee67a7..eb788b0cab2 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/startanalysis/StartAnalysisRankingTableEntryDTO.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartAnalysisRankingTableEntryDTO.java @@ -1,4 +1,4 @@ -package com.sap.sailing.dashboards.gwt.shared.dto.startanalysis; +package com.sap.sailing.dashboards.gwt.shared.dto; import java.io.Serializable; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartLineAdvantageDTO.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartLineAdvantageDTO.java index 9d2c7fe26f2..c77a1efab80 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartLineAdvantageDTO.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartLineAdvantageDTO.java @@ -2,15 +2,15 @@ package com.sap.sailing.dashboards.gwt.shared.dto; import java.util.Comparator; -import com.google.gwt.user.client.rpc.IsSerializable; -import com.sap.sailing.dashboards.gwt.client.startanalysis.StartlineAdvantageType; +import com.sap.sailing.dashboards.gwt.shared.StartlineAdvantageType; +import com.sap.sailing.gwt.dispatch.client.Result; -public class StartLineAdvantageDTO extends AverageDTO implements IsSerializable { +public class StartLineAdvantageDTO extends AverageDTO implements Result { - public double startLineAdvantage; - public double distanceToRCBoatInMeters; + public Double startLineAdvantage; + public Double distanceToRCBoatInMeters; public StartlineAdvantageType startLineAdvatageType; - public double confidence; + public Double confidence; public StartLineAdvantageDTO(){} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartlineAdvantagesWithMaxAndAverageDTO.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartlineAdvantagesWithMaxAndAverageDTO.java index 58c74a7973d..52ab09d94ba 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartlineAdvantagesWithMaxAndAverageDTO.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/StartlineAdvantagesWithMaxAndAverageDTO.java @@ -1,17 +1,16 @@ package com.sap.sailing.dashboards.gwt.shared.dto; -import java.util.List; - -import com.google.gwt.user.client.rpc.IsSerializable; +import com.sap.sailing.gwt.dispatch.client.Result; /** * @author Alexander Ries (D062114) * */ -public class StartlineAdvantagesWithMaxAndAverageDTO extends AverageDTO implements IsSerializable { +public class StartlineAdvantagesWithMaxAndAverageDTO extends AverageDTO implements Result { - public double maximum; - public List advantages; + public Double maximum; + public Number [][] distanceToRCBoatToStartlineAdvantage; + public Number [][] distanceToRCBoatToConfidence; public StartlineAdvantagesWithMaxAndAverageDTO(){} } diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/startanalysis/WindAndAdvantagesInfoForStartLineDTO.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/WindAndAdvantagesInfoForStartLineDTO.java similarity index 69% rename from java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/startanalysis/WindAndAdvantagesInfoForStartLineDTO.java rename to java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/WindAndAdvantagesInfoForStartLineDTO.java index 4243fded2f4..c806405269b 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/startanalysis/WindAndAdvantagesInfoForStartLineDTO.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/WindAndAdvantagesInfoForStartLineDTO.java @@ -1,9 +1,7 @@ -package com.sap.sailing.dashboards.gwt.shared.dto.startanalysis; +package com.sap.sailing.dashboards.gwt.shared.dto; import java.io.Serializable; -import com.sap.sailing.dashboards.gwt.shared.dto.StartLineAdvantageDTO; - public class WindAndAdvantagesInfoForStartLineDTO implements Serializable{ private static final long serialVersionUID = -1325150193180234561L; diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/WindBotComponentDTO.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/WindBotComponentDTO.java deleted file mode 100644 index 897ebec43f2..00000000000 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/shared/dto/WindBotComponentDTO.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.sap.sailing.dashboards.gwt.shared.dto; - -import java.io.Serializable; - -import com.sap.sailing.domain.common.Position; - -public class WindBotComponentDTO implements Serializable{ - - private static final long serialVersionUID = 7552196781982269601L; - - public String id; - public double liveWindSpeedInKts; - public double liveWindDirectionInDegrees; - public double averageWindSpeedInKts; - public double averageWindDirectionInDegrees; - public Position position; - - public WindBotComponentDTO() {} -} diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/RibDashboard.gwt.xml b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/RibDashboard.gwt.xml index 0c676581a97..8dfe25e1e71 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/RibDashboard.gwt.xml +++ b/java/com.sap.sailing.dashboards.gwt/src/main/resources/com/sap/sailing/dashboards/gwt/RibDashboard.gwt.xml @@ -43,8 +43,7 @@ - + @@ -53,11 +52,13 @@ - + + + + diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties index cf29e983595..e8ec0210da2 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties @@ -57,4 +57,7 @@ RankGainsOrLosses=Positions-Verluste oder -Gewinne DistanceToStarboardSideAtStart=Distanz zur Steuerbordseite (% der Länge) NumberOfPodiumFinish=Azahl der Podium-Siege NumberOfWins=Anzahl der Siege -RankGainsOrLossesBetweenFirstMarkAndFinish=Positions-Verluste oder -Gewinne (1ter Wegpunkt -> Ziel) \ No newline at end of file +RankGainsOrLossesBetweenFirstMarkAndFinish=Positions-Verluste oder -Gewinne (1ter Wegpunkt -> Ziel) +IsTracked=Ist getracked +NumberOfCompetitorFixes=Anzahl der Teilnehmer-Fixes +NumberOfMarkFixes=Anzahl der Tonnen-Fixes \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_en.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_en.properties index 5fc160d042a..4dbc578b18f 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_en.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_en.properties @@ -57,4 +57,7 @@ RankGainsOrLosses=Rank Gains or Losses DistanceToStarboardSideAtStart=Distance to Starboard Side (% of Length) NumberOfPodiumFinish=Number of Podium Finishes NumberOfWins=Number of Wins -RankGainsOrLossesBetweenFirstMarkAndFinish=Rank Gains or Losses (1st Mark -> Finish) \ No newline at end of file +RankGainsOrLossesBetweenFirstMarkAndFinish=Rank Gains or Losses (1st Mark -> Finish) +IsTracked=Is Tracked +NumberOfCompetitorFixes=Number of Competitor Fixes +NumberOfMarkFixes=Number of Mark Fixes \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrievalChainDefinitions.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrievalChainDefinitions.java index 6c1dc531dc7..6e5345237db 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrievalChainDefinitions.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrievalChainDefinitions.java @@ -47,6 +47,7 @@ public class SailingDataRetrievalChainDefinitions { leaderboardRetrieverChainDefinition, HasTrackedRaceContext.class, "RaceSailingDomainRetrieverChain"); trackedRaceRetrieverChainDefinition.endWith(LeaderboardRetrievalProcessor.class, TrackedRaceRetrievalProcessor.class, HasTrackedRaceContext.class, "Race"); + dataRetrieverChainDefinitions.add(trackedRaceRetrieverChainDefinition); final DataRetrieverChainDefinition markPassingRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( trackedRaceRetrieverChainDefinition, HasMarkPassingContext.class, "MarkPassingSailingDomainRetrieverChain"); diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasLeaderboardContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasLeaderboardContext.java index e072aced084..e5b6215a193 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasLeaderboardContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasLeaderboardContext.java @@ -2,11 +2,13 @@ package com.sap.sailing.datamining.data; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.polars.PolarDataService; +import com.sap.sse.datamining.annotations.Dimension; public interface HasLeaderboardContext { - Leaderboard getLeaderboard(); PolarDataService getPolarDataService(); + @Dimension(messageKey="Leaderboard") + String getName(); } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedRaceContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedRaceContext.java index 2fc0d426eff..aed8f28d024 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedRaceContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedRaceContext.java @@ -8,6 +8,7 @@ import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sse.datamining.annotations.Connector; import com.sap.sse.datamining.annotations.Dimension; +import com.sap.sse.datamining.annotations.Statistic; public interface HasTrackedRaceContext { @@ -34,4 +35,13 @@ public interface HasTrackedRaceContext { @Dimension(messageKey="Year", ordinal=2) public Integer getYear(); + @Dimension(messageKey="IsTracked", ordinal=6) + public Boolean isTracked(); + + @Statistic(messageKey="NumberOfCompetitorFixes", resultDecimals=0, ordinal=0) + public int getNumberOfCompetitorFixes(); + + @Statistic(messageKey="NumberOfMarkFixes", resultDecimals=0, ordinal=1) + public int getNumberOfMarkFixes(); + } \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/LeaderboardWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/LeaderboardWithContext.java index 9f2ced4e8af..ef398fe8c7b 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/LeaderboardWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/LeaderboardWithContext.java @@ -5,7 +5,6 @@ import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.polars.PolarDataService; public class LeaderboardWithContext implements HasLeaderboardContext { - private final Leaderboard leaderboard; private final PolarDataService polarDataService; @@ -19,9 +18,13 @@ public class LeaderboardWithContext implements HasLeaderboardContext { return leaderboard; } - @Override - public PolarDataService getPolarDataService() { - return polarDataService; - } + @Override + public PolarDataService getPolarDataService() { + return polarDataService; + } + @Override + public String getName() { + return getLeaderboard().getName(); + } } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java index 0f996858ab0..d899829ec8d 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java @@ -5,12 +5,19 @@ import java.util.Calendar; import com.sap.sailing.datamining.data.HasLeaderboardContext; import com.sap.sailing.datamining.data.HasTrackedRaceContext; import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.common.tracking.GPSFix; +import com.sap.sailing.domain.common.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.impl.MillisecondsTimePoint; public class TrackedRaceWithContext implements HasTrackedRaceContext { @@ -84,4 +91,39 @@ public class TrackedRaceWithContext implements HasTrackedRaceContext { return calendar.get(Calendar.YEAR); } + @Override + public Boolean isTracked() { + return getTrackedRace().hasStarted(MillisecondsTimePoint.now()); + } + + @Override + public int getNumberOfCompetitorFixes() { + int number = 0; + for (Competitor competitor : getRace().getCompetitors()) { + GPSFixTrack track = getTrackedRace().getTrack(competitor); + track.lockForRead(); + try { + number += Util.size(track.getRawFixes()); + } finally { + track.unlockAfterRead(); + } + } + return number; + } + + @Override + public int getNumberOfMarkFixes() { + int number = 0; + for (Mark mark : getTrackedRace().getMarks()) { + GPSFixTrack track = getTrackedRace().getTrack(mark); + track.lockForRead(); + try { + number += Util.size(track.getRawFixes()); + } finally { + track.unlockAfterRead(); + } + } + return number; + } + } \ No newline at end of file diff --git a/java/com.sap.sailing.domain.common/.settings/org.eclipse.core.resources.prefs b/java/com.sap.sailing.domain.common/.settings/org.eclipse.core.resources.prefs index 5912fe30df5..c111da40d73 100755 --- a/java/com.sap.sailing.domain.common/.settings/org.eclipse.core.resources.prefs +++ b/java/com.sap.sailing.domain.common/.settings/org.eclipse.core.resources.prefs @@ -1,4 +1,5 @@ eclipse.preferences.version=1 encoding//src/com/sap/sailing/domain/common/AbstractBearing.java=UTF-8 +encoding//src/com/sap/sailing/domain/common/confidence/impl/ConfidenceBasedAveragerImpl.java=utf-8 encoding//src/com/sap/sailing/domain/common/impl/AbstractSpeedWithAbstractBearingImpl.java=UTF-8 encoding//src/com/sap/sailing/domain/common/tracking/impl/CompactGPSFixMovingImpl.java=UTF-8 diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Bearing.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Bearing.java index 4508b693b6b..82e8e5660ec 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Bearing.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Bearing.java @@ -2,7 +2,13 @@ package com.sap.sailing.domain.common; import java.io.Serializable; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; + public interface Bearing extends Serializable { + Bearing NORTH = new DegreeBearingImpl(0); + Bearing EAST = new DegreeBearingImpl(90); + Bearing SOUTH = new DegreeBearingImpl(180); + Bearing WEST = new DegreeBearingImpl(270); double getDegrees(); diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/RaceCompetitorIdsAsStringWithMD5Hash.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/RaceCompetitorIdsAsStringWithMD5Hash.java new file mode 100644 index 00000000000..d4073411507 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/RaceCompetitorIdsAsStringWithMD5Hash.java @@ -0,0 +1,105 @@ +package com.sap.sailing.domain.common; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import com.sap.sse.common.Util; + +/** + * Manages the set of competitors participating in a specific race. This is not trivial for a {@link RaceBoardPanel} + * because it is provided with the {@link CompetitorSelectionProvider} that is also used by the {@link LeaderboardPanel} + * and therefore has all competitors of the entire regatta, not just those of the race that the race board is showing. + *

+ * + * The back-end knows the race-specific set of competitors and can tell the client. However, we'd like to keep the + * network traffic required to keep this information up to date at a minimum. The key idea is to use an MD5 hash across + * the ordered race's competitor IDs and send that with each {@link GetRaceMapDataAction} request. The server can then + * compare this hash with the current race's competitor set and send an update if and only if there is a difference + * detected. This will, for the most part, reduce the communication to an initial request by the client as long as the + * race's competitors are not known, and the additional MD5 hash being sent with each request. + *

+ * + * Objects of this type start out with an "unknown" race competitor set. When the + * {@link #getMd5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID() MD5 hash} for the currently + * known race competitors is asked, null will initially be returned. When the client has received the race + * competitors the client is expected to call {@link #setIdsAsStringsOfCompetitorsInRace(Iterable)}. If the argument to + * this call was not null, + * {@link #getMd5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID()} will from then on return + * the MD5 hash of the competitor IDs in order. + * + * @author Axel Uhl (D043530) + * + */ +public class RaceCompetitorIdsAsStringWithMD5Hash implements Serializable { + private static final long serialVersionUID = 6881670366184539259L; + + /** + * The {@link #competitorSelection} manages the competitors for the entire leaderboard which may be a + * superset of the competitors participating in the race shown by this map. For certain operations, + * however, it is useful to know exactly the competitors participating in the race shown by this map. + * For example, boat positions should only be requested from the server for those competitors, not all + * in the regatta, particularly when racing in split fleets.

+ * + * To keep this set consistent, the client sends a secure hash of this set to the server where the hash + * will be compared with the hash across the set of competitor IDs as string.

+ * + * If this field is null, nothing is known yet about the competitor-to-race assignment. + */ + private Set idsAsStringOfCompetitorsParticipatingInRace; + + /** + * The MD5 hash of the {@link #idsAsStringOfCompetitorsParticipatingInRace} where the competitors are first ordered + * alphanumerically by their ID as String using {@link String#compareTo(String)}, then concatenating these strings + * and converting to a byte[] using a UTF-8 encoding. May be null if it hasn't been computed before + * or if {@link #idsAsStringOfCompetitorsParticipatingInRace} is still null. + */ + private byte[] md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID; + + /** + * Starts out with an unknown set of race competitors. The {@link #getIdsOfCompetitorsParticipatingInRaceAsStrings()} + * and the {@link #getMd5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID()} will return + * null until a call to {@link #setIdsAsStringsOfCompetitorsInRace(Iterable)} with a non- + * null argument has been performed. + */ + public RaceCompetitorIdsAsStringWithMD5Hash() { + } + + /** + * Immediately calls {@link #setIdsAsStringsOfCompetitorsInRace(Set)} with the argument provided. If that + * was null then this has the same effect as calling {@link #RaceCompetitorIdsAsStringWithMD5Hash()}. + */ + public RaceCompetitorIdsAsStringWithMD5Hash(Set idsAsStringOfCompetitorsParticipatingInRace) + throws UnsupportedEncodingException, NoSuchAlgorithmException { + this.setIdsAsStringsOfCompetitorsInRace(idsAsStringOfCompetitorsParticipatingInRace); + } + + public void setIdsAsStringsOfCompetitorsInRace(Set idsAsStringsOfCompetitorsInRace) throws UnsupportedEncodingException, NoSuchAlgorithmException { + this.idsAsStringOfCompetitorsParticipatingInRace = idsAsStringsOfCompetitorsInRace; + md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID = computeMD5(idsAsStringsOfCompetitorsInRace); + } + + public byte[] getMd5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID() { + return md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID; + } + + public Set getIdsOfCompetitorsParticipatingInRaceAsStrings() { + return idsAsStringOfCompetitorsParticipatingInRace; + } + + private byte[] computeMD5(Iterable idsAsStringsOfCompetitorsInRace) throws NoSuchAlgorithmException, UnsupportedEncodingException { + List l = new ArrayList<>(); + Util.addAll(idsAsStringsOfCompetitorsInRace, l); + Collections.sort(l); + final MessageDigest md5 = MessageDigest.getInstance("MD5"); + for (String c : l) { + md5.update(c.getBytes("UTF-8")); + } + return md5.digest(); + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/AbstractLeaderboardDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/AbstractLeaderboardDTO.java index 8a9fa9017a9..ff197288789 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/AbstractLeaderboardDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/AbstractLeaderboardDTO.java @@ -1,430 +1,433 @@ -package com.sap.sailing.domain.common.dto; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -import com.sap.sailing.domain.common.LeaderboardType; -import com.sap.sailing.domain.common.RaceIdentifier; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sse.common.Util; - -public abstract class AbstractLeaderboardDTO implements Serializable { - private static final long serialVersionUID = -205106531931903527L; - - public String name; - - private List races; - public Map competitorDisplayNames; - public Map rows; - public boolean hasCarriedPoints; - public int[] discardThresholds; - - /** - * Set to the non-null regatta name if this DTO represents a RegattaLeaderboard. - */ - public String regattaName; - public String displayName; - public UUID defaultCourseAreaId; - public String defaultCourseAreaName; - public ScoringSchemeType scoringScheme; - public LeaderboardType type; - - private Long delayToLiveInMillisForLatestRace; - - public AbstractLeaderboardDTO() { - races = new ArrayList(); - } - - public Set getBoatClasses() { - Set result = new HashSet(); - if(rows != null) { - for (CompetitorDTO competitor : rows.keySet()) { - result.add(competitor.getBoatClass()); - } - } - return result; - } - - public String getDisplayName() { - return displayName; - } - - public String getDisplayName(CompetitorDTO competitor) { - if (competitorDisplayNames == null || competitorDisplayNames.get(competitor) == null) { - return competitor.getName(); - } else { - return competitorDisplayNames.get(competitor); - } - } - - /** - * If the race column whose name is specified in raceColumnName has at least one competitor who has valid - * {@link LeaderboardEntryDTO#legDetails} for that race column, the maximum number of entries of all such competitors in - * the leg details is returned, telling the number of legs that the race column shall display. Otherwise, -1 is returned. - * If you specify an non null preselectedRace the leg count is calculated only for that race for the column - * holding that race. All other columns will be unaffected by preselectedRace and will return their regular - * leg count. Note that this may lead to a situation where some competitors may have sailed more legs than others because - * their fleet association may have varied across races.

- * - * See also bug 2604 and bug 2035. - */ - public int getLegCount(String raceColumnName, RaceIdentifier preselectedRace) { - int result = -1; - final boolean preselectedRaceIsInRaceColumn = isRaceInColumn(preselectedRace, raceColumnName); - for (LeaderboardRowDTO row : rows.values()) { - LeaderboardEntryDTO leaderboardEntryDTO = row.fieldsByRaceColumnName.get(raceColumnName); - if (leaderboardEntryDTO != null && leaderboardEntryDTO.legDetails != null - // when no race is pre-selected, always use the actual leg count; - // otherwise, if the pre-selected race is in the column identified by - // raceColumnName, use the entry only if it's in the pre-selected race; - // otherwise (a race is pre-selected but a different column's leg count - // is requested), use the regular leg count as is. - && (preselectedRace == null || !preselectedRaceIsInRaceColumn || preselectedRace.equals(leaderboardEntryDTO.race))) { - result = Math.max(result, leaderboardEntryDTO.legDetails.size()); - } - } - return result; - } - - private boolean isRaceInColumn(RaceIdentifier preselectedRace, String raceColumnName) { - RaceColumnDTO raceColumn = getRaceColumnByName(raceColumnName); - return raceColumn.containsRace(preselectedRace); - } - - /** - * Tells if the competitor scored (and therefore presumably participated) in a medal race represented - * in this leaderboard. - */ - public boolean scoredInMedalRace(CompetitorDTO competitor) { - LeaderboardRowDTO row = rows.get(competitor); - for (RaceColumnDTO race : races) { - if (race.isMedalRace() && row.fieldsByRaceColumnName.get(race.getRaceColumnName()).totalPoints > 0) { - return true; - } - } - return false; - } - - public Double getNetPoints(CompetitorDTO competitor, String nameOfLastRaceSoFar) { - Double netPoints = null; - LeaderboardRowDTO row = rows.get(competitor); - if (row != null) { - LeaderboardEntryDTO field = row.fieldsByRaceColumnName.get(nameOfLastRaceSoFar); - if (field != null && field.netPoints != null) { - netPoints = field.netPoints; - } - } - return netPoints; - } - - public boolean raceIsTracked(String raceColumnName) { - for (RaceColumnDTO race : races) { - if (race.getRaceColumnName().equals(raceColumnName)) { - for (FleetDTO fleet : race.getFleets()) { - if (race.isTrackedRace(fleet)) { - return true; - } - } - } - } - return false; - - } - - public boolean raceIsMedalRace(String raceColumnName) { - return getRaceColumnByName(raceColumnName).isMedalRace(); - } - - /** - * If the {@link RaceColumnDTO} by the name raceColumnName doesn't exist yet within this leaderboard - * DTO, it is created, setting is {@link RaceColumnDTO#isValidInTotalScore()} to true. This method - * ensures that a fleet named fleetName is present. If it's not present yet, it's added to the race - * column's fleet name list. The trackedRaceIdentifier and race are associated with the - * column for the fleet identified by fleetName. - * - * @param explicitFactor - * factor by which to multiply the race column's points for the overall score; if null, the - * default will be determined by whether or not the column is marked as medal race - * @param effectiveFactor - * is what you get when you call {@link RaceColumn#getFactor()} on the race column that the resulting - * {@link RaceColumnDTO} represents - * @param regattaName - * must not be null if seriesName is not null; specified - * separately from trackedRaceIdentifier because a column may belong to regatta/series - * despite not having a tracked race associated - * @param seriesName - * if null, this method will produce a {@link RaceColumnDTO}, otherwise a - * {@link RaceColumnInSeriesDTO} - * @param fleetDTO - * must not be null - */ - public RaceColumnDTO addRace(String raceColumnName, Double explicitFactor, double effectiveFactor, - String regattaName, String seriesName, FleetDTO fleetDTO, boolean medalRace, RegattaAndRaceIdentifier trackedRaceIdentifier, RaceDTO race) { - assert fleetDTO != null; - RaceColumnDTO raceColumnDTO = getRaceColumnByName(raceColumnName); - if (raceColumnDTO == null) { - raceColumnDTO = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName, medalRace, - explicitFactor, regattaName, seriesName); - races.add(raceColumnDTO); - } - raceColumnDTO.setEffectiveFactor(effectiveFactor); - boolean contains = false; - for (FleetDTO fleet : raceColumnDTO.getFleets()) { - if (fleet.getName().equals(fleetDTO.getName())) { - contains = true; - break; - } - } - if (!contains) { - raceColumnDTO.addFleet(fleetDTO); - } - raceColumnDTO.setRaceIdentifier(fleetDTO, trackedRaceIdentifier); - raceColumnDTO.setRace(fleetDTO, race); - return raceColumnDTO; - } - - public RaceColumnDTO createEmptyRaceColumn(String raceColumnName, boolean medalRace, String regattaName, String seriesName) { - final RaceColumnDTO raceColumn = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName, - medalRace, /* explicit factor */ null, regattaName, seriesName); - races.add(raceColumn); - return raceColumn; - } - - public RaceColumnDTO getRaceColumnByName(String raceColumnName) { - for (RaceColumnDTO race : races) { - if (race.getRaceColumnName().equals(raceColumnName)) { - return race; - } - } - return null; - } - - public int getRaceColumnsCount() { - return races.size(); - } - - public int getRacesCount() { - int result = 0; - for (RaceColumnDTO race : getRaceList()) { - result += race.getFleets().size(); - } - return result; - } - - public int getTrackedRacesCount() { - int result = 0; - for (RaceColumnDTO raceColumn : getRaceList()) { - for (FleetDTO fleet : raceColumn.getFleets()) { - RaceDTO race = raceColumn.getRace(fleet); - if(race != null && race.trackedRace != null && race.trackedRace.hasGPSData && race.trackedRace.hasWindData) { - result++; - } - } - } - return result; - } - - public List getRaceList() { - return races; - } - - public void setRaceList(List raceList) { - this.races = raceList; - } - - public boolean raceListContains(String raceColumnName) { - return getRaceColumnByName(raceColumnName) != null; - } - - public void moveRaceUp(String raceColumnName) { - RaceColumnDTO race = getRaceColumnByName(raceColumnName); - int index = races.indexOf(race); - index--; - if (index >= 0) { - races.remove(index + 1); - races.add(index, race); - } - } - - public void moveRaceDown(String raceColumnName) { - RaceColumnDTO race = getRaceColumnByName(raceColumnName); - int index = races.indexOf(race); - if (index != -1) { - index++; - if (index < races.size()) { - races.remove(index - 1); - races.add(index, race); - } - } - } - - public void setIsMedalRace(String raceColumnName, boolean medalRace) { - getRaceColumnByName(raceColumnName).setMedalRace(medalRace); - } - - /** - * @return The earliest start date of the races, or null if no start dates of the races are available. - */ - public Date getStartDate() { - Date leaderboardStart = null; - for (RaceColumnDTO race : getRaceList()) { - for (FleetDTO fleet: race.getFleets()) { - Date raceStart = race.getStartDate(fleet); - if (raceStart != null) { - if (leaderboardStart == null) { - leaderboardStart = new Date(); - } else { - leaderboardStart = leaderboardStart.before(raceStart) ? leaderboardStart : raceStart; - } - } - } - } - return leaderboardStart; - } - - /** - * Takes the {@link PlacemarkOrderDTO} of all races in this leaderboard, if the PlacemarkOrderDTO for the race is - * available, and fills all {@link PlacemarkDTO} in a new PlacemarkOrderDTO.
- * The order of the races in this leaderboard determine the order of the PlacemarkDTOs in the PlacemarkOrderDTO. - * - * @return The places of this leaderboard in form of a {@link PlacemarkOrderDTO}, or null if the - * {@link PlacemarkOrderDTO places} of no race are available - */ - public PlacemarkOrderDTO getPlaces() { - PlacemarkOrderDTO leaderboardPlaces = null; - for (RaceColumnDTO race : getRaceList()) { - PlacemarkOrderDTO racePlaces = race.getPlaces(); - if (racePlaces != null) { - if (leaderboardPlaces == null) { - leaderboardPlaces = new PlacemarkOrderDTO(); - } - leaderboardPlaces.getPlacemarks().addAll(racePlaces.getPlacemarks()); - } - } - return leaderboardPlaces; - } - - /** - * @return true if the leaderboard contains a race which is live - */ - public boolean hasLiveRace(long serverTimePointAsMillis) { - for (RaceColumnDTO race : getRaceList()) { - for (FleetDTO fleet : race.getFleets()) { - if (race.isLive(fleet, serverTimePointAsMillis)) { - return true; - } - } - } - return false; - } - - public List> getLiveRaces(long serverTimePointAsMillis) { - List> result = new ArrayList>(); - for (RaceColumnDTO race : getRaceList()) { - for (FleetDTO fleet : race.getFleets()) { - if (race.isLive(fleet, serverTimePointAsMillis)) { - result.add(new Util.Pair(race, fleet)); - } - } - } - return result; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((competitorDisplayNames == null) ? 0 : competitorDisplayNames.hashCode()); - result = prime * result + Arrays.hashCode(discardThresholds); - result = prime * result + (hasCarriedPoints ? 1231 : 1237); - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((scoringScheme == null) ? 0 : scoringScheme.hashCode()); - if (races == null) { - result = prime * result; - } else { - List raceNames = new ArrayList(); - for (RaceColumnDTO race : races) { - raceNames.add(race.getName()); - } - result = prime * result + raceNames.hashCode(); - } - result = prime * result + ((rows == null) ? 0 : rows.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - AbstractLeaderboardDTO other = (AbstractLeaderboardDTO) obj; - if (competitorDisplayNames == null) { - if (other.competitorDisplayNames != null) - return false; - } else if (!competitorDisplayNames.equals(other.competitorDisplayNames)) - return false; - if (!Arrays.equals(discardThresholds, other.discardThresholds)) - return false; - if (hasCarriedPoints != other.hasCarriedPoints) - return false; - if (type != other.type) - return false; - if (scoringScheme != other.scoringScheme) - return false; - if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; - if (races == null) { - if (other.races != null) - return false; - } else { - // compare race column names only, not the contents - if (races.size() != (other.races==null?0:other.races.size())) { - return false; - } - List raceColumnNames = new ArrayList(races.size()); - List otherRaceColumnNames = new ArrayList(races.size()); - for (RaceColumnDTO race : races) { - raceColumnNames.add(race.getName()); - } - if (other.races != null) { - for (RaceColumnDTO otherRace : other.races) { - otherRaceColumnNames.add(otherRace.getName()); - } - } - if (!raceColumnNames.equals(otherRaceColumnNames)) - return false; - if (rows == null) { - if (other.rows != null) - return false; - } else if (!rows.equals(other.rows)) - return false; - } - return true; - } - - public boolean isDisplayNameSet(CompetitorDTO competitor) { - return competitorDisplayNames.get(competitor) != null; - } - - public Long getDelayToLiveInMillisForLatestRace() { - return delayToLiveInMillisForLatestRace; - } - - public void setDelayToLiveInMillisForLatestRace(Long delayToLiveInMillisForLatestRace) { - this.delayToLiveInMillisForLatestRace = delayToLiveInMillisForLatestRace; - } -} +package com.sap.sailing.domain.common.dto; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import com.sap.sailing.domain.common.LeaderboardType; +import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sse.common.Util; + +public abstract class AbstractLeaderboardDTO implements Serializable { + private static final long serialVersionUID = -205106531931903527L; + + public String name; + + private List races; + public Map competitorDisplayNames; + public Map rows; + public boolean hasCarriedPoints; + public int[] discardThresholds; + + /** + * Set to the non-null regatta name if this DTO represents a RegattaLeaderboard. + */ + public String regattaName; + public String displayName; + public UUID defaultCourseAreaId; + public String defaultCourseAreaName; + public ScoringSchemeType scoringScheme; + public LeaderboardType type; + + private Long delayToLiveInMillisForLatestRace; + + public AbstractLeaderboardDTO() { + races = new ArrayList(); + } + + public Set getBoatClasses() { + Set result = new HashSet(); + if(rows != null) { + for (CompetitorDTO competitor : rows.keySet()) { + result.add(competitor.getBoatClass()); + } + } + return result; + } + + public String getDisplayName() { + return displayName; + } + + public String getDisplayName(CompetitorDTO competitor) { + if (competitorDisplayNames == null || competitorDisplayNames.get(competitor) == null) { + return competitor.getName(); + } else { + return competitorDisplayNames.get(competitor); + } + } + + /** + * If the race column whose name is specified in raceColumnName has at least one competitor who has valid + * {@link LeaderboardEntryDTO#legDetails} for that race column, the maximum number of entries of all such competitors in + * the leg details is returned, telling the number of legs that the race column shall display. Otherwise, -1 is returned. + * If you specify an non null preselectedRace the leg count is calculated only for that race for the column + * holding that race. All other columns will be unaffected by preselectedRace and will return their regular + * leg count. Note that this may lead to a situation where some competitors may have sailed more legs than others because + * their fleet association may have varied across races.

+ * + * See also bug 2604 and bug 2035. + */ + public int getLegCount(String raceColumnName, RaceIdentifier preselectedRace) { + int result = -1; + final boolean preselectedRaceIsInRaceColumn = isRaceInColumn(preselectedRace, raceColumnName); + for (LeaderboardRowDTO row : rows.values()) { + LeaderboardEntryDTO leaderboardEntryDTO = row.fieldsByRaceColumnName.get(raceColumnName); + if (leaderboardEntryDTO != null && leaderboardEntryDTO.legDetails != null + // when no race is pre-selected, always use the actual leg count; + // otherwise, if the pre-selected race is in the column identified by + // raceColumnName, use the entry only if it's in the pre-selected race; + // otherwise (a race is pre-selected but a different column's leg count + // is requested), use the regular leg count as is. + && (preselectedRace == null || !preselectedRaceIsInRaceColumn || preselectedRace.equals(leaderboardEntryDTO.race))) { + result = Math.max(result, leaderboardEntryDTO.legDetails.size()); + } + } + return result; + } + + private boolean isRaceInColumn(RaceIdentifier preselectedRace, String raceColumnName) { + RaceColumnDTO raceColumn = getRaceColumnByName(raceColumnName); + return raceColumn.containsRace(preselectedRace); + } + + /** + * Tells if the competitor scored (and therefore presumably participated) in a medal race represented + * in this leaderboard. + */ + public boolean scoredInMedalRace(CompetitorDTO competitor) { + LeaderboardRowDTO row = rows.get(competitor); + for (RaceColumnDTO race : races) { + if (race.isMedalRace() && row.fieldsByRaceColumnName.get(race.getRaceColumnName()).totalPoints > 0) { + return true; + } + } + return false; + } + + public Double getNetPoints(CompetitorDTO competitor, String nameOfLastRaceSoFar) { + Double netPoints = null; + LeaderboardRowDTO row = rows.get(competitor); + if (row != null) { + LeaderboardEntryDTO field = row.fieldsByRaceColumnName.get(nameOfLastRaceSoFar); + if (field != null && field.netPoints != null) { + netPoints = field.netPoints; + } + } + return netPoints; + } + + public boolean raceIsTracked(String raceColumnName) { + for (RaceColumnDTO race : races) { + if (race.getRaceColumnName().equals(raceColumnName)) { + for (FleetDTO fleet : race.getFleets()) { + if (race.isTrackedRace(fleet)) { + return true; + } + } + } + } + return false; + + } + + public boolean raceIsMedalRace(String raceColumnName) { + return getRaceColumnByName(raceColumnName).isMedalRace(); + } + + /** + * If the {@link RaceColumnDTO} by the name raceColumnName doesn't exist yet within this leaderboard + * DTO, it is created, setting is {@link RaceColumnDTO#isValidInTotalScore()} to true. This method + * ensures that a fleet named fleetName is present. If it's not present yet, it's added to the race + * column's fleet name list. The trackedRaceIdentifier and race are associated with the + * column for the fleet identified by fleetName. + * + * @param explicitFactor + * factor by which to multiply the race column's points for the overall score; if null, the + * default will be determined by whether or not the column is marked as medal race + * @param effectiveFactor + * is what you get when you call {@link RaceColumn#getFactor()} on the race column that the resulting + * {@link RaceColumnDTO} represents + * @param regattaName + * must not be null if seriesName is not null; specified + * separately from trackedRaceIdentifier because a column may belong to regatta/series + * despite not having a tracked race associated + * @param seriesName + * if null, this method will produce a {@link RaceColumnDTO}, otherwise a + * {@link RaceColumnInSeriesDTO} + * @param fleetDTO + * must not be null + */ + public RaceColumnDTO addRace(String raceColumnName, Double explicitFactor, double effectiveFactor, + String regattaName, String seriesName, FleetDTO fleetDTO, boolean medalRace, + RegattaAndRaceIdentifier trackedRaceIdentifier, RaceDTO race, boolean isMetaLeaderboardColumn) { + assert fleetDTO != null; + RaceColumnDTO raceColumnDTO = getRaceColumnByName(raceColumnName); + if (raceColumnDTO == null) { + raceColumnDTO = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName, medalRace, + explicitFactor, regattaName, seriesName, isMetaLeaderboardColumn); + races.add(raceColumnDTO); + } + raceColumnDTO.setEffectiveFactor(effectiveFactor); + boolean contains = false; + for (FleetDTO fleet : raceColumnDTO.getFleets()) { + if (fleet.getName().equals(fleetDTO.getName())) { + contains = true; + break; + } + } + if (!contains) { + raceColumnDTO.addFleet(fleetDTO); + } + raceColumnDTO.setRaceIdentifier(fleetDTO, trackedRaceIdentifier); + raceColumnDTO.setRace(fleetDTO, race); + return raceColumnDTO; + } + + public RaceColumnDTO createEmptyRaceColumn(String raceColumnName, boolean medalRace, String regattaName, + String seriesName, boolean isMetaLeaderboardColumn) { + final RaceColumnDTO raceColumn = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName, + medalRace, /* explicit factor */ null, regattaName, seriesName, isMetaLeaderboardColumn); + races.add(raceColumn); + return raceColumn; + } + + public RaceColumnDTO getRaceColumnByName(String raceColumnName) { + for (RaceColumnDTO race : races) { + if (race.getRaceColumnName().equals(raceColumnName)) { + return race; + } + } + return null; + } + + public int getRaceColumnsCount() { + return races.size(); + } + + public int getRacesCount() { + int result = 0; + for (RaceColumnDTO race : getRaceList()) { + result += race.getFleets().size(); + } + return result; + } + + public int getTrackedRacesCount() { + int result = 0; + for (RaceColumnDTO raceColumn : getRaceList()) { + for (FleetDTO fleet : raceColumn.getFleets()) { + RaceDTO race = raceColumn.getRace(fleet); + if(race != null && race.trackedRace != null && race.trackedRace.hasGPSData && race.trackedRace.hasWindData) { + result++; + } + } + } + return result; + } + + public List getRaceList() { + return races; + } + + public void setRaceList(List raceList) { + this.races = raceList; + } + + public boolean raceListContains(String raceColumnName) { + return getRaceColumnByName(raceColumnName) != null; + } + + public void moveRaceUp(String raceColumnName) { + RaceColumnDTO race = getRaceColumnByName(raceColumnName); + int index = races.indexOf(race); + index--; + if (index >= 0) { + races.remove(index + 1); + races.add(index, race); + } + } + + public void moveRaceDown(String raceColumnName) { + RaceColumnDTO race = getRaceColumnByName(raceColumnName); + int index = races.indexOf(race); + if (index != -1) { + index++; + if (index < races.size()) { + races.remove(index - 1); + races.add(index, race); + } + } + } + + public void setIsMedalRace(String raceColumnName, boolean medalRace) { + getRaceColumnByName(raceColumnName).setMedalRace(medalRace); + } + + /** + * @return The earliest start date of the races, or null if no start dates of the races are available. + */ + public Date getStartDate() { + Date leaderboardStart = null; + for (RaceColumnDTO race : getRaceList()) { + for (FleetDTO fleet: race.getFleets()) { + Date raceStart = race.getStartDate(fleet); + if (raceStart != null) { + if (leaderboardStart == null) { + leaderboardStart = new Date(); + } else { + leaderboardStart = leaderboardStart.before(raceStart) ? leaderboardStart : raceStart; + } + } + } + } + return leaderboardStart; + } + + /** + * Takes the {@link PlacemarkOrderDTO} of all races in this leaderboard, if the PlacemarkOrderDTO for the race is + * available, and fills all {@link PlacemarkDTO} in a new PlacemarkOrderDTO.
+ * The order of the races in this leaderboard determine the order of the PlacemarkDTOs in the PlacemarkOrderDTO. + * + * @return The places of this leaderboard in form of a {@link PlacemarkOrderDTO}, or null if the + * {@link PlacemarkOrderDTO places} of no race are available + */ + public PlacemarkOrderDTO getPlaces() { + PlacemarkOrderDTO leaderboardPlaces = null; + for (RaceColumnDTO race : getRaceList()) { + PlacemarkOrderDTO racePlaces = race.getPlaces(); + if (racePlaces != null) { + if (leaderboardPlaces == null) { + leaderboardPlaces = new PlacemarkOrderDTO(); + } + leaderboardPlaces.getPlacemarks().addAll(racePlaces.getPlacemarks()); + } + } + return leaderboardPlaces; + } + + /** + * @return true if the leaderboard contains a race which is live + */ + public boolean hasLiveRace(long serverTimePointAsMillis) { + for (RaceColumnDTO race : getRaceList()) { + for (FleetDTO fleet : race.getFleets()) { + if (race.isLive(fleet, serverTimePointAsMillis)) { + return true; + } + } + } + return false; + } + + public List> getLiveRaces(long serverTimePointAsMillis) { + List> result = new ArrayList>(); + for (RaceColumnDTO race : getRaceList()) { + for (FleetDTO fleet : race.getFleets()) { + if (race.isLive(fleet, serverTimePointAsMillis)) { + result.add(new Util.Pair(race, fleet)); + } + } + } + return result; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((competitorDisplayNames == null) ? 0 : competitorDisplayNames.hashCode()); + result = prime * result + Arrays.hashCode(discardThresholds); + result = prime * result + (hasCarriedPoints ? 1231 : 1237); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((scoringScheme == null) ? 0 : scoringScheme.hashCode()); + if (races == null) { + result = prime * result; + } else { + List raceNames = new ArrayList(); + for (RaceColumnDTO race : races) { + raceNames.add(race.getName()); + } + result = prime * result + raceNames.hashCode(); + } + result = prime * result + ((rows == null) ? 0 : rows.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AbstractLeaderboardDTO other = (AbstractLeaderboardDTO) obj; + if (competitorDisplayNames == null) { + if (other.competitorDisplayNames != null) + return false; + } else if (!competitorDisplayNames.equals(other.competitorDisplayNames)) + return false; + if (!Arrays.equals(discardThresholds, other.discardThresholds)) + return false; + if (hasCarriedPoints != other.hasCarriedPoints) + return false; + if (type != other.type) + return false; + if (scoringScheme != other.scoringScheme) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (races == null) { + if (other.races != null) + return false; + } else { + // compare race column names only, not the contents + if (races.size() != (other.races==null?0:other.races.size())) { + return false; + } + List raceColumnNames = new ArrayList(races.size()); + List otherRaceColumnNames = new ArrayList(races.size()); + for (RaceColumnDTO race : races) { + raceColumnNames.add(race.getName()); + } + if (other.races != null) { + for (RaceColumnDTO otherRace : other.races) { + otherRaceColumnNames.add(otherRace.getName()); + } + } + if (!raceColumnNames.equals(otherRaceColumnNames)) + return false; + if (rows == null) { + if (other.rows != null) + return false; + } else if (!rows.equals(other.rows)) + return false; + } + return true; + } + + public boolean isDisplayNameSet(CompetitorDTO competitor) { + return competitorDisplayNames.get(competitor) != null; + } + + public Long getDelayToLiveInMillisForLatestRace() { + return delayToLiveInMillisForLatestRace; + } + + public void setDelayToLiveInMillisForLatestRace(Long delayToLiveInMillisForLatestRace) { + this.delayToLiveInMillisForLatestRace = delayToLiveInMillisForLatestRace; + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BasicRaceDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BasicRaceDTO.java new file mode 100644 index 00000000000..8938136783c --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BasicRaceDTO.java @@ -0,0 +1,112 @@ +package com.sap.sailing.domain.common.dto; + +import java.util.Date; + +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.TimingConstants; + +/** + * Master data about a single race that is to be transferred to the client. Holds only timing and a bit + * of information about the tracked race, if any. See also {@link RaceDTO} for a more comprehensive set + * of data about a race to be serialized to a client.

+ * + * @author Axel Uhl (d043530) + * + */ +public class BasicRaceDTO extends NamedDTO { + private static final long serialVersionUID = -7884808503795229609L; + + public Date startOfRace; + public Date endOfRace; + public TrackedRaceDTO trackedRace; + + public BasicRaceDTO() {} // for GWT serialization only + + public BasicRaceDTO(RegattaAndRaceIdentifier raceIdentifier, TrackedRaceDTO trackedRace) { + super(raceIdentifier.getRaceName()); + this.trackedRace = trackedRace; + } + + /** + * @see {@link TrackedRace#isLive} for further explanation. + * @param serverTimePointAsMillis + * the time point (in server clock time) at which to determine whether the race is/was live + * @return true if serverTimePointAsMillis is between (inclusively) the start and end time + * point of the "live" interval as defined above. + */ + public boolean isLive(long serverTimePointAsMillis) { + final Date startOfLivePeriod; + final Date endOfLivePeriod; + if (trackedRace == null || !trackedRace.hasGPSData || !trackedRace.hasWindData) { + startOfLivePeriod = null; + endOfLivePeriod = null; + } else { + if (startOfRace == null) { + startOfLivePeriod = trackedRace.startOfTracking; + } else { + startOfLivePeriod = new Date(startOfRace.getTime() - TimingConstants.PRE_START_PHASE_DURATION_IN_MILLIS); + } + if (endOfRace == null) { + if (trackedRace.timePointOfNewestEvent != null) { + endOfLivePeriod = new Date(trackedRace.timePointOfNewestEvent.getTime() + + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS); + } else { + endOfLivePeriod = null; + } + } else { + endOfLivePeriod = new Date(endOfRace.getTime() + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS); + } + } + + // if an empty timepoint is given then take the start of the race + if (serverTimePointAsMillis == 0) { + serverTimePointAsMillis = startOfLivePeriod.getTime()+1; + } + + // whenLastTrackedRaceWasLive is null if there is no tracked race for fleet, or the tracked race hasn't started yet at the server time + // when this DTO was assembled, or there were no GPS or wind data + final boolean result = + startOfLivePeriod != null && + endOfLivePeriod != null && + startOfLivePeriod.getTime() <= serverTimePointAsMillis && + serverTimePointAsMillis <= endOfLivePeriod.getTime(); + return result; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((endOfRace == null) ? 0 : endOfRace.hashCode()); + result = prime * result + ((startOfRace == null) ? 0 : startOfRace.hashCode()); + result = prime * result + ((trackedRace == null) ? 0 : trackedRace.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + BasicRaceDTO other = (BasicRaceDTO) obj; + if (endOfRace == null) { + if (other.endOfRace != null) + return false; + } else if (!endOfRace.equals(other.endOfRace)) + return false; + if (startOfRace == null) { + if (other.startOfRace != null) + return false; + } else if (!startOfRace.equals(other.startOfRace)) + return false; + if (trackedRace == null) { + if (other.trackedRace != null) + return false; + } else if (!trackedRace.equals(other.trackedRace)) + return false; + return true; + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardDTO.java index 64012fec26c..dd9b5e94981 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardDTO.java @@ -62,7 +62,6 @@ public class LeaderboardDTO extends AbstractLeaderboardDTO implements Serializab /** * @param uuidGenerator used to provide the {@link #id ID} for this object (see also {@link #getId()}) and for any clones produced * from it by the {@link #clone()} operation. - * @param hasOverallDetails TODO */ public LeaderboardDTO(Date timePointOfLastCorrectionsValidity, String comment, ScoringSchemeType scoringScheme, boolean higherScoreIsBetter, UUIDGenerator uuidGenerator, boolean hasOverallDetails) { initCollections(); @@ -79,7 +78,7 @@ public class LeaderboardDTO extends AbstractLeaderboardDTO implements Serializab this.suppressedCompetitors = new ArrayList(); } - public LeaderboardDTO(String id) { + protected LeaderboardDTO(String id) { initCollections(); this.id = id; } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/MetaLeaderboardRaceColumnDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/MetaLeaderboardRaceColumnDTO.java new file mode 100644 index 00000000000..475718b447b --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/MetaLeaderboardRaceColumnDTO.java @@ -0,0 +1,49 @@ +package com.sap.sailing.domain.common.dto; + +import java.util.ArrayList; + +public class MetaLeaderboardRaceColumnDTO extends RaceColumnDTO { + + private static final long serialVersionUID = 6152752963316150432L; + + private ArrayList raceList = new ArrayList(); + + public boolean isLive(FleetDTO fleet, long serverTimePointAsMillis) { + for (BasicRaceDTO race : raceList) { + if (race.isLive(serverTimePointAsMillis)) { + return true; + } + } + return false; + } + + public void addRace(BasicRaceDTO race) { + raceList.add(race); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((raceList == null) ? 0 : raceList.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + MetaLeaderboardRaceColumnDTO other = (MetaLeaderboardRaceColumnDTO) obj; + if (raceList == null) { + if (other.raceList != null) + return false; + } else if (!raceList.equals(other.raceList)) + return false; + return true; + } + +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnDTOFactory.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnDTOFactory.java index 7d8a0efbb67..46083549e93 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnDTOFactory.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnDTOFactory.java @@ -7,15 +7,17 @@ public class RaceColumnDTOFactory { * @param regattaName * must not be null if seriesName is not null * @param seriesName - * when null, a regular {@link RaceColumnDTO} will be produced and initialized from the - * properties passed; otherwise, a {@link RaceColumnInSeriesDTO} will be created. + * when null, a regular {@link RaceColumnDTO} or {@link MetaLeaderboardRaceColumnDTO} will + * be produced (depending on isMetaLeaderboardColumn) and initialized from the properties + * passed; otherwise, a {@link RaceColumnInSeriesDTO} will be created. */ - public RaceColumnDTO createRaceColumnDTO(String columnName, boolean isMedal, Double explicitFactor, String regattaName, String seriesName) { + public RaceColumnDTO createRaceColumnDTO(String columnName, boolean isMedal, Double explicitFactor, + String regattaName, String seriesName, boolean isMetaLeaderboardColumn) { final RaceColumnDTO raceColumnDTO; if (seriesName != null) { raceColumnDTO = new RaceColumnInSeriesDTO(seriesName, regattaName); } else { - raceColumnDTO = new RaceColumnDTO(); + raceColumnDTO = isMetaLeaderboardColumn ? new MetaLeaderboardRaceColumnDTO() : new RaceColumnDTO(); } fillRaceColumnDTO(raceColumnDTO, columnName, isMedal, explicitFactor); return raceColumnDTO; diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceDTO.java index eba9caf39b7..6a8e90c617f 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceDTO.java @@ -1,10 +1,7 @@ package com.sap.sailing.domain.common.dto; -import java.util.Date; - import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaNameAndRaceName; -import com.sap.sailing.domain.common.TimingConstants; /** * Master data about a single race that is to be transferred to the client.

@@ -12,7 +9,7 @@ import com.sap.sailing.domain.common.TimingConstants; * @author Axel Uhl (d043530) * */ -public class RaceDTO extends NamedDTO { +public class RaceDTO extends BasicRaceDTO { private static final long serialVersionUID = 2613189982608149975L; public enum RaceLiveState { NOT_TRACKED, TRACKED, TRACKED_AND_LIVE, TRACKED_BUT_NOT_SCHEDULED }; @@ -24,13 +21,10 @@ public class RaceDTO extends NamedDTO { */ public boolean isTracked; - public Date startOfRace; - public Date endOfRace; public RaceStatusDTO status; public PlacemarkOrderDTO places; - public TrackedRaceDTO trackedRace; public TrackedRaceStatisticsDTO trackedRaceStatistics; private String regattaName; @@ -43,21 +37,20 @@ public class RaceDTO extends NamedDTO { } public RaceDTO(RegattaAndRaceIdentifier raceIdentifier, TrackedRaceDTO trackedRace, boolean isCurrentlyTracked) { - super(raceIdentifier.getRaceName()); + super(raceIdentifier, trackedRace); this.regattaName = raceIdentifier.getRegattaName(); - this.trackedRace = trackedRace; this.isTracked = isCurrentlyTracked; } public RaceLiveState getLiveState(long serverTimePointAsMillis) { - RaceLiveState result = RaceLiveState.NOT_TRACKED; - if(trackedRace != null && trackedRace.hasGPSData && trackedRace.hasWindData) { + RaceLiveState result = RaceLiveState.NOT_TRACKED; + if (trackedRace != null && trackedRace.hasGPSData && trackedRace.hasWindData) { result = RaceLiveState.TRACKED; - if(isLive(serverTimePointAsMillis)) { + if (isLive(serverTimePointAsMillis)) { result = RaceLiveState.TRACKED_AND_LIVE; - if(startOfRace == null) { + if (startOfRace == null) { result = RaceLiveState.TRACKED_BUT_NOT_SCHEDULED; - } + } } } return result; @@ -71,64 +64,15 @@ public class RaceDTO extends NamedDTO { return regattaName; } - /** - * @see {@link TrackedRace#isLive} for further explanation. - * @param serverTimePointAsMillis - * the time point (in server clock time) at which to determine whether the race is/was live - * @return true if serverTimePointAsMillis is between (inclusively) the start and end time - * point of the "live" interval as defined above. - */ - public boolean isLive(long serverTimePointAsMillis) { - final Date startOfLivePeriod; - final Date endOfLivePeriod; - if (trackedRace == null || !trackedRace.hasGPSData || !trackedRace.hasWindData) { - startOfLivePeriod = null; - endOfLivePeriod = null; - } else { - if (startOfRace == null) { - startOfLivePeriod = trackedRace.startOfTracking; - } else { - startOfLivePeriod = new Date(startOfRace.getTime() - TimingConstants.PRE_START_PHASE_DURATION_IN_MILLIS); - } - if (endOfRace == null) { - if (trackedRace.timePointOfNewestEvent != null) { - endOfLivePeriod = new Date(trackedRace.timePointOfNewestEvent.getTime() - + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS); - } else { - endOfLivePeriod = null; - } - } else { - endOfLivePeriod = new Date(endOfRace.getTime() + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS); - } - } - - // if an empty timepoint is given then take the start of the race - if (serverTimePointAsMillis == 0) { - serverTimePointAsMillis = startOfLivePeriod.getTime()+1; - } - - // whenLastTrackedRaceWasLive is null if there is no tracked race for fleet, or the tracked race hasn't started yet at the server time - // when this DTO was assembled, or there were no GPS or wind data - final boolean result = - startOfLivePeriod != null && - endOfLivePeriod != null && - startOfLivePeriod.getTime() <= serverTimePointAsMillis && - serverTimePointAsMillis <= endOfLivePeriod.getTime(); - return result; - } - @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); result = prime * result + ((boatClass == null) ? 0 : boatClass.hashCode()); - result = prime * result + ((endOfRace == null) ? 0 : endOfRace.hashCode()); result = prime * result + (isTracked ? 1231 : 1237); result = prime * result + ((places == null) ? 0 : places.hashCode()); result = prime * result + ((regattaName == null) ? 0 : regattaName.hashCode()); - result = prime * result + ((startOfRace == null) ? 0 : startOfRace.hashCode()); result = prime * result + ((status == null) ? 0 : status.hashCode()); - result = prime * result + ((trackedRace == null) ? 0 : trackedRace.hashCode()); return result; } @@ -146,11 +90,6 @@ public class RaceDTO extends NamedDTO { return false; } else if (!boatClass.equals(other.boatClass)) return false; - if (endOfRace == null) { - if (other.endOfRace != null) - return false; - } else if (!endOfRace.equals(other.endOfRace)) - return false; if (isTracked != other.isTracked) return false; if (places == null) { @@ -163,21 +102,11 @@ public class RaceDTO extends NamedDTO { return false; } else if (!regattaName.equals(other.regattaName)) return false; - if (startOfRace == null) { - if (other.startOfRace != null) - return false; - } else if (!startOfRace.equals(other.startOfRace)) - return false; if (status == null) { if (other.status != null) return false; } else if (!status.equals(other.status)) return false; - if (trackedRace == null) { - if (other.trackedRace != null) - return false; - } else if (!trackedRace.equals(other.trackedRace)) - return false; return true; } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/QuadTree.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/QuadTree.java index 51f42e5f100..798f3ffbe55 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/QuadTree.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/QuadTree.java @@ -1,164 +1,90 @@ -// ********************************************************************** -// -// -// -// BBN Technologies -// 10 Moulton Street -// Cambridge, MA 02138 -// (617) 873-8000 -// -// Copyright (C) BBNT Solutions LLC. All rights reserved. -// -// -// ********************************************************************** -// -// $Source$ -// $RCSfile$ -// $Revision: 184 $ -// $Date: 2008-09-16 16:17:21 +0200 (Tue, 16 Sep 2008) $ -// $Author: axel.uhl $ -// -// ********************************************************************** - package com.sap.sailing.domain.common.quadtree; import java.io.Serializable; -import java.util.Collection; -import java.util.Vector; +import com.sap.sailing.domain.common.Bounds; import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.impl.BoundsImpl; import com.sap.sailing.domain.common.impl.DegreePosition; -import com.sap.sailing.domain.common.quadtree.impl.Bounds; -import com.sap.sailing.domain.common.quadtree.impl.QuadTreeNode; +import com.sap.sailing.domain.common.quadtree.impl.Node; +import com.sap.sailing.domain.common.quadtree.impl.Node.GetResult; /** - * The QuadTree lets you organize objects in a grid, that redefines itself and focuses more gridding when more objects - * appear in a certain area. - *

- * - * Note that this class is not thread safe. If multiple threads can access the same instance concurrently, callers have - * to ensure proper synchronization. Concurrent reads are permissible while any write should block all other operations. + * A spatial data structure that provides efficient (O(log n)) access to nearest neighbors and + * to objects in a certain radius around another object. The location of objects is provided as + * {@link Position}. * * @param * type of object stored by coordinates * @author Axel Uhl (D043530) */ public class QuadTree implements Serializable { + private static final long serialVersionUID = -5500716775749946017L; - static final long serialVersionUID = -7707825592455579873L; - - private QuadTreeNode top; + private final Node root; + + private final static int DEFAULT_MAX_NODE_ITEMS = 20; public QuadTree() { - this(new Bounds(new DegreePosition(-90.0, -180.0), new DegreePosition(90.0, 180.0)), 20, QuadTreeNode.NO_MIN_SIZE); + this(new BoundsImpl(new DegreePosition(-90.0, -180.0), new DegreePosition(90.0, 180.0)), DEFAULT_MAX_NODE_ITEMS); } public QuadTree(Position southWest, Position northEast, int maxItems) { - this(new Bounds(southWest, northEast), maxItems, QuadTreeNode.NO_MIN_SIZE); + this(new BoundsImpl(southWest, northEast), maxItems); } - public QuadTree(Bounds bounds, int maxItems, double minSize) { - top = new QuadTreeNode(bounds, maxItems, minSize); + private QuadTree(Bounds bounds, int maxItems) { + root = new Node(bounds, maxItems); } - /** - * Add a object into the tree at a location. - * - * @param lat up-down location in QuadTree Grid (latitude, y) - * @param lon left-right location in QuadTree Grid (longitude, x) - * @return true if the insertion worked. - * @throws RuntimeException in case the leaf's lat/lng lies outside of the node's bounds. - * This would typically be caused by the point being outside the whole quad tree's bounds. - */ - public void put(Position point, T obj) { - getTop().put(point, obj); + public T put(Position point, T obj) { + return root.put(point, obj); } - /** - * Remove a object out of the tree at a location. - * - * @param lat up-down location in QuadTree Grid (latitude, y) - * @param lon left-right location in QuadTree Grid (longitude, x) - * @return the object removed, null if the object not found. - */ public T remove(Position point, T obj) { - return getTop().remove(point, obj); + return root.remove(point); } - public void replace(Position point, T newObj) { - getTop().replace(point, newObj); - } - - /** Clear the tree. */ + /** + * Remove all elements from this tree. + */ public void clear() { - getTop().clear(); + root.clear(); } /** - * Get an object closest to a lat/lon. - * - * @param lat up-down location in QuadTree Grid (latitude, y) - * @param lon left-right location in QuadTree Grid (longitude, x) - * @return the object that was found. + * Get the value nearest to point. If the tree is empty, null is returned. Distance + * is calculated using the method {@link #getLatLngDistance(Position, Position)} which is an approximation only, + * based on Euklidian geometry with the latitude/longitude values. */ public T get(Position point) { - return getTop().get(point); + final GetResult result = root.get(point); + return result == null ? null : result.getValue(); } /** - * Get an object closest to a lat/lon, within a maximum distance. + * Get the value closest to point, within a maximum distance, where distance + * is computed by the rules of {@link #getLatLngDistance(Position, Position)}. If no key + * is found within that distance, null is returned. * - * @param lat up-down location in QuadTree Grid (latitude, y) - * @param lon left-right location in QuadTree Grid (longitude, x) - * @param withinDistance maximum get distance. The distance is given - * as the square root of the sum of - * the squares of the latitude and longitude differences, respectively. - * It therefore does not correspond to any distance in meters or - * any euclidian distance at all. However, it should be good enough - * (at least outside the polar regions, and in particular for smaller - * regions), and in particular to find minimum distances. - * @return the object that was found, null if nothing is within - * the maximum distance. + * @param withinDistance + * maximum get distance. The distance is given as the square root of the sum of the squares of the + * latitude and longitude differences, respectively. It therefore does not correspond to any distance in + * meters or any euclidian distance at all. However, it should be good enough (at least outside the polar + * regions, and in particular for smaller regions), and in particular to find minimum distances. + * See {@link #getLatLngDistance(Position, Position)}. + * @return the object that was found, null if nothing is within the maximum distance. */ public T get(Position point, double withinDistance) { - return getTop().get(point, withinDistance); + final GetResult result = root.get(point, withinDistance); + return result == null ? null : result.getValue(); } /** - * Get all the objects within a bounding box. - * - * @return Vector of objects. + * Get all values withing the bounds */ - public Collection get(Bounds rect) { - return get(rect, new Vector()); - } - - /** - * Get all the objects within a bounding box, and return the - * objects within a given Vector. - * - * @param vector a vector to add objects to. - * @return Vector of objects. - */ - private Collection get(Bounds rect, Collection vector) { - - if (vector == null) { - vector = new Vector(); - } - // crossing the dateline, right?? Or at least containing the - // entire earth. Might be trouble for VERY LARGE scales. The - // last check is for micro-errors that happen to lon points - // where there might be a smudge overlap for very small - // scales. - if (rect.getSouthWest().getLngDeg() > rect.getNorthEast().getLngDeg() || (Math.abs(rect.getSouthWest().getLngDeg() - rect.getNorthEast().getLngDeg()) < .001)) { - return getTop().get(new Bounds(rect.getSouthWest(), new DegreePosition(rect.getNorthEast().getLatDeg(), 180)), - getTop().get(new Bounds(new DegreePosition(rect.getSouthWest().getLatDeg(), -180), rect.getNorthEast()), vector)); - } else - return getTop().get(rect, vector); - } - - protected QuadTreeNode getTop() { - return top; + public Iterable get(Bounds bounds) { + return root.get(bounds); } /** @@ -166,8 +92,10 @@ public class QuadTree implements Serializable { * and doing the "sqrt thing" */ public static double getLatLngDistance(Position a, Position b) { - double distance = Math.sqrt((a.getLatDeg() - b.getLatDeg()) * (a.getLatDeg() - b.getLatDeg()) + (a.getLngDeg() - b.getLngDeg()) - * (a.getLngDeg() - b.getLngDeg())); + final double dy = a.getLatDeg() - b.getLatDeg(); + final double dx = (a.getLngDeg() - b.getLngDeg()); + final double dxWrapped = Math.abs(dx) <= 180 ? dx : 360.-Math.abs(dx); + double distance = Math.sqrt(dy * dy + dxWrapped * dxWrapped); return distance; } } \ No newline at end of file diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/impl/Bounds.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/impl/Bounds.java deleted file mode 100644 index 9599f7e5f69..00000000000 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/impl/Bounds.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.sap.sailing.domain.common.quadtree.impl; - -import com.sap.sailing.domain.common.Position; - -public class Bounds { - private final Position southWest; - private final Position northEast; - - public Bounds(Position southWest, Position northEast) { - this.southWest = southWest; - this.northEast = northEast; - } - - public Position getSouthWest() { - return southWest; - } - - public Position getNorthEast() { - return northEast; - } - - public boolean contains(Position p) { - return p.getLatDeg() >= getSouthWest().getLatDeg() && p.getLatDeg() <= getNorthEast().getLatDeg() && - (getNorthEast().getLngDeg() >= getSouthWest().getLngDeg() && p.getLngDeg() >= getSouthWest().getLngDeg() && p.getLngDeg() <= getNorthEast().getLngDeg() || - // cross date line bounds - getNorthEast().getLngDeg() < getSouthWest().getLngDeg() && - (p.getLngDeg() <= 180 && p.getLngDeg() >= getSouthWest().getLngDeg() || - p.getLngDeg() >= -180 && p.getLngDeg() <= getNorthEast().getLngDeg())); - } - - public boolean containsBounds(Bounds rect) { - return this.contains(rect.getSouthWest()) && this.contains(rect.getNorthEast()) || - rect.contains(this.getSouthWest()) && rect.contains(this.getNorthEast()); - } - -} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/impl/MutableDistance.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/impl/MutableDistance.java deleted file mode 100644 index 33abdb50680..00000000000 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/impl/MutableDistance.java +++ /dev/null @@ -1,34 +0,0 @@ -// ********************************************************************** -// -// -// -// BBN Technologies -// 10 Moulton Street -// Cambridge, MA 02138 -// (617) 873-8000 -// -// Copyright (C) BBNT Solutions LLC. All rights reserved. -// -// -// ********************************************************************** -// -// $Source$ -// $RCSfile$ -// $Revision: 15 $ -// $Date: 2008-04-20 02:18:58 +0200 (Sun, 20 Apr 2008) $ -// $Author: uhl $ -// -// ********************************************************************** - -package com.sap.sailing.domain.common.quadtree.impl; - -/** - * A *really* simple class used as a changable double. - */ -public class MutableDistance { - public double value = 0; - - public MutableDistance(double distance) { - value = distance; - } -} \ No newline at end of file diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/impl/Node.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/impl/Node.java new file mode 100644 index 00000000000..fb0d5aa6026 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/impl/Node.java @@ -0,0 +1,423 @@ +package com.sap.sailing.domain.common.quadtree.impl; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.sap.sailing.domain.common.Bounds; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.impl.BoundsImpl; +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.common.quadtree.QuadTree; +import com.sap.sse.common.Util; + +/** + * A node in a {@link QuadTree}. There may be internal nodes that have no elements in them but have exactly + * four child nodes, or leaf nodes that have no children but contain item elements. + * + * @author Axel Uhl (D043530) + * + * @param + */ +public class Node { + // TODO remove when done + public static int visitedItems; + public static int visitedNodes; + public static int skippedNodes; + + /** + * Either an array of exactly four child nodes which are then all non-null, or null, + * meaning that this node is a child node, having no children but potentially having items. A node that has children + * has no items. Quadrants are numbered as usual in geometry. See {@link #NE}, {@link #NW}, {@link #SW} and + * {@link #SE}. + */ + private Node[] children; + + private Map items; + + private final Bounds bounds; + + /** + * The maximum number of items to hold in {@link #items}. If {@link #put(Position, Object) adding} an item to this node + * would increase the item collection's size beyond this number, the node is {@link #split() split} into four new leaf + * nodes, and its items are distributed across those new leaf nodes. This number must be a positive integer. + */ + private final int maxItems; + + /** + * The quadrant index in {@link #children} for the north-east subtree + */ + private final int NE = 0; + + /** + * The quadrant index in {@link #children} for the north-west subtree + */ + private final int NW = 1; + + /** + * The quadrant index in {@link #children} for the south-west subtree + */ + private final int SW = 2; + + /** + * The quadrant index in {@link #children} for the south-east subtree + */ + private final int SE = 3; + + /** + * Creates a new node with the bounds as specified. The node starts out empty, as a leaf node that has + * an empty set of items. + * + * @param bounds + * must not have a north latitude less than the south latitude, or an {@link IllegalArgumentException} + * will result + */ + public Node(Bounds bounds, int maxItems) { + if (bounds.getNorthEast().getLatDeg() < bounds.getSouthEast().getLatDeg()) { + throw new IllegalArgumentException("North border of bounds "+bounds+" is further south than its south border"); + } + if (maxItems <= 0) { + throw new IllegalArgumentException("Maximum number of items must be positive but was "+maxItems); + } + this.maxItems = maxItems; + this.bounds = bounds; + items = new HashMap<>(maxItems); + } + + private void split() { + assert children == null; + assert items != null; + createChildren(); + distributeItemsToChildren(); + assert children != null; + assert items == null; + } + + private void distributeItemsToChildren() { + assert items != null; + assert children != null; + final Map myItems = items; + items = null; + for (final Entry item : myItems.entrySet()) { + getChild(item.getKey()).put(item.getKey(), item.getValue()); + } + assert items == null; + } + + private Node getChild(Position key) { + assert children != null; + assert items == null; + assert bounds.contains(key); + for (final Node child : children) { + if (child.bounds.contains(key)) { + return child; + } + } + throw new RuntimeException("Internal error: position "+key+" is within node bounds "+bounds+" but no child contains it"); + } + + /** + * Adds the value to this node and ensures that the node still meets the requirements regarding size. + * If necessary, the node is split with its items distributed across the new children. If a value already existed at + * position key, it is replaced. + * + * @param key + * the position at which to insert value. Must be contained in this node's {@link #bounds}. + * If it is not, an {@link IllegalArgumentException} will be thrown. Must not be null. + * @return the value previously at position key or null if there was no value at that + * position. + */ + public T put(Position key, T value) { + if (value == null) { + throw new NullPointerException("Cannot insert null values into this node"); + } + if (key == null) { + throw new NullPointerException("null keys not allowed"); + } + if (!bounds.contains(key)) { + throw new IllegalArgumentException("key "+key+" must be within this node's bounds "+bounds); + } + final T result; + if (items != null) { + result = items.put(key, value); + if (result == null) { + // the size of this node has increased by one; check size constraint + if (items.size() > maxItems) { + split(); + } + } + } else { + result = getChild(key).put(key, value); + } + return result; + } + + /** + * Removes the element at position key from this node or any child nodes if such an element exists + * + * @return the value removed, or null if no element existed at position key in this node + * or any of its children + */ + public T remove(Position key) { + final T result; + if (key != null) { + if (items != null) { + result = items.remove(key); + } else { + result = getChild(key).remove(key); + } + } else { + result = null; + } + return result; + } + + private void createChildren() { + assert children == null; + @SuppressWarnings("unchecked") + final Node[] newChildren = (Node[]) new Node[4]; + children = newChildren; + final Position middleWest = new DegreePosition((bounds.getSouthWest().getLatDeg() + bounds.getNorthEast().getLatDeg())/2., + bounds.getSouthWest().getLngDeg()); + final Position southCenter = new DegreePosition(bounds.getSouthWest().getLatDeg(), + (bounds.getNorthEast().getLngDeg() + bounds.getSouthWest().getLngDeg()) / 2. - + // adjust for date line crossing if necessary + (bounds.getNorthEast().getLngDeg() >= bounds.getSouthWest().getLngDeg() ? 0. : 360.)); + final Position middleCenter = new DegreePosition(middleWest.getLatDeg(), southCenter.getLngDeg()); + final Position middleEast = new DegreePosition(middleWest.getLatDeg(), bounds.getNorthEast().getLngDeg()); + final Position northCenter = new DegreePosition(bounds.getNorthEast().getLatDeg(), southCenter.getLngDeg()); + children[NE] = new Node(new BoundsImpl(middleCenter, bounds.getNorthEast()), maxItems); + children[NW] = new Node(new BoundsImpl(middleWest, northCenter), maxItems); + children[SW] = new Node(new BoundsImpl(bounds.getSouthWest(), middleCenter), maxItems); + children[SE] = new Node(new BoundsImpl(southCenter, middleEast), maxItems); + assert children != null; + assert children.length == 4; + assert children[NE] != null && children[NW] != null && children[SW] != null && children[SE] != null; + } + + /** + * Remove all elements from this node by either removing all its items locally or by removing all children + * and converting this back into a leaf node. + */ + public void clear() { + if (items != null) { + items.clear(); + } else { + items = new HashMap<>(); + children = null; + } + } + + public static class GetResult { + private final Position position; + private final T value; + private final double distance; + public GetResult(Position position, T value, double distance) { + super(); + this.position = position; + this.value = value; + this.distance = distance; + } + public Position getPosition() { + return position; + } + public T getValue() { + return value; + } + public double getDistance() { + return distance; + } + @Override + public String toString() { + return "GetResult [position=" + position + ", value=" + value + ", distance=" + distance + "]"; + } + } + + /** + * Get the value nearest to point. If the node is empty, null is returned. Distance is + * calculated using the method {@link QuadTree#getLatLngDistance(Position, Position)} which is an approximation + * only, based on Euklidian geometry with the latitude/longitude values. + *

+ * + * If this is a leaf node, the nearest key by the definition above is used to determine the corresponding value and + * return it. Otherwise, the children are traversed. For the first child, the nearest key is determined recursively. + * Other children only need to be traversed if their bounds are closer to point than the key found so + * far. + * + * @param withinDistance + * a key is only considered if it has less than this {@link QuadTree#getLatLngDistance(Position, Position) + * distance} to point + */ + public GetResult get(Position point, double withinDistance) { + assert (items == null) != (children == null); + visitedNodes++; + GetResult result = null; + double minDistance = withinDistance; + if (items != null) { + for (final Entry item : items.entrySet()) { + double itemDistanceToPoint = QuadTree.getLatLngDistance(point, item.getKey()); + visitedItems++; + if (itemDistanceToPoint < minDistance) { + result = new GetResult(item.getKey(), item.getValue(), itemDistanceToPoint); + minDistance = itemDistanceToPoint; + } + } + } else { + // Traverse children in order of ascending distance, making it more likely to find good, near + // candidates early, hence enabling skipping more children, investigating fewer items. However, + // doing the full java.util.Collections / Arrays sort thing with Comparators and such is way too + // expensive as measurements have shown, and the overhead for just the four elements is too high. + // It is much more efficient to traverse the small children array multiple times and in each pass + // remembering the next minimal child distance so that during the next pass those children can be + // inspected. This process repeats until all children have been handled, taking at most four passes. + final double[] childDistanceToPoint = new double[4]; + double minChildDistanceToPoint = Double.MAX_VALUE; // minimal distance of those children not yet done + for (int i=0; i<4; i++) { + childDistanceToPoint[i] = children[i].getDistance(point); + if (childDistanceToPoint[i] < minChildDistanceToPoint) { + minChildDistanceToPoint = childDistanceToPoint[i]; + } + } + final boolean done[] = new boolean[4]; // have we handled that child already (skipped or searched)? + int doneCount = 0; + while (doneCount < 4) { + double nextMinChildDistanceToPoint = Double.MAX_VALUE; + for (int i=0; doneCount<4 && i<4; i++) { + if (!done[i]) { + if (childDistanceToPoint[i] == minChildDistanceToPoint) { + final Node child = children[i]; + // If we have a possible result already, only investigate the child node if it is nearer to point + // than the result candidate and not an empty leaf node (which can be checked more quickly than + // performing the rather expensive distance calculation) + if (!child.isEmptyLeaf() && childDistanceToPoint[i] < minDistance) { + assert minDistance <= withinDistance; + final GetResult childResult = child.get(point, minDistance); + if (childResult != null) { + if (childResult.getDistance() < minDistance) { + result = childResult; + minDistance = childResult.getDistance(); + } + } + } else { + skippedNodes++; + } + done[i] = true; + doneCount++; + } else { + if (childDistanceToPoint[i] < nextMinChildDistanceToPoint) { + nextMinChildDistanceToPoint = childDistanceToPoint[i]; // the distance to handle in the next pass + } + } + } + } + minChildDistanceToPoint = nextMinChildDistanceToPoint; + } + } + return result; + } + + /** + * Get the value nearest to point. If the node is empty, null is returned. Distance is + * calculated using the method {@link QuadTree#getLatLngDistance(Position, Position)} which is an approximation + * only, based on Euklidian geometry with the latitude/longitude values. + *

+ * + * If this is a leaf node, the nearest key by the definition above is used to determine the corresponding value and + * return it. Otherwise, the children are traversed. For the first child, the nearest key is determined recursively. + * Other children only need to be traversed if their bounds are closer to point than the key found so + * far. + */ + public GetResult get(Position point) { + return get(point, Double.MAX_VALUE); + } + + /** + * To avoid expensive distance checks, a shortcut can be to check whether this node is an empty + * leaf node. This is the case if {@link #items} is not null (indicating a leaf node) + * but empty. + */ + private boolean isEmptyLeaf() { + return items != null && items.isEmpty(); + } + + /** + * Computes a "distance" of this node's {@link #bounds} to the point based on the distance definition + * provided by {@link QuadTree#getLatLngDistance(Position, Position)}. If point is + * {@link Bounds#contains(Position) contained} in this node's {@link #bounds}, the distance returned is + * 0. Otherwise, the distance to the nearest border of this node's {@link #bounds} is determined. + */ + protected double getDistance(Position point) { + final double result; + if (bounds.contains(point)) { + result = 0; + } else { + // Find out the point on the border defined by bounds that is nearest to point. + final double latDegNearestOnBorder = + point.getLatDeg() > bounds.getNorthEast().getLatDeg() ? bounds.getNorthEast().getLatDeg() : + point.getLatDeg() < bounds.getSouthWest().getLatDeg() ? bounds.getSouthWest().getLatDeg() : point.getLatDeg(); + // Now construct a Bounds object of zero latitude span at point's latitude and with the longitude span + // copied from this Node's bounds. If these bounds contain point, use point's longitude as it is spanned by + // this node's bounds. Otherwise, extend the bounds to contain point, then check whether the west or east border + // was changed to contain point; point was closer to the border that was changed to include point + final Bounds boundsAtPointLat = new BoundsImpl(new DegreePosition(point.getLatDeg(), bounds.getSouthWest().getLngDeg()), + new DegreePosition(point.getLatDeg(), bounds.getNorthEast().getLngDeg())); + final double lngDegNearestOnBorder; + if (boundsAtPointLat.contains(point)) { + lngDegNearestOnBorder = point.getLngDeg(); + } else { + final Bounds boundsAtPointLatExtendedToIncludePoint = boundsAtPointLat.extend(point); + if (boundsAtPointLatExtendedToIncludePoint.getSouthWest().getLngDeg() == bounds.getSouthWest().getLngDeg()) { + lngDegNearestOnBorder = bounds.getNorthEast().getLngDeg(); + } else { + lngDegNearestOnBorder = bounds.getSouthWest().getLngDeg(); + } + } + result = QuadTree.getLatLngDistance(point, new DegreePosition(latDegNearestOnBorder, lngDegNearestOnBorder)); + } + return result; + } + + /** + * Fetches all values within the bounds specified. Always returns a non-null iterable + * which may, however, be empty. + * + * @param bounds + * It is permissible for bounds to not even intersect with this node's {@link #bounds}. + * Then, however, the result will be empty. + */ + public Iterable get(Bounds bounds) { + final Iterable result; + if (this.bounds.intersects(bounds)) { + Set preResult = new HashSet<>(); + if (items != null) { + if (bounds.contains(this.bounds)) { + // all items must be contained, so no per-item check is required + preResult.addAll(items.values()); + } else { + for (final Entry item : items.entrySet()) { + if (bounds.contains(item.getKey())) { + preResult.add(item.getValue()); + } + } + } + } else { + for (final Node child : children) { + Util.addAll(child.get(bounds), preResult); + } + } + result = preResult; + } else { + result = Collections.emptySet(); + } + return result; + } + + @Override + public String toString() { + return ""+bounds+": "+(items==null?0:items.size())+" items in node, "+ + (children==null?0:children.length)+" child nodes"; + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/impl/QuadTreeLeaf.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/impl/QuadTreeLeaf.java deleted file mode 100644 index 1ea4950e23f..00000000000 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/impl/QuadTreeLeaf.java +++ /dev/null @@ -1,55 +0,0 @@ -// ********************************************************************** -// -// -// -// BBN Technologies -// 10 Moulton Street -// Cambridge, MA 02138 -// (617) 873-8000 -// -// Copyright (C) BBNT Solutions LLC. All rights reserved. -// -// -// ********************************************************************** -// -// $Source: -// /cvs/distapps/openmap/src/openmap/com/bbn/openmap/util/quadtree/QuadTreeLeaf.java,v -// $ -// $RCSfile$ -// $Revision: 45 $ -// $Date: 2008-06-08 22:04:43 +0200 (Sun, 08 Jun 2008) $ -// $Author: uhl $ -// -// ********************************************************************** - -package com.sap.sailing.domain.common.quadtree.impl; - -import java.io.Serializable; - -import com.sap.sailing.domain.common.Position; - -public class QuadTreeLeaf implements Serializable { - - static final long serialVersionUID = 7885745536157252519L; - - private Position point; - private T object; - - public QuadTreeLeaf(Position point, T obj) { - this.point = point; - this.object = obj; - } - - public Position getPoint() { - return point; - } - - public T getObject() { - return object; - } - - public String toString() { - return "QuadTreeLeaf at (" + point.getLatDeg() + ", " + point.getLngDeg() + ") with object " + getObject(); - } - -} \ No newline at end of file diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/impl/QuadTreeNode.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/impl/QuadTreeNode.java deleted file mode 100644 index ee6229d64a6..00000000000 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/quadtree/impl/QuadTreeNode.java +++ /dev/null @@ -1,407 +0,0 @@ -// ********************************************************************** -// -// -// -// BBN Technologies -// 10 Moulton Street -// Cambridge, MA 02138 -// (617) 873-8000 -// -// Copyright (C) BBNT Solutions LLC. All rights reserved. -// -// -// ********************************************************************** -// -// $Source$ -// $RCSfile$ -// $Revision: 101 $ -// $Date: 2008-07-09 23:09:42 +0200 (Wed, 09 Jul 2008) $ -// $Author: axel.uhl $ -// -// ********************************************************************** - -package com.sap.sailing.domain.common.quadtree.impl; - -import java.io.Serializable; -import java.util.Collection; -import java.util.Iterator; -import java.util.Vector; - -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.impl.DegreePosition; -import com.sap.sailing.domain.common.quadtree.QuadTree; - -/** - * The QuadTreeNode is the part of the QuadTree that either holds - * children nodes, or objects as leaves. Currently, the nodes that - * have children do not hold items that span across children - * boundaries, since this was designed to handle point data. - */ -public class QuadTreeNode implements Serializable { - - static final long serialVersionUID = -6111633198469889444L; - - private final static int NORTHWEST = 0; - private final static int NORTHEAST = 1; - private final static int SOUTHEAST = 2; - private final static int SOUTHWEST = 3; - public final static double NO_MIN_SIZE = -1; - - private Vector> items; - private QuadTreeNode[] children; - private int maxItems; - private double minSize; - private Bounds bounds; - - /** - * Added to avoid problems when a node is completely filled with a - * single point value. - */ - private boolean allTheSamePoint; - private Position firstPoint; - - /** - * Constructor to use if you are going to store the objects in - * lat/lon space, and there is really no smallest node size. - * - * @param maximumItems number of items to hold in a node before - * splitting itself into four children and redispensing the - * items into them. - */ - public QuadTreeNode(Bounds rect, int maximumItems) { - this(rect, maximumItems, NO_MIN_SIZE); - } - - /** - * Constructor to use if you are going to store the objects in x/y - * space, and there is a smallest node size because you don't want - * the nodes to be smaller than a group of pixels. - * - * @param north northern border of node coverage. - * @param west western border of node coverage. - * @param south southern border of node coverage. - * @param east eastern border of node coverage. - * @param maximumItems number of items to hold in a node before - * splitting itself into four children and redispensing the - * items into them. - * @param minimumSize the minimum difference between the - * boundaries of the node. - */ - public QuadTreeNode(Bounds rect, int maximumItems, double minimumSize) { - bounds = rect; - maxItems = maximumItems; - minSize = minimumSize; - items = new Vector>(); - } - - /** Return true if the node has children. */ - public boolean hasChildren() { - if (children != null) - return true; - else - return false; - } - - /** - * This method splits the node into four children, and disperses - * the items into the children. The split only happens if the - * boundary size of the node is larger than the minimum size (if - * we care). The items in this node are cleared after they are put - * into the children. - */ - @SuppressWarnings("unchecked") - protected void split() { - // Make sure we're bigger than the minimum, if we care, - if (minSize != NO_MIN_SIZE) { - if (Math.abs(bounds.getNorthEast().getLatDeg() - bounds.getSouthWest().getLatDeg()) < minSize - && Math.abs(bounds.getNorthEast().getLngDeg() - bounds.getSouthWest().getLngDeg()) < minSize) - return; - } - - double nsHalf = (bounds.getNorthEast().getLatDeg() + bounds.getSouthWest().getLatDeg()) / 2.0; - double ewHalf = (bounds.getNorthEast().getLngDeg() + bounds.getSouthWest().getLngDeg()) / 2.0; - children = new QuadTreeNode[4]; - children[NORTHWEST] = new QuadTreeNode(new Bounds(new DegreePosition(nsHalf, bounds.getSouthWest().getLngDeg()), new DegreePosition(bounds.getNorthEast().getLatDeg(), ewHalf)), maxItems); - children[NORTHEAST] = new QuadTreeNode(new Bounds(new DegreePosition(nsHalf, ewHalf), bounds.getNorthEast()), maxItems); - children[SOUTHEAST] = new QuadTreeNode(new Bounds(new DegreePosition(bounds.getSouthWest().getLatDeg(), ewHalf), new DegreePosition(nsHalf, bounds.getNorthEast().getLngDeg())), maxItems); - children[SOUTHWEST] = new QuadTreeNode(new Bounds(bounds.getSouthWest(), new DegreePosition(nsHalf, ewHalf)), maxItems); - Vector> temp = new Vector>(items); - items.removeAllElements(); - for (Iterator> i=temp.iterator(); i.hasNext(); ) { - put(i.next()); - } - } - - /** - * Get the node that covers a certain lat/lon pair. - * - * @param lat up-down location in QuadTree Grid (latitude, y) - * @param lon left-right location in QuadTree Grid (longitude, x) - * @return node if child covers the point, null if the point is - * out of range. - */ - protected QuadTreeNode getChild(Position point) { - if (bounds.contains(point)) { - if (children != null) { - for (int i = 0; i < children.length; i++) { - if (children[i].bounds.contains(point)) - return children[i].getChild(point); - } - } else - return this; // no children, lat, lon here... - } - return null; - } - - /** - * Add a object into the tree at a location. - * - * @param lat up-down location in QuadTree Grid (latitude, y) - * @param lon left-right location in QuadTree Grid (longitude, x) - * @param obj object to add to the tree. - * @return true if the pution worked. - * @throws RuntimeException in case the leaf's lat/lng lies outside of the node's bounds. - * This would typically be caused by the point being outside the whole quad tree's bounds. - */ - public void put(Position point, T obj) { - put(new QuadTreeLeaf(point, obj)); - } - - public void replace(Position point, T newObj) { - boolean inThis = false; - - if (children == null) { - inThis = true; - } else { - QuadTreeNode child = getChild(point); - if (child == null) { - inThis = true; - } else { - child.replace(point, newObj); - } - } - - if (inThis) { - items.clear(); - items.add(new QuadTreeLeaf(point, newObj)); - } - } - - /** - * Add a QuadTreeLeaf into the tree at a location. - * - * @param leaf object-location composite - * @return true if the pution worked. - * @throws RuntimeException in case the leaf's lat/lng lies outside of the node's bounds. - * This would typically be caused by the point being outside the whole quad tree's bounds. - */ - public void put(QuadTreeLeaf leaf) { - if (children == null) { - this.items.addElement(leaf); - if (this.items.size() == 1) { - this.allTheSamePoint = true; - this.firstPoint = leaf.getPoint(); - } else { - if (!this.firstPoint.equals(leaf.getPoint())) { - this.allTheSamePoint = false; - } - } - if (this.items.size() > maxItems && !this.allTheSamePoint) { - split(); - } - } else { - QuadTreeNode node = getChild(leaf.getPoint()); - if (node != null) { - node.put(leaf); - } else { - throw new RuntimeException("leaf "+leaf+" not contained in bounds (("+ - bounds.getSouthWest().getLatDeg()+", "+bounds.getSouthWest().getLngDeg()+"), ("+ - bounds.getNorthEast().getLatDeg()+", "+bounds.getNorthEast().getLngDeg()+"))"); - } - } - } - - /** - * Remove a object out of the tree at a location. - * - * @param lat up-down location in QuadTree Grid (latitude, y) - * @param lon left-right location in QuadTree Grid (longitude, x) - * @return the object removed, null if the object not found. - */ - public T remove(Position point, T obj) { - return remove(new QuadTreeLeaf(point, obj)); - } - - /** - * Remove a QuadTreeLeaf out of the tree at a location. - * - * @param leaf object-location composite - * @return the object removed, null if the object not found. - */ - public T remove(QuadTreeLeaf leaf) { - if (children == null) { - // This must be the node that has it... - for (int i = 0; i < items.size(); i++) { - QuadTreeLeaf qtl = items.elementAt(i); - if (leaf.getObject() == qtl.getObject()) { - items.removeElementAt(i); - return qtl.getObject(); - } - } - } else { - QuadTreeNode node = getChild(leaf.getPoint()); - if (node != null) { - return node.remove(leaf); - } - } - return null; - } - - /** Clear the tree below this node. */ - public void clear() { - this.items.removeAllElements(); - if (children != null) { - for (int i = 0; i < children.length; i++) { - children[i].clear(); - } - children = null; - } - } - - /** - * Get an object closest to a point. - * - * @param lat up-down location in QuadTree Grid (latitude, y) - * @param lon left-right location in QuadTree Grid (longitude, x) - * @return the object that matches the best distance, null if no - * object was found. - */ - public T get(Position point) { - return get(point, Double.POSITIVE_INFINITY); - } - - /** - * Get an object closest to a point. If there are children at - * this node, then the children are searched. The children are - * checked first, to see if they are closer than the best distance - * already found. If a closer object is found, bestDistance will - * be updated with a new Double object that has the new distance. - * - * @param lat up-down location in QuadTree Grid (latitude, y) - * @param lon left-right location in QuadTree Grid (longitude, x) - * @param withinDistance maximum get distance. The distance is given - * as the square root of the sum of - * the squares of the latitude and longitude differences, respectively. - * It therefore does not correspond to any distance in meters or - * any euclidian distance at all. However, it should be good enough - * (at least outside the polar regions, and in particular for smaller - * regions), and in particular to find minimum distances. - * @return the object that matches the best distance, null if no - * closer object was found. - */ - public T get(Position point, double withinDistance) { - return get(point, new MutableDistance(withinDistance)); - } - - /** - * Get an object closest to a point. If there are children at - * this node, then the children are searched. The children are - * checked first, to see if they are closer than the best distance - * already found. If a closer object is found, bestDistance will - * be updated with a new Double object that has the new distance. - * @param point location in QuadTree Grid - * @param bestDistance the closest distance of the object found so - * far. The distance is given as the square root of the sum of - * the squares of the latitude and longitude differences, respectively. - * It therefore does not correspond to any distance in meters or - * any euclidian distance at all. However, it should be good enough - * (at least outside the polar regions, and in particular for smaller - * regions), and in particular to find minimum distances. - * - * @return the object that matches the best distance, null if no - * closer object was found. - */ - public T get(Position point, MutableDistance bestDistance) { - T closest = null; - if (children == null) { - // This must be the node that has it... - for (QuadTreeLeaf qtl:items) { - double distance = QuadTree.getLatLngDistance(point, qtl.getPoint()); - - if (distance < bestDistance.value) { - bestDistance.value = distance; - closest = qtl.getObject(); - } - } - return closest; - } else { - // Check the distance of the bounds of the children, - // versus the bestDistance. If there is a boundary that - // is closer, then it is possible that another node has an - // object that is closer. - for (int i = 0; i < children.length; i++) { - double childDistance = borderDistance(children[i].bounds, point); - if (childDistance < bestDistance.value) { - T test = children[i].get(point, bestDistance); - if (test != null) - closest = test; - } - } - } - return closest; - } - - /** - * A utility method to figure out the closest distance of a bound's border - * to a point. If the point is inside the bounds, return 0. - * - * @return closest distance to the point. - */ - private static double borderDistance(Bounds bounds, Position point) { - - double nsdistance; - double ewdistance; - - if (bounds.getSouthWest().getLatDeg() <= point.getLatDeg() && point.getLatDeg() <= bounds.getNorthEast().getLatDeg()) { - nsdistance = 0; - } else { - nsdistance = Math.min((Math.abs(point.getLatDeg() - bounds.getNorthEast().getLatDeg())), (Math.abs(point.getLatDeg() - - bounds.getSouthWest().getLatDeg()))); - } - - if (bounds.getSouthWest().getLngDeg() <= point.getLngDeg() && point.getLngDeg() <= bounds.getNorthEast().getLngDeg()) { - ewdistance = 0; - } else { - ewdistance = Math.min((Math.abs(point.getLngDeg() - bounds.getNorthEast().getLngDeg())), - (Math.abs(point.getLngDeg() - bounds.getSouthWest().getLngDeg()))); - } - - double distance = Math.sqrt(nsdistance*nsdistance + ewdistance*ewdistance); - return distance; - } - - /** - * Get all the objects within a bounding box. - * - * @param rect boundary of area to fill. - * @param vector current vector of objects. - * @return updated Vector of objects. - */ - public Collection get(Bounds rect, Collection vector) { - if (children == null) { - for (Iterator> i=items.iterator(); i.hasNext(); ) { - QuadTreeLeaf qtl = i.next(); - if (rect.contains(new DegreePosition(qtl.getPoint().getLatDeg(), qtl.getPoint().getLngDeg()))) { - vector.add(qtl.getObject()); - } - } - } else { - for (int i = 0; i < children.length; i++) { - if (children[i].bounds.containsBounds(rect)) { - children[i].get(rect, vector); - } - } - } - return vector; - } -} \ No newline at end of file diff --git a/java/com.sap.sailing.domain.igtimiadapter/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.igtimiadapter/META-INF/MANIFEST.MF index f202d557b69..c6468dce9ce 100644 --- a/java/com.sap.sailing.domain.igtimiadapter/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.igtimiadapter/META-INF/MANIFEST.MF @@ -21,15 +21,16 @@ Import-Package: javax.ws.rs;version="1.1.1", javax.ws.rs.core;version="1.1.1", javax.ws.rs.ext;version="1.1.1", org.apache.commons.lang, + org.codehaus.jackson.jaxrs;version="1.9.13", org.eclipse.jetty.io;version="9.0.0", org.eclipse.jetty.util.component;version="9.0.4", + org.eclipse.jetty.util.ssl, org.eclipse.jetty.websocket.api;version="9.0.0", org.eclipse.jetty.websocket.api.annotations;version="9.0.0", org.eclipse.jetty.websocket.api.extensions;version="9.0.0", org.eclipse.jetty.websocket.client;version="9.0.0", org.eclipse.jetty.websocket.common;version="9.0.0", - org.osgi.framework;version="1.7.0", - org.codehaus.jackson.jaxrs;version="1.9.13" + org.osgi.framework;version="1.7.0" Web-ContextPath: /igtimi Bundle-Activator: com.sap.sailing.domain.igtimiadapter.impl.Activator Export-Package: com.sap.sailing.domain.igtimiadapter, diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/websocket/WebSocketConnectionManager.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/websocket/WebSocketConnectionManager.java index c259794d9c8..31f42a5b7d0 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/websocket/WebSocketConnectionManager.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/websocket/WebSocketConnectionManager.java @@ -10,6 +10,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketAdapter; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; @@ -53,6 +54,8 @@ public class WebSocketConnectionManager extends WebSocketAdapter implements Live private static final int LOG_EVERY_SO_MANY_MESSAGES = 100; + private static final long CONNECTION_TIMEOUT_IN_MILLIS = 5000; + public WebSocketConnectionManager(IgtimiConnectionFactoryImpl connectionFactory, Iterable deviceSerialNumbers, Account account) throws Exception { this.timer = new Timer("Timer for WebSocketConnectionManager for units "+deviceSerialNumbers+" and account "+account); this.deviceIds = deviceSerialNumbers; @@ -60,7 +63,7 @@ public class WebSocketConnectionManager extends WebSocketAdapter implements Live this.fixFactory = new FixFactory(); this.connectionFactory = connectionFactory; this.listeners = new ConcurrentHashMap<>(); - client = new WebSocketClient(); + client = new WebSocketClient(new SslContextFactory()); configurationMessage = connectionFactory.getWebSocketConfigurationMessage(account, deviceSerialNumbers); request = new ClientUpgradeRequest(); reconnect(); @@ -227,15 +230,15 @@ public class WebSocketConnectionManager extends WebSocketAdapter implements Live IOException lastException = null; for (URI uri : connectionFactory.getWebsocketServers()) { try { - if (uri.getScheme().equals("ws")) { - // as Jetty 9.0.4 currently doesn't seem to support wss, explicitly - // look for ws connectivity + if (uri.getScheme().equals("ws") || uri.getScheme().equals("wss")) { logger.log(Level.INFO, "Trying to connect to " + uri + " for " + this); client.start(); client.connect(this, uri, request); - logger.log(Level.INFO, "Successfully connected to " + uri + " for " + this); - lastException = null; - break; // successfully connected + if (waitForConnection(CONNECTION_TIMEOUT_IN_MILLIS)) { + logger.log(Level.INFO, "Successfully connected to " + uri + " for " + this); + lastException = null; + break; // successfully connected + } } } catch (IOException e) { logger.log(Level.SEVERE, "Couldn't connect to "+uri+" for "+this, e); diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_de.properties b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_de.properties index 867bffd121d..ccc7eb59aba 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_de.properties +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_de.properties @@ -1,5 +1,7 @@ trackingInvitationFor=Einladung zum Tracken für -scanQRCodeOrVisitUrlToRegisterAs=Installiere die SAP Sailing Smartphone Tracking App. Dann scanne den QRCode oder klicke den Link mit deinem Smartphone an um dich zu registrieren als +scanQRCodeOrVisitUrlToRegisterAs=Installiere die SAP Sailing Boat Tracker App. Dann scanne den QRCode oder klicke den Link mit deinem Smartphone an, um dich zu registrieren als welcomeTo=Willkommen zu +iOSUsers=iOS-Benutzer +androidUsers=Android-Benutzer alternativelyVisitThisLink=Alternativ kannst du, wenn du die App bereits installiert hast, diesen Link anklicken buoyTender=Bojenleger \ No newline at end of file diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_en.properties b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_en.properties index c3b6dc55932..a4bc26b7384 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_en.properties +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_en.properties @@ -1,5 +1,7 @@ trackingInvitationFor=Tracking Invitation for -scanQRCodeOrVisitUrlToRegisterAs=Make sure you have the SAP Sailing Smartphone Tracking App installed. Then scan the following QRCode or click the link using your smartphone to register as +scanQRCodeOrVisitUrlToRegisterAs=Make sure you have the SAP Sailing Boat Tracker App installed. Then scan the following QRCode or click the link using your smartphone to register as welcomeTo=Welcome to +iOSUsers=iOS Users +androidUsers=Android Users alternativelyVisitThisLink=Alternatively, when you have the app already installed, you can click this Link buoyTender=buoy tender \ No newline at end of file diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java index 976fe1ed89d..fb6db3bc447 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogTrackingAdapterImpl.java @@ -81,6 +81,12 @@ import com.sap.sse.util.impl.NonGwtUrlHelper; public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { private static final Logger logger = Logger.getLogger(RaceLogTrackingAdapterImpl.class.getName()); + /** + * The URL prefix that the iOS app will recognize as a deep link and pass anything after this prefix on + * to the app for analysis + */ + private static final String IOS_DEEP_LINK_PREFIX = "comsapsailingtracker://"; + private final DomainFactory domainFactory; private final long delayToLiveInMillis; @@ -342,10 +348,13 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter { // taken from http://www.tutorialspoint.com/javamail_api/javamail_api_send_inlineimage_in_email.htm BodyPart messageTextPart = new MimeBodyPart(); String htmlText = String.format("

%s %s

" + "

%s %s

" - + "
" + "%s", + + "
%s: %s
%s: %s", RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "welcomeTo"), leaderboardName, - RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "scanQRCodeOrVisitUrlToRegisterAs"), invitee, url, url, - RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "alternativelyVisitThisLink")); + RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "scanQRCodeOrVisitUrlToRegisterAs"), invitee, + url, RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "iOSUsers"), + IOS_DEEP_LINK_PREFIX+url, RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "alternativelyVisitThisLink"), + RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "androidUsers"), + url, RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "alternativelyVisitThisLink")); try { messageTextPart.setContent(htmlText, "text/html"); diff --git a/java/com.sap.sailing.domain.test/resources/IncrementalLeaderboardDTO.ser b/java/com.sap.sailing.domain.test/resources/IncrementalLeaderboardDTO.ser index 1fd2fb452b3..8b0225928aa 100755 Binary files a/java/com.sap.sailing.domain.test/resources/IncrementalLeaderboardDTO.ser and b/java/com.sap.sailing.domain.test/resources/IncrementalLeaderboardDTO.ser differ diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PositionTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PositionTest.java index 1d3ab650c1a..8302305296e 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PositionTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PositionTest.java @@ -20,6 +20,14 @@ import com.sap.sailing.domain.common.tracking.impl.CompactGPSFixMovingImpl; import com.sap.sse.common.impl.MillisecondsTimePoint; public class PositionTest { + @Test + public void testDistanceAcrossDateline() { + Position p1 = new DegreePosition(0, 179); + Position p2 = new DegreePosition(0, -179); + assertEquals(120., p1.getDistance(p2).getNauticalMiles(), 0.1); + assertEquals(120., p2.getDistance(p1).getNauticalMiles(), 0.1); + } + @Test public void testEqualityBetweenCompactAndVerbosePosition() { Position p1 = new DegreePosition(49.2, 008.3); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/QuadTreeTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/QuadTreeTest.java index d557698c362..1092a109018 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/QuadTreeTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/QuadTreeTest.java @@ -1,39 +1,1189 @@ package com.sap.sailing.domain.test; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; +import com.sap.sailing.domain.common.Bounds; import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.impl.BoundsImpl; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.quadtree.QuadTree; -import com.sap.sailing.domain.common.quadtree.impl.QuadTreeNode; +import com.sap.sailing.domain.common.quadtree.impl.Node; +import com.sap.sailing.domain.test.measurements.Measurement; +import com.sap.sailing.domain.test.measurements.MeasurementCase; +import com.sap.sailing.domain.test.measurements.MeasurementXMLFile; +import com.sap.sse.common.Util; public class QuadTreeTest { private class GLatLngQuadTree extends QuadTree { private static final long serialVersionUID = 386138477174564517L; - public GLatLngQuadTree(Position southWest, Position northEast, int i) { - super(southWest, northEast, i); + public GLatLngQuadTree(Position southWest, Position northEast, int maxItems) { + super(southWest, northEast, maxItems); } - public void put(Position x) { - super.put(x, x); + public Position put(Position x) { + return super.put(x, x); } } + + private DegreePosition p1; + private DegreePosition p2; + private DegreePosition p3; + private GLatLngQuadTree quadtree; + private static MeasurementXMLFile performanceReport; + private static MeasurementCase performanceReportCase; + + @BeforeClass + public static void setUpMeasurement() { + performanceReport = new MeasurementXMLFile(QuadTreeTest.class); + performanceReportCase = performanceReport.addCase(QuadTreeTest.class.getSimpleName()); + } - private static class QuadTreeWithPublicGetTop extends QuadTree { - private static final long serialVersionUID = -783622065160380333L; - @Override - public QuadTreeNode getTop() { - return super.getTop(); - } + @Before + public void setUp() { + quadtree = new GLatLngQuadTree(new DegreePosition(49, + 7), new DegreePosition(51, 9), /* maxItems */30); + quadtree.put(new DegreePosition(50.88505000000001, 7.995520000000001)); + quadtree.put(new DegreePosition(50.884930000000004, 7.996440000000001)); + quadtree.put(new DegreePosition(50.884840000000004, 7.99666)); + quadtree.put(new DegreePosition(50.8843, 7.997490000000001)); + quadtree.put(new DegreePosition(50.884240000000005, 7.997730000000001)); + quadtree.put(new DegreePosition(50.88423, 7.997980000000001)); + quadtree.put(new DegreePosition(50.8847, 7.999010000000001)); + quadtree.put(new DegreePosition(50.884600000000006, 7.999130000000001)); + quadtree.put(new DegreePosition(50.88448, 7.99938)); + quadtree.put(new DegreePosition(50.88421, 8.00014)); + quadtree.put(new DegreePosition(50.88382000000001, 8.00174)); + quadtree.put(new DegreePosition(50.88355000000001, 8.00263)); + quadtree.put(new DegreePosition(50.883570000000006, 8.00309)); + quadtree.put(new DegreePosition(50.883700000000005, 8.003530000000001)); + quadtree.put(new DegreePosition(50.88212000000001, 8.00539)); + quadtree.put(new DegreePosition(50.881620000000005, 8.006260000000001)); + quadtree.put(new DegreePosition(50.881080000000004, 8.007610000000001)); + quadtree.put(new DegreePosition(50.88103, 8.007900000000001)); + quadtree.put(new DegreePosition(50.88103, 8.009030000000001)); + quadtree.put(new DegreePosition(50.880880000000005, 8.009640000000001)); + quadtree.put(new DegreePosition(50.88047, 8.010860000000001)); + quadtree.put(new DegreePosition(50.880030000000005, 8.012500000000001)); + quadtree.put(new DegreePosition(50.87866, 8.016810000000001)); + quadtree.put(new DegreePosition(50.87832, 8.017790000000002)); + quadtree.put(new DegreePosition(50.878240000000005, 8.01793)); + quadtree.put(new DegreePosition(50.878150000000005, 8.01795)); + quadtree.put(new DegreePosition(50.87803, 8.01782)); + quadtree.put(new DegreePosition(50.87776, 8.01717)); + quadtree.put(new DegreePosition(50.8774, 8.01654)); + quadtree.put(new DegreePosition(50.87709, 8.01622)); + quadtree.put(new DegreePosition(50.87585000000001, 8.013850000000001)); + quadtree.put(new DegreePosition(50.87538000000001, 8.01304)); + quadtree.put(new DegreePosition(50.87402, 8.01111)); + quadtree.put(new DegreePosition(50.87028, 8.003960000000001)); + quadtree.put(new DegreePosition(50.869670000000006, 8.003160000000001)); + quadtree.put(new DegreePosition(50.86925, 8.002780000000001)); + quadtree.put(new DegreePosition(50.86858, 8.00244)); + quadtree.put(new DegreePosition(50.868, 8.002310000000001)); + quadtree.put(new DegreePosition(50.86739000000001, 8.00237)); + quadtree.put(new DegreePosition(50.867070000000005, 8.00248)); + quadtree.put(new DegreePosition(50.86674000000001, 8.00267)); + quadtree.put(new DegreePosition(50.865950000000005, 8.00333)); + quadtree.put(new DegreePosition(50.86545, 8.00398)); + quadtree.put(new DegreePosition(50.86459000000001, 8.0053)); + quadtree.put(new DegreePosition(50.86424, 8.00577)); + quadtree.put(new DegreePosition(50.863870000000006, 8.00618)); + quadtree.put(new DegreePosition(50.86352, 8.0065)); + quadtree.put(new DegreePosition(50.862970000000004, 8.006820000000001)); + quadtree.put(new DegreePosition(50.862370000000006, 8.00703)); + quadtree.put(new DegreePosition(50.86110000000001, 8.00722)); + quadtree.put(new DegreePosition(50.86061, 8.00721)); + quadtree.put(new DegreePosition(50.859860000000005, 8.00711)); + quadtree.put(new DegreePosition(50.85918, 8.006920000000001)); + quadtree.put(new DegreePosition(50.85824, 8.006120000000001)); + quadtree.put(new DegreePosition(50.85784, 8.005640000000001)); + quadtree.put(new DegreePosition(50.85757, 8.0052)); + quadtree.put(new DegreePosition(50.85752, 8.00496)); + quadtree.put(new DegreePosition(50.85752, 8.00463)); + quadtree.put(new DegreePosition(50.85963, 8.00209)); + quadtree.put(new DegreePosition(50.86046, 8.0008)); + quadtree.put(new DegreePosition(50.86081, 8.00008)); + quadtree.put(new DegreePosition(50.86113, 7.999230000000001)); + quadtree.put(new DegreePosition(50.86128, 7.998690000000001)); + quadtree.put(new DegreePosition(50.861430000000006, 7.997870000000001)); + quadtree.put(new DegreePosition(50.86149, 7.99706)); + quadtree.put(new DegreePosition(50.86149, 7.996150000000001)); + quadtree.put(new DegreePosition(50.86144, 7.9955300000000005)); + quadtree.put(new DegreePosition(50.86130000000001, 7.99476)); + quadtree.put(new DegreePosition(50.86106, 7.993900000000001)); + quadtree.put(new DegreePosition(50.860220000000005, 7.991490000000001)); + quadtree.put(new DegreePosition(50.859970000000004, 7.990360000000001)); + quadtree.put(new DegreePosition(50.859930000000006, 7.987780000000001)); + quadtree.put(new DegreePosition(50.859970000000004, 7.98737)); + quadtree.put(new DegreePosition(50.86059, 7.984820000000001)); + quadtree.put(new DegreePosition(50.86065000000001, 7.984260000000001)); + quadtree.put(new DegreePosition(50.860620000000004, 7.98359)); + quadtree.put(new DegreePosition(50.8605, 7.9831900000000005)); + quadtree.put(new DegreePosition(50.860310000000005, 7.98284)); + quadtree.put(new DegreePosition(50.86001, 7.982540000000001)); + quadtree.put(new DegreePosition(50.85971000000001, 7.982450000000001)); + quadtree.put(new DegreePosition(50.859410000000004, 7.98251)); + quadtree.put(new DegreePosition(50.85918, 7.982670000000001)); + quadtree.put(new DegreePosition(50.85898, 7.98291)); + quadtree.put(new DegreePosition(50.858790000000006, 7.983370000000001)); + quadtree.put(new DegreePosition(50.858520000000006, 7.9846)); + quadtree.put(new DegreePosition(50.8584, 7.98488)); + quadtree.put(new DegreePosition(50.858140000000006, 7.9851600000000005)); + quadtree.put(new DegreePosition(50.85756000000001, 7.9855100000000006)); + quadtree.put(new DegreePosition(50.85616, 7.985920000000001)); + quadtree.put(new DegreePosition(50.85548000000001, 7.98603)); + quadtree.put(new DegreePosition(50.85210000000001, 7.9863100000000005)); + quadtree.put(new DegreePosition(50.850970000000004, 7.98662)); + quadtree.put(new DegreePosition(50.849230000000006, 7.98739)); + quadtree.put(new DegreePosition(50.849030000000006, 7.98758)); + quadtree.put(new DegreePosition(50.84798000000001, 7.988300000000001)); + quadtree.put(new DegreePosition(50.84763, 7.988600000000001)); + quadtree.put(new DegreePosition(50.84695000000001, 7.989240000000001)); + quadtree.put(new DegreePosition(50.84591, 7.9903900000000005)); + quadtree.put(new DegreePosition(50.84514, 7.99143)); + quadtree.put(new DegreePosition(50.844370000000005, 7.992660000000001)); + quadtree.put(new DegreePosition(50.84355000000001, 7.994140000000001)); + quadtree.put(new DegreePosition(50.842800000000004, 7.9958800000000005)); + quadtree.put(new DegreePosition(50.84174, 7.99898)); + quadtree.put(new DegreePosition(50.84141, 7.9998700000000005)); + quadtree.put(new DegreePosition(50.84085, 8.00121)); + quadtree.put(new DegreePosition(50.84001000000001, 8.00285)); + quadtree.put(new DegreePosition(50.83823, 8.00586)); + quadtree.put(new DegreePosition(50.83747, 8.00736)); + quadtree.put(new DegreePosition(50.83702, 8.008410000000001)); + quadtree.put(new DegreePosition(50.83662, 8.00956)); + quadtree.put(new DegreePosition(50.83628, 8.01093)); + quadtree.put(new DegreePosition(50.83605000000001, 8.012160000000002)); + quadtree.put(new DegreePosition(50.83545, 8.016810000000001)); + quadtree.put(new DegreePosition(50.83491, 8.01938)); + quadtree.put(new DegreePosition(50.834250000000004, 8.02161)); + quadtree.put(new DegreePosition(50.83332000000001, 8.025210000000001)); + quadtree.put(new DegreePosition(50.8331, 8.0264)); + quadtree.put(new DegreePosition(50.83299, 8.027230000000001)); + quadtree.put(new DegreePosition(50.832890000000006, 8.028220000000001)); + quadtree.put(new DegreePosition(50.832820000000005, 8.0296)); + quadtree.put(new DegreePosition(50.83283, 8.030740000000002)); + quadtree.put(new DegreePosition(50.832950000000004, 8.03252)); + quadtree.put(new DegreePosition(50.833180000000006, 8.034790000000001)); + quadtree.put(new DegreePosition(50.833200000000005, 8.036140000000001)); + quadtree.put(new DegreePosition(50.83316000000001, 8.03739)); + quadtree.put(new DegreePosition(50.83312, 8.03801)); + quadtree.put(new DegreePosition(50.83299, 8.03907)); + quadtree.put(new DegreePosition(50.832730000000005, 8.04087)); + quadtree.put(new DegreePosition(50.83243, 8.042290000000001)); + quadtree.put(new DegreePosition(50.831880000000005, 8.04424)); + quadtree.put(new DegreePosition(50.83072000000001, 8.047070000000001)); + quadtree.put(new DegreePosition(50.8286, 8.051340000000001)); + quadtree.put(new DegreePosition(50.82793, 8.052610000000001)); + quadtree.put(new DegreePosition(50.827650000000006, 8.053410000000001)); + quadtree.put(new DegreePosition(50.827290000000005, 8.054680000000001)); + quadtree.put(new DegreePosition(50.82674, 8.05762)); + quadtree.put(new DegreePosition(50.826620000000005, 8.05882)); + quadtree.put(new DegreePosition(50.826550000000005, 8.06226)); + quadtree.put(new DegreePosition(50.82645, 8.0639)); + quadtree.put(new DegreePosition(50.8263, 8.065420000000001)); + quadtree.put(new DegreePosition(50.82591000000001, 8.06756)); + quadtree.put(new DegreePosition(50.825540000000004, 8.06884)); + quadtree.put(new DegreePosition(50.82486, 8.070490000000001)); + quadtree.put(new DegreePosition(50.82444, 8.07141)); + quadtree.put(new DegreePosition(50.82368, 8.07266)); + quadtree.put(new DegreePosition(50.821760000000005, 8.075130000000001)); + quadtree.put(new DegreePosition(50.821020000000004, 8.07614)); + quadtree.put(new DegreePosition(50.8205, 8.077)); + quadtree.put(new DegreePosition(50.819950000000006, 8.078240000000001)); + quadtree.put(new DegreePosition(50.81955000000001, 8.079450000000001)); + quadtree.put(new DegreePosition(50.819120000000005, 8.08103)); + quadtree.put(new DegreePosition(50.81859000000001, 8.08351)); + quadtree.put(new DegreePosition(50.818130000000004, 8.08783)); + quadtree.put(new DegreePosition(50.81796000000001, 8.088840000000001)); + quadtree.put(new DegreePosition(50.81774000000001, 8.08971)); + quadtree.put(new DegreePosition(50.817420000000006, 8.090670000000001)); + quadtree.put(new DegreePosition(50.81671000000001, 8.09238)); + quadtree.put(new DegreePosition(50.81647, 8.09285)); + quadtree.put(new DegreePosition(50.81557, 8.094370000000001)); + quadtree.put(new DegreePosition(50.814960000000006, 8.09521)); + quadtree.put(new DegreePosition(50.81427000000001, 8.09603)); + quadtree.put(new DegreePosition(50.81369, 8.0966)); + quadtree.put(new DegreePosition(50.81291, 8.09722)); + quadtree.put(new DegreePosition(50.81228, 8.09763)); + quadtree.put(new DegreePosition(50.81022, 8.098590000000002)); + quadtree.put(new DegreePosition(50.80885000000001, 8.098980000000001)); + quadtree.put(new DegreePosition(50.806900000000006, 8.09918)); + quadtree.put(new DegreePosition(50.805350000000004, 8.09904)); + quadtree.put(new DegreePosition(50.804460000000006, 8.098790000000001)); + quadtree.put(new DegreePosition(50.803180000000005, 8.09833)); + quadtree.put(new DegreePosition(50.79974000000001, 8.09685)); + quadtree.put(new DegreePosition(50.798950000000005, 8.09665)); + quadtree.put(new DegreePosition(50.797990000000006, 8.09664)); + quadtree.put(new DegreePosition(50.79683000000001, 8.096760000000002)); + quadtree.put(new DegreePosition(50.795730000000006, 8.09699)); + quadtree.put(new DegreePosition(50.794630000000005, 8.097520000000001)); + quadtree.put(new DegreePosition(50.79238, 8.099340000000002)); + quadtree.put(new DegreePosition(50.79153, 8.10016)); + quadtree.put(new DegreePosition(50.79068, 8.10111)); + quadtree.put(new DegreePosition(50.789970000000004, 8.10195)); + quadtree.put(new DegreePosition(50.78891, 8.103380000000001)); + quadtree.put(new DegreePosition(50.78833, 8.104230000000001)); + quadtree.put(new DegreePosition(50.78759, 8.10539)); + quadtree.put(new DegreePosition(50.786970000000004, 8.10646)); + quadtree.put(new DegreePosition(50.78605, 8.10823)); + quadtree.put(new DegreePosition(50.78566000000001, 8.10905)); + quadtree.put(new DegreePosition(50.784890000000004, 8.11107)); + quadtree.put(new DegreePosition(50.784670000000006, 8.11194)); + quadtree.put(new DegreePosition(50.78437, 8.11344)); + quadtree.put(new DegreePosition(50.784200000000006, 8.11463)); + quadtree.put(new DegreePosition(50.78407000000001, 8.11609)); + quadtree.put(new DegreePosition(50.78407000000001, 8.11761)); + quadtree.put(new DegreePosition(50.78412, 8.118820000000001)); + quadtree.put(new DegreePosition(50.784240000000004, 8.11996)); + quadtree.put(new DegreePosition(50.78457, 8.121500000000001)); + quadtree.put(new DegreePosition(50.785250000000005, 8.12428)); + quadtree.put(new DegreePosition(50.78555, 8.125720000000001)); + quadtree.put(new DegreePosition(50.78586000000001, 8.12766)); + quadtree.put(new DegreePosition(50.786060000000006, 8.129610000000001)); + quadtree.put(new DegreePosition(50.78614, 8.13101)); + quadtree.put(new DegreePosition(50.78609, 8.13326)); + quadtree.put(new DegreePosition(50.78598, 8.13451)); + quadtree.put(new DegreePosition(50.78578, 8.135900000000001)); + quadtree.put(new DegreePosition(50.7854, 8.137880000000001)); + quadtree.put(new DegreePosition(50.78511, 8.13907)); + quadtree.put(new DegreePosition(50.784330000000004, 8.14145)); + quadtree.put(new DegreePosition(50.783840000000005, 8.14282)); + quadtree.put(new DegreePosition(50.78352, 8.143510000000001)); + quadtree.put(new DegreePosition(50.78285, 8.1446)); + quadtree.put(new DegreePosition(50.781650000000006, 8.14638)); + quadtree.put(new DegreePosition(50.781330000000004, 8.146920000000001)); + quadtree.put(new DegreePosition(50.780280000000005, 8.148060000000001)); + quadtree.put(new DegreePosition(50.77908000000001, 8.149090000000001)); + quadtree.put(new DegreePosition(50.7781, 8.14973)); + quadtree.put(new DegreePosition(50.77716, 8.150160000000001)); + quadtree.put(new DegreePosition(50.77579, 8.15053)); + quadtree.put(new DegreePosition(50.773810000000005, 8.150770000000001)); + quadtree.put(new DegreePosition(50.772470000000006, 8.15085)); + quadtree.put(new DegreePosition(50.77046000000001, 8.15074)); + quadtree.put(new DegreePosition(50.76778, 8.15074)); + quadtree.put(new DegreePosition(50.76690000000001, 8.15079)); + quadtree.put(new DegreePosition(50.76615, 8.15093)); + quadtree.put(new DegreePosition(50.76543, 8.15116)); + quadtree.put(new DegreePosition(50.764480000000006, 8.151670000000001)); + quadtree.put(new DegreePosition(50.763690000000004, 8.15222)); + quadtree.put(new DegreePosition(50.76299, 8.152890000000001)); + quadtree.put(new DegreePosition(50.76243, 8.15352)); + quadtree.put(new DegreePosition(50.76185, 8.15432)); + quadtree.put(new DegreePosition(50.761190000000006, 8.15549)); + quadtree.put(new DegreePosition(50.760670000000005, 8.1567)); + quadtree.put(new DegreePosition(50.76019, 8.15832)); + quadtree.put(new DegreePosition(50.759910000000005, 8.15996)); + quadtree.put(new DegreePosition(50.75983, 8.16092)); + quadtree.put(new DegreePosition(50.75974, 8.16243)); + quadtree.put(new DegreePosition(50.759780000000006, 8.164000000000001)); + quadtree.put(new DegreePosition(50.75992, 8.16568)); + quadtree.put(new DegreePosition(50.75997, 8.168050000000001)); + quadtree.put(new DegreePosition(50.75992, 8.16923)); + quadtree.put(new DegreePosition(50.759820000000005, 8.170480000000001)); + quadtree.put(new DegreePosition(50.75959, 8.172130000000001)); + quadtree.put(new DegreePosition(50.759190000000004, 8.17365)); + quadtree.put(new DegreePosition(50.75893000000001, 8.17436)); + quadtree.put(new DegreePosition(50.75638000000001, 8.18037)); + quadtree.put(new DegreePosition(50.755810000000004, 8.181840000000001)); + quadtree.put(new DegreePosition(50.755410000000005, 8.18307)); + quadtree.put(new DegreePosition(50.755160000000004, 8.184000000000001)); + quadtree.put(new DegreePosition(50.75482, 8.185960000000001)); + quadtree.put(new DegreePosition(50.75464, 8.18824)); + quadtree.put(new DegreePosition(50.75457, 8.19096)); + quadtree.put(new DegreePosition(50.754670000000004, 8.193750000000001)); + quadtree.put(new DegreePosition(50.75482, 8.19589)); + quadtree.put(new DegreePosition(50.75533, 8.20124)); + quadtree.put(new DegreePosition(50.75542, 8.202910000000001)); + quadtree.put(new DegreePosition(50.755390000000006, 8.20532)); + quadtree.put(new DegreePosition(50.75526000000001, 8.206850000000001)); + quadtree.put(new DegreePosition(50.75502, 8.20803)); + quadtree.put(new DegreePosition(50.75486, 8.20864)); + quadtree.put(new DegreePosition(50.75448, 8.209750000000001)); + quadtree.put(new DegreePosition(50.75424, 8.210320000000001)); + quadtree.put(new DegreePosition(50.75347000000001, 8.211730000000001)); + quadtree.put(new DegreePosition(50.75301, 8.21236)); + quadtree.put(new DegreePosition(50.75206000000001, 8.213370000000001)); + quadtree.put(new DegreePosition(50.74982000000001, 8.215440000000001)); + quadtree.put(new DegreePosition(50.748900000000006, 8.21657)); + quadtree.put(new DegreePosition(50.74851, 8.21718)); + quadtree.put(new DegreePosition(50.748200000000004, 8.21775)); + quadtree.put(new DegreePosition(50.74777, 8.21879)); + quadtree.put(new DegreePosition(50.7475, 8.21968)); + quadtree.put(new DegreePosition(50.747240000000005, 8.220880000000001)); + quadtree.put(new DegreePosition(50.7471, 8.22189)); + quadtree.put(new DegreePosition(50.74703, 8.222710000000001)); + quadtree.put(new DegreePosition(50.746990000000004, 8.22573)); + quadtree.put(new DegreePosition(50.74696, 8.22653)); + quadtree.put(new DegreePosition(50.74687, 8.22747)); + quadtree.put(new DegreePosition(50.746680000000005, 8.228530000000001)); + quadtree.put(new DegreePosition(50.746410000000004, 8.22976)); + quadtree.put(new DegreePosition(50.74609, 8.23095)); + quadtree.put(new DegreePosition(50.74515, 8.23367)); + quadtree.put(new DegreePosition(50.744620000000005, 8.235650000000001)); + quadtree.put(new DegreePosition(50.744310000000006, 8.237150000000002)); + quadtree.put(new DegreePosition(50.744150000000005, 8.238480000000001)); + quadtree.put(new DegreePosition(50.7441, 8.240540000000001)); + quadtree.put(new DegreePosition(50.74418000000001, 8.24193)); + quadtree.put(new DegreePosition(50.74445000000001, 8.24365)); + quadtree.put(new DegreePosition(50.745020000000004, 8.245940000000001)); + quadtree.put(new DegreePosition(50.74539000000001, 8.24769)); + quadtree.put(new DegreePosition(50.74557, 8.24881)); + quadtree.put(new DegreePosition(50.74562, 8.249590000000001)); + quadtree.put(new DegreePosition(50.7456, 8.250290000000001)); + quadtree.put(new DegreePosition(50.74542, 8.25223)); + quadtree.put(new DegreePosition(50.74519, 8.253340000000001)); + quadtree.put(new DegreePosition(50.7449, 8.2543)); + quadtree.put(new DegreePosition(50.74461, 8.25505)); + quadtree.put(new DegreePosition(50.74306000000001, 8.25831)); + quadtree.put(new DegreePosition(50.74279000000001, 8.25904)); + quadtree.put(new DegreePosition(50.74255, 8.25988)); + quadtree.put(new DegreePosition(50.742270000000005, 8.2612)); + quadtree.put(new DegreePosition(50.74213, 8.262070000000001)); + quadtree.put(new DegreePosition(50.742070000000005, 8.26314)); + quadtree.put(new DegreePosition(50.742090000000005, 8.26567)); + quadtree.put(new DegreePosition(50.741980000000005, 8.26735)); + quadtree.put(new DegreePosition(50.74181, 8.2687)); + quadtree.put(new DegreePosition(50.741670000000006, 8.26931)); + quadtree.put(new DegreePosition(50.741080000000004, 8.27088)); + quadtree.put(new DegreePosition(50.74072, 8.27158)); + quadtree.put(new DegreePosition(50.740030000000004, 8.27257)); + quadtree.put(new DegreePosition(50.73962, 8.273000000000001)); + quadtree.put(new DegreePosition(50.73875, 8.27365)); + quadtree.put(new DegreePosition(50.73783, 8.27405)); + quadtree.put(new DegreePosition(50.73745, 8.27416)); + quadtree.put(new DegreePosition(50.736830000000005, 8.27424)); + quadtree.put(new DegreePosition(50.734120000000004, 8.274090000000001)); + quadtree.put(new DegreePosition(50.73353, 8.27411)); + quadtree.put(new DegreePosition(50.732890000000005, 8.27424)); + quadtree.put(new DegreePosition(50.73174, 8.274790000000001)); + quadtree.put(new DegreePosition(50.73114, 8.27524)); + quadtree.put(new DegreePosition(50.7306, 8.27574)); + quadtree.put(new DegreePosition(50.72831000000001, 8.27865)); + quadtree.put(new DegreePosition(50.727430000000005, 8.279480000000001)); + quadtree.put(new DegreePosition(50.72471, 8.28134)); + quadtree.put(new DegreePosition(50.72368, 8.282210000000001)); + quadtree.put(new DegreePosition(50.7229, 8.28307)); + quadtree.put(new DegreePosition(50.72126, 8.28542)); + quadtree.put(new DegreePosition(50.72023, 8.28669)); + quadtree.put(new DegreePosition(50.71963, 8.28725)); + quadtree.put(new DegreePosition(50.71885, 8.287880000000001)); + quadtree.put(new DegreePosition(50.718120000000006, 8.28832)); + quadtree.put(new DegreePosition(50.7173, 8.2887)); + quadtree.put(new DegreePosition(50.71656, 8.28897)); + quadtree.put(new DegreePosition(50.71553, 8.28916)); + quadtree.put(new DegreePosition(50.71408, 8.28923)); + quadtree.put(new DegreePosition(50.713060000000006, 8.28913)); + quadtree.put(new DegreePosition(50.709050000000005, 8.28805)); + quadtree.put(new DegreePosition(50.70792, 8.28781)); + quadtree.put(new DegreePosition(50.70673000000001, 8.28772)); + quadtree.put(new DegreePosition(50.705130000000004, 8.28782)); + quadtree.put(new DegreePosition(50.70335000000001, 8.28827)); + quadtree.put(new DegreePosition(50.70219, 8.28876)); + quadtree.put(new DegreePosition(50.70096, 8.289520000000001)); + quadtree.put(new DegreePosition(50.697030000000005, 8.29296)); + quadtree.put(new DegreePosition(50.69567000000001, 8.294)); + quadtree.put(new DegreePosition(50.69485, 8.294540000000001)); + quadtree.put(new DegreePosition(50.69411, 8.29495)); + quadtree.put(new DegreePosition(50.6933, 8.295300000000001)); + quadtree.put(new DegreePosition(50.692690000000006, 8.295490000000001)); + quadtree.put(new DegreePosition(50.692040000000006, 8.29561)); + quadtree.put(new DegreePosition(50.69153000000001, 8.29564)); + quadtree.put(new DegreePosition(50.69035, 8.295390000000001)); + quadtree.put(new DegreePosition(50.68981, 8.29517)); + quadtree.put(new DegreePosition(50.68932, 8.2949)); + quadtree.put(new DegreePosition(50.688970000000005, 8.29465)); + quadtree.put(new DegreePosition(50.68847, 8.29418)); + quadtree.put(new DegreePosition(50.687760000000004, 8.293230000000001)); + quadtree.put(new DegreePosition(50.68713, 8.29218)); + quadtree.put(new DegreePosition(50.68657, 8.29096)); + quadtree.put(new DegreePosition(50.68558, 8.289000000000001)); + quadtree.put(new DegreePosition(50.684940000000005, 8.28805)); + quadtree.put(new DegreePosition(50.684430000000006, 8.28739)); + quadtree.put(new DegreePosition(50.682970000000005, 8.2859)); + quadtree.put(new DegreePosition(50.68198, 8.28515)); + quadtree.put(new DegreePosition(50.68065000000001, 8.28434)); + quadtree.put(new DegreePosition(50.679410000000004, 8.28377)); + quadtree.put(new DegreePosition(50.67868000000001, 8.28353)); + quadtree.put(new DegreePosition(50.67734, 8.283330000000001)); + quadtree.put(new DegreePosition(50.676190000000005, 8.28331)); + quadtree.put(new DegreePosition(50.674110000000006, 8.28378)); + quadtree.put(new DegreePosition(50.672760000000004, 8.28443)); + quadtree.put(new DegreePosition(50.67183000000001, 8.28505)); + quadtree.put(new DegreePosition(50.671130000000005, 8.28561)); + quadtree.put(new DegreePosition(50.67058, 8.286140000000001)); + quadtree.put(new DegreePosition(50.66993, 8.28687)); + quadtree.put(new DegreePosition(50.66884, 8.28832)); + quadtree.put(new DegreePosition(50.66789000000001, 8.28984)); + quadtree.put(new DegreePosition(50.667030000000004, 8.291440000000001)); + quadtree.put(new DegreePosition(50.66601000000001, 8.29364)); + quadtree.put(new DegreePosition(50.665350000000004, 8.295240000000002)); + quadtree.put(new DegreePosition(50.66480000000001, 8.29676)); + quadtree.put(new DegreePosition(50.66442000000001, 8.29808)); + quadtree.put(new DegreePosition(50.66405, 8.299660000000001)); + quadtree.put(new DegreePosition(50.663720000000005, 8.30133)); + quadtree.put(new DegreePosition(50.66330000000001, 8.304300000000001)); + quadtree.put(new DegreePosition(50.66290000000001, 8.307960000000001)); + quadtree.put(new DegreePosition(50.662670000000006, 8.309560000000001)); + quadtree.put(new DegreePosition(50.6623, 8.31123)); + quadtree.put(new DegreePosition(50.66174, 8.313260000000001)); + quadtree.put(new DegreePosition(50.660970000000006, 8.315370000000001)); + quadtree.put(new DegreePosition(50.660320000000006, 8.316650000000001)); + quadtree.put(new DegreePosition(50.659740000000006, 8.317590000000001)); + quadtree.put(new DegreePosition(50.65899, 8.318570000000001)); + quadtree.put(new DegreePosition(50.658210000000004, 8.3193)); + quadtree.put(new DegreePosition(50.657070000000004, 8.31994)); + quadtree.put(new DegreePosition(50.65578000000001, 8.320300000000001)); + quadtree.put(new DegreePosition(50.65391, 8.320400000000001)); + quadtree.put(new DegreePosition(50.65328, 8.320500000000001)); + quadtree.put(new DegreePosition(50.65240000000001, 8.32074)); + quadtree.put(new DegreePosition(50.65128000000001, 8.32118)); + quadtree.put(new DegreePosition(50.65091, 8.32137)); + quadtree.put(new DegreePosition(50.6465, 8.32409)); + quadtree.put(new DegreePosition(50.645360000000004, 8.32454)); + quadtree.put(new DegreePosition(50.644740000000006, 8.32469)); + quadtree.put(new DegreePosition(50.64193, 8.32482)); + quadtree.put(new DegreePosition(50.639920000000004, 8.32511)); + quadtree.put(new DegreePosition(50.63812, 8.325650000000001)); + quadtree.put(new DegreePosition(50.63673000000001, 8.326400000000001)); + quadtree.put(new DegreePosition(50.635740000000006, 8.32704)); + quadtree.put(new DegreePosition(50.634570000000004, 8.32797)); + quadtree.put(new DegreePosition(50.63354, 8.32891)); + quadtree.put(new DegreePosition(50.63253, 8.32995)); + quadtree.put(new DegreePosition(50.629650000000005, 8.33336)); + quadtree.put(new DegreePosition(50.62903000000001, 8.33422)); + quadtree.put(new DegreePosition(50.628550000000004, 8.335)); + quadtree.put(new DegreePosition(50.62765, 8.33667)); + quadtree.put(new DegreePosition(50.627250000000004, 8.33755)); + quadtree.put(new DegreePosition(50.626760000000004, 8.33876)); + quadtree.put(new DegreePosition(50.62648, 8.33961)); + quadtree.put(new DegreePosition(50.62586, 8.341890000000001)); + quadtree.put(new DegreePosition(50.62556000000001, 8.34319)); + quadtree.put(new DegreePosition(50.62519, 8.34521)); + quadtree.put(new DegreePosition(50.624990000000004, 8.34656)); + quadtree.put(new DegreePosition(50.62485, 8.34781)); + quadtree.put(new DegreePosition(50.6246, 8.3511)); + quadtree.put(new DegreePosition(50.62435000000001, 8.352910000000001)); + quadtree.put(new DegreePosition(50.624050000000004, 8.3544)); + quadtree.put(new DegreePosition(50.623810000000006, 8.35543)); + quadtree.put(new DegreePosition(50.62366, 8.35593)); + quadtree.put(new DegreePosition(50.622240000000005, 8.359910000000001)); + quadtree.put(new DegreePosition(50.621770000000005, 8.36156)); + quadtree.put(new DegreePosition(50.62156, 8.362480000000001)); + quadtree.put(new DegreePosition(50.621230000000004, 8.3644)); + quadtree.put(new DegreePosition(50.62078, 8.36975)); + quadtree.put(new DegreePosition(50.620520000000006, 8.37114)); + quadtree.put(new DegreePosition(50.620160000000006, 8.37249)); + quadtree.put(new DegreePosition(50.61988, 8.373320000000001)); + quadtree.put(new DegreePosition(50.61932, 8.374690000000001)); + quadtree.put(new DegreePosition(50.61887, 8.375950000000001)); + quadtree.put(new DegreePosition(50.618570000000005, 8.37725)); + quadtree.put(new DegreePosition(50.61827, 8.37898)); + quadtree.put(new DegreePosition(50.618170000000006, 8.380270000000001)); + quadtree.put(new DegreePosition(50.61816, 8.382190000000001)); + quadtree.put(new DegreePosition(50.618520000000004, 8.389460000000001)); + quadtree.put(new DegreePosition(50.618570000000005, 8.39309)); + quadtree.put(new DegreePosition(50.618500000000004, 8.394480000000001)); + quadtree.put(new DegreePosition(50.618410000000004, 8.395470000000001)); + quadtree.put(new DegreePosition(50.61824000000001, 8.39675)); + quadtree.put(new DegreePosition(50.618080000000006, 8.39764)); + quadtree.put(new DegreePosition(50.61777000000001, 8.39906)); + quadtree.put(new DegreePosition(50.61713, 8.40116)); + quadtree.put(new DegreePosition(50.61679, 8.40215)); + quadtree.put(new DegreePosition(50.612660000000005, 8.413)); + quadtree.put(new DegreePosition(50.61168000000001, 8.415470000000001)); + quadtree.put(new DegreePosition(50.611430000000006, 8.41623)); + quadtree.put(new DegreePosition(50.61102, 8.41787)); + quadtree.put(new DegreePosition(50.61056000000001, 8.42032)); + quadtree.put(new DegreePosition(50.61019, 8.4238)); + quadtree.put(new DegreePosition(50.60992, 8.42535)); + quadtree.put(new DegreePosition(50.60947, 8.42707)); + quadtree.put(new DegreePosition(50.60904000000001, 8.42833)); + quadtree.put(new DegreePosition(50.60857000000001, 8.42956)); + quadtree.put(new DegreePosition(50.60792000000001, 8.43158)); + quadtree.put(new DegreePosition(50.60761, 8.432680000000001)); + quadtree.put(new DegreePosition(50.60723, 8.434370000000001)); + quadtree.put(new DegreePosition(50.60701, 8.43645)); + quadtree.put(new DegreePosition(50.60698000000001, 8.43796)); + quadtree.put(new DegreePosition(50.6071, 8.44045)); + quadtree.put(new DegreePosition(50.60718000000001, 8.443100000000001)); + quadtree.put(new DegreePosition(50.60717, 8.444220000000001)); + quadtree.put(new DegreePosition(50.607020000000006, 8.446140000000002)); + quadtree.put(new DegreePosition(50.606770000000004, 8.44772)); + quadtree.put(new DegreePosition(50.60647, 8.44902)); + quadtree.put(new DegreePosition(50.606100000000005, 8.450230000000001)); + quadtree.put(new DegreePosition(50.60551, 8.45195)); + quadtree.put(new DegreePosition(50.605030000000006, 8.45358)); + quadtree.put(new DegreePosition(50.60465000000001, 8.45495)); + quadtree.put(new DegreePosition(50.60445000000001, 8.455950000000001)); + quadtree.put(new DegreePosition(50.60427000000001, 8.457230000000001)); + quadtree.put(new DegreePosition(50.60421, 8.458060000000001)); + quadtree.put(new DegreePosition(50.60419, 8.45945)); + quadtree.put(new DegreePosition(50.6043, 8.46292)); + quadtree.put(new DegreePosition(50.604290000000006, 8.46471)); + quadtree.put(new DegreePosition(50.60416000000001, 8.4664)); + quadtree.put(new DegreePosition(50.603930000000005, 8.468)); + quadtree.put(new DegreePosition(50.603790000000004, 8.46873)); + quadtree.put(new DegreePosition(50.603570000000005, 8.469650000000001)); + quadtree.put(new DegreePosition(50.60318, 8.471)); + quadtree.put(new DegreePosition(50.60242, 8.473030000000001)); + quadtree.put(new DegreePosition(50.60193, 8.474110000000001)); + quadtree.put(new DegreePosition(50.60146, 8.47493)); + quadtree.put(new DegreePosition(50.600730000000006, 8.47606)); + quadtree.put(new DegreePosition(50.599160000000005, 8.478340000000001)); + quadtree.put(new DegreePosition(50.59787000000001, 8.480580000000002)); + quadtree.put(new DegreePosition(50.59725, 8.48202)); + quadtree.put(new DegreePosition(50.59673, 8.48366)); + quadtree.put(new DegreePosition(50.596270000000004, 8.48583)); + quadtree.put(new DegreePosition(50.59617, 8.48643)); + quadtree.put(new DegreePosition(50.596000000000004, 8.48804)); + quadtree.put(new DegreePosition(50.59592000000001, 8.490260000000001)); + quadtree.put(new DegreePosition(50.59588, 8.49361)); + quadtree.put(new DegreePosition(50.59581000000001, 8.49463)); + quadtree.put(new DegreePosition(50.595710000000004, 8.49549)); + quadtree.put(new DegreePosition(50.59539, 8.49712)); + quadtree.put(new DegreePosition(50.59505000000001, 8.49849)); + quadtree.put(new DegreePosition(50.594030000000004, 8.50145)); + quadtree.put(new DegreePosition(50.59357000000001, 8.50289)); + quadtree.put(new DegreePosition(50.593340000000005, 8.50379)); + quadtree.put(new DegreePosition(50.593160000000005, 8.50468)); + quadtree.put(new DegreePosition(50.59292000000001, 8.506120000000001)); + quadtree.put(new DegreePosition(50.59279, 8.50716)); + quadtree.put(new DegreePosition(50.592740000000006, 8.50793)); + quadtree.put(new DegreePosition(50.592740000000006, 8.50988)); + quadtree.put(new DegreePosition(50.59282, 8.51248)); + quadtree.put(new DegreePosition(50.59273, 8.513850000000001)); + quadtree.put(new DegreePosition(50.59245000000001, 8.5157)); + quadtree.put(new DegreePosition(50.592110000000005, 8.51736)); + quadtree.put(new DegreePosition(50.59109, 8.52072)); + quadtree.put(new DegreePosition(50.58995, 8.52393)); + quadtree.put(new DegreePosition(50.58919, 8.525780000000001)); + quadtree.put(new DegreePosition(50.588350000000005, 8.52753)); + quadtree.put(new DegreePosition(50.586940000000006, 8.53031)); + quadtree.put(new DegreePosition(50.585660000000004, 8.5327)); + quadtree.put(new DegreePosition(50.58467, 8.534270000000001)); + quadtree.put(new DegreePosition(50.582950000000004, 8.53668)); + quadtree.put(new DegreePosition(50.58055, 8.539750000000002)); + quadtree.put(new DegreePosition(50.57909, 8.541250000000002)); + quadtree.put(new DegreePosition(50.578230000000005, 8.542060000000001)); + quadtree.put(new DegreePosition(50.57721, 8.542910000000001)); + quadtree.put(new DegreePosition(50.57509, 8.544360000000001)); + quadtree.put(new DegreePosition(50.572190000000006, 8.546000000000001)); + quadtree.put(new DegreePosition(50.56996, 8.54697)); + quadtree.put(new DegreePosition(50.567350000000005, 8.548210000000001)); + quadtree.put(new DegreePosition(50.566590000000005, 8.548670000000001)); + quadtree.put(new DegreePosition(50.565740000000005, 8.5493)); + quadtree.put(new DegreePosition(50.56519, 8.54981)); + quadtree.put(new DegreePosition(50.56466, 8.55035)); + quadtree.put(new DegreePosition(50.56418000000001, 8.551010000000002)); + quadtree.put(new DegreePosition(50.56376, 8.551630000000001)); + quadtree.put(new DegreePosition(50.56293, 8.55311)); + quadtree.put(new DegreePosition(50.56206, 8.55527)); + quadtree.put(new DegreePosition(50.56136000000001, 8.55738)); + quadtree.put(new DegreePosition(50.56071000000001, 8.55915)); + quadtree.put(new DegreePosition(50.560230000000004, 8.560160000000002)); + quadtree.put(new DegreePosition(50.55978, 8.560970000000001)); + quadtree.put(new DegreePosition(50.55922, 8.56179)); + quadtree.put(new DegreePosition(50.558910000000004, 8.56215)); + quadtree.put(new DegreePosition(50.55834, 8.562700000000001)); + quadtree.put(new DegreePosition(50.55731, 8.56347)); + quadtree.put(new DegreePosition(50.55677000000001, 8.56377)); + quadtree.put(new DegreePosition(50.55624, 8.563960000000002)); + quadtree.put(new DegreePosition(50.55556000000001, 8.564110000000001)); + quadtree.put(new DegreePosition(50.55353, 8.564390000000001)); + quadtree.put(new DegreePosition(50.552040000000005, 8.56466)); + quadtree.put(new DegreePosition(50.55109, 8.56494)); + quadtree.put(new DegreePosition(50.550160000000005, 8.56536)); + quadtree.put(new DegreePosition(50.549730000000004, 8.565620000000001)); + quadtree.put(new DegreePosition(50.54919, 8.56601)); + quadtree.put(new DegreePosition(50.548460000000006, 8.56667)); + quadtree.put(new DegreePosition(50.54798, 8.567250000000001)); + quadtree.put(new DegreePosition(50.5469, 8.56872)); + quadtree.put(new DegreePosition(50.54536, 8.57126)); + quadtree.put(new DegreePosition(50.542260000000006, 8.576590000000001)); + quadtree.put(new DegreePosition(50.541430000000005, 8.577720000000001)); + quadtree.put(new DegreePosition(50.540580000000006, 8.57877)); + quadtree.put(new DegreePosition(50.539480000000005, 8.57994)); + quadtree.put(new DegreePosition(50.537870000000005, 8.58139)); + quadtree.put(new DegreePosition(50.53676, 8.582270000000001)); + quadtree.put(new DegreePosition(50.534110000000005, 8.58411)); + quadtree.put(new DegreePosition(50.53333000000001, 8.58478)); + quadtree.put(new DegreePosition(50.53266000000001, 8.585450000000002)); + quadtree.put(new DegreePosition(50.5317, 8.586540000000001)); + quadtree.put(new DegreePosition(50.53099, 8.587610000000002)); + quadtree.put(new DegreePosition(50.53056, 8.58835)); + quadtree.put(new DegreePosition(50.530080000000005, 8.58932)); + quadtree.put(new DegreePosition(50.52947, 8.590900000000001)); + quadtree.put(new DegreePosition(50.529050000000005, 8.592310000000001)); + quadtree.put(new DegreePosition(50.528800000000004, 8.593430000000001)); + quadtree.put(new DegreePosition(50.528620000000004, 8.594510000000001)); + quadtree.put(new DegreePosition(50.52828, 8.597700000000001)); + quadtree.put(new DegreePosition(50.527750000000005, 8.60692)); + quadtree.put(new DegreePosition(50.52731000000001, 8.61212)); + quadtree.put(new DegreePosition(50.526300000000006, 8.62259)); + quadtree.put(new DegreePosition(50.525800000000004, 8.626420000000001)); + quadtree.put(new DegreePosition(50.52514000000001, 8.630510000000001)); + quadtree.put(new DegreePosition(50.52481, 8.63277)); + quadtree.put(new DegreePosition(50.524010000000004, 8.6376)); + quadtree.put(new DegreePosition(50.52364000000001, 8.64067)); + quadtree.put(new DegreePosition(50.522420000000004, 8.645380000000001)); + quadtree.put(new DegreePosition(50.52181, 8.64794)); + quadtree.put(new DegreePosition(50.521530000000006, 8.648950000000001)); + quadtree.put(new DegreePosition(50.52031, 8.654100000000001)); + quadtree.put(new DegreePosition(50.51888, 8.65944)); + quadtree.put(new DegreePosition(50.51749, 8.66398)); + quadtree.put(new DegreePosition(50.51699000000001, 8.66531)); + quadtree.put(new DegreePosition(50.516650000000006, 8.666110000000002)); + quadtree.put(new DegreePosition(50.515150000000006, 8.66932)); + quadtree.put(new DegreePosition(50.5146, 8.67041)); + quadtree.put(new DegreePosition(50.513670000000005, 8.6721)); + quadtree.put(new DegreePosition(50.51285000000001, 8.673440000000001)); + quadtree.put(new DegreePosition(50.511810000000004, 8.674930000000002)); + quadtree.put(new DegreePosition(50.510630000000006, 8.67633)); + quadtree.put(new DegreePosition(50.50940000000001, 8.677700000000002)); + quadtree.put(new DegreePosition(50.508810000000004, 8.67824)); + quadtree.put(new DegreePosition(50.50750000000001, 8.67932)); + quadtree.put(new DegreePosition(50.50681, 8.679810000000002)); + quadtree.put(new DegreePosition(50.50526000000001, 8.680760000000001)); + quadtree.put(new DegreePosition(50.504470000000005, 8.68116)); + quadtree.put(new DegreePosition(50.50274, 8.681830000000001)); + quadtree.put(new DegreePosition(50.50198, 8.682070000000001)); + quadtree.put(new DegreePosition(50.498810000000006, 8.682690000000001)); + quadtree.put(new DegreePosition(50.49728, 8.68313)); + quadtree.put(new DegreePosition(50.49476000000001, 8.68368)); + quadtree.put(new DegreePosition(50.49369, 8.684050000000001)); + quadtree.put(new DegreePosition(50.49269, 8.68449)); + quadtree.put(new DegreePosition(50.49157, 8.68506)); + quadtree.put(new DegreePosition(50.490520000000004, 8.685740000000001)); + quadtree.put(new DegreePosition(50.48989, 8.68622)); + quadtree.put(new DegreePosition(50.48901000000001, 8.686950000000001)); + quadtree.put(new DegreePosition(50.488040000000005, 8.687940000000001)); + quadtree.put(new DegreePosition(50.487010000000005, 8.68913)); + quadtree.put(new DegreePosition(50.48583000000001, 8.69068)); + quadtree.put(new DegreePosition(50.484260000000006, 8.69318)); + quadtree.put(new DegreePosition(50.483340000000005, 8.69505)); + quadtree.put(new DegreePosition(50.48272000000001, 8.69658)); + quadtree.put(new DegreePosition(50.482110000000006, 8.698260000000001)); + quadtree.put(new DegreePosition(50.481390000000005, 8.70049)); + quadtree.put(new DegreePosition(50.48093, 8.70208)); + quadtree.put(new DegreePosition(50.48021000000001, 8.705020000000001)); + quadtree.put(new DegreePosition(50.47822000000001, 8.71437)); + quadtree.put(new DegreePosition(50.477940000000004, 8.715430000000001)); + quadtree.put(new DegreePosition(50.47721000000001, 8.717260000000001)); + quadtree.put(new DegreePosition(50.47652, 8.718820000000001)); + quadtree.put(new DegreePosition(50.476240000000004, 8.71929)); + quadtree.put(new DegreePosition(50.475660000000005, 8.72009)); + quadtree.put(new DegreePosition(50.47426, 8.72159)); + quadtree.put(new DegreePosition(50.47375, 8.722000000000001)); + quadtree.put(new DegreePosition(50.47307000000001, 8.7224)); + quadtree.put(new DegreePosition(50.472390000000004, 8.72273)); + quadtree.put(new DegreePosition(50.47173, 8.72295)); + quadtree.put(new DegreePosition(50.471050000000005, 8.723090000000001)); + quadtree.put(new DegreePosition(50.46989000000001, 8.723080000000001)); + quadtree.put(new DegreePosition(50.46876, 8.72295)); + quadtree.put(new DegreePosition(50.466840000000005, 8.72241)); + quadtree.put(new DegreePosition(50.465230000000005, 8.721820000000001)); + quadtree.put(new DegreePosition(50.46338, 8.72098)); + quadtree.put(new DegreePosition(50.463100000000004, 8.72091)); + quadtree.put(new DegreePosition(50.461200000000005, 8.71997)); + quadtree.put(new DegreePosition(50.46023, 8.71925)); + quadtree.put(new DegreePosition(50.459410000000005, 8.718570000000001)); + quadtree.put(new DegreePosition(50.458600000000004, 8.7178)); + quadtree.put(new DegreePosition(50.45769000000001, 8.716790000000001)); + quadtree.put(new DegreePosition(50.456610000000005, 8.71546)); + quadtree.put(new DegreePosition(50.455490000000005, 8.7139)); + quadtree.put(new DegreePosition(50.45445, 8.712250000000001)); + quadtree.put(new DegreePosition(50.45373000000001, 8.710980000000001)); + quadtree.put(new DegreePosition(50.45327, 8.71006)); + quadtree.put(new DegreePosition(50.45239, 8.70799)); + quadtree.put(new DegreePosition(50.44941000000001, 8.70059)); + quadtree.put(new DegreePosition(50.448820000000005, 8.69945)); + quadtree.put(new DegreePosition(50.448460000000004, 8.69888)); + quadtree.put(new DegreePosition(50.447900000000004, 8.69806)); + quadtree.put(new DegreePosition(50.44698, 8.69688)); + quadtree.put(new DegreePosition(50.44641000000001, 8.6963)); + quadtree.put(new DegreePosition(50.44548, 8.695500000000001)); + quadtree.put(new DegreePosition(50.44501, 8.695160000000001)); + quadtree.put(new DegreePosition(50.44411, 8.69472)); + quadtree.put(new DegreePosition(50.44333, 8.69443)); + quadtree.put(new DegreePosition(50.44202000000001, 8.694180000000001)); + quadtree.put(new DegreePosition(50.44145, 8.694170000000002)); + quadtree.put(new DegreePosition(50.440560000000005, 8.69425)); + quadtree.put(new DegreePosition(50.432640000000006, 8.69574)); + quadtree.put(new DegreePosition(50.42264, 8.697370000000001)); + quadtree.put(new DegreePosition(50.405300000000004, 8.70054)); + quadtree.put(new DegreePosition(50.395720000000004, 8.70219)); + quadtree.put(new DegreePosition(50.391580000000005, 8.70298)); + quadtree.put(new DegreePosition(50.38085, 8.7048)); + quadtree.put(new DegreePosition(50.375750000000004, 8.70561)); + quadtree.put(new DegreePosition(50.374950000000005, 8.70569)); + quadtree.put(new DegreePosition(50.373850000000004, 8.70564)); + quadtree.put(new DegreePosition(50.37259, 8.705400000000001)); + quadtree.put(new DegreePosition(50.37050000000001, 8.70457)); + quadtree.put(new DegreePosition(50.369170000000004, 8.70387)); + quadtree.put(new DegreePosition(50.367610000000006, 8.702910000000001)); + quadtree.put(new DegreePosition(50.36612, 8.7018)); + quadtree.put(new DegreePosition(50.36075, 8.6972)); + quadtree.put(new DegreePosition(50.35831, 8.69533)); + quadtree.put(new DegreePosition(50.356950000000005, 8.69443)); + quadtree.put(new DegreePosition(50.35558, 8.69372)); + quadtree.put(new DegreePosition(50.354400000000005, 8.69335)); + quadtree.put(new DegreePosition(50.35246, 8.69284)); + quadtree.put(new DegreePosition(50.35105, 8.692620000000002)); + quadtree.put(new DegreePosition(50.350170000000006, 8.69257)); + quadtree.put(new DegreePosition(50.34955, 8.692580000000001)); + quadtree.put(new DegreePosition(50.34857, 8.69275)); + quadtree.put(new DegreePosition(50.346230000000006, 8.693470000000001)); + quadtree.put(new DegreePosition(50.341210000000004, 8.69532)); + quadtree.put(new DegreePosition(50.34002, 8.695680000000001)); + quadtree.put(new DegreePosition(50.33867000000001, 8.695910000000001)); + quadtree.put(new DegreePosition(50.33753, 8.69594)); + quadtree.put(new DegreePosition(50.3361, 8.695860000000001)); + quadtree.put(new DegreePosition(50.333580000000005, 8.69542)); + quadtree.put(new DegreePosition(50.32048, 8.69252)); + quadtree.put(new DegreePosition(50.31851, 8.69196)); + quadtree.put(new DegreePosition(50.31781, 8.6917)); + quadtree.put(new DegreePosition(50.31709000000001, 8.69139)); + quadtree.put(new DegreePosition(50.31595, 8.690760000000001)); + quadtree.put(new DegreePosition(50.314600000000006, 8.68979)); + quadtree.put(new DegreePosition(50.31338, 8.68871)); + quadtree.put(new DegreePosition(50.31116, 8.68621)); + quadtree.put(new DegreePosition(50.30666, 8.680890000000002)); + quadtree.put(new DegreePosition(50.3034, 8.67722)); + quadtree.put(new DegreePosition(50.302640000000004, 8.67643)); + quadtree.put(new DegreePosition(50.301930000000006, 8.675830000000001)); + quadtree.put(new DegreePosition(50.30136, 8.675410000000001)); + quadtree.put(new DegreePosition(50.30044, 8.67488)); + quadtree.put(new DegreePosition(50.298860000000005, 8.674140000000001)); + quadtree.put(new DegreePosition(50.29008, 8.67112)); + quadtree.put(new DegreePosition(50.286120000000004, 8.669820000000001)); + quadtree.put(new DegreePosition(50.27376, 8.665550000000001)); + quadtree.put(new DegreePosition(50.26568, 8.662920000000002)); + quadtree.put(new DegreePosition(50.26428000000001, 8.662640000000001)); + quadtree.put(new DegreePosition(50.26334000000001, 8.662560000000001)); + quadtree.put(new DegreePosition(50.248900000000006, 8.662740000000001)); + quadtree.put(new DegreePosition(50.24667, 8.662730000000002)); + quadtree.put(new DegreePosition(50.231930000000006, 8.662970000000001)); + quadtree.put(new DegreePosition(50.21923, 8.6631)); + quadtree.put(new DegreePosition(50.21822, 8.66305)); + quadtree.put(new DegreePosition(50.21732, 8.66291)); + quadtree.put(new DegreePosition(50.216240000000006, 8.66267)); + quadtree.put(new DegreePosition(50.215270000000004, 8.662370000000001)); + quadtree.put(new DegreePosition(50.214200000000005, 8.66192)); + quadtree.put(new DegreePosition(50.21316, 8.661370000000002)); + quadtree.put(new DegreePosition(50.21162, 8.660350000000001)); + quadtree.put(new DegreePosition(50.210660000000004, 8.659600000000001)); + quadtree.put(new DegreePosition(50.209430000000005, 8.65841)); + quadtree.put(new DegreePosition(50.20864, 8.6575)); + quadtree.put(new DegreePosition(50.208090000000006, 8.656830000000001)); + quadtree.put(new DegreePosition(50.206920000000004, 8.65522)); + quadtree.put(new DegreePosition(50.206500000000005, 8.65456)); + quadtree.put(new DegreePosition(50.20488, 8.651760000000001)); + quadtree.put(new DegreePosition(50.203190000000006, 8.649080000000001)); + quadtree.put(new DegreePosition(50.19881, 8.64192)); + quadtree.put(new DegreePosition(50.196600000000004, 8.637440000000002)); + quadtree.put(new DegreePosition(50.188770000000005, 8.6239)); + quadtree.put(new DegreePosition(50.18795, 8.62259)); + quadtree.put(new DegreePosition(50.187070000000006, 8.621360000000001)); + quadtree.put(new DegreePosition(50.18627000000001, 8.6204)); + quadtree.put(new DegreePosition(50.185120000000005, 8.619190000000001)); + quadtree.put(new DegreePosition(50.18388, 8.61808)); + quadtree.put(new DegreePosition(50.182770000000005, 8.61726)); + quadtree.put(new DegreePosition(50.18178, 8.61665)); + quadtree.put(new DegreePosition(50.17159, 8.61101)); + quadtree.put(new DegreePosition(50.16395000000001, 8.606710000000001)); + quadtree.put(new DegreePosition(50.15317, 8.600760000000001)); + quadtree.put(new DegreePosition(50.149210000000004, 8.598640000000001)); + quadtree.put(new DegreePosition(50.14303, 8.59515)); + quadtree.put(new DegreePosition(50.13889, 8.592960000000001)); + quadtree.put(new DegreePosition(50.13496000000001, 8.590810000000001)); + quadtree.put(new DegreePosition(50.1332, 8.590190000000002)); + quadtree.put(new DegreePosition(50.13172, 8.58994)); + quadtree.put(new DegreePosition(50.13055000000001, 8.58993)); + quadtree.put(new DegreePosition(50.129450000000006, 8.59008)); + quadtree.put(new DegreePosition(50.12838000000001, 8.590340000000001)); + quadtree.put(new DegreePosition(50.12713, 8.59083)); + quadtree.put(new DegreePosition(50.12621000000001, 8.591320000000001)); + quadtree.put(new DegreePosition(50.118970000000004, 8.595970000000001)); + quadtree.put(new DegreePosition(50.10857000000001, 8.60274)); + quadtree.put(new DegreePosition(50.105970000000006, 8.604600000000001)); + quadtree.put(new DegreePosition(50.105050000000006, 8.605410000000001)); + quadtree.put(new DegreePosition(50.104200000000006, 8.60622)); + quadtree.put(new DegreePosition(50.10295000000001, 8.607560000000001)); + quadtree.put(new DegreePosition(50.10038, 8.61049)); + quadtree.put(new DegreePosition(50.09879, 8.61238)); + quadtree.put(new DegreePosition(50.09743, 8.61368)); + quadtree.put(new DegreePosition(50.09572000000001, 8.6151)); + quadtree.put(new DegreePosition(50.09489000000001, 8.61561)); + quadtree.put(new DegreePosition(50.093360000000004, 8.616290000000001)); + quadtree.put(new DegreePosition(50.09149000000001, 8.616940000000001)); + quadtree.put(new DegreePosition(50.087880000000006, 8.617780000000002)); + quadtree.put(new DegreePosition(50.08559, 8.618160000000001)); + quadtree.put(new DegreePosition(50.08471, 8.61824)); + quadtree.put(new DegreePosition(50.083000000000006, 8.61814)); + quadtree.put(new DegreePosition(50.081680000000006, 8.617930000000001)); + quadtree.put(new DegreePosition(50.080400000000004, 8.617560000000001)); + quadtree.put(new DegreePosition(50.0797, 8.6173)); + quadtree.put(new DegreePosition(50.07835000000001, 8.61668)); + quadtree.put(new DegreePosition(50.06631, 8.61067)); + quadtree.put(new DegreePosition(50.06239, 8.608640000000001)); + quadtree.put(new DegreePosition(50.05212, 8.60364)); + quadtree.put(new DegreePosition(50.03866000000001, 8.596810000000001)); + quadtree.put(new DegreePosition(50.03486, 8.59505)); + quadtree.put(new DegreePosition(50.033010000000004, 8.5943)); + quadtree.put(new DegreePosition(50.03032, 8.59347)); + quadtree.put(new DegreePosition(50.02763, 8.59284)); + quadtree.put(new DegreePosition(50.025240000000004, 8.59252)); + quadtree.put(new DegreePosition(50.024260000000005, 8.59247)); + quadtree.put(new DegreePosition(50.02208, 8.59247)); + quadtree.put(new DegreePosition(50.020790000000005, 8.592550000000001)); + quadtree.put(new DegreePosition(50.019270000000006, 8.59271)); + quadtree.put(new DegreePosition(50.016510000000004, 8.593240000000002)); + quadtree.put(new DegreePosition(49.983160000000005, 8.60124)); + quadtree.put(new DegreePosition(49.976580000000006, 8.60275)); + quadtree.put(new DegreePosition(49.97316000000001, 8.60363)); + quadtree.put(new DegreePosition(49.96228000000001, 8.60614)); + quadtree.put(new DegreePosition(49.95414, 8.60811)); + quadtree.put(new DegreePosition(49.95309, 8.608310000000001)); + quadtree.put(new DegreePosition(49.95217, 8.60857)); + quadtree.put(new DegreePosition(49.948350000000005, 8.609440000000001)); + quadtree.put(new DegreePosition(49.943200000000004, 8.610750000000001)); + quadtree.put(new DegreePosition(49.941720000000004, 8.611040000000001)); + quadtree.put(new DegreePosition(49.935280000000006, 8.612530000000001)); + quadtree.put(new DegreePosition(49.933550000000004, 8.6128)); + quadtree.put(new DegreePosition(49.93207, 8.612960000000001)); + quadtree.put(new DegreePosition(49.93084, 8.61302)); + quadtree.put(new DegreePosition(49.928740000000005, 8.61299)); + quadtree.put(new DegreePosition(49.926950000000005, 8.61283)); + quadtree.put(new DegreePosition(49.92591, 8.61269)); + quadtree.put(new DegreePosition(49.92389000000001, 8.612260000000001)); + quadtree.put(new DegreePosition(49.92083, 8.6113)); + quadtree.put(new DegreePosition(49.91532, 8.609390000000001)); + quadtree.put(new DegreePosition(49.914170000000006, 8.60905)); + quadtree.put(new DegreePosition(49.91042, 8.60819)); + quadtree.put(new DegreePosition(49.89902000000001, 8.60598)); + quadtree.put(new DegreePosition(49.89663, 8.605450000000001)); + quadtree.put(new DegreePosition(49.887750000000004, 8.603710000000001)); + quadtree.put(new DegreePosition(49.886900000000004, 8.603610000000002)); + quadtree.put(new DegreePosition(49.88288000000001, 8.60288)); + quadtree.put(new DegreePosition(49.87717000000001, 8.60196)); + quadtree.put(new DegreePosition(49.874700000000004, 8.60147)); + quadtree.put(new DegreePosition(49.87281, 8.60119)); + quadtree.put(new DegreePosition(49.870450000000005, 8.60072)); + quadtree.put(new DegreePosition(49.86542000000001, 8.599530000000001)); + quadtree.put(new DegreePosition(49.86164, 8.59872)); + quadtree.put(new DegreePosition(49.861450000000005, 8.59861)); + quadtree.put(new DegreePosition(49.86047000000001, 8.598360000000001)); + quadtree.put(new DegreePosition(49.857470000000006, 8.597240000000001)); + quadtree.put(new DegreePosition(49.85718000000001, 8.59717)); + quadtree.put(new DegreePosition(49.85662000000001, 8.597150000000001)); + quadtree.put(new DegreePosition(49.856350000000006, 8.59721)); + quadtree.put(new DegreePosition(49.85586000000001, 8.59744)); + quadtree.put(new DegreePosition(49.85559000000001, 8.597660000000001)); + quadtree.put(new DegreePosition(49.855250000000005, 8.598090000000001)); + quadtree.put(new DegreePosition(49.854940000000006, 8.59862)); + quadtree.put(new DegreePosition(49.85474000000001, 8.599110000000001)); + quadtree.put(new DegreePosition(49.85452, 8.599860000000001)); + quadtree.put(new DegreePosition(49.85405, 8.60206)); + quadtree.put(new DegreePosition(49.8539, 8.602490000000001)); + quadtree.put(new DegreePosition(49.85325, 8.603750000000002)); + quadtree.put(new DegreePosition(49.851670000000006, 8.605870000000001)); + quadtree.put(new DegreePosition(49.85031000000001, 8.6079)); + quadtree.put(new DegreePosition(49.84915, 8.609850000000002)); + quadtree.put(new DegreePosition(49.846590000000006, 8.61508)); + quadtree.put(new DegreePosition(49.84149000000001, 8.626510000000001)); + quadtree.put(new DegreePosition(49.840500000000006, 8.62828)); + quadtree.put(new DegreePosition(49.839980000000004, 8.62898)); + quadtree.put(new DegreePosition(49.838530000000006, 8.63066)); + quadtree.put(new DegreePosition(49.83744, 8.6317)); + quadtree.put(new DegreePosition(49.83612, 8.632670000000001)); + quadtree.put(new DegreePosition(49.83462, 8.63345)); + quadtree.put(new DegreePosition(49.83399000000001, 8.63371)); + quadtree.put(new DegreePosition(49.832080000000005, 8.63414)); + quadtree.put(new DegreePosition(49.831320000000005, 8.63423)); + quadtree.put(new DegreePosition(49.830540000000006, 8.6342)); + quadtree.put(new DegreePosition(49.82978000000001, 8.63409)); + quadtree.put(new DegreePosition(49.82876, 8.633780000000002)); + quadtree.put(new DegreePosition(49.82734000000001, 8.633180000000001)); + quadtree.put(new DegreePosition(49.82462, 8.63161)); + quadtree.put(new DegreePosition(49.821630000000006, 8.62977)); + quadtree.put(new DegreePosition(49.820370000000004, 8.6292)); + quadtree.put(new DegreePosition(49.81908000000001, 8.62875)); + quadtree.put(new DegreePosition(49.81687, 8.62841)); + quadtree.put(new DegreePosition(49.81134, 8.62866)); + quadtree.put(new DegreePosition(49.81027, 8.62861)); + quadtree.put(new DegreePosition(49.80881, 8.62828)); + quadtree.put(new DegreePosition(49.80682, 8.627690000000001)); + quadtree.put(new DegreePosition(49.80575, 8.62719)); + quadtree.put(new DegreePosition(49.804100000000005, 8.62627)); + quadtree.put(new DegreePosition(49.80192, 8.624780000000001)); + quadtree.put(new DegreePosition(49.79887, 8.622520000000002)); + quadtree.put(new DegreePosition(49.797380000000004, 8.62151)); + quadtree.put(new DegreePosition(49.796110000000006, 8.620750000000001)); + quadtree.put(new DegreePosition(49.79467, 8.62005)); + quadtree.put(new DegreePosition(49.79345000000001, 8.619520000000001)); + quadtree.put(new DegreePosition(49.792060000000006, 8.61903)); + quadtree.put(new DegreePosition(49.79064, 8.61869)); + quadtree.put(new DegreePosition(49.78909, 8.618390000000002)); + quadtree.put(new DegreePosition(49.78412, 8.61762)); + quadtree.put(new DegreePosition(49.78295000000001, 8.61738)); + quadtree.put(new DegreePosition(49.78119, 8.61692)); + quadtree.put(new DegreePosition(49.77975000000001, 8.616380000000001)); + quadtree.put(new DegreePosition(49.778780000000005, 8.61593)); + quadtree.put(new DegreePosition(49.777640000000005, 8.61528)); + quadtree.put(new DegreePosition(49.77610000000001, 8.61415)); + quadtree.put(new DegreePosition(49.77506, 8.613190000000001)); + quadtree.put(new DegreePosition(49.77357000000001, 8.611600000000001)); + quadtree.put(new DegreePosition(49.77299000000001, 8.61087)); + quadtree.put(new DegreePosition(49.770830000000004, 8.607850000000001)); + quadtree.put(new DegreePosition(49.76859, 8.604610000000001)); + quadtree.put(new DegreePosition(49.768170000000005, 8.60405)); + quadtree.put(new DegreePosition(49.76608, 8.60152)); + quadtree.put(new DegreePosition(49.764790000000005, 8.60018)); + quadtree.put(new DegreePosition(49.762150000000005, 8.59793)); + quadtree.put(new DegreePosition(49.76151, 8.597430000000001)); + quadtree.put(new DegreePosition(49.76024, 8.59665)); + quadtree.put(new DegreePosition(49.757780000000004, 8.59553)); + quadtree.put(new DegreePosition(49.756260000000005, 8.59491)); + quadtree.put(new DegreePosition(49.75267, 8.59369)); + quadtree.put(new DegreePosition(49.75119, 8.593290000000001)); + quadtree.put(new DegreePosition(49.74869, 8.59272)); + quadtree.put(new DegreePosition(49.745580000000004, 8.592160000000002)); + quadtree.put(new DegreePosition(49.742180000000005, 8.59171)); + quadtree.put(new DegreePosition(49.739290000000004, 8.59154)); + quadtree.put(new DegreePosition(49.735220000000005, 8.591550000000002)); + quadtree.put(new DegreePosition(49.73221, 8.59176)); + quadtree.put(new DegreePosition(49.72905, 8.59223)); + quadtree.put(new DegreePosition(49.72579, 8.592830000000001)); + quadtree.put(new DegreePosition(49.722390000000004, 8.59373)); + quadtree.put(new DegreePosition(49.715450000000004, 8.59572)); + quadtree.put(new DegreePosition(49.705760000000005, 8.599260000000001)); + quadtree.put(new DegreePosition(49.6989, 8.602160000000001)); + quadtree.put(new DegreePosition(49.698060000000005, 8.602400000000001)); + quadtree.put(new DegreePosition(49.695820000000005, 8.602820000000001)); + quadtree.put(new DegreePosition(49.693780000000004, 8.602960000000001)); + quadtree.put(new DegreePosition(49.69301, 8.602960000000001)); + quadtree.put(new DegreePosition(49.6916, 8.602860000000002)); + quadtree.put(new DegreePosition(49.69035, 8.602640000000001)); + quadtree.put(new DegreePosition(49.689150000000005, 8.60234)); + quadtree.put(new DegreePosition(49.68645, 8.60135)); + quadtree.put(new DegreePosition(49.68348, 8.600140000000001)); + quadtree.put(new DegreePosition(49.68186000000001, 8.599680000000001)); + quadtree.put(new DegreePosition(49.67985, 8.599250000000001)); + quadtree.put(new DegreePosition(49.678070000000005, 8.59914)); + quadtree.put(new DegreePosition(49.676970000000004, 8.599160000000001)); + quadtree.put(new DegreePosition(49.67475, 8.59946)); + quadtree.put(new DegreePosition(49.672610000000006, 8.60003)); + quadtree.put(new DegreePosition(49.669450000000005, 8.6013)); + quadtree.put(new DegreePosition(49.667790000000004, 8.60214)); + quadtree.put(new DegreePosition(49.6655, 8.60356)); + quadtree.put(new DegreePosition(49.66145, 8.60621)); + quadtree.put(new DegreePosition(49.65959, 8.607330000000001)); + quadtree.put(new DegreePosition(49.657700000000006, 8.6083)); + quadtree.put(new DegreePosition(49.65603, 8.60891)); + quadtree.put(new DegreePosition(49.65431, 8.609340000000001)); + quadtree.put(new DegreePosition(49.65194, 8.60959)); + quadtree.put(new DegreePosition(49.649770000000004, 8.609620000000001)); + quadtree.put(new DegreePosition(49.64312, 8.609250000000001)); + quadtree.put(new DegreePosition(49.64190000000001, 8.60926)); + quadtree.put(new DegreePosition(49.64029000000001, 8.609380000000002)); + quadtree.put(new DegreePosition(49.63906, 8.609530000000001)); + quadtree.put(new DegreePosition(49.63758000000001, 8.60983)); + quadtree.put(new DegreePosition(49.63456000000001, 8.610570000000001)); + quadtree.put(new DegreePosition(49.632870000000004, 8.61125)); + quadtree.put(new DegreePosition(49.630630000000004, 8.612340000000001)); + quadtree.put(new DegreePosition(49.62894000000001, 8.61344)); + quadtree.put(new DegreePosition(49.627930000000006, 8.61416)); + quadtree.put(new DegreePosition(49.62348, 8.61804)); + quadtree.put(new DegreePosition(49.622220000000006, 8.61931)); + quadtree.put(new DegreePosition(49.618030000000005, 8.623240000000001)); + quadtree.put(new DegreePosition(49.6152, 8.6256)); + quadtree.put(new DegreePosition(49.61211, 8.627540000000002)); + quadtree.put(new DegreePosition(49.61057, 8.62833)); + quadtree.put(new DegreePosition(49.60898, 8.62899)); + quadtree.put(new DegreePosition(49.607150000000004, 8.629620000000001)); + quadtree.put(new DegreePosition(49.604940000000006, 8.630170000000001)); + quadtree.put(new DegreePosition(49.603770000000004, 8.63035)); + quadtree.put(new DegreePosition(49.60065, 8.630640000000001)); + quadtree.put(new DegreePosition(49.59897, 8.63086)); + quadtree.put(new DegreePosition(49.59581000000001, 8.63113)); + quadtree.put(new DegreePosition(49.59308000000001, 8.63165)); + quadtree.put(new DegreePosition(49.59147, 8.63212)); + quadtree.put(new DegreePosition(49.58559, 8.634110000000002)); + quadtree.put(new DegreePosition(49.58408000000001, 8.634540000000001)); + quadtree.put(new DegreePosition(49.582060000000006, 8.635010000000001)); + quadtree.put(new DegreePosition(49.580670000000005, 8.635200000000001)); + quadtree.put(new DegreePosition(49.57885, 8.635330000000002)); + quadtree.put(new DegreePosition(49.57715, 8.63531)); + quadtree.put(new DegreePosition(49.57417, 8.6349)); + quadtree.put(new DegreePosition(49.572190000000006, 8.63455)); + quadtree.put(new DegreePosition(49.570730000000005, 8.63413)); + quadtree.put(new DegreePosition(49.568430000000006, 8.6333)); + quadtree.put(new DegreePosition(49.565090000000005, 8.63171)); + quadtree.put(new DegreePosition(49.560050000000004, 8.62922)); + quadtree.put(new DegreePosition(49.55922, 8.62894)); + quadtree.put(new DegreePosition(49.55603000000001, 8.628010000000002)); + quadtree.put(new DegreePosition(49.55425, 8.62767)); + quadtree.put(new DegreePosition(49.55111, 8.62743)); + quadtree.put(new DegreePosition(49.548930000000006, 8.62748)); + quadtree.put(new DegreePosition(49.548120000000004, 8.62757)); + quadtree.put(new DegreePosition(49.54666, 8.62784)); + quadtree.put(new DegreePosition(49.54466000000001, 8.62827)); + quadtree.put(new DegreePosition(49.54215000000001, 8.62898)); + quadtree.put(new DegreePosition(49.539440000000006, 8.62991)); + quadtree.put(new DegreePosition(49.537850000000006, 8.63052)); + quadtree.put(new DegreePosition(49.52872000000001, 8.63442)); + quadtree.put(new DegreePosition(49.527300000000004, 8.63508)); + quadtree.put(new DegreePosition(49.52526, 8.635860000000001)); + quadtree.put(new DegreePosition(49.52337000000001, 8.636510000000001)); + quadtree.put(new DegreePosition(49.52058, 8.637360000000001)); + quadtree.put(new DegreePosition(49.518550000000005, 8.637910000000002)); + quadtree.put(new DegreePosition(49.51503, 8.638770000000001)); + quadtree.put(new DegreePosition(49.51098, 8.639330000000001)); + quadtree.put(new DegreePosition(49.50845, 8.63959)); + quadtree.put(new DegreePosition(49.50417, 8.639740000000002)); + quadtree.put(new DegreePosition(49.499810000000004, 8.63963)); + quadtree.put(new DegreePosition(49.49546, 8.63912)); + quadtree.put(new DegreePosition(49.49306000000001, 8.638910000000001)); + quadtree.put(new DegreePosition(49.490320000000004, 8.638760000000001)); + quadtree.put(new DegreePosition(49.48669, 8.63865)); + quadtree.put(new DegreePosition(49.484480000000005, 8.638760000000001)); + quadtree.put(new DegreePosition(49.48073, 8.639330000000001)); + quadtree.put(new DegreePosition(49.4776, 8.640130000000001)); + quadtree.put(new DegreePosition(49.476310000000005, 8.64052)); + quadtree.put(new DegreePosition(49.468500000000006, 8.64342)); + quadtree.put(new DegreePosition(49.464470000000006, 8.64483)); + quadtree.put(new DegreePosition(49.461290000000005, 8.64577)); + quadtree.put(new DegreePosition(49.458690000000004, 8.64629)); + quadtree.put(new DegreePosition(49.457530000000006, 8.646460000000001)); + quadtree.put(new DegreePosition(49.455380000000005, 8.64666)); + quadtree.put(new DegreePosition(49.453050000000005, 8.64668)); + quadtree.put(new DegreePosition(49.451750000000004, 8.64661)); + quadtree.put(new DegreePosition(49.450430000000004, 8.64647)); + quadtree.put(new DegreePosition(49.44711, 8.64578)); + quadtree.put(new DegreePosition(49.445350000000005, 8.6453)); + quadtree.put(new DegreePosition(49.44398, 8.64478)); + quadtree.put(new DegreePosition(49.44091, 8.64338)); + quadtree.put(new DegreePosition(49.439840000000004, 8.64282)); + quadtree.put(new DegreePosition(49.438340000000004, 8.64193)); + quadtree.put(new DegreePosition(49.43139000000001, 8.63724)); + quadtree.put(new DegreePosition(49.42933000000001, 8.636080000000002)); + quadtree.put(new DegreePosition(49.427890000000005, 8.63537)); + quadtree.put(new DegreePosition(49.426460000000006, 8.634820000000001)); + quadtree.put(new DegreePosition(49.42436000000001, 8.634160000000001)); + quadtree.put(new DegreePosition(49.422200000000004, 8.63371)); + quadtree.put(new DegreePosition(49.42033000000001, 8.633500000000002)); + quadtree.put(new DegreePosition(49.41946, 8.63347)); + quadtree.put(new DegreePosition(49.41798000000001, 8.63348)); + quadtree.put(new DegreePosition(49.415710000000004, 8.633700000000001)); + quadtree.put(new DegreePosition(49.41463, 8.6339)); + quadtree.put(new DegreePosition(49.412670000000006, 8.63437)); + quadtree.put(new DegreePosition(49.411120000000004, 8.6349)); + quadtree.put(new DegreePosition(49.40739000000001, 8.636330000000001)); + quadtree.put(new DegreePosition(49.40572, 8.636800000000001)); + quadtree.put(new DegreePosition(49.39164, 8.63865)); + quadtree.put(new DegreePosition(49.39021, 8.63879)); + quadtree.put(new DegreePosition(49.38855, 8.638720000000001)); + quadtree.put(new DegreePosition(49.38721, 8.638480000000001)); + quadtree.put(new DegreePosition(49.38656, 8.638290000000001)); + quadtree.put(new DegreePosition(49.38440000000001, 8.63746)); + quadtree.put(new DegreePosition(49.37805, 8.63387)); + quadtree.put(new DegreePosition(49.376940000000005, 8.63334)); + quadtree.put(new DegreePosition(49.376000000000005, 8.63297)); + quadtree.put(new DegreePosition(49.37500000000001, 8.63269)); + quadtree.put(new DegreePosition(49.373780000000004, 8.632480000000001)); + quadtree.put(new DegreePosition(49.36988, 8.63222)); + quadtree.put(new DegreePosition(49.36872, 8.632190000000001)); + quadtree.put(new DegreePosition(49.364360000000005, 8.631910000000001)); + quadtree.put(new DegreePosition(49.35228000000001, 8.63127)); + quadtree.put(new DegreePosition(49.34911, 8.631160000000001)); + quadtree.put(new DegreePosition(49.33944, 8.63067)); + quadtree.put(new DegreePosition(49.31497, 8.62917)); + quadtree.put(new DegreePosition(49.312200000000004, 8.62892)); + quadtree.put(new DegreePosition(49.31073000000001, 8.628670000000001)); + quadtree.put(new DegreePosition(49.303380000000004, 8.626940000000001)); + quadtree.put(new DegreePosition(49.30293, 8.62654)); + quadtree.put(new DegreePosition(49.30285000000001, 8.62644)); + quadtree.put(new DegreePosition(49.302690000000005, 8.62608)); + quadtree.put(new DegreePosition(49.30268, 8.62584)); + quadtree.put(new DegreePosition(49.30299, 8.62268)); + quadtree.put(new DegreePosition(49.30303000000001, 8.622010000000001)); + quadtree.put(new DegreePosition(49.30299, 8.621730000000001)); + quadtree.put(new DegreePosition(49.30293, 8.6216)); + quadtree.put(new DegreePosition(49.30274000000001, 8.62147)); + quadtree.put(new DegreePosition(49.30238000000001, 8.62139)); + quadtree.put(new DegreePosition(49.302290000000006, 8.621310000000001)); + quadtree.put(new DegreePosition(49.301950000000005, 8.62127)); + quadtree.put(new DegreePosition(49.30187, 8.62279)); + quadtree.put(new DegreePosition(49.30172, 8.62456)); + quadtree.put(new DegreePosition(49.30134, 8.62697)); + quadtree.put(new DegreePosition(49.30116, 8.627690000000001)); + quadtree.put(new DegreePosition(49.30053, 8.629800000000001)); + quadtree.put(new DegreePosition(49.299820000000004, 8.63142)); + quadtree.put(new DegreePosition(49.29916, 8.6327)); + quadtree.put(new DegreePosition(49.299530000000004, 8.63279)); + quadtree.put(new DegreePosition(49.30006, 8.63272)); + quadtree.put(new DegreePosition(49.30068000000001, 8.63277)); + quadtree.put(new DegreePosition(49.30082, 8.63283)); + quadtree.put(new DegreePosition(49.301050000000004, 8.633040000000001)); + quadtree.put(new DegreePosition(49.300340000000006, 8.634780000000001)); + quadtree.put(new DegreePosition(49.29995, 8.63593)); + quadtree.put(new DegreePosition(49.299690000000005, 8.63723)); + quadtree.put(new DegreePosition(49.299600000000005, 8.638200000000001)); + quadtree.put(new DegreePosition(49.29963, 8.63973)); + quadtree.put(new DegreePosition(49.299710000000005, 8.64104)); + quadtree.put(new DegreePosition(49.29831, 8.643080000000001)); + quadtree.put(new DegreePosition(49.29796, 8.64334)); + quadtree.put(new DegreePosition(49.297920000000005, 8.643450000000001)); + quadtree.put(new DegreePosition(49.297920000000005, 8.643590000000001)); + quadtree.put(new DegreePosition(49.29798, 8.64389)); + quadtree.put(new DegreePosition(49.299420000000005, 8.646180000000001)); + quadtree.put(new DegreePosition(49.299580000000006, 8.64652)); + quadtree.put(new DegreePosition(49.29955, 8.646640000000001)); + quadtree.put(new DegreePosition(49.29957, 8.64677)); + quadtree.put(new DegreePosition(49.299710000000005, 8.64681)); + quadtree.put(new DegreePosition(49.29977, 8.646730000000002)); + quadtree.put(new DegreePosition(49.299780000000005, 8.646640000000001)); + quadtree.put(new DegreePosition(49.299690000000005, 8.64648)); + quadtree.put(new DegreePosition(49.2997, 8.64638)); + quadtree.put(new DegreePosition(49.29992000000001, 8.64586)); + quadtree.put(new DegreePosition(49.29995, 8.64563)); + quadtree.put(new DegreePosition(49.300790000000006, 8.644)); + quadtree.put(new DegreePosition(49.301120000000004, 8.643650000000001)); + quadtree.put(new DegreePosition(49.301390000000005, 8.64348)); + quadtree.put(new DegreePosition(49.30283000000001, 8.642980000000001)); + quadtree.put(new DegreePosition(49.30346, 8.642560000000001)); + quadtree.put(new DegreePosition(49.30378, 8.64241)); + quadtree.put(new DegreePosition(49.309740000000005, 8.64105)); + quadtree.put(new DegreePosition(49.309760000000004, 8.64207)); + quadtree.put(new DegreePosition(49.309140000000006, 8.64212)); + quadtree.put(new DegreePosition(49.309140000000006, 8.643880000000001)); + quadtree.put(new DegreePosition(49.30917, 8.64403)); + quadtree.put(new DegreePosition(49.30923000000001, 8.644110000000001)); + quadtree.put(new DegreePosition(49.30937, 8.644110000000001)); + + p1 = new DegreePosition(50.47498691278631, 8.701171875); + p2 = new DegreePosition(50.47149085139956, 8.7066650390625); + p3 = new DegreePosition(50.47848271564207, 8.7066650390625); } @Test public void testNoNPEDuringSecondPutInSameLeaf() { - final QuadTreeWithPublicGetTop qt = new QuadTreeWithPublicGetTop(); + final QuadTree qt = new QuadTree(); final Position p = new DegreePosition(0, 0); final NullPointerException[] npe = new NullPointerException[1]; final boolean[] stop = new boolean[1]; @@ -72,1132 +1222,6 @@ public class QuadTreeTest { @Test public void testDistance() { - GLatLngQuadTree quadtree = new GLatLngQuadTree(new DegreePosition(49.29, - 7.9), new DegreePosition(50.88506, 8.75), /* maxItems */30); - quadtree.put(new DegreePosition(50.88505000000001, 7.995520000000001)); - quadtree.put(new DegreePosition(50.884930000000004, 7.996440000000001)); - quadtree.put(new DegreePosition(50.884840000000004, 7.99666)); - quadtree.put(new DegreePosition(50.8843, 7.997490000000001)); - quadtree.put(new DegreePosition(50.884240000000005, 7.997730000000001)); - quadtree.put(new DegreePosition(50.88423, 7.997980000000001)); - quadtree.put(new DegreePosition(50.8847, 7.999010000000001)); - quadtree.put(new DegreePosition(50.884600000000006, 7.999130000000001)); - quadtree.put(new DegreePosition(50.88448, 7.99938)); - quadtree.put(new DegreePosition(50.88421, 8.00014)); - quadtree.put(new DegreePosition(50.88382000000001, 8.00174)); - quadtree.put(new DegreePosition(50.88355000000001, 8.00263)); - quadtree.put(new DegreePosition(50.883570000000006, 8.00309)); - quadtree.put(new DegreePosition(50.883700000000005, 8.003530000000001)); - quadtree.put(new DegreePosition(50.88212000000001, 8.00539)); - quadtree.put(new DegreePosition(50.881620000000005, 8.006260000000001)); - quadtree.put(new DegreePosition(50.881080000000004, 8.007610000000001)); - quadtree.put(new DegreePosition(50.88103, 8.007900000000001)); - quadtree.put(new DegreePosition(50.88103, 8.009030000000001)); - quadtree.put(new DegreePosition(50.880880000000005, 8.009640000000001)); - quadtree.put(new DegreePosition(50.88047, 8.010860000000001)); - quadtree.put(new DegreePosition(50.880030000000005, 8.012500000000001)); - quadtree.put(new DegreePosition(50.87866, 8.016810000000001)); - quadtree.put(new DegreePosition(50.87832, 8.017790000000002)); - quadtree.put(new DegreePosition(50.878240000000005, 8.01793)); - quadtree.put(new DegreePosition(50.878150000000005, 8.01795)); - quadtree.put(new DegreePosition(50.87803, 8.01782)); - quadtree.put(new DegreePosition(50.87776, 8.01717)); - quadtree.put(new DegreePosition(50.8774, 8.01654)); - quadtree.put(new DegreePosition(50.87709, 8.01622)); - quadtree.put(new DegreePosition(50.87585000000001, 8.013850000000001)); - quadtree.put(new DegreePosition(50.87538000000001, 8.01304)); - quadtree.put(new DegreePosition(50.87402, 8.01111)); - quadtree.put(new DegreePosition(50.87028, 8.003960000000001)); - quadtree.put(new DegreePosition(50.869670000000006, 8.003160000000001)); - quadtree.put(new DegreePosition(50.86925, 8.002780000000001)); - quadtree.put(new DegreePosition(50.86858, 8.00244)); - quadtree.put(new DegreePosition(50.868, 8.002310000000001)); - quadtree.put(new DegreePosition(50.86739000000001, 8.00237)); - quadtree.put(new DegreePosition(50.867070000000005, 8.00248)); - quadtree.put(new DegreePosition(50.86674000000001, 8.00267)); - quadtree.put(new DegreePosition(50.865950000000005, 8.00333)); - quadtree.put(new DegreePosition(50.86545, 8.00398)); - quadtree.put(new DegreePosition(50.86459000000001, 8.0053)); - quadtree.put(new DegreePosition(50.86424, 8.00577)); - quadtree.put(new DegreePosition(50.863870000000006, 8.00618)); - quadtree.put(new DegreePosition(50.86352, 8.0065)); - quadtree.put(new DegreePosition(50.862970000000004, 8.006820000000001)); - quadtree.put(new DegreePosition(50.862370000000006, 8.00703)); - quadtree.put(new DegreePosition(50.86110000000001, 8.00722)); - quadtree.put(new DegreePosition(50.86061, 8.00721)); - quadtree.put(new DegreePosition(50.859860000000005, 8.00711)); - quadtree.put(new DegreePosition(50.85918, 8.006920000000001)); - quadtree.put(new DegreePosition(50.85824, 8.006120000000001)); - quadtree.put(new DegreePosition(50.85784, 8.005640000000001)); - quadtree.put(new DegreePosition(50.85757, 8.0052)); - quadtree.put(new DegreePosition(50.85752, 8.00496)); - quadtree.put(new DegreePosition(50.85752, 8.00463)); - quadtree.put(new DegreePosition(50.85963, 8.00209)); - quadtree.put(new DegreePosition(50.86046, 8.0008)); - quadtree.put(new DegreePosition(50.86081, 8.00008)); - quadtree.put(new DegreePosition(50.86113, 7.999230000000001)); - quadtree.put(new DegreePosition(50.86128, 7.998690000000001)); - quadtree.put(new DegreePosition(50.861430000000006, 7.997870000000001)); - quadtree.put(new DegreePosition(50.86149, 7.99706)); - quadtree.put(new DegreePosition(50.86149, 7.996150000000001)); - quadtree.put(new DegreePosition(50.86144, 7.9955300000000005)); - quadtree.put(new DegreePosition(50.86130000000001, 7.99476)); - quadtree.put(new DegreePosition(50.86106, 7.993900000000001)); - quadtree.put(new DegreePosition(50.860220000000005, 7.991490000000001)); - quadtree.put(new DegreePosition(50.859970000000004, 7.990360000000001)); - quadtree.put(new DegreePosition(50.859930000000006, 7.987780000000001)); - quadtree.put(new DegreePosition(50.859970000000004, 7.98737)); - quadtree.put(new DegreePosition(50.86059, 7.984820000000001)); - quadtree.put(new DegreePosition(50.86065000000001, 7.984260000000001)); - quadtree.put(new DegreePosition(50.860620000000004, 7.98359)); - quadtree.put(new DegreePosition(50.8605, 7.9831900000000005)); - quadtree.put(new DegreePosition(50.860310000000005, 7.98284)); - quadtree.put(new DegreePosition(50.86001, 7.982540000000001)); - quadtree.put(new DegreePosition(50.85971000000001, 7.982450000000001)); - quadtree.put(new DegreePosition(50.859410000000004, 7.98251)); - quadtree.put(new DegreePosition(50.85918, 7.982670000000001)); - quadtree.put(new DegreePosition(50.85898, 7.98291)); - quadtree.put(new DegreePosition(50.858790000000006, 7.983370000000001)); - quadtree.put(new DegreePosition(50.858520000000006, 7.9846)); - quadtree.put(new DegreePosition(50.8584, 7.98488)); - quadtree.put(new DegreePosition(50.858140000000006, 7.9851600000000005)); - quadtree.put(new DegreePosition(50.85756000000001, 7.9855100000000006)); - quadtree.put(new DegreePosition(50.85616, 7.985920000000001)); - quadtree.put(new DegreePosition(50.85548000000001, 7.98603)); - quadtree.put(new DegreePosition(50.85210000000001, 7.9863100000000005)); - quadtree.put(new DegreePosition(50.850970000000004, 7.98662)); - quadtree.put(new DegreePosition(50.849230000000006, 7.98739)); - quadtree.put(new DegreePosition(50.849030000000006, 7.98758)); - quadtree.put(new DegreePosition(50.84798000000001, 7.988300000000001)); - quadtree.put(new DegreePosition(50.84763, 7.988600000000001)); - quadtree.put(new DegreePosition(50.84695000000001, 7.989240000000001)); - quadtree.put(new DegreePosition(50.84591, 7.9903900000000005)); - quadtree.put(new DegreePosition(50.84514, 7.99143)); - quadtree.put(new DegreePosition(50.844370000000005, 7.992660000000001)); - quadtree.put(new DegreePosition(50.84355000000001, 7.994140000000001)); - quadtree.put(new DegreePosition(50.842800000000004, 7.9958800000000005)); - quadtree.put(new DegreePosition(50.84174, 7.99898)); - quadtree.put(new DegreePosition(50.84141, 7.9998700000000005)); - quadtree.put(new DegreePosition(50.84085, 8.00121)); - quadtree.put(new DegreePosition(50.84001000000001, 8.00285)); - quadtree.put(new DegreePosition(50.83823, 8.00586)); - quadtree.put(new DegreePosition(50.83747, 8.00736)); - quadtree.put(new DegreePosition(50.83702, 8.008410000000001)); - quadtree.put(new DegreePosition(50.83662, 8.00956)); - quadtree.put(new DegreePosition(50.83628, 8.01093)); - quadtree.put(new DegreePosition(50.83605000000001, 8.012160000000002)); - quadtree.put(new DegreePosition(50.83545, 8.016810000000001)); - quadtree.put(new DegreePosition(50.83491, 8.01938)); - quadtree.put(new DegreePosition(50.834250000000004, 8.02161)); - quadtree.put(new DegreePosition(50.83332000000001, 8.025210000000001)); - quadtree.put(new DegreePosition(50.8331, 8.0264)); - quadtree.put(new DegreePosition(50.83299, 8.027230000000001)); - quadtree.put(new DegreePosition(50.832890000000006, 8.028220000000001)); - quadtree.put(new DegreePosition(50.832820000000005, 8.0296)); - quadtree.put(new DegreePosition(50.83283, 8.030740000000002)); - quadtree.put(new DegreePosition(50.832950000000004, 8.03252)); - quadtree.put(new DegreePosition(50.833180000000006, 8.034790000000001)); - quadtree.put(new DegreePosition(50.833200000000005, 8.036140000000001)); - quadtree.put(new DegreePosition(50.83316000000001, 8.03739)); - quadtree.put(new DegreePosition(50.83312, 8.03801)); - quadtree.put(new DegreePosition(50.83299, 8.03907)); - quadtree.put(new DegreePosition(50.832730000000005, 8.04087)); - quadtree.put(new DegreePosition(50.83243, 8.042290000000001)); - quadtree.put(new DegreePosition(50.831880000000005, 8.04424)); - quadtree.put(new DegreePosition(50.83072000000001, 8.047070000000001)); - quadtree.put(new DegreePosition(50.8286, 8.051340000000001)); - quadtree.put(new DegreePosition(50.82793, 8.052610000000001)); - quadtree.put(new DegreePosition(50.827650000000006, 8.053410000000001)); - quadtree.put(new DegreePosition(50.827290000000005, 8.054680000000001)); - quadtree.put(new DegreePosition(50.82674, 8.05762)); - quadtree.put(new DegreePosition(50.826620000000005, 8.05882)); - quadtree.put(new DegreePosition(50.826550000000005, 8.06226)); - quadtree.put(new DegreePosition(50.82645, 8.0639)); - quadtree.put(new DegreePosition(50.8263, 8.065420000000001)); - quadtree.put(new DegreePosition(50.82591000000001, 8.06756)); - quadtree.put(new DegreePosition(50.825540000000004, 8.06884)); - quadtree.put(new DegreePosition(50.82486, 8.070490000000001)); - quadtree.put(new DegreePosition(50.82444, 8.07141)); - quadtree.put(new DegreePosition(50.82368, 8.07266)); - quadtree.put(new DegreePosition(50.821760000000005, 8.075130000000001)); - quadtree.put(new DegreePosition(50.821020000000004, 8.07614)); - quadtree.put(new DegreePosition(50.8205, 8.077)); - quadtree.put(new DegreePosition(50.819950000000006, 8.078240000000001)); - quadtree.put(new DegreePosition(50.81955000000001, 8.079450000000001)); - quadtree.put(new DegreePosition(50.819120000000005, 8.08103)); - quadtree.put(new DegreePosition(50.81859000000001, 8.08351)); - quadtree.put(new DegreePosition(50.818130000000004, 8.08783)); - quadtree.put(new DegreePosition(50.81796000000001, 8.088840000000001)); - quadtree.put(new DegreePosition(50.81774000000001, 8.08971)); - quadtree.put(new DegreePosition(50.817420000000006, 8.090670000000001)); - quadtree.put(new DegreePosition(50.81671000000001, 8.09238)); - quadtree.put(new DegreePosition(50.81647, 8.09285)); - quadtree.put(new DegreePosition(50.81557, 8.094370000000001)); - quadtree.put(new DegreePosition(50.814960000000006, 8.09521)); - quadtree.put(new DegreePosition(50.81427000000001, 8.09603)); - quadtree.put(new DegreePosition(50.81369, 8.0966)); - quadtree.put(new DegreePosition(50.81291, 8.09722)); - quadtree.put(new DegreePosition(50.81228, 8.09763)); - quadtree.put(new DegreePosition(50.81022, 8.098590000000002)); - quadtree.put(new DegreePosition(50.80885000000001, 8.098980000000001)); - quadtree.put(new DegreePosition(50.806900000000006, 8.09918)); - quadtree.put(new DegreePosition(50.805350000000004, 8.09904)); - quadtree.put(new DegreePosition(50.804460000000006, 8.098790000000001)); - quadtree.put(new DegreePosition(50.803180000000005, 8.09833)); - quadtree.put(new DegreePosition(50.79974000000001, 8.09685)); - quadtree.put(new DegreePosition(50.798950000000005, 8.09665)); - quadtree.put(new DegreePosition(50.797990000000006, 8.09664)); - quadtree.put(new DegreePosition(50.79683000000001, 8.096760000000002)); - quadtree.put(new DegreePosition(50.795730000000006, 8.09699)); - quadtree.put(new DegreePosition(50.794630000000005, 8.097520000000001)); - quadtree.put(new DegreePosition(50.79238, 8.099340000000002)); - quadtree.put(new DegreePosition(50.79153, 8.10016)); - quadtree.put(new DegreePosition(50.79068, 8.10111)); - quadtree.put(new DegreePosition(50.789970000000004, 8.10195)); - quadtree.put(new DegreePosition(50.78891, 8.103380000000001)); - quadtree.put(new DegreePosition(50.78833, 8.104230000000001)); - quadtree.put(new DegreePosition(50.78759, 8.10539)); - quadtree.put(new DegreePosition(50.786970000000004, 8.10646)); - quadtree.put(new DegreePosition(50.78605, 8.10823)); - quadtree.put(new DegreePosition(50.78566000000001, 8.10905)); - quadtree.put(new DegreePosition(50.784890000000004, 8.11107)); - quadtree.put(new DegreePosition(50.784670000000006, 8.11194)); - quadtree.put(new DegreePosition(50.78437, 8.11344)); - quadtree.put(new DegreePosition(50.784200000000006, 8.11463)); - quadtree.put(new DegreePosition(50.78407000000001, 8.11609)); - quadtree.put(new DegreePosition(50.78407000000001, 8.11761)); - quadtree.put(new DegreePosition(50.78412, 8.118820000000001)); - quadtree.put(new DegreePosition(50.784240000000004, 8.11996)); - quadtree.put(new DegreePosition(50.78457, 8.121500000000001)); - quadtree.put(new DegreePosition(50.785250000000005, 8.12428)); - quadtree.put(new DegreePosition(50.78555, 8.125720000000001)); - quadtree.put(new DegreePosition(50.78586000000001, 8.12766)); - quadtree.put(new DegreePosition(50.786060000000006, 8.129610000000001)); - quadtree.put(new DegreePosition(50.78614, 8.13101)); - quadtree.put(new DegreePosition(50.78609, 8.13326)); - quadtree.put(new DegreePosition(50.78598, 8.13451)); - quadtree.put(new DegreePosition(50.78578, 8.135900000000001)); - quadtree.put(new DegreePosition(50.7854, 8.137880000000001)); - quadtree.put(new DegreePosition(50.78511, 8.13907)); - quadtree.put(new DegreePosition(50.784330000000004, 8.14145)); - quadtree.put(new DegreePosition(50.783840000000005, 8.14282)); - quadtree.put(new DegreePosition(50.78352, 8.143510000000001)); - quadtree.put(new DegreePosition(50.78285, 8.1446)); - quadtree.put(new DegreePosition(50.781650000000006, 8.14638)); - quadtree.put(new DegreePosition(50.781330000000004, 8.146920000000001)); - quadtree.put(new DegreePosition(50.780280000000005, 8.148060000000001)); - quadtree.put(new DegreePosition(50.77908000000001, 8.149090000000001)); - quadtree.put(new DegreePosition(50.7781, 8.14973)); - quadtree.put(new DegreePosition(50.77716, 8.150160000000001)); - quadtree.put(new DegreePosition(50.77579, 8.15053)); - quadtree.put(new DegreePosition(50.773810000000005, 8.150770000000001)); - quadtree.put(new DegreePosition(50.772470000000006, 8.15085)); - quadtree.put(new DegreePosition(50.77046000000001, 8.15074)); - quadtree.put(new DegreePosition(50.76778, 8.15074)); - quadtree.put(new DegreePosition(50.76690000000001, 8.15079)); - quadtree.put(new DegreePosition(50.76615, 8.15093)); - quadtree.put(new DegreePosition(50.76543, 8.15116)); - quadtree.put(new DegreePosition(50.764480000000006, 8.151670000000001)); - quadtree.put(new DegreePosition(50.763690000000004, 8.15222)); - quadtree.put(new DegreePosition(50.76299, 8.152890000000001)); - quadtree.put(new DegreePosition(50.76243, 8.15352)); - quadtree.put(new DegreePosition(50.76185, 8.15432)); - quadtree.put(new DegreePosition(50.761190000000006, 8.15549)); - quadtree.put(new DegreePosition(50.760670000000005, 8.1567)); - quadtree.put(new DegreePosition(50.76019, 8.15832)); - quadtree.put(new DegreePosition(50.759910000000005, 8.15996)); - quadtree.put(new DegreePosition(50.75983, 8.16092)); - quadtree.put(new DegreePosition(50.75974, 8.16243)); - quadtree.put(new DegreePosition(50.759780000000006, 8.164000000000001)); - quadtree.put(new DegreePosition(50.75992, 8.16568)); - quadtree.put(new DegreePosition(50.75997, 8.168050000000001)); - quadtree.put(new DegreePosition(50.75992, 8.16923)); - quadtree.put(new DegreePosition(50.759820000000005, 8.170480000000001)); - quadtree.put(new DegreePosition(50.75959, 8.172130000000001)); - quadtree.put(new DegreePosition(50.759190000000004, 8.17365)); - quadtree.put(new DegreePosition(50.75893000000001, 8.17436)); - quadtree.put(new DegreePosition(50.75638000000001, 8.18037)); - quadtree.put(new DegreePosition(50.755810000000004, 8.181840000000001)); - quadtree.put(new DegreePosition(50.755410000000005, 8.18307)); - quadtree.put(new DegreePosition(50.755160000000004, 8.184000000000001)); - quadtree.put(new DegreePosition(50.75482, 8.185960000000001)); - quadtree.put(new DegreePosition(50.75464, 8.18824)); - quadtree.put(new DegreePosition(50.75457, 8.19096)); - quadtree.put(new DegreePosition(50.754670000000004, 8.193750000000001)); - quadtree.put(new DegreePosition(50.75482, 8.19589)); - quadtree.put(new DegreePosition(50.75533, 8.20124)); - quadtree.put(new DegreePosition(50.75542, 8.202910000000001)); - quadtree.put(new DegreePosition(50.755390000000006, 8.20532)); - quadtree.put(new DegreePosition(50.75526000000001, 8.206850000000001)); - quadtree.put(new DegreePosition(50.75502, 8.20803)); - quadtree.put(new DegreePosition(50.75486, 8.20864)); - quadtree.put(new DegreePosition(50.75448, 8.209750000000001)); - quadtree.put(new DegreePosition(50.75424, 8.210320000000001)); - quadtree.put(new DegreePosition(50.75347000000001, 8.211730000000001)); - quadtree.put(new DegreePosition(50.75301, 8.21236)); - quadtree.put(new DegreePosition(50.75206000000001, 8.213370000000001)); - quadtree.put(new DegreePosition(50.74982000000001, 8.215440000000001)); - quadtree.put(new DegreePosition(50.748900000000006, 8.21657)); - quadtree.put(new DegreePosition(50.74851, 8.21718)); - quadtree.put(new DegreePosition(50.748200000000004, 8.21775)); - quadtree.put(new DegreePosition(50.74777, 8.21879)); - quadtree.put(new DegreePosition(50.7475, 8.21968)); - quadtree.put(new DegreePosition(50.747240000000005, 8.220880000000001)); - quadtree.put(new DegreePosition(50.7471, 8.22189)); - quadtree.put(new DegreePosition(50.74703, 8.222710000000001)); - quadtree.put(new DegreePosition(50.746990000000004, 8.22573)); - quadtree.put(new DegreePosition(50.74696, 8.22653)); - quadtree.put(new DegreePosition(50.74687, 8.22747)); - quadtree.put(new DegreePosition(50.746680000000005, 8.228530000000001)); - quadtree.put(new DegreePosition(50.746410000000004, 8.22976)); - quadtree.put(new DegreePosition(50.74609, 8.23095)); - quadtree.put(new DegreePosition(50.74515, 8.23367)); - quadtree.put(new DegreePosition(50.744620000000005, 8.235650000000001)); - quadtree.put(new DegreePosition(50.744310000000006, 8.237150000000002)); - quadtree.put(new DegreePosition(50.744150000000005, 8.238480000000001)); - quadtree.put(new DegreePosition(50.7441, 8.240540000000001)); - quadtree.put(new DegreePosition(50.74418000000001, 8.24193)); - quadtree.put(new DegreePosition(50.74445000000001, 8.24365)); - quadtree.put(new DegreePosition(50.745020000000004, 8.245940000000001)); - quadtree.put(new DegreePosition(50.74539000000001, 8.24769)); - quadtree.put(new DegreePosition(50.74557, 8.24881)); - quadtree.put(new DegreePosition(50.74562, 8.249590000000001)); - quadtree.put(new DegreePosition(50.7456, 8.250290000000001)); - quadtree.put(new DegreePosition(50.74542, 8.25223)); - quadtree.put(new DegreePosition(50.74519, 8.253340000000001)); - quadtree.put(new DegreePosition(50.7449, 8.2543)); - quadtree.put(new DegreePosition(50.74461, 8.25505)); - quadtree.put(new DegreePosition(50.74306000000001, 8.25831)); - quadtree.put(new DegreePosition(50.74279000000001, 8.25904)); - quadtree.put(new DegreePosition(50.74255, 8.25988)); - quadtree.put(new DegreePosition(50.742270000000005, 8.2612)); - quadtree.put(new DegreePosition(50.74213, 8.262070000000001)); - quadtree.put(new DegreePosition(50.742070000000005, 8.26314)); - quadtree.put(new DegreePosition(50.742090000000005, 8.26567)); - quadtree.put(new DegreePosition(50.741980000000005, 8.26735)); - quadtree.put(new DegreePosition(50.74181, 8.2687)); - quadtree.put(new DegreePosition(50.741670000000006, 8.26931)); - quadtree.put(new DegreePosition(50.741080000000004, 8.27088)); - quadtree.put(new DegreePosition(50.74072, 8.27158)); - quadtree.put(new DegreePosition(50.740030000000004, 8.27257)); - quadtree.put(new DegreePosition(50.73962, 8.273000000000001)); - quadtree.put(new DegreePosition(50.73875, 8.27365)); - quadtree.put(new DegreePosition(50.73783, 8.27405)); - quadtree.put(new DegreePosition(50.73745, 8.27416)); - quadtree.put(new DegreePosition(50.736830000000005, 8.27424)); - quadtree.put(new DegreePosition(50.734120000000004, 8.274090000000001)); - quadtree.put(new DegreePosition(50.73353, 8.27411)); - quadtree.put(new DegreePosition(50.732890000000005, 8.27424)); - quadtree.put(new DegreePosition(50.73174, 8.274790000000001)); - quadtree.put(new DegreePosition(50.73114, 8.27524)); - quadtree.put(new DegreePosition(50.7306, 8.27574)); - quadtree.put(new DegreePosition(50.72831000000001, 8.27865)); - quadtree.put(new DegreePosition(50.727430000000005, 8.279480000000001)); - quadtree.put(new DegreePosition(50.72471, 8.28134)); - quadtree.put(new DegreePosition(50.72368, 8.282210000000001)); - quadtree.put(new DegreePosition(50.7229, 8.28307)); - quadtree.put(new DegreePosition(50.72126, 8.28542)); - quadtree.put(new DegreePosition(50.72023, 8.28669)); - quadtree.put(new DegreePosition(50.71963, 8.28725)); - quadtree.put(new DegreePosition(50.71885, 8.287880000000001)); - quadtree.put(new DegreePosition(50.718120000000006, 8.28832)); - quadtree.put(new DegreePosition(50.7173, 8.2887)); - quadtree.put(new DegreePosition(50.71656, 8.28897)); - quadtree.put(new DegreePosition(50.71553, 8.28916)); - quadtree.put(new DegreePosition(50.71408, 8.28923)); - quadtree.put(new DegreePosition(50.713060000000006, 8.28913)); - quadtree.put(new DegreePosition(50.709050000000005, 8.28805)); - quadtree.put(new DegreePosition(50.70792, 8.28781)); - quadtree.put(new DegreePosition(50.70673000000001, 8.28772)); - quadtree.put(new DegreePosition(50.705130000000004, 8.28782)); - quadtree.put(new DegreePosition(50.70335000000001, 8.28827)); - quadtree.put(new DegreePosition(50.70219, 8.28876)); - quadtree.put(new DegreePosition(50.70096, 8.289520000000001)); - quadtree.put(new DegreePosition(50.697030000000005, 8.29296)); - quadtree.put(new DegreePosition(50.69567000000001, 8.294)); - quadtree.put(new DegreePosition(50.69485, 8.294540000000001)); - quadtree.put(new DegreePosition(50.69411, 8.29495)); - quadtree.put(new DegreePosition(50.6933, 8.295300000000001)); - quadtree.put(new DegreePosition(50.692690000000006, 8.295490000000001)); - quadtree.put(new DegreePosition(50.692040000000006, 8.29561)); - quadtree.put(new DegreePosition(50.69153000000001, 8.29564)); - quadtree.put(new DegreePosition(50.69035, 8.295390000000001)); - quadtree.put(new DegreePosition(50.68981, 8.29517)); - quadtree.put(new DegreePosition(50.68932, 8.2949)); - quadtree.put(new DegreePosition(50.688970000000005, 8.29465)); - quadtree.put(new DegreePosition(50.68847, 8.29418)); - quadtree.put(new DegreePosition(50.687760000000004, 8.293230000000001)); - quadtree.put(new DegreePosition(50.68713, 8.29218)); - quadtree.put(new DegreePosition(50.68657, 8.29096)); - quadtree.put(new DegreePosition(50.68558, 8.289000000000001)); - quadtree.put(new DegreePosition(50.684940000000005, 8.28805)); - quadtree.put(new DegreePosition(50.684430000000006, 8.28739)); - quadtree.put(new DegreePosition(50.682970000000005, 8.2859)); - quadtree.put(new DegreePosition(50.68198, 8.28515)); - quadtree.put(new DegreePosition(50.68065000000001, 8.28434)); - quadtree.put(new DegreePosition(50.679410000000004, 8.28377)); - quadtree.put(new DegreePosition(50.67868000000001, 8.28353)); - quadtree.put(new DegreePosition(50.67734, 8.283330000000001)); - quadtree.put(new DegreePosition(50.676190000000005, 8.28331)); - quadtree.put(new DegreePosition(50.674110000000006, 8.28378)); - quadtree.put(new DegreePosition(50.672760000000004, 8.28443)); - quadtree.put(new DegreePosition(50.67183000000001, 8.28505)); - quadtree.put(new DegreePosition(50.671130000000005, 8.28561)); - quadtree.put(new DegreePosition(50.67058, 8.286140000000001)); - quadtree.put(new DegreePosition(50.66993, 8.28687)); - quadtree.put(new DegreePosition(50.66884, 8.28832)); - quadtree.put(new DegreePosition(50.66789000000001, 8.28984)); - quadtree.put(new DegreePosition(50.667030000000004, 8.291440000000001)); - quadtree.put(new DegreePosition(50.66601000000001, 8.29364)); - quadtree.put(new DegreePosition(50.665350000000004, 8.295240000000002)); - quadtree.put(new DegreePosition(50.66480000000001, 8.29676)); - quadtree.put(new DegreePosition(50.66442000000001, 8.29808)); - quadtree.put(new DegreePosition(50.66405, 8.299660000000001)); - quadtree.put(new DegreePosition(50.663720000000005, 8.30133)); - quadtree.put(new DegreePosition(50.66330000000001, 8.304300000000001)); - quadtree.put(new DegreePosition(50.66290000000001, 8.307960000000001)); - quadtree.put(new DegreePosition(50.662670000000006, 8.309560000000001)); - quadtree.put(new DegreePosition(50.6623, 8.31123)); - quadtree.put(new DegreePosition(50.66174, 8.313260000000001)); - quadtree.put(new DegreePosition(50.660970000000006, 8.315370000000001)); - quadtree.put(new DegreePosition(50.660320000000006, 8.316650000000001)); - quadtree.put(new DegreePosition(50.659740000000006, 8.317590000000001)); - quadtree.put(new DegreePosition(50.65899, 8.318570000000001)); - quadtree.put(new DegreePosition(50.658210000000004, 8.3193)); - quadtree.put(new DegreePosition(50.657070000000004, 8.31994)); - quadtree.put(new DegreePosition(50.65578000000001, 8.320300000000001)); - quadtree.put(new DegreePosition(50.65391, 8.320400000000001)); - quadtree.put(new DegreePosition(50.65328, 8.320500000000001)); - quadtree.put(new DegreePosition(50.65240000000001, 8.32074)); - quadtree.put(new DegreePosition(50.65128000000001, 8.32118)); - quadtree.put(new DegreePosition(50.65091, 8.32137)); - quadtree.put(new DegreePosition(50.6465, 8.32409)); - quadtree.put(new DegreePosition(50.645360000000004, 8.32454)); - quadtree.put(new DegreePosition(50.644740000000006, 8.32469)); - quadtree.put(new DegreePosition(50.64193, 8.32482)); - quadtree.put(new DegreePosition(50.639920000000004, 8.32511)); - quadtree.put(new DegreePosition(50.63812, 8.325650000000001)); - quadtree.put(new DegreePosition(50.63673000000001, 8.326400000000001)); - quadtree.put(new DegreePosition(50.635740000000006, 8.32704)); - quadtree.put(new DegreePosition(50.634570000000004, 8.32797)); - quadtree.put(new DegreePosition(50.63354, 8.32891)); - quadtree.put(new DegreePosition(50.63253, 8.32995)); - quadtree.put(new DegreePosition(50.629650000000005, 8.33336)); - quadtree.put(new DegreePosition(50.62903000000001, 8.33422)); - quadtree.put(new DegreePosition(50.628550000000004, 8.335)); - quadtree.put(new DegreePosition(50.62765, 8.33667)); - quadtree.put(new DegreePosition(50.627250000000004, 8.33755)); - quadtree.put(new DegreePosition(50.626760000000004, 8.33876)); - quadtree.put(new DegreePosition(50.62648, 8.33961)); - quadtree.put(new DegreePosition(50.62586, 8.341890000000001)); - quadtree.put(new DegreePosition(50.62556000000001, 8.34319)); - quadtree.put(new DegreePosition(50.62519, 8.34521)); - quadtree.put(new DegreePosition(50.624990000000004, 8.34656)); - quadtree.put(new DegreePosition(50.62485, 8.34781)); - quadtree.put(new DegreePosition(50.6246, 8.3511)); - quadtree.put(new DegreePosition(50.62435000000001, 8.352910000000001)); - quadtree.put(new DegreePosition(50.624050000000004, 8.3544)); - quadtree.put(new DegreePosition(50.623810000000006, 8.35543)); - quadtree.put(new DegreePosition(50.62366, 8.35593)); - quadtree.put(new DegreePosition(50.622240000000005, 8.359910000000001)); - quadtree.put(new DegreePosition(50.621770000000005, 8.36156)); - quadtree.put(new DegreePosition(50.62156, 8.362480000000001)); - quadtree.put(new DegreePosition(50.621230000000004, 8.3644)); - quadtree.put(new DegreePosition(50.62078, 8.36975)); - quadtree.put(new DegreePosition(50.620520000000006, 8.37114)); - quadtree.put(new DegreePosition(50.620160000000006, 8.37249)); - quadtree.put(new DegreePosition(50.61988, 8.373320000000001)); - quadtree.put(new DegreePosition(50.61932, 8.374690000000001)); - quadtree.put(new DegreePosition(50.61887, 8.375950000000001)); - quadtree.put(new DegreePosition(50.618570000000005, 8.37725)); - quadtree.put(new DegreePosition(50.61827, 8.37898)); - quadtree.put(new DegreePosition(50.618170000000006, 8.380270000000001)); - quadtree.put(new DegreePosition(50.61816, 8.382190000000001)); - quadtree.put(new DegreePosition(50.618520000000004, 8.389460000000001)); - quadtree.put(new DegreePosition(50.618570000000005, 8.39309)); - quadtree.put(new DegreePosition(50.618500000000004, 8.394480000000001)); - quadtree.put(new DegreePosition(50.618410000000004, 8.395470000000001)); - quadtree.put(new DegreePosition(50.61824000000001, 8.39675)); - quadtree.put(new DegreePosition(50.618080000000006, 8.39764)); - quadtree.put(new DegreePosition(50.61777000000001, 8.39906)); - quadtree.put(new DegreePosition(50.61713, 8.40116)); - quadtree.put(new DegreePosition(50.61679, 8.40215)); - quadtree.put(new DegreePosition(50.612660000000005, 8.413)); - quadtree.put(new DegreePosition(50.61168000000001, 8.415470000000001)); - quadtree.put(new DegreePosition(50.611430000000006, 8.41623)); - quadtree.put(new DegreePosition(50.61102, 8.41787)); - quadtree.put(new DegreePosition(50.61056000000001, 8.42032)); - quadtree.put(new DegreePosition(50.61019, 8.4238)); - quadtree.put(new DegreePosition(50.60992, 8.42535)); - quadtree.put(new DegreePosition(50.60947, 8.42707)); - quadtree.put(new DegreePosition(50.60904000000001, 8.42833)); - quadtree.put(new DegreePosition(50.60857000000001, 8.42956)); - quadtree.put(new DegreePosition(50.60792000000001, 8.43158)); - quadtree.put(new DegreePosition(50.60761, 8.432680000000001)); - quadtree.put(new DegreePosition(50.60723, 8.434370000000001)); - quadtree.put(new DegreePosition(50.60701, 8.43645)); - quadtree.put(new DegreePosition(50.60698000000001, 8.43796)); - quadtree.put(new DegreePosition(50.6071, 8.44045)); - quadtree.put(new DegreePosition(50.60718000000001, 8.443100000000001)); - quadtree.put(new DegreePosition(50.60717, 8.444220000000001)); - quadtree.put(new DegreePosition(50.607020000000006, 8.446140000000002)); - quadtree.put(new DegreePosition(50.606770000000004, 8.44772)); - quadtree.put(new DegreePosition(50.60647, 8.44902)); - quadtree.put(new DegreePosition(50.606100000000005, 8.450230000000001)); - quadtree.put(new DegreePosition(50.60551, 8.45195)); - quadtree.put(new DegreePosition(50.605030000000006, 8.45358)); - quadtree.put(new DegreePosition(50.60465000000001, 8.45495)); - quadtree.put(new DegreePosition(50.60445000000001, 8.455950000000001)); - quadtree.put(new DegreePosition(50.60427000000001, 8.457230000000001)); - quadtree.put(new DegreePosition(50.60421, 8.458060000000001)); - quadtree.put(new DegreePosition(50.60419, 8.45945)); - quadtree.put(new DegreePosition(50.6043, 8.46292)); - quadtree.put(new DegreePosition(50.604290000000006, 8.46471)); - quadtree.put(new DegreePosition(50.60416000000001, 8.4664)); - quadtree.put(new DegreePosition(50.603930000000005, 8.468)); - quadtree.put(new DegreePosition(50.603790000000004, 8.46873)); - quadtree.put(new DegreePosition(50.603570000000005, 8.469650000000001)); - quadtree.put(new DegreePosition(50.60318, 8.471)); - quadtree.put(new DegreePosition(50.60242, 8.473030000000001)); - quadtree.put(new DegreePosition(50.60193, 8.474110000000001)); - quadtree.put(new DegreePosition(50.60146, 8.47493)); - quadtree.put(new DegreePosition(50.600730000000006, 8.47606)); - quadtree.put(new DegreePosition(50.599160000000005, 8.478340000000001)); - quadtree.put(new DegreePosition(50.59787000000001, 8.480580000000002)); - quadtree.put(new DegreePosition(50.59725, 8.48202)); - quadtree.put(new DegreePosition(50.59673, 8.48366)); - quadtree.put(new DegreePosition(50.596270000000004, 8.48583)); - quadtree.put(new DegreePosition(50.59617, 8.48643)); - quadtree.put(new DegreePosition(50.596000000000004, 8.48804)); - quadtree.put(new DegreePosition(50.59592000000001, 8.490260000000001)); - quadtree.put(new DegreePosition(50.59588, 8.49361)); - quadtree.put(new DegreePosition(50.59581000000001, 8.49463)); - quadtree.put(new DegreePosition(50.595710000000004, 8.49549)); - quadtree.put(new DegreePosition(50.59539, 8.49712)); - quadtree.put(new DegreePosition(50.59505000000001, 8.49849)); - quadtree.put(new DegreePosition(50.594030000000004, 8.50145)); - quadtree.put(new DegreePosition(50.59357000000001, 8.50289)); - quadtree.put(new DegreePosition(50.593340000000005, 8.50379)); - quadtree.put(new DegreePosition(50.593160000000005, 8.50468)); - quadtree.put(new DegreePosition(50.59292000000001, 8.506120000000001)); - quadtree.put(new DegreePosition(50.59279, 8.50716)); - quadtree.put(new DegreePosition(50.592740000000006, 8.50793)); - quadtree.put(new DegreePosition(50.592740000000006, 8.50988)); - quadtree.put(new DegreePosition(50.59282, 8.51248)); - quadtree.put(new DegreePosition(50.59273, 8.513850000000001)); - quadtree.put(new DegreePosition(50.59245000000001, 8.5157)); - quadtree.put(new DegreePosition(50.592110000000005, 8.51736)); - quadtree.put(new DegreePosition(50.59109, 8.52072)); - quadtree.put(new DegreePosition(50.58995, 8.52393)); - quadtree.put(new DegreePosition(50.58919, 8.525780000000001)); - quadtree.put(new DegreePosition(50.588350000000005, 8.52753)); - quadtree.put(new DegreePosition(50.586940000000006, 8.53031)); - quadtree.put(new DegreePosition(50.585660000000004, 8.5327)); - quadtree.put(new DegreePosition(50.58467, 8.534270000000001)); - quadtree.put(new DegreePosition(50.582950000000004, 8.53668)); - quadtree.put(new DegreePosition(50.58055, 8.539750000000002)); - quadtree.put(new DegreePosition(50.57909, 8.541250000000002)); - quadtree.put(new DegreePosition(50.578230000000005, 8.542060000000001)); - quadtree.put(new DegreePosition(50.57721, 8.542910000000001)); - quadtree.put(new DegreePosition(50.57509, 8.544360000000001)); - quadtree.put(new DegreePosition(50.572190000000006, 8.546000000000001)); - quadtree.put(new DegreePosition(50.56996, 8.54697)); - quadtree.put(new DegreePosition(50.567350000000005, 8.548210000000001)); - quadtree.put(new DegreePosition(50.566590000000005, 8.548670000000001)); - quadtree.put(new DegreePosition(50.565740000000005, 8.5493)); - quadtree.put(new DegreePosition(50.56519, 8.54981)); - quadtree.put(new DegreePosition(50.56466, 8.55035)); - quadtree.put(new DegreePosition(50.56418000000001, 8.551010000000002)); - quadtree.put(new DegreePosition(50.56376, 8.551630000000001)); - quadtree.put(new DegreePosition(50.56293, 8.55311)); - quadtree.put(new DegreePosition(50.56206, 8.55527)); - quadtree.put(new DegreePosition(50.56136000000001, 8.55738)); - quadtree.put(new DegreePosition(50.56071000000001, 8.55915)); - quadtree.put(new DegreePosition(50.560230000000004, 8.560160000000002)); - quadtree.put(new DegreePosition(50.55978, 8.560970000000001)); - quadtree.put(new DegreePosition(50.55922, 8.56179)); - quadtree.put(new DegreePosition(50.558910000000004, 8.56215)); - quadtree.put(new DegreePosition(50.55834, 8.562700000000001)); - quadtree.put(new DegreePosition(50.55731, 8.56347)); - quadtree.put(new DegreePosition(50.55677000000001, 8.56377)); - quadtree.put(new DegreePosition(50.55624, 8.563960000000002)); - quadtree.put(new DegreePosition(50.55556000000001, 8.564110000000001)); - quadtree.put(new DegreePosition(50.55353, 8.564390000000001)); - quadtree.put(new DegreePosition(50.552040000000005, 8.56466)); - quadtree.put(new DegreePosition(50.55109, 8.56494)); - quadtree.put(new DegreePosition(50.550160000000005, 8.56536)); - quadtree.put(new DegreePosition(50.549730000000004, 8.565620000000001)); - quadtree.put(new DegreePosition(50.54919, 8.56601)); - quadtree.put(new DegreePosition(50.548460000000006, 8.56667)); - quadtree.put(new DegreePosition(50.54798, 8.567250000000001)); - quadtree.put(new DegreePosition(50.5469, 8.56872)); - quadtree.put(new DegreePosition(50.54536, 8.57126)); - quadtree.put(new DegreePosition(50.542260000000006, 8.576590000000001)); - quadtree.put(new DegreePosition(50.541430000000005, 8.577720000000001)); - quadtree.put(new DegreePosition(50.540580000000006, 8.57877)); - quadtree.put(new DegreePosition(50.539480000000005, 8.57994)); - quadtree.put(new DegreePosition(50.537870000000005, 8.58139)); - quadtree.put(new DegreePosition(50.53676, 8.582270000000001)); - quadtree.put(new DegreePosition(50.534110000000005, 8.58411)); - quadtree.put(new DegreePosition(50.53333000000001, 8.58478)); - quadtree.put(new DegreePosition(50.53266000000001, 8.585450000000002)); - quadtree.put(new DegreePosition(50.5317, 8.586540000000001)); - quadtree.put(new DegreePosition(50.53099, 8.587610000000002)); - quadtree.put(new DegreePosition(50.53056, 8.58835)); - quadtree.put(new DegreePosition(50.530080000000005, 8.58932)); - quadtree.put(new DegreePosition(50.52947, 8.590900000000001)); - quadtree.put(new DegreePosition(50.529050000000005, 8.592310000000001)); - quadtree.put(new DegreePosition(50.528800000000004, 8.593430000000001)); - quadtree.put(new DegreePosition(50.528620000000004, 8.594510000000001)); - quadtree.put(new DegreePosition(50.52828, 8.597700000000001)); - quadtree.put(new DegreePosition(50.527750000000005, 8.60692)); - quadtree.put(new DegreePosition(50.52731000000001, 8.61212)); - quadtree.put(new DegreePosition(50.526300000000006, 8.62259)); - quadtree.put(new DegreePosition(50.525800000000004, 8.626420000000001)); - quadtree.put(new DegreePosition(50.52514000000001, 8.630510000000001)); - quadtree.put(new DegreePosition(50.52481, 8.63277)); - quadtree.put(new DegreePosition(50.524010000000004, 8.6376)); - quadtree.put(new DegreePosition(50.52364000000001, 8.64067)); - quadtree.put(new DegreePosition(50.522420000000004, 8.645380000000001)); - quadtree.put(new DegreePosition(50.52181, 8.64794)); - quadtree.put(new DegreePosition(50.521530000000006, 8.648950000000001)); - quadtree.put(new DegreePosition(50.52031, 8.654100000000001)); - quadtree.put(new DegreePosition(50.51888, 8.65944)); - quadtree.put(new DegreePosition(50.51749, 8.66398)); - quadtree.put(new DegreePosition(50.51699000000001, 8.66531)); - quadtree.put(new DegreePosition(50.516650000000006, 8.666110000000002)); - quadtree.put(new DegreePosition(50.515150000000006, 8.66932)); - quadtree.put(new DegreePosition(50.5146, 8.67041)); - quadtree.put(new DegreePosition(50.513670000000005, 8.6721)); - quadtree.put(new DegreePosition(50.51285000000001, 8.673440000000001)); - quadtree.put(new DegreePosition(50.511810000000004, 8.674930000000002)); - quadtree.put(new DegreePosition(50.510630000000006, 8.67633)); - quadtree.put(new DegreePosition(50.50940000000001, 8.677700000000002)); - quadtree.put(new DegreePosition(50.508810000000004, 8.67824)); - quadtree.put(new DegreePosition(50.50750000000001, 8.67932)); - quadtree.put(new DegreePosition(50.50681, 8.679810000000002)); - quadtree.put(new DegreePosition(50.50526000000001, 8.680760000000001)); - quadtree.put(new DegreePosition(50.504470000000005, 8.68116)); - quadtree.put(new DegreePosition(50.50274, 8.681830000000001)); - quadtree.put(new DegreePosition(50.50198, 8.682070000000001)); - quadtree.put(new DegreePosition(50.498810000000006, 8.682690000000001)); - quadtree.put(new DegreePosition(50.49728, 8.68313)); - quadtree.put(new DegreePosition(50.49476000000001, 8.68368)); - quadtree.put(new DegreePosition(50.49369, 8.684050000000001)); - quadtree.put(new DegreePosition(50.49269, 8.68449)); - quadtree.put(new DegreePosition(50.49157, 8.68506)); - quadtree.put(new DegreePosition(50.490520000000004, 8.685740000000001)); - quadtree.put(new DegreePosition(50.48989, 8.68622)); - quadtree.put(new DegreePosition(50.48901000000001, 8.686950000000001)); - quadtree.put(new DegreePosition(50.488040000000005, 8.687940000000001)); - quadtree.put(new DegreePosition(50.487010000000005, 8.68913)); - quadtree.put(new DegreePosition(50.48583000000001, 8.69068)); - quadtree.put(new DegreePosition(50.484260000000006, 8.69318)); - quadtree.put(new DegreePosition(50.483340000000005, 8.69505)); - quadtree.put(new DegreePosition(50.48272000000001, 8.69658)); - quadtree.put(new DegreePosition(50.482110000000006, 8.698260000000001)); - quadtree.put(new DegreePosition(50.481390000000005, 8.70049)); - quadtree.put(new DegreePosition(50.48093, 8.70208)); - quadtree.put(new DegreePosition(50.48021000000001, 8.705020000000001)); - quadtree.put(new DegreePosition(50.47822000000001, 8.71437)); - quadtree.put(new DegreePosition(50.477940000000004, 8.715430000000001)); - quadtree.put(new DegreePosition(50.47721000000001, 8.717260000000001)); - quadtree.put(new DegreePosition(50.47652, 8.718820000000001)); - quadtree.put(new DegreePosition(50.476240000000004, 8.71929)); - quadtree.put(new DegreePosition(50.475660000000005, 8.72009)); - quadtree.put(new DegreePosition(50.47426, 8.72159)); - quadtree.put(new DegreePosition(50.47375, 8.722000000000001)); - quadtree.put(new DegreePosition(50.47307000000001, 8.7224)); - quadtree.put(new DegreePosition(50.472390000000004, 8.72273)); - quadtree.put(new DegreePosition(50.47173, 8.72295)); - quadtree.put(new DegreePosition(50.471050000000005, 8.723090000000001)); - quadtree.put(new DegreePosition(50.46989000000001, 8.723080000000001)); - quadtree.put(new DegreePosition(50.46876, 8.72295)); - quadtree.put(new DegreePosition(50.466840000000005, 8.72241)); - quadtree.put(new DegreePosition(50.465230000000005, 8.721820000000001)); - quadtree.put(new DegreePosition(50.46338, 8.72098)); - quadtree.put(new DegreePosition(50.463100000000004, 8.72091)); - quadtree.put(new DegreePosition(50.461200000000005, 8.71997)); - quadtree.put(new DegreePosition(50.46023, 8.71925)); - quadtree.put(new DegreePosition(50.459410000000005, 8.718570000000001)); - quadtree.put(new DegreePosition(50.458600000000004, 8.7178)); - quadtree.put(new DegreePosition(50.45769000000001, 8.716790000000001)); - quadtree.put(new DegreePosition(50.456610000000005, 8.71546)); - quadtree.put(new DegreePosition(50.455490000000005, 8.7139)); - quadtree.put(new DegreePosition(50.45445, 8.712250000000001)); - quadtree.put(new DegreePosition(50.45373000000001, 8.710980000000001)); - quadtree.put(new DegreePosition(50.45327, 8.71006)); - quadtree.put(new DegreePosition(50.45239, 8.70799)); - quadtree.put(new DegreePosition(50.44941000000001, 8.70059)); - quadtree.put(new DegreePosition(50.448820000000005, 8.69945)); - quadtree.put(new DegreePosition(50.448460000000004, 8.69888)); - quadtree.put(new DegreePosition(50.447900000000004, 8.69806)); - quadtree.put(new DegreePosition(50.44698, 8.69688)); - quadtree.put(new DegreePosition(50.44641000000001, 8.6963)); - quadtree.put(new DegreePosition(50.44548, 8.695500000000001)); - quadtree.put(new DegreePosition(50.44501, 8.695160000000001)); - quadtree.put(new DegreePosition(50.44411, 8.69472)); - quadtree.put(new DegreePosition(50.44333, 8.69443)); - quadtree.put(new DegreePosition(50.44202000000001, 8.694180000000001)); - quadtree.put(new DegreePosition(50.44145, 8.694170000000002)); - quadtree.put(new DegreePosition(50.440560000000005, 8.69425)); - quadtree.put(new DegreePosition(50.432640000000006, 8.69574)); - quadtree.put(new DegreePosition(50.42264, 8.697370000000001)); - quadtree.put(new DegreePosition(50.405300000000004, 8.70054)); - quadtree.put(new DegreePosition(50.395720000000004, 8.70219)); - quadtree.put(new DegreePosition(50.391580000000005, 8.70298)); - quadtree.put(new DegreePosition(50.38085, 8.7048)); - quadtree.put(new DegreePosition(50.375750000000004, 8.70561)); - quadtree.put(new DegreePosition(50.374950000000005, 8.70569)); - quadtree.put(new DegreePosition(50.373850000000004, 8.70564)); - quadtree.put(new DegreePosition(50.37259, 8.705400000000001)); - quadtree.put(new DegreePosition(50.37050000000001, 8.70457)); - quadtree.put(new DegreePosition(50.369170000000004, 8.70387)); - quadtree.put(new DegreePosition(50.367610000000006, 8.702910000000001)); - quadtree.put(new DegreePosition(50.36612, 8.7018)); - quadtree.put(new DegreePosition(50.36075, 8.6972)); - quadtree.put(new DegreePosition(50.35831, 8.69533)); - quadtree.put(new DegreePosition(50.356950000000005, 8.69443)); - quadtree.put(new DegreePosition(50.35558, 8.69372)); - quadtree.put(new DegreePosition(50.354400000000005, 8.69335)); - quadtree.put(new DegreePosition(50.35246, 8.69284)); - quadtree.put(new DegreePosition(50.35105, 8.692620000000002)); - quadtree.put(new DegreePosition(50.350170000000006, 8.69257)); - quadtree.put(new DegreePosition(50.34955, 8.692580000000001)); - quadtree.put(new DegreePosition(50.34857, 8.69275)); - quadtree.put(new DegreePosition(50.346230000000006, 8.693470000000001)); - quadtree.put(new DegreePosition(50.341210000000004, 8.69532)); - quadtree.put(new DegreePosition(50.34002, 8.695680000000001)); - quadtree.put(new DegreePosition(50.33867000000001, 8.695910000000001)); - quadtree.put(new DegreePosition(50.33753, 8.69594)); - quadtree.put(new DegreePosition(50.3361, 8.695860000000001)); - quadtree.put(new DegreePosition(50.333580000000005, 8.69542)); - quadtree.put(new DegreePosition(50.32048, 8.69252)); - quadtree.put(new DegreePosition(50.31851, 8.69196)); - quadtree.put(new DegreePosition(50.31781, 8.6917)); - quadtree.put(new DegreePosition(50.31709000000001, 8.69139)); - quadtree.put(new DegreePosition(50.31595, 8.690760000000001)); - quadtree.put(new DegreePosition(50.314600000000006, 8.68979)); - quadtree.put(new DegreePosition(50.31338, 8.68871)); - quadtree.put(new DegreePosition(50.31116, 8.68621)); - quadtree.put(new DegreePosition(50.30666, 8.680890000000002)); - quadtree.put(new DegreePosition(50.3034, 8.67722)); - quadtree.put(new DegreePosition(50.302640000000004, 8.67643)); - quadtree.put(new DegreePosition(50.301930000000006, 8.675830000000001)); - quadtree.put(new DegreePosition(50.30136, 8.675410000000001)); - quadtree.put(new DegreePosition(50.30044, 8.67488)); - quadtree.put(new DegreePosition(50.298860000000005, 8.674140000000001)); - quadtree.put(new DegreePosition(50.29008, 8.67112)); - quadtree.put(new DegreePosition(50.286120000000004, 8.669820000000001)); - quadtree.put(new DegreePosition(50.27376, 8.665550000000001)); - quadtree.put(new DegreePosition(50.26568, 8.662920000000002)); - quadtree.put(new DegreePosition(50.26428000000001, 8.662640000000001)); - quadtree.put(new DegreePosition(50.26334000000001, 8.662560000000001)); - quadtree.put(new DegreePosition(50.248900000000006, 8.662740000000001)); - quadtree.put(new DegreePosition(50.24667, 8.662730000000002)); - quadtree.put(new DegreePosition(50.231930000000006, 8.662970000000001)); - quadtree.put(new DegreePosition(50.21923, 8.6631)); - quadtree.put(new DegreePosition(50.21822, 8.66305)); - quadtree.put(new DegreePosition(50.21732, 8.66291)); - quadtree.put(new DegreePosition(50.216240000000006, 8.66267)); - quadtree.put(new DegreePosition(50.215270000000004, 8.662370000000001)); - quadtree.put(new DegreePosition(50.214200000000005, 8.66192)); - quadtree.put(new DegreePosition(50.21316, 8.661370000000002)); - quadtree.put(new DegreePosition(50.21162, 8.660350000000001)); - quadtree.put(new DegreePosition(50.210660000000004, 8.659600000000001)); - quadtree.put(new DegreePosition(50.209430000000005, 8.65841)); - quadtree.put(new DegreePosition(50.20864, 8.6575)); - quadtree.put(new DegreePosition(50.208090000000006, 8.656830000000001)); - quadtree.put(new DegreePosition(50.206920000000004, 8.65522)); - quadtree.put(new DegreePosition(50.206500000000005, 8.65456)); - quadtree.put(new DegreePosition(50.20488, 8.651760000000001)); - quadtree.put(new DegreePosition(50.203190000000006, 8.649080000000001)); - quadtree.put(new DegreePosition(50.19881, 8.64192)); - quadtree.put(new DegreePosition(50.196600000000004, 8.637440000000002)); - quadtree.put(new DegreePosition(50.188770000000005, 8.6239)); - quadtree.put(new DegreePosition(50.18795, 8.62259)); - quadtree.put(new DegreePosition(50.187070000000006, 8.621360000000001)); - quadtree.put(new DegreePosition(50.18627000000001, 8.6204)); - quadtree.put(new DegreePosition(50.185120000000005, 8.619190000000001)); - quadtree.put(new DegreePosition(50.18388, 8.61808)); - quadtree.put(new DegreePosition(50.182770000000005, 8.61726)); - quadtree.put(new DegreePosition(50.18178, 8.61665)); - quadtree.put(new DegreePosition(50.17159, 8.61101)); - quadtree.put(new DegreePosition(50.16395000000001, 8.606710000000001)); - quadtree.put(new DegreePosition(50.15317, 8.600760000000001)); - quadtree.put(new DegreePosition(50.149210000000004, 8.598640000000001)); - quadtree.put(new DegreePosition(50.14303, 8.59515)); - quadtree.put(new DegreePosition(50.13889, 8.592960000000001)); - quadtree.put(new DegreePosition(50.13496000000001, 8.590810000000001)); - quadtree.put(new DegreePosition(50.1332, 8.590190000000002)); - quadtree.put(new DegreePosition(50.13172, 8.58994)); - quadtree.put(new DegreePosition(50.13055000000001, 8.58993)); - quadtree.put(new DegreePosition(50.129450000000006, 8.59008)); - quadtree.put(new DegreePosition(50.12838000000001, 8.590340000000001)); - quadtree.put(new DegreePosition(50.12713, 8.59083)); - quadtree.put(new DegreePosition(50.12621000000001, 8.591320000000001)); - quadtree.put(new DegreePosition(50.118970000000004, 8.595970000000001)); - quadtree.put(new DegreePosition(50.10857000000001, 8.60274)); - quadtree.put(new DegreePosition(50.105970000000006, 8.604600000000001)); - quadtree.put(new DegreePosition(50.105050000000006, 8.605410000000001)); - quadtree.put(new DegreePosition(50.104200000000006, 8.60622)); - quadtree.put(new DegreePosition(50.10295000000001, 8.607560000000001)); - quadtree.put(new DegreePosition(50.10038, 8.61049)); - quadtree.put(new DegreePosition(50.09879, 8.61238)); - quadtree.put(new DegreePosition(50.09743, 8.61368)); - quadtree.put(new DegreePosition(50.09572000000001, 8.6151)); - quadtree.put(new DegreePosition(50.09489000000001, 8.61561)); - quadtree.put(new DegreePosition(50.093360000000004, 8.616290000000001)); - quadtree.put(new DegreePosition(50.09149000000001, 8.616940000000001)); - quadtree.put(new DegreePosition(50.087880000000006, 8.617780000000002)); - quadtree.put(new DegreePosition(50.08559, 8.618160000000001)); - quadtree.put(new DegreePosition(50.08471, 8.61824)); - quadtree.put(new DegreePosition(50.083000000000006, 8.61814)); - quadtree.put(new DegreePosition(50.081680000000006, 8.617930000000001)); - quadtree.put(new DegreePosition(50.080400000000004, 8.617560000000001)); - quadtree.put(new DegreePosition(50.0797, 8.6173)); - quadtree.put(new DegreePosition(50.07835000000001, 8.61668)); - quadtree.put(new DegreePosition(50.06631, 8.61067)); - quadtree.put(new DegreePosition(50.06239, 8.608640000000001)); - quadtree.put(new DegreePosition(50.05212, 8.60364)); - quadtree.put(new DegreePosition(50.03866000000001, 8.596810000000001)); - quadtree.put(new DegreePosition(50.03486, 8.59505)); - quadtree.put(new DegreePosition(50.033010000000004, 8.5943)); - quadtree.put(new DegreePosition(50.03032, 8.59347)); - quadtree.put(new DegreePosition(50.02763, 8.59284)); - quadtree.put(new DegreePosition(50.025240000000004, 8.59252)); - quadtree.put(new DegreePosition(50.024260000000005, 8.59247)); - quadtree.put(new DegreePosition(50.02208, 8.59247)); - quadtree.put(new DegreePosition(50.020790000000005, 8.592550000000001)); - quadtree.put(new DegreePosition(50.019270000000006, 8.59271)); - quadtree.put(new DegreePosition(50.016510000000004, 8.593240000000002)); - quadtree.put(new DegreePosition(49.983160000000005, 8.60124)); - quadtree.put(new DegreePosition(49.976580000000006, 8.60275)); - quadtree.put(new DegreePosition(49.97316000000001, 8.60363)); - quadtree.put(new DegreePosition(49.96228000000001, 8.60614)); - quadtree.put(new DegreePosition(49.95414, 8.60811)); - quadtree.put(new DegreePosition(49.95309, 8.608310000000001)); - quadtree.put(new DegreePosition(49.95217, 8.60857)); - quadtree.put(new DegreePosition(49.948350000000005, 8.609440000000001)); - quadtree.put(new DegreePosition(49.943200000000004, 8.610750000000001)); - quadtree.put(new DegreePosition(49.941720000000004, 8.611040000000001)); - quadtree.put(new DegreePosition(49.935280000000006, 8.612530000000001)); - quadtree.put(new DegreePosition(49.933550000000004, 8.6128)); - quadtree.put(new DegreePosition(49.93207, 8.612960000000001)); - quadtree.put(new DegreePosition(49.93084, 8.61302)); - quadtree.put(new DegreePosition(49.928740000000005, 8.61299)); - quadtree.put(new DegreePosition(49.926950000000005, 8.61283)); - quadtree.put(new DegreePosition(49.92591, 8.61269)); - quadtree.put(new DegreePosition(49.92389000000001, 8.612260000000001)); - quadtree.put(new DegreePosition(49.92083, 8.6113)); - quadtree.put(new DegreePosition(49.91532, 8.609390000000001)); - quadtree.put(new DegreePosition(49.914170000000006, 8.60905)); - quadtree.put(new DegreePosition(49.91042, 8.60819)); - quadtree.put(new DegreePosition(49.89902000000001, 8.60598)); - quadtree.put(new DegreePosition(49.89663, 8.605450000000001)); - quadtree.put(new DegreePosition(49.887750000000004, 8.603710000000001)); - quadtree.put(new DegreePosition(49.886900000000004, 8.603610000000002)); - quadtree.put(new DegreePosition(49.88288000000001, 8.60288)); - quadtree.put(new DegreePosition(49.87717000000001, 8.60196)); - quadtree.put(new DegreePosition(49.874700000000004, 8.60147)); - quadtree.put(new DegreePosition(49.87281, 8.60119)); - quadtree.put(new DegreePosition(49.870450000000005, 8.60072)); - quadtree.put(new DegreePosition(49.86542000000001, 8.599530000000001)); - quadtree.put(new DegreePosition(49.86164, 8.59872)); - quadtree.put(new DegreePosition(49.861450000000005, 8.59861)); - quadtree.put(new DegreePosition(49.86047000000001, 8.598360000000001)); - quadtree.put(new DegreePosition(49.857470000000006, 8.597240000000001)); - quadtree.put(new DegreePosition(49.85718000000001, 8.59717)); - quadtree.put(new DegreePosition(49.85662000000001, 8.597150000000001)); - quadtree.put(new DegreePosition(49.856350000000006, 8.59721)); - quadtree.put(new DegreePosition(49.85586000000001, 8.59744)); - quadtree.put(new DegreePosition(49.85559000000001, 8.597660000000001)); - quadtree.put(new DegreePosition(49.855250000000005, 8.598090000000001)); - quadtree.put(new DegreePosition(49.854940000000006, 8.59862)); - quadtree.put(new DegreePosition(49.85474000000001, 8.599110000000001)); - quadtree.put(new DegreePosition(49.85452, 8.599860000000001)); - quadtree.put(new DegreePosition(49.85405, 8.60206)); - quadtree.put(new DegreePosition(49.8539, 8.602490000000001)); - quadtree.put(new DegreePosition(49.85325, 8.603750000000002)); - quadtree.put(new DegreePosition(49.851670000000006, 8.605870000000001)); - quadtree.put(new DegreePosition(49.85031000000001, 8.6079)); - quadtree.put(new DegreePosition(49.84915, 8.609850000000002)); - quadtree.put(new DegreePosition(49.846590000000006, 8.61508)); - quadtree.put(new DegreePosition(49.84149000000001, 8.626510000000001)); - quadtree.put(new DegreePosition(49.840500000000006, 8.62828)); - quadtree.put(new DegreePosition(49.839980000000004, 8.62898)); - quadtree.put(new DegreePosition(49.838530000000006, 8.63066)); - quadtree.put(new DegreePosition(49.83744, 8.6317)); - quadtree.put(new DegreePosition(49.83612, 8.632670000000001)); - quadtree.put(new DegreePosition(49.83462, 8.63345)); - quadtree.put(new DegreePosition(49.83399000000001, 8.63371)); - quadtree.put(new DegreePosition(49.832080000000005, 8.63414)); - quadtree.put(new DegreePosition(49.831320000000005, 8.63423)); - quadtree.put(new DegreePosition(49.830540000000006, 8.6342)); - quadtree.put(new DegreePosition(49.82978000000001, 8.63409)); - quadtree.put(new DegreePosition(49.82876, 8.633780000000002)); - quadtree.put(new DegreePosition(49.82734000000001, 8.633180000000001)); - quadtree.put(new DegreePosition(49.82462, 8.63161)); - quadtree.put(new DegreePosition(49.821630000000006, 8.62977)); - quadtree.put(new DegreePosition(49.820370000000004, 8.6292)); - quadtree.put(new DegreePosition(49.81908000000001, 8.62875)); - quadtree.put(new DegreePosition(49.81687, 8.62841)); - quadtree.put(new DegreePosition(49.81134, 8.62866)); - quadtree.put(new DegreePosition(49.81027, 8.62861)); - quadtree.put(new DegreePosition(49.80881, 8.62828)); - quadtree.put(new DegreePosition(49.80682, 8.627690000000001)); - quadtree.put(new DegreePosition(49.80575, 8.62719)); - quadtree.put(new DegreePosition(49.804100000000005, 8.62627)); - quadtree.put(new DegreePosition(49.80192, 8.624780000000001)); - quadtree.put(new DegreePosition(49.79887, 8.622520000000002)); - quadtree.put(new DegreePosition(49.797380000000004, 8.62151)); - quadtree.put(new DegreePosition(49.796110000000006, 8.620750000000001)); - quadtree.put(new DegreePosition(49.79467, 8.62005)); - quadtree.put(new DegreePosition(49.79345000000001, 8.619520000000001)); - quadtree.put(new DegreePosition(49.792060000000006, 8.61903)); - quadtree.put(new DegreePosition(49.79064, 8.61869)); - quadtree.put(new DegreePosition(49.78909, 8.618390000000002)); - quadtree.put(new DegreePosition(49.78412, 8.61762)); - quadtree.put(new DegreePosition(49.78295000000001, 8.61738)); - quadtree.put(new DegreePosition(49.78119, 8.61692)); - quadtree.put(new DegreePosition(49.77975000000001, 8.616380000000001)); - quadtree.put(new DegreePosition(49.778780000000005, 8.61593)); - quadtree.put(new DegreePosition(49.777640000000005, 8.61528)); - quadtree.put(new DegreePosition(49.77610000000001, 8.61415)); - quadtree.put(new DegreePosition(49.77506, 8.613190000000001)); - quadtree.put(new DegreePosition(49.77357000000001, 8.611600000000001)); - quadtree.put(new DegreePosition(49.77299000000001, 8.61087)); - quadtree.put(new DegreePosition(49.770830000000004, 8.607850000000001)); - quadtree.put(new DegreePosition(49.76859, 8.604610000000001)); - quadtree.put(new DegreePosition(49.768170000000005, 8.60405)); - quadtree.put(new DegreePosition(49.76608, 8.60152)); - quadtree.put(new DegreePosition(49.764790000000005, 8.60018)); - quadtree.put(new DegreePosition(49.762150000000005, 8.59793)); - quadtree.put(new DegreePosition(49.76151, 8.597430000000001)); - quadtree.put(new DegreePosition(49.76024, 8.59665)); - quadtree.put(new DegreePosition(49.757780000000004, 8.59553)); - quadtree.put(new DegreePosition(49.756260000000005, 8.59491)); - quadtree.put(new DegreePosition(49.75267, 8.59369)); - quadtree.put(new DegreePosition(49.75119, 8.593290000000001)); - quadtree.put(new DegreePosition(49.74869, 8.59272)); - quadtree.put(new DegreePosition(49.745580000000004, 8.592160000000002)); - quadtree.put(new DegreePosition(49.742180000000005, 8.59171)); - quadtree.put(new DegreePosition(49.739290000000004, 8.59154)); - quadtree.put(new DegreePosition(49.735220000000005, 8.591550000000002)); - quadtree.put(new DegreePosition(49.73221, 8.59176)); - quadtree.put(new DegreePosition(49.72905, 8.59223)); - quadtree.put(new DegreePosition(49.72579, 8.592830000000001)); - quadtree.put(new DegreePosition(49.722390000000004, 8.59373)); - quadtree.put(new DegreePosition(49.715450000000004, 8.59572)); - quadtree.put(new DegreePosition(49.705760000000005, 8.599260000000001)); - quadtree.put(new DegreePosition(49.6989, 8.602160000000001)); - quadtree.put(new DegreePosition(49.698060000000005, 8.602400000000001)); - quadtree.put(new DegreePosition(49.695820000000005, 8.602820000000001)); - quadtree.put(new DegreePosition(49.693780000000004, 8.602960000000001)); - quadtree.put(new DegreePosition(49.69301, 8.602960000000001)); - quadtree.put(new DegreePosition(49.6916, 8.602860000000002)); - quadtree.put(new DegreePosition(49.69035, 8.602640000000001)); - quadtree.put(new DegreePosition(49.689150000000005, 8.60234)); - quadtree.put(new DegreePosition(49.68645, 8.60135)); - quadtree.put(new DegreePosition(49.68348, 8.600140000000001)); - quadtree.put(new DegreePosition(49.68186000000001, 8.599680000000001)); - quadtree.put(new DegreePosition(49.67985, 8.599250000000001)); - quadtree.put(new DegreePosition(49.678070000000005, 8.59914)); - quadtree.put(new DegreePosition(49.676970000000004, 8.599160000000001)); - quadtree.put(new DegreePosition(49.67475, 8.59946)); - quadtree.put(new DegreePosition(49.672610000000006, 8.60003)); - quadtree.put(new DegreePosition(49.669450000000005, 8.6013)); - quadtree.put(new DegreePosition(49.667790000000004, 8.60214)); - quadtree.put(new DegreePosition(49.6655, 8.60356)); - quadtree.put(new DegreePosition(49.66145, 8.60621)); - quadtree.put(new DegreePosition(49.65959, 8.607330000000001)); - quadtree.put(new DegreePosition(49.657700000000006, 8.6083)); - quadtree.put(new DegreePosition(49.65603, 8.60891)); - quadtree.put(new DegreePosition(49.65431, 8.609340000000001)); - quadtree.put(new DegreePosition(49.65194, 8.60959)); - quadtree.put(new DegreePosition(49.649770000000004, 8.609620000000001)); - quadtree.put(new DegreePosition(49.64312, 8.609250000000001)); - quadtree.put(new DegreePosition(49.64190000000001, 8.60926)); - quadtree.put(new DegreePosition(49.64029000000001, 8.609380000000002)); - quadtree.put(new DegreePosition(49.63906, 8.609530000000001)); - quadtree.put(new DegreePosition(49.63758000000001, 8.60983)); - quadtree.put(new DegreePosition(49.63456000000001, 8.610570000000001)); - quadtree.put(new DegreePosition(49.632870000000004, 8.61125)); - quadtree.put(new DegreePosition(49.630630000000004, 8.612340000000001)); - quadtree.put(new DegreePosition(49.62894000000001, 8.61344)); - quadtree.put(new DegreePosition(49.627930000000006, 8.61416)); - quadtree.put(new DegreePosition(49.62348, 8.61804)); - quadtree.put(new DegreePosition(49.622220000000006, 8.61931)); - quadtree.put(new DegreePosition(49.618030000000005, 8.623240000000001)); - quadtree.put(new DegreePosition(49.6152, 8.6256)); - quadtree.put(new DegreePosition(49.61211, 8.627540000000002)); - quadtree.put(new DegreePosition(49.61057, 8.62833)); - quadtree.put(new DegreePosition(49.60898, 8.62899)); - quadtree.put(new DegreePosition(49.607150000000004, 8.629620000000001)); - quadtree.put(new DegreePosition(49.604940000000006, 8.630170000000001)); - quadtree.put(new DegreePosition(49.603770000000004, 8.63035)); - quadtree.put(new DegreePosition(49.60065, 8.630640000000001)); - quadtree.put(new DegreePosition(49.59897, 8.63086)); - quadtree.put(new DegreePosition(49.59581000000001, 8.63113)); - quadtree.put(new DegreePosition(49.59308000000001, 8.63165)); - quadtree.put(new DegreePosition(49.59147, 8.63212)); - quadtree.put(new DegreePosition(49.58559, 8.634110000000002)); - quadtree.put(new DegreePosition(49.58408000000001, 8.634540000000001)); - quadtree.put(new DegreePosition(49.582060000000006, 8.635010000000001)); - quadtree.put(new DegreePosition(49.580670000000005, 8.635200000000001)); - quadtree.put(new DegreePosition(49.57885, 8.635330000000002)); - quadtree.put(new DegreePosition(49.57715, 8.63531)); - quadtree.put(new DegreePosition(49.57417, 8.6349)); - quadtree.put(new DegreePosition(49.572190000000006, 8.63455)); - quadtree.put(new DegreePosition(49.570730000000005, 8.63413)); - quadtree.put(new DegreePosition(49.568430000000006, 8.6333)); - quadtree.put(new DegreePosition(49.565090000000005, 8.63171)); - quadtree.put(new DegreePosition(49.560050000000004, 8.62922)); - quadtree.put(new DegreePosition(49.55922, 8.62894)); - quadtree.put(new DegreePosition(49.55603000000001, 8.628010000000002)); - quadtree.put(new DegreePosition(49.55425, 8.62767)); - quadtree.put(new DegreePosition(49.55111, 8.62743)); - quadtree.put(new DegreePosition(49.548930000000006, 8.62748)); - quadtree.put(new DegreePosition(49.548120000000004, 8.62757)); - quadtree.put(new DegreePosition(49.54666, 8.62784)); - quadtree.put(new DegreePosition(49.54466000000001, 8.62827)); - quadtree.put(new DegreePosition(49.54215000000001, 8.62898)); - quadtree.put(new DegreePosition(49.539440000000006, 8.62991)); - quadtree.put(new DegreePosition(49.537850000000006, 8.63052)); - quadtree.put(new DegreePosition(49.52872000000001, 8.63442)); - quadtree.put(new DegreePosition(49.527300000000004, 8.63508)); - quadtree.put(new DegreePosition(49.52526, 8.635860000000001)); - quadtree.put(new DegreePosition(49.52337000000001, 8.636510000000001)); - quadtree.put(new DegreePosition(49.52058, 8.637360000000001)); - quadtree.put(new DegreePosition(49.518550000000005, 8.637910000000002)); - quadtree.put(new DegreePosition(49.51503, 8.638770000000001)); - quadtree.put(new DegreePosition(49.51098, 8.639330000000001)); - quadtree.put(new DegreePosition(49.50845, 8.63959)); - quadtree.put(new DegreePosition(49.50417, 8.639740000000002)); - quadtree.put(new DegreePosition(49.499810000000004, 8.63963)); - quadtree.put(new DegreePosition(49.49546, 8.63912)); - quadtree.put(new DegreePosition(49.49306000000001, 8.638910000000001)); - quadtree.put(new DegreePosition(49.490320000000004, 8.638760000000001)); - quadtree.put(new DegreePosition(49.48669, 8.63865)); - quadtree.put(new DegreePosition(49.484480000000005, 8.638760000000001)); - quadtree.put(new DegreePosition(49.48073, 8.639330000000001)); - quadtree.put(new DegreePosition(49.4776, 8.640130000000001)); - quadtree.put(new DegreePosition(49.476310000000005, 8.64052)); - quadtree.put(new DegreePosition(49.468500000000006, 8.64342)); - quadtree.put(new DegreePosition(49.464470000000006, 8.64483)); - quadtree.put(new DegreePosition(49.461290000000005, 8.64577)); - quadtree.put(new DegreePosition(49.458690000000004, 8.64629)); - quadtree.put(new DegreePosition(49.457530000000006, 8.646460000000001)); - quadtree.put(new DegreePosition(49.455380000000005, 8.64666)); - quadtree.put(new DegreePosition(49.453050000000005, 8.64668)); - quadtree.put(new DegreePosition(49.451750000000004, 8.64661)); - quadtree.put(new DegreePosition(49.450430000000004, 8.64647)); - quadtree.put(new DegreePosition(49.44711, 8.64578)); - quadtree.put(new DegreePosition(49.445350000000005, 8.6453)); - quadtree.put(new DegreePosition(49.44398, 8.64478)); - quadtree.put(new DegreePosition(49.44091, 8.64338)); - quadtree.put(new DegreePosition(49.439840000000004, 8.64282)); - quadtree.put(new DegreePosition(49.438340000000004, 8.64193)); - quadtree.put(new DegreePosition(49.43139000000001, 8.63724)); - quadtree.put(new DegreePosition(49.42933000000001, 8.636080000000002)); - quadtree.put(new DegreePosition(49.427890000000005, 8.63537)); - quadtree.put(new DegreePosition(49.426460000000006, 8.634820000000001)); - quadtree.put(new DegreePosition(49.42436000000001, 8.634160000000001)); - quadtree.put(new DegreePosition(49.422200000000004, 8.63371)); - quadtree.put(new DegreePosition(49.42033000000001, 8.633500000000002)); - quadtree.put(new DegreePosition(49.41946, 8.63347)); - quadtree.put(new DegreePosition(49.41798000000001, 8.63348)); - quadtree.put(new DegreePosition(49.415710000000004, 8.633700000000001)); - quadtree.put(new DegreePosition(49.41463, 8.6339)); - quadtree.put(new DegreePosition(49.412670000000006, 8.63437)); - quadtree.put(new DegreePosition(49.411120000000004, 8.6349)); - quadtree.put(new DegreePosition(49.40739000000001, 8.636330000000001)); - quadtree.put(new DegreePosition(49.40572, 8.636800000000001)); - quadtree.put(new DegreePosition(49.39164, 8.63865)); - quadtree.put(new DegreePosition(49.39021, 8.63879)); - quadtree.put(new DegreePosition(49.38855, 8.638720000000001)); - quadtree.put(new DegreePosition(49.38721, 8.638480000000001)); - quadtree.put(new DegreePosition(49.38656, 8.638290000000001)); - quadtree.put(new DegreePosition(49.38440000000001, 8.63746)); - quadtree.put(new DegreePosition(49.37805, 8.63387)); - quadtree.put(new DegreePosition(49.376940000000005, 8.63334)); - quadtree.put(new DegreePosition(49.376000000000005, 8.63297)); - quadtree.put(new DegreePosition(49.37500000000001, 8.63269)); - quadtree.put(new DegreePosition(49.373780000000004, 8.632480000000001)); - quadtree.put(new DegreePosition(49.36988, 8.63222)); - quadtree.put(new DegreePosition(49.36872, 8.632190000000001)); - quadtree.put(new DegreePosition(49.364360000000005, 8.631910000000001)); - quadtree.put(new DegreePosition(49.35228000000001, 8.63127)); - quadtree.put(new DegreePosition(49.34911, 8.631160000000001)); - quadtree.put(new DegreePosition(49.33944, 8.63067)); - quadtree.put(new DegreePosition(49.31497, 8.62917)); - quadtree.put(new DegreePosition(49.312200000000004, 8.62892)); - quadtree.put(new DegreePosition(49.31073000000001, 8.628670000000001)); - quadtree.put(new DegreePosition(49.303380000000004, 8.626940000000001)); - quadtree.put(new DegreePosition(49.30293, 8.62654)); - quadtree.put(new DegreePosition(49.30285000000001, 8.62644)); - quadtree.put(new DegreePosition(49.302690000000005, 8.62608)); - quadtree.put(new DegreePosition(49.30268, 8.62584)); - quadtree.put(new DegreePosition(49.30299, 8.62268)); - quadtree.put(new DegreePosition(49.30303000000001, 8.622010000000001)); - quadtree.put(new DegreePosition(49.30299, 8.621730000000001)); - quadtree.put(new DegreePosition(49.30293, 8.6216)); - quadtree.put(new DegreePosition(49.30274000000001, 8.62147)); - quadtree.put(new DegreePosition(49.30238000000001, 8.62139)); - quadtree.put(new DegreePosition(49.302290000000006, 8.621310000000001)); - quadtree.put(new DegreePosition(49.301950000000005, 8.62127)); - quadtree.put(new DegreePosition(49.30187, 8.62279)); - quadtree.put(new DegreePosition(49.30172, 8.62456)); - quadtree.put(new DegreePosition(49.30134, 8.62697)); - quadtree.put(new DegreePosition(49.30116, 8.627690000000001)); - quadtree.put(new DegreePosition(49.30053, 8.629800000000001)); - quadtree.put(new DegreePosition(49.299820000000004, 8.63142)); - quadtree.put(new DegreePosition(49.29916, 8.6327)); - quadtree.put(new DegreePosition(49.299530000000004, 8.63279)); - quadtree.put(new DegreePosition(49.30006, 8.63272)); - quadtree.put(new DegreePosition(49.30068000000001, 8.63277)); - quadtree.put(new DegreePosition(49.30082, 8.63283)); - quadtree.put(new DegreePosition(49.301050000000004, 8.633040000000001)); - quadtree.put(new DegreePosition(49.300340000000006, 8.634780000000001)); - quadtree.put(new DegreePosition(49.29995, 8.63593)); - quadtree.put(new DegreePosition(49.299690000000005, 8.63723)); - quadtree.put(new DegreePosition(49.299600000000005, 8.638200000000001)); - quadtree.put(new DegreePosition(49.29963, 8.63973)); - quadtree.put(new DegreePosition(49.299710000000005, 8.64104)); - quadtree.put(new DegreePosition(49.29831, 8.643080000000001)); - quadtree.put(new DegreePosition(49.29796, 8.64334)); - quadtree.put(new DegreePosition(49.297920000000005, 8.643450000000001)); - quadtree.put(new DegreePosition(49.297920000000005, 8.643590000000001)); - quadtree.put(new DegreePosition(49.29798, 8.64389)); - quadtree.put(new DegreePosition(49.299420000000005, 8.646180000000001)); - quadtree.put(new DegreePosition(49.299580000000006, 8.64652)); - quadtree.put(new DegreePosition(49.29955, 8.646640000000001)); - quadtree.put(new DegreePosition(49.29957, 8.64677)); - quadtree.put(new DegreePosition(49.299710000000005, 8.64681)); - quadtree.put(new DegreePosition(49.29977, 8.646730000000002)); - quadtree.put(new DegreePosition(49.299780000000005, 8.646640000000001)); - quadtree.put(new DegreePosition(49.299690000000005, 8.64648)); - quadtree.put(new DegreePosition(49.2997, 8.64638)); - quadtree.put(new DegreePosition(49.29992000000001, 8.64586)); - quadtree.put(new DegreePosition(49.29995, 8.64563)); - quadtree.put(new DegreePosition(49.300790000000006, 8.644)); - quadtree.put(new DegreePosition(49.301120000000004, 8.643650000000001)); - quadtree.put(new DegreePosition(49.301390000000005, 8.64348)); - quadtree.put(new DegreePosition(49.30283000000001, 8.642980000000001)); - quadtree.put(new DegreePosition(49.30346, 8.642560000000001)); - quadtree.put(new DegreePosition(49.30378, 8.64241)); - quadtree.put(new DegreePosition(49.309740000000005, 8.64105)); - quadtree.put(new DegreePosition(49.309760000000004, 8.64207)); - quadtree.put(new DegreePosition(49.309140000000006, 8.64212)); - quadtree.put(new DegreePosition(49.309140000000006, 8.643880000000001)); - quadtree.put(new DegreePosition(49.30917, 8.64403)); - quadtree.put(new DegreePosition(49.30923000000001, 8.644110000000001)); - quadtree.put(new DegreePosition(49.30937, 8.644110000000001)); - - Position p1 = new DegreePosition(50.47498691278631, 8.701171875); - Position p2 = new DegreePosition(50.47149085139956, 8.7066650390625); - Position p3 = new DegreePosition(50.47848271564207, 8.7066650390625); - double maxDistance = 0.06242236766906711; Position p1Nearest = quadtree.get(p1, maxDistance); Position p2Nearest = quadtree.get(p2, maxDistance); @@ -1207,4 +1231,141 @@ public class QuadTreeTest { assertNotNull(p3Nearest); } + @Test + public void testNearest() { + double maxDistance = 0.06242236766906711; + Position p1Nearest = quadtree.get(p1, maxDistance); + Position p2Nearest = quadtree.get(p2, maxDistance); + Position p3Nearest = quadtree.get(p3, maxDistance); + assertSame(getNearestByApproximateDistance(p1), p1Nearest); + assertSame(getNearestByApproximateDistance(p2), p2Nearest); + assertSame(getNearestByApproximateDistance(p3), p3Nearest); + } + + private Position getNearestByApproximateDistance(Position pos) { + double minDistance = Double.MAX_VALUE; + Position nearestByDistance = null; + for (final Position p : quadtree.get(new BoundsImpl(new DegreePosition(-90, -180), new DegreePosition(90, 180)))) { // get all + if (QuadTree.getLatLngDistance(p, pos) < minDistance) { + nearestByDistance = p; + minDistance = QuadTree.getLatLngDistance(p, pos); + } + } + return nearestByDistance; + } + + @Test + public void testTooFarAwayProducesEmptyResults() { + double maxDistance = 0.06242236766906711; + Position p1Nearest = quadtree.get(new DegreePosition(0, 0), maxDistance); + assertNull(p1Nearest); + } + + @Test + public void measureLookupNearestPerformance() { + long start = System.currentTimeMillis(); + final int ITERATIONS = 100000; + for (int i=0; i positions = new ArrayList<>(); + for (int i=0; i extends Node { + public NodeWithPublicGetDistance(Bounds bounds, int maxItems) { + super(bounds, maxItems); + } + + @Override + public double getDistance(Position point) { + return super.getDistance(point); + } + } + + @Test + public void testNodeDistance() { + /* + {SW: (50.625,-5.625), NE: (53.4375,0.0)}: 15 items in node, 0 child nodes + (51.0,-6.0) + */ + NodeWithPublicGetDistance node = new NodeWithPublicGetDistance<>(new BoundsImpl(new DegreePosition(50.625,-5.625), new DegreePosition(53.4375,0.0)), + /* maxItems */ 20); + Position p = new DegreePosition(51.0, -6.0); + // lat is in bounds; lon is nearby, -5.625+6.0 should be the distance + final double expectedDistance = -5.625 + 6.0; + assertEquals(expectedDistance, node.getDistance(p), 0.0001); + } + + @AfterClass + public static void writeMeasurements() throws IOException { + performanceReport.write(); + } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SmartFutureCacheTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SmartFutureCacheTest.java index fac12b17506..38db6cd2252 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SmartFutureCacheTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SmartFutureCacheTest.java @@ -53,15 +53,19 @@ public class SmartFutureCacheTest { @Test public void testExceptionInComputeCacheUpdate() { final boolean[] throwException = new boolean[1]; - throwException[0] = true; + synchronized (this) { // make sure that other threads see this after the synchronized block has completed + throwException[0] = true; + } SmartFutureCache sfc = new SmartFutureCache( new SmartFutureCache.AbstractCacheUpdater() { @Override public String computeCacheUpdate(String key, EmptyUpdateInterval updateInterval) { - if (throwException[0]) { - throw new NullPointerException("Humba"); - } else { - return "Humba"; + synchronized (SmartFutureCacheTest.this) { // make sure we see the latest change to throwException + if (throwException[0]) { + throw new NullPointerException("Humba"); + } else { + return "Humba"; + } } } }, "SmartFutureCacheTest.testExceptionInComputeCacheUpdate"); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/measurements/Measurement.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/measurements/Measurement.java index e9ed61c0ab6..741067764e8 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/measurements/Measurement.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/measurements/Measurement.java @@ -1,5 +1,13 @@ package com.sap.sailing.domain.test.measurements; +/** + * Create an instance of this class and {@link MeasurementCase#addMeasurement(Measurement) add it} to a + * measurement case. The final reporting is then done by invoking {@link MeasurementXMLFile#write()} on + * the measurement file created before. + * + * @author Axel Uhl (D043530) + * + */ public class Measurement { private final String name; private final double value; @@ -10,11 +18,11 @@ public class Measurement { this.value = value; } - public String getName() { + String getName() { return name; } - public double getValue() { + double getValue() { return value; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/measurements/MeasurementCase.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/measurements/MeasurementCase.java index 0bd2a97b0a4..6e8b7577c22 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/measurements/MeasurementCase.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/measurements/MeasurementCase.java @@ -3,17 +3,24 @@ package com.sap.sailing.domain.test.measurements; import java.util.HashSet; import java.util.Set; +/** + * Obtain an instance of this class by invoking {@link MeasurementXMLFile#addCase(String)}, then add + * measurements using the {@link #addMeasurement(Measurement)} method. + * + * @author Axel Uhl (D043530) + * + */ public class MeasurementCase { private final String name; private final Set measurements; - public MeasurementCase(String name) { + MeasurementCase(String name) { super(); this.name = name; measurements = new HashSet<>(); } - public String getName() { + String getName() { return name; } @@ -21,7 +28,7 @@ public class MeasurementCase { measurements.add(measurement); } - public Iterable getMeasurements() { + Iterable getMeasurements() { return measurements; } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/measurements/MeasurementXMLFile.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/measurements/MeasurementXMLFile.java index f192eb1c1b0..240b021f9f2 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/measurements/MeasurementXMLFile.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/measurements/MeasurementXMLFile.java @@ -15,7 +15,15 @@ import com.sap.sse.common.Util; * <measurement> tags embedded which list named values for use by the Hudson/Jenkins Measurements Plot plug-in. * The output is written to the bin/surefire-reports/ directory by the {@link #write()} method, but only if * a bin/ directory exists as subdirectory of the current working directory. The surefire-reports - * subdirectory will be created if it doesn't exist. + * subdirectory will be created if it doesn't exist.

+ * + * Use this class as in the following example: + *

+ *       MeasurementXMLFile performanceReport = new MeasurementXMLFile(this.getClass());
+ *       MeasurementCase performanceReportCase = performanceReport.addCase(getClass().getSimpleName());
+ *       performanceReportCase.addMeasurement(new Measurement("This is a measurement name", 12345); // 12345 is the value to be recorded
+ *       performanceReport.write();
+ * 
* * @author Axel Uhl (D043530) * diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithFixedRank.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithFixedRank.java index cebb4552807..dadc21866c5 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithFixedRank.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithFixedRank.java @@ -81,6 +81,11 @@ public class MockedTrackedRaceWithFixedRank extends MockedTrackedRace { public Boat getBoatOfCompetitorById(Serializable competitorID) { return null; } + + @Override + public byte[] getCompetitorMD5() { + return null; + } } @Override diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithFixedRankAndManyCompetitors.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithFixedRankAndManyCompetitors.java index 2eacc2264bd..02ac83a163d 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithFixedRankAndManyCompetitors.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithFixedRankAndManyCompetitors.java @@ -55,6 +55,10 @@ public class MockedTrackedRaceWithFixedRankAndManyCompetitors extends MockedTrac public Boat getBoatOfCompetitorById(Serializable competitorID) { return null; } + @Override + public byte[] getCompetitorMD5() { + return null; + } }; } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java index eca3d7cebdc..da309266290 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java @@ -175,7 +175,6 @@ public interface DomainFactory { * must have been created before through * {@link #getOrCreateTrackedRegatta(com.sap.sailing.domain.base.Regatta)} because otherwise the link to * the {@link IEvent} can't be established - * @param raceLogResolver TODO * @param tokenToRetrieveAssociatedRace * used to update the set of{@link RaceDefinition}s received by the {@link RaceCourseReceiver} created by * this call diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java index 50c376d419c..dc74e7f3e35 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java @@ -409,7 +409,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements addListenersForStoredDataAndStartController(receivers); } - private URI checkForCachedStoredData(URI storedURI){ + private URI checkForCachedStoredData(URI storedURI) { final String CACHE_DIR_PROPERTY = "tractrac.mtb.cache.dir"; if (System.getProperty(CACHE_DIR_PROPERTY) != null) { final String directory = System.getProperty(CACHE_DIR_PROPERTY); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceDefinition.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceDefinition.java index b55928727f9..144d6c2ddd3 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceDefinition.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceDefinition.java @@ -2,6 +2,7 @@ package com.sap.sailing.domain.base; import java.io.Serializable; +import com.sap.sailing.domain.common.RaceCompetitorIdsAsStringWithMD5Hash; import com.sap.sse.common.Named; import com.sap.sse.common.WithID; @@ -31,4 +32,11 @@ public interface RaceDefinition extends Named, WithID { */ Boat getBoatOfCompetitorById(Serializable competitorID); + /** + * The MD5 hash as produced by + * {@link RaceCompetitorIdsAsStringWithMD5Hash#getMd5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID()} + * for this race's competitor set. + */ + byte[] getCompetitorMD5(); + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java index a60d5e91926..52219d1545c 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java @@ -257,47 +257,26 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain double radiusCalculationFactor = 10.0; Placemark startBest = null; Placemark finishBest = null; + Position startPosition = null; // Get start postition - Iterator startMarks = race.getRace().getCourse().getFirstWaypoint().getMarks().iterator(); - GPSFix startMarkFix = startMarks.hasNext() ? race.getOrCreateTrack(startMarks.next()).getLastRawFix() : null; - Position startPosition = startMarkFix != null ? startMarkFix.getPosition() : null; - if (startPosition != null) { - try { - // Get distance to nearest placemark and calculate the search radius - Placemark startNearest = ReverseGeocoder.INSTANCE.getPlacemarkNearest(startPosition); - if (startNearest != null) { - Distance startNearestDistance = startNearest.distanceFrom(startPosition); - double startRadius = startNearestDistance.getKilometers() * radiusCalculationFactor; - - // Get the estimated best start place - startBest = ReverseGeocoder.INSTANCE.getPlacemarkLast(startPosition, startRadius, - new Placemark.ByPopulationDistanceRatio(startPosition)); - } - } catch (IOException e) { - logger.throwing(TrackedRaceImpl.class.getName(), "getPlaceOrder()", e); - } catch (org.json.simple.parser.ParseException e) { - logger.throwing(TrackedRaceImpl.class.getName(), "getPlaceOrder()", e); - } - } - - // Get finish position - Iterator finishMarks = race.getRace().getCourse().getFirstWaypoint().getMarks().iterator(); - GPSFix finishMarkFix = finishMarks.hasNext() ? race.getOrCreateTrack(finishMarks.next()).getLastRawFix() : null; - Position finishPosition = finishMarkFix != null ? finishMarkFix.getPosition() : null; - if (startPosition != null && finishPosition != null) { - if (startPosition.getDistance(finishPosition).getKilometers() <= ReverseGeocoder.POSITION_CACHE_DISTANCE_LIMIT_IN_KM) { - finishBest = startBest; - } else { + final Waypoint firstWaypoint = race.getRace().getCourse().getFirstWaypoint(); + if (firstWaypoint != null) { + Iterator startMarks = firstWaypoint.getMarks().iterator(); + GPSFix startMarkFix = startMarks.hasNext() ? race.getOrCreateTrack(startMarks.next()).getLastRawFix() : null; + startPosition = startMarkFix != null ? startMarkFix.getPosition() : null; + if (startPosition != null) { try { // Get distance to nearest placemark and calculate the search radius - Placemark finishNearest = ReverseGeocoder.INSTANCE.getPlacemarkNearest(finishPosition); - Distance finishNearestDistance = finishNearest.distanceFrom(finishPosition); - double finishRadius = finishNearestDistance.getKilometers() * radiusCalculationFactor; - - // Get the estimated best finish place - finishBest = ReverseGeocoder.INSTANCE.getPlacemarkLast(finishPosition, finishRadius, - new Placemark.ByPopulationDistanceRatio(finishPosition)); + Placemark startNearest = ReverseGeocoder.INSTANCE.getPlacemarkNearest(startPosition); + if (startNearest != null) { + Distance startNearestDistance = startNearest.distanceFrom(startPosition); + double startRadius = startNearestDistance.getKilometers() * radiusCalculationFactor; + + // Get the estimated best start place + startBest = ReverseGeocoder.INSTANCE.getPlacemarkLast(startPosition, startRadius, + new Placemark.ByPopulationDistanceRatio(startPosition)); + } } catch (IOException e) { logger.throwing(TrackedRaceImpl.class.getName(), "getPlaceOrder()", e); } catch (org.json.simple.parser.ParseException e) { @@ -305,6 +284,34 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain } } } + + // Get finish position + final Waypoint lastWaypoint = race.getRace().getCourse().getLastWaypoint(); + if (lastWaypoint != null) { + Iterator finishMarks = firstWaypoint.getMarks().iterator(); + GPSFix finishMarkFix = finishMarks.hasNext() ? race.getOrCreateTrack(finishMarks.next()).getLastRawFix() : null; + Position finishPosition = finishMarkFix != null ? finishMarkFix.getPosition() : null; + if (startPosition != null && finishPosition != null) { + if (startPosition.getDistance(finishPosition).getKilometers() <= ReverseGeocoder.POSITION_CACHE_DISTANCE_LIMIT_IN_KM) { + finishBest = startBest; + } else { + try { + // Get distance to nearest placemark and calculate the search radius + Placemark finishNearest = ReverseGeocoder.INSTANCE.getPlacemarkNearest(finishPosition); + Distance finishNearestDistance = finishNearest.distanceFrom(finishPosition); + double finishRadius = finishNearestDistance.getKilometers() * radiusCalculationFactor; + + // Get the estimated best finish place + finishBest = ReverseGeocoder.INSTANCE.getPlacemarkLast(finishPosition, finishRadius, + new Placemark.ByPopulationDistanceRatio(finishPosition)); + } catch (IOException e) { + logger.throwing(TrackedRaceImpl.class.getName(), "getPlaceOrder()", e); + } catch (org.json.simple.parser.ParseException e) { + logger.throwing(TrackedRaceImpl.class.getName(), "getPlaceOrder()", e); + } + } + } + } Util.Pair placemarks = new Util.Pair(startBest, finishBest); return placemarks; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceDefinitionImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceDefinitionImpl.java index c62df996b41..9545afb6742 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceDefinitionImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceDefinitionImpl.java @@ -1,6 +1,8 @@ package com.sap.sailing.domain.base.impl; import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -15,6 +17,7 @@ import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Course; import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.common.RaceCompetitorIdsAsStringWithMD5Hash; import com.sap.sse.common.impl.NamedImpl; public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition { @@ -27,6 +30,7 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition { private final Map competitorBoats; private final BoatClass boatClass; private final Serializable id; + private final RaceCompetitorIdsAsStringWithMD5Hash raceCompetitorsMD5Hash; public RaceDefinitionImpl(String name, Course course, BoatClass boatClass, Iterable competitors) { this(name, course, boatClass, competitors, Collections.emptyMap()); @@ -49,9 +53,11 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition { this.competitors = new HashSet<>(); this.competitorsById = new LinkedHashMap<>(); this.competitorBoats = new HashMap<>(); + final Set idsOfCompetitorsAsString = new HashSet<>(); for (Competitor competitor : competitors) { Competitor competitorWithEqualID = competitorsById.put(competitor.getId(), competitor); this.competitors.add(competitor); + idsOfCompetitorsAsString.add(competitor.getId().toString()); if (competitorWithEqualID != null && competitorWithEqualID != competitor) { throw new IllegalArgumentException("Two distinct competitors with equal ID "+competitor.getId()+" are not allowed within the single race "+name); } @@ -65,6 +71,11 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition { " which is not part of race "+getName()+"'s set of competitors"); } } + try { + raceCompetitorsMD5Hash = new RaceCompetitorIdsAsStringWithMD5Hash(idsOfCompetitorsAsString); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { + throw new RuntimeException("Internal error: issue with UTF8 or MD5 for encoding competitor IDs as MD5 hash", e); + } this.boatClass = boatClass; this.id = id; } @@ -78,11 +89,16 @@ public class RaceDefinitionImpl extends NamedImpl implements RaceDefinition { public Course getCourse() { return course; } - + @Override public Iterable getCompetitors() { return Collections.unmodifiableSet(competitors); } + + @Override + public byte[] getCompetitorMD5() { + return raceCompetitorsMD5Hash.getMd5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID(); + } @Override public Competitor getCompetitorById(Serializable competitorID) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java index de92f254836..454843ba3e2 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java @@ -1,1907 +1,1937 @@ -package com.sap.sailing.domain.leaderboard.impl; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.NavigableSet; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.RunnableFuture; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.sap.sailing.domain.abstractlog.race.InvalidatesLeaderboardCache; -import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.Course; -import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.Leg; -import com.sap.sailing.domain.base.RaceColumn; -import com.sap.sailing.domain.base.RaceColumnInSeries; -import com.sap.sailing.domain.base.RaceColumnListener; -import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.LegType; -import com.sap.sailing.domain.common.ManeuverType; -import com.sap.sailing.domain.common.MaxPointsReason; -import com.sap.sailing.domain.common.NoWindError; -import com.sap.sailing.domain.common.NoWindException; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.domain.common.RegattaNameAndRaceName; -import com.sap.sailing.domain.common.Speed; -import com.sap.sailing.domain.common.SpeedWithBearing; -import com.sap.sailing.domain.common.dto.CompetitorDTO; -import com.sap.sailing.domain.common.dto.FleetDTO; -import com.sap.sailing.domain.common.dto.LeaderboardDTO; -import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO; -import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; -import com.sap.sailing.domain.common.dto.LegEntryDTO; -import com.sap.sailing.domain.common.dto.RaceDTO; -import com.sap.sailing.domain.common.tracking.GPSFixMoving; -import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher; -import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; -import com.sap.sailing.domain.leaderboard.ScoreCorrection.Result; -import com.sap.sailing.domain.leaderboard.SettableScoreCorrection; -import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; -import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCache; -import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache; -import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater; -import com.sap.sailing.domain.racelog.RaceLogIdentifier; -import com.sap.sailing.domain.ranking.RankingMetric.CompetitorRankingInfo; -import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; -import com.sap.sailing.domain.tracking.GPSFixTrack; -import com.sap.sailing.domain.tracking.Maneuver; -import com.sap.sailing.domain.tracking.MarkPassing; -import com.sap.sailing.domain.tracking.MarkPassingManeuver; -import com.sap.sailing.domain.tracking.RaceChangeListener; -import com.sap.sailing.domain.tracking.TrackedLeg; -import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; -import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache; -import com.sap.sailing.domain.tracking.WindPositionMode; -import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener; -import com.sap.sailing.util.impl.RaceColumnListeners; -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.concurrent.LockUtil; -import com.sap.sse.concurrent.NamedReentrantReadWriteLock; -import com.sap.sse.util.impl.ThreadFactoryWithPriority; - -/** - * Base implementation for various types of leaderboards. The {@link RaceColumnListener} implementation forwards events - * received to all {@link RaceColumnListener} subscribed with this leaderboard. To which objects this leaderboard subscribes - * as {@link RaceColumnListener} is left to the concrete subclasses to implement, but the race columns seem like useful - * candidates. - * - * @author Axel Uhl (d043530) - * - */ -public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, RaceColumnListener { - private static final long serialVersionUID = 330156778603279333L; - - private static final Logger logger = Logger.getLogger(AbstractSimpleLeaderboardImpl.class.getName()); - - static final Double DOUBLE_0 = new Double(0); - - private final SettableScoreCorrection scoreCorrection; - - private ThresholdBasedResultDiscardingRule crossLeaderboardResultDiscardingRule; - - /** - * The optional display name mappings for competitors. This allows a user to override the tracking-provided - * competitor names for display in a leaderboard. - */ - private final Map displayNames; - - /** the display name of the leaderboard */ - private String displayName; - - /** - * Backs the {@link #getCarriedPoints(Competitor)} API with data. Can be used to prime this leaderboard - * with aggregated results of races not tracked / displayed by this leaderboard in detail. The points - * provided by this map are considered by {@link #getTotalPoints(Competitor, TimePoint)}. - */ - private final Map carriedPoints; - - private final RaceColumnListeners raceColumnListeners; - - /** - * A set that manages the difference between {@link #getCompetitors()} and {@link #getAllCompetitors()}. Access - * is controlled by the {@link #suppressedCompetitorsLock} lock. - */ - private final Set suppressedCompetitors; - private final NamedReentrantReadWriteLock suppressedCompetitorsLock; - - private transient Map, RunnableFuture> raceDetailsAtEndOfTrackingCache; - - /** - * This executor needs to be a different one than {@link #executor} because the tasks run by {@link #executor} - * can depend on the results of the tasks run by {@link #raceDetailsExecutor}, and an {@link Executor} doesn't - * move a task that is blocked by waiting for another {@link FutureTask} to the side but blocks permanently, - * ending in a deadlock (one that cannot easily be detected by the Eclipse debugger either). - */ - private transient Executor raceDetailsExecutor; - - /** - * Used to remove all these listeners from their tracked races when this servlet is {@link #destroy() destroyed}. - */ - private transient Set cacheInvalidationListeners; - - private transient ThreadPoolExecutor executor; - - private transient LiveLeaderboardUpdater liveLeaderboardUpdater; - - private transient LeaderboardDTOCache leaderboardDTOCache; - - /** - * A leaderboard entry representing a snapshot of a cell at a given time point for a single race/competitor. - * - * @author Axel Uhl (d043530) - * - */ - class EntryImpl implements Entry { - private final Callable trackedRankProvider; - private final Double netPoints; - private final Callable netPointsUncorrectedProvider; - private final boolean isNetPointsCorrected; - private final Double totalPoints; - private final MaxPointsReason maxPointsReason; - private final boolean discarded; - private final Fleet fleet; - - private EntryImpl(Callable trackedRankProvider, Double netPoints, - Callable netPointsUncorrectedProvider, boolean isNetPointsCorrected, - Double totalPoints, MaxPointsReason maxPointsReason, boolean discarded, Fleet fleet) { - super(); - this.trackedRankProvider = trackedRankProvider; - this.netPoints = netPoints; - this.netPointsUncorrectedProvider = netPointsUncorrectedProvider; - this.isNetPointsCorrected = isNetPointsCorrected; - this.totalPoints = totalPoints; - this.maxPointsReason = maxPointsReason; - this.discarded = discarded; - this.fleet = fleet; - } - @Override - public int getTrackedRank() { - try { - return trackedRankProvider.call(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - @Override - public Double getNetPoints() { - return netPoints; - } - @Override - public boolean isNetPointsCorrected() { - return isNetPointsCorrected; - } - @Override - public Double getTotalPoints() { - return totalPoints; - } - @Override - public MaxPointsReason getMaxPointsReason() { - return maxPointsReason; - } - @Override - public boolean isDiscarded() { - return discarded; - } - @Override - public Fleet getFleet() { - return fleet; - } - @Override - public Double getNetPointsUncorrected() { - try { - return netPointsUncorrectedProvider.call(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - - /** - * Computing the competitors can be a bit expensive, particularly if the fleet is large and there may be suppressed - * competitors, and the leaderboard may be a meta-leaderboard that refers to other leaderboards which each have - * several tracked races attached from where the competitors need to be retrieved. Ideally, the competitors list - * would be cached, but that is again difficult because we would have to monitor all changes in all dependent - * leaderboards and columns and tracked races properly. - *

- * - * As it turns out, one of the most frequent uses of the {@link AbstractSimpleLeaderboardImpl#getCompetitors} - * competitors list is to determine their number which in turn is only required for high-point scoring systems and - * for computing the default score for penalties. Again, the most frequently used low-point family of scoring schemes - * does not require this number. Yet, the scoring scheme requires an argument for polymorphic use by those that - * need it. Instead of computing it for each call, this interface lets us defer the actual calculation until the - * point when it's really needed. Once asked, this object will cache the result. Therefore, a new one should be - * constructed each time the number shall be computed. - * - * @author Axel Uhl (D043530) - * - */ - public class NumberOfCompetitorsFetcherImpl implements NumberOfCompetitorsInLeaderboardFetcher { - private int numberOfCompetitors = -1; - - @Override - public int getNumberOfCompetitorsInLeaderboard() { - if (numberOfCompetitors == -1) { - numberOfCompetitors = Util.size(getCompetitors()); - } - return numberOfCompetitors; - } - } - - /** - * Handles the invalidation of the {@link SailingServiceImpl#raceDetailsAtEndOfTrackingCache} entries if the tracked - * race changes in any way. In particular, for {@link #statusChanged}, when the status changes away from LOADING, - * calculations may start or resume, making it necessary to clear the cache. - * - * @author Axel Uhl (D043530) - * - */ - private class CacheInvalidationListener extends AbstractRaceChangeListener { - private final TrackedRace trackedRace; - private final Competitor competitor; - - public CacheInvalidationListener(TrackedRace trackedRace, Competitor competitor) { - this.trackedRace = trackedRace; - this.competitor = competitor; - } - - public TrackedRace getTrackedRace() { - return trackedRace; - } - - public void removeFromTrackedRace() { - trackedRace.removeListener(this); - } - - private void invalidateCacheAndRemoveThisListenerFromTrackedRace() { - synchronized (raceDetailsAtEndOfTrackingCache) { - raceDetailsAtEndOfTrackingCache.remove(new com.sap.sse.common.Util.Pair(trackedRace, competitor)); - removeFromTrackedRace(); - } - } - - @Override - protected void defaultAction() { - invalidateCacheAndRemoveThisListenerFromTrackedRace(); - } - } - - private static class UUIDGenerator implements LeaderboardDTO.UUIDGenerator { - @Override - public String generateRandomUUID() { - return UUID.randomUUID().toString(); - } - } - - public AbstractSimpleLeaderboardImpl(ThresholdBasedResultDiscardingRule resultDiscardingRule) { - this.carriedPoints = new HashMap(); - this.scoreCorrection = createScoreCorrection(); - this.displayNames = new HashMap(); - this.crossLeaderboardResultDiscardingRule = resultDiscardingRule; - this.suppressedCompetitors = new HashSet(); - this.suppressedCompetitorsLock = new NamedReentrantReadWriteLock("suppressedCompetitorsLock", /* fair */ false); - this.raceColumnListeners = new RaceColumnListeners(); - this.raceDetailsAtEndOfTrackingCache = new HashMap, RunnableFuture>(); - initTransientFields(); - } - - /** - * Produces the score correction object to use in this leaderboard. Used by the constructor. Subclasses may override - * this method to create a more specific type of score correction. This implementation produces an object of type - * {@link ScoreCorrectionImpl}. - */ - protected SettableScoreCorrection createScoreCorrection() { - return new ScoreCorrectionImpl(this); - } - - private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { - ois.defaultReadObject(); - initTransientFields(); - } - - private void initTransientFields() { - this.raceDetailsAtEndOfTrackingCache = new HashMap, RunnableFuture>(); - this.raceDetailsExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); - this.cacheInvalidationListeners = new HashSet(); - // When many updates are triggered in a short period of time by a single thread, ensure that the single thread - // providing the updates is not outperformed by all the re-calculations happening here. Leave at least one - // core to other things, but by using at least three threads ensure that no simplistic deadlocks may occur. - final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3); - executor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE, - /* maximumPoolSize */ THREAD_POOL_SIZE, - /* keepAliveTime */ 60, TimeUnit.SECONDS, - /* workQueue */ new LinkedBlockingQueue(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY-1, /* daemon */ true)); - } - - @Override - public void destroy() { - for (CacheInvalidationListener cacheInvalidationListener : cacheInvalidationListeners) { - cacheInvalidationListener.removeFromTrackedRace(); - } - } - - @Override - public SettableScoreCorrection getScoreCorrection() { - return scoreCorrection; - } - - @Override - public String getDisplayName(Competitor competitor) { - return displayNames.get(competitor); - } - - @Override - public String getDisplayName() { - return displayName; - } - - @Override - public void setDisplayName(String displayName) { - this.displayName = displayName; - } - - @Override - public ResultDiscardingRule getResultDiscardingRule() { - return crossLeaderboardResultDiscardingRule; - } - - @Override - public void setCarriedPoints(Competitor competitor, double carriedPoints) { - Double oldCarriedPoints = this.carriedPoints.put(competitor, carriedPoints); - if (!Util.equalsWithNull(oldCarriedPoints, carriedPoints)) { - getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, carriedPoints); - } - } - - @Override - public double getCarriedPoints(Competitor competitor) { - Double result = carriedPoints.get(competitor); - return result == null ? 0 : result; - } - - @Override - public Map getCompetitorsForWhichThereAreCarriedPoints() { - return Collections.unmodifiableMap(carriedPoints); - } - - @Override - public void unsetCarriedPoints(Competitor competitor) { - Double oldCarriedPoints = carriedPoints.remove(competitor); - if (oldCarriedPoints != null) { - getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, null); - } - } - - @Override - public boolean hasCarriedPoints() { - return !carriedPoints.isEmpty(); - } - - @Override - public boolean hasCarriedPoints(Competitor competitor) { - return carriedPoints.containsKey(competitor); - } - - @Override - public void setDisplayName(Competitor competitor, String displayName) { - String oldDisplayName = displayNames.get(competitor); - displayNames.put(competitor, displayName); - if (!Util.equalsWithNull(oldDisplayName, displayName)) { - getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName); - } - } - - @Override - public void setCrossLeaderboardResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule) { - ResultDiscardingRule oldDiscardingRule = getResultDiscardingRule(); - this.crossLeaderboardResultDiscardingRule = discardingRule; - getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, discardingRule); - } - - @Override - public Double getNetPoints(final Competitor competitor, final RaceColumn raceColumn, final TimePoint timePoint) { - return getScoreCorrection().getCorrectedScore( - new Callable() { - public Integer call() throws NoWindException { - return getTrackedRank(competitor, raceColumn, timePoint); - } - }, competitor, - raceColumn, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()).getCorrectedScore(); - } - - @Override - public MaxPointsReason getMaxPointsReason(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { - return getScoreCorrection().getMaxPointsReason(competitor, raceColumn, timePoint); - } - - @Override - public boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { - return isDiscarded(competitor, raceColumn, getRaceColumns(), timePoint); - } - - private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, - Iterable raceColumnsToConsider, TimePoint timePoint) { - final Set discardedRaceColumns = getResultDiscardingRule() - .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); - return isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns); - } - - /** - * Same as {@link #isDiscarded(Competitor, RaceColumn, TimePoint)}, only that the set of discarded race columns can - * be specified which is useful when total points are to be computed for more than one column for the same - * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done - * once and not again for each column (which would lead to quadratic effort). - * - * @param discardedRaceColumns - * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}. - * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint) - * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}. - */ - private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, - final Set discardedRaceColumns) { - return !raceColumn.isMedalRace() - && getMaxPointsReason(competitor, raceColumn, timePoint).isDiscardable() - && discardedRaceColumns.contains(raceColumn); - } - - @Override - public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { - return getTotalPoints(competitor, raceColumn, getRaceColumns(), timePoint); - } - - @Override - public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, - Iterable raceColumnsToConsider, TimePoint timePoint) throws NoWindException { - final Set discardedRaceColumns = getResultDiscardingRule() - .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); - return getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns); - } - - /** - * Same as {@link #getTotalPoints(Competitor, RaceColumn, Iterable, TimePoint)}, only that the set of discarded race columns can - * be specified which is useful when total points are to be computed for more than one column for the same - * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done - * once and not again for each column (which would lead to quadratic effort). - * - * @param discardedRaceColumns - * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}. - * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint) - * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}. - */ - @Override - public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, Set discardedRaceColumns) { - Double result; - if (isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns)) { - result = 0.0; - } else { - final Double netPoints = getNetPoints(competitor, raceColumn, timePoint); - if (netPoints == null) { - result = null; - } else { - result = raceColumn.getFactor() * netPoints; - } - } - return result; - } - - @Override - public Double getTotalPoints(Competitor competitor, TimePoint timePoint) { - return getTotalPoints(competitor, getRaceColumns(), timePoint); - } - - @Override - public Double getTotalPoints(Competitor competitor, final Iterable raceColumnsToConsider, - TimePoint timePoint) { - // when a column with isStartsWithZeroScore() is found, only reset score if the competitor scored in any race from there on - boolean needToResetScoreUponNextNonEmptyEntry = false; - double result = getCarriedPoints(competitor); - final Set discardedRaceColumns = getResultDiscardingRule() - .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); - for (RaceColumn raceColumn : raceColumnsToConsider) { - if (raceColumn.isStartsWithZeroScore()) { - needToResetScoreUponNextNonEmptyEntry = true; - } - if (getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint)) { - final Double totalPoints = getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns); - if (totalPoints != null) { - if (needToResetScoreUponNextNonEmptyEntry) { - result = 0; - needToResetScoreUponNextNonEmptyEntry = false; - } - result += totalPoints; - } - } - } - return result; - } - - /** - * All competitors with non-null net points are added to the result which is then sorted by net points in ascending - * order. The fleet, if ordered, is the primary ordering criterion, followed by the net points. - */ - @Override - public List getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { - final Map> netPointsAndFleet = new HashMap>(); - for (Competitor competitor : getCompetitors()) { - Double netPoints = getNetPoints(competitor, raceColumn, timePoint); - if (netPoints != null) { - netPointsAndFleet.put(competitor, new com.sap.sse.common.Util.Pair(netPoints, raceColumn.getFleetOfCompetitor(competitor))); - } - } - List result = new ArrayList(netPointsAndFleet.keySet()); - Collections.sort(result, new Comparator() { - @Override - public int compare(Competitor o1, Competitor o2) { - int comparisonResult; - if (o1 == o2) { - comparisonResult = 0; - } else { - if (raceColumn.hasSplitFleets() && !raceColumn.hasSplitFleetContiguousScoring()) { - // only check fleets if there are more than one and the column is not to be contiguously scored even in case - // of split fleets - final Fleet o1Fleet = netPointsAndFleet.get(o1).getB(); - final Fleet o2Fleet = netPointsAndFleet.get(o2).getB(); - if (o1Fleet == null) { - if (o2Fleet == null) { - comparisonResult = 0; - } else { - comparisonResult = 1; // o1 ranks "worse" because it doesn't have a fleet set while o2 - // has - } - } else { - if (o2Fleet == null) { - comparisonResult = -1; // o1 ranks "better" because it has a fleet set while o2 hasn't - } else { - comparisonResult = o1Fleet.compareTo(o2Fleet); - } - } - } else { - // either there are no split fleets or the split isn't relevant for scoring as for ordered fleets - // the scoring runs contiguously from top to bottom - comparisonResult = 0; - } - if (comparisonResult == 0) { - comparisonResult = getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare( - netPointsAndFleet.get(o1).getA(), netPointsAndFleet.get(o2).getA()); - } - } - return comparisonResult; - } - }); - return result; - } - - @Override - public List getCompetitorsFromBestToWorst(TimePoint timePoint) { - return getCompetitorsFromBestToWorst(getRaceColumns(), timePoint); - } - - private List getCompetitorsFromBestToWorst(Iterable raceColumnsToConsider, TimePoint timePoint) { - List result = new ArrayList(); - for (Competitor competitor : getCompetitors()) { - result.add(competitor); - } - Collections.sort(result, getTotalRankComparator(raceColumnsToConsider, timePoint)); - return result; - } - - @Override - public int getTotalRankOfCompetitor(Competitor competitor, TimePoint timePoint) throws NoWindException { - List competitorsFromBestToWorst = getCompetitorsFromBestToWorst(timePoint); - return competitorsFromBestToWorst.indexOf(competitor) + 1; - } - - protected Comparator getTotalRankComparator(Iterable raceColumnsToConsider, TimePoint timePoint) { - return new LeaderboardTotalRankComparator(this, timePoint, getScoringScheme(), /* nullScoresAreBetter */ false, raceColumnsToConsider); - } - - @Override - public RaceColumn getRaceColumnByName(String columnName) { - RaceColumn result = null; - for (RaceColumn r : getRaceColumns()) { - if (r.getName().equals(columnName)) { - result = r; - break; - } - } - return result; - } - - @Override - public Competitor getCompetitorByName(String competitorName) { - for (Competitor competitor : getAllCompetitors()) { - if (competitor.getName().equals(competitorName)) { - return competitor; - } - } - return null; - } - - @Override - public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { - TrackedRace trackedRaceForCompetitorInColumn; - return getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint) && - (getScoreCorrection().isScoreCorrected(competitor, raceColumn, timePoint) || - ((trackedRaceForCompetitorInColumn=raceColumn.getTrackedRace(competitor)) != null && - trackedRaceForCompetitorInColumn.hasStarted(timePoint) && - trackedRaceForCompetitorInColumn.getRank(competitor, timePoint) != 0)); - } - - @Override - public void addRaceColumnListener(RaceColumnListener listener) { - getRaceColumnListeners().addRaceColumnListener(listener); - } - - @Override - public void removeRaceColumnListener(RaceColumnListener listener) { - getRaceColumnListeners().removeRaceColumnListener(listener); - } - - @Override - public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint) throws NoWindException { - final Set discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); - return getEntry(competitor, race, timePoint, discardedRaceColumns); - } - - @Override - public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint, - Set discardedRaceColumns) throws NoWindException { - Callable trackedRankProvider = new Callable() { - public Integer call() throws NoWindException { - return getTrackedRank(competitor, race, timePoint); - } - }; - final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, race, - timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); - boolean discarded = isDiscarded(competitor, race, timePoint, discardedRaceColumns); - final Double correctedScore = correctedResults.getCorrectedScore(); - return new EntryImpl(trackedRankProvider, correctedScore, new Callable() { - @Override - public Double call() { - return correctedResults.getUncorrectedScore(); - } - }, correctedResults.isCorrected(), discarded ? DOUBLE_0 : correctedScore == null ? null - : Double.valueOf(correctedScore * race.getFactor()), correctedResults.getMaxPointsReason(), discarded, - race.getFleetOfCompetitor(competitor)); - } - - @Override - public Map> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint) throws NoWindException { - Map> result = new LinkedHashMap<>(); - List raceColumnsToConsider = new ArrayList<>(); - for (RaceColumn raceColumn : getRaceColumns()) { - raceColumnsToConsider.add(raceColumn); - result.put(raceColumn, getCompetitorsFromBestToWorst(raceColumnsToConsider, timePoint)); - } - return result; - } - - @Override - public Map> getTotalPointsSumAfterRaceColumn(final TimePoint timePoint) - throws NoWindException { - final Map> result = new LinkedHashMap<>(); - List raceColumnsToConsider = new ArrayList<>(); - Map>> futures = new HashMap<>(); - for (final RaceColumn raceColumn : getRaceColumns()) { - raceColumnsToConsider.add(raceColumn); - final Iterable finalRaceColumnsToConsider = new ArrayList<>(raceColumnsToConsider); - futures.put(raceColumn, executor.submit(new Callable>() { - @Override - public Map call() { - Map totalPointsSumPerCompetitorInColumn = new HashMap<>(); - for (Competitor competitor : getCompetitors()) { - totalPointsSumPerCompetitorInColumn.put(competitor, getTotalPoints(competitor, finalRaceColumnsToConsider, timePoint)); - } - synchronized (result) { - return totalPointsSumPerCompetitorInColumn; - } - } - })); - } - for (RaceColumn raceColumn : getRaceColumns()) { - try { - result.put(raceColumn, futures.get(raceColumn).get()); - } catch (InterruptedException | ExecutionException e) { - if (e.getCause() instanceof NoWindError) { - throw ((NoWindError) e.getCause()).getCause(); - } else { - throw new RuntimeException(e); // no caught exceptions occur in the futures executed - } - } - } - return result; - } - - @Override - public Map, Entry> getContent(final TimePoint timePoint) throws NoWindException { - Map, Entry> result = new HashMap, Entry>(); - Map> discardedRaces = new HashMap>(); - for (final RaceColumn raceColumn : getRaceColumns()) { - for (final Competitor competitor : getCompetitors()) { - Callable trackedRankProvider = new Callable() { - @Override - public Integer call() throws Exception { - return getTrackedRank(competitor, raceColumn, timePoint); - } - }; - final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, raceColumn, - timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); - Set discardedRacesForCompetitor = discardedRaces.get(competitor); - if (discardedRacesForCompetitor == null) { - discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); - discardedRaces.put(competitor, discardedRacesForCompetitor); - } - boolean discarded = discardedRacesForCompetitor.contains(raceColumn); - final Double correctedScore = correctedResults.getCorrectedScore(); - Entry entry = new EntryImpl(trackedRankProvider, correctedScore, - new Callable() { @Override public Double call() { return correctedResults.getUncorrectedScore(); } }, - correctedResults.isCorrected(), - discarded ? DOUBLE_0 : (correctedScore==null?null: - Double.valueOf((correctedScore * raceColumn.getFactor()))), correctedResults.getMaxPointsReason(), - discarded, raceColumn.getFleetOfCompetitor(competitor)); - result.put(new com.sap.sse.common.Util.Pair(competitor, raceColumn), entry); - } - } - return result; - } - - @Override - public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { - getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace); - } - - @Override - public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { - getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace); - // It's generally possible that a leaderboard links to the same tracked race in multiple columns / fleets; - // only if it no longer references the trackedRace currently unlinked from one column/fleet, also unlink - // all cache invalidation listeners for said trackedRace - if (!Util.contains(getTrackedRaces(), trackedRace)) { - synchronized (cacheInvalidationListeners) { - for (Iterator cacheInvalidationListenerIter=cacheInvalidationListeners.iterator(); - cacheInvalidationListenerIter.hasNext(); ) { - CacheInvalidationListener cacheInvalidationListener = cacheInvalidationListenerIter.next(); - if (cacheInvalidationListener.getTrackedRace() == trackedRace) { - cacheInvalidationListener.removeFromTrackedRace(); - cacheInvalidationListenerIter.remove(); - } - } - } - } - } - - @Override - public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { - getRaceColumnListeners().notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace); - } - - @Override - public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { - getRaceColumnListeners().notifyListenersAboutIsStartsWithZeroScoreChanged(raceColumn, newIsStartsWithZeroScore); - } - - @Override - public void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, boolean firstColumnIsNonDiscardableCarryForward) { - getRaceColumnListeners().notifyListenersAboutIsFirstColumnIsNonDiscardableCarryForwardChanged(raceColumn, firstColumnIsNonDiscardableCarryForward); - } - - @Override - public void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) { - getRaceColumnListeners().notifyListenersAboutHasSplitFleetContiguousScoringChanged(raceColumn, hasSplitFleetContiguousScoring); - } - - @Override - public void raceColumnMoved(RaceColumn raceColumn, int newIndex) { - getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(raceColumn, newIndex); - } - - @Override - public void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) { - getRaceColumnListeners().notifyListenersAboutFactorChanged(raceColumn, oldFactor, newFactor); - } - - /** - * A leaderboard will only accept the addition of a race column if the column's name is unique across the leaderboard. - */ - @Override - public boolean canAddRaceColumnToContainer(RaceColumn newRaceColumn) { - boolean result = true; - for (RaceColumn raceColumn : getRaceColumns()) { - if (raceColumn.getName().equals(newRaceColumn.getName())) { - result = false; - break; - } - } - return result; - } - - @Override - public void raceColumnAddedToContainer(RaceColumn raceColumn) { - getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(raceColumn); - } - - @Override - public void raceColumnRemovedFromContainer(RaceColumn raceColumn) { - getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn); - } - - @Override - public void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) { - getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName); - } - - @Override - public void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule, ResultDiscardingRule newDiscardingRule) { - getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, newDiscardingRule); - } - - @Override - public boolean isTransient() { - return false; - } - - /** - * Finds out the time point when any of the {@link Leaderboard#getTrackedRaces() tracked races currently attached to - * the leaderboard} and the {@link Leaderboard#getScoreCorrection() score corrections} have last been - * modified. If no tracked race is attached and no time-stamped score corrections have been applied to the leaderboard, - * null is returned. The time point computed this way is a good choice for normalizing queries for later time - * points in an attempt to achieve more cache hits.

- * - * Note, however, that the result does not tell about structural changes to the leaderboard and therefore cannot be used - * to determine the need for cache invalidation. For example, if a column is added to a leaderboard after the time point - * returned by this method but that column's attached tracked race has finished before the time point returned by this method, - * the result of this method won't change. Still, the contents of the leaderboard will change by a change in column structure. - * A different means to determine the possibility of changes that happened to this leaderboard must be used for cache - * management. Such a facility has to listen for score correction changes, tracked races being attached or detached and - * the column structure changing. - * - * @see TrackedRace#getTimePointOfNewestEvent() - * @see SettableScoreCorrection#getTimePointOfLastCorrectionsValidity() - */ - @Override - public TimePoint getTimePointOfLatestModification() { - TimePoint result = null; - for (TrackedRace trackedRace : getTrackedRaces()) { - if (result == null || (trackedRace.getTimePointOfNewestEvent() != null && trackedRace.getTimePointOfNewestEvent().after(result))) { - result = trackedRace.getTimePointOfNewestEvent(); - } - } - TimePoint timePointOfLastScoreCorrection = getScoreCorrection().getTimePointOfLastCorrectionsValidity(); - if (timePointOfLastScoreCorrection != null && (result == null || timePointOfLastScoreCorrection.after(result))) { - result = timePointOfLastScoreCorrection; - } - return result; - } - - @Override - public com.sap.sse.common.Util.Pair getMaximumSpeedOverGround(Competitor competitor, TimePoint timePoint) { - com.sap.sse.common.Util.Pair result = null; - // TODO should we ensure that competitor participated in all race columns? - for (TrackedRace trackedRace : getTrackedRaces()) { - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { - NavigableSet markPassings = trackedRace.getMarkPassings(competitor); - if (!markPassings.isEmpty()) { - TimePoint from = markPassings.first().getTimePoint(); - TimePoint to; - if (timePoint.after(markPassings.last().getTimePoint()) && - markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) { - // stop counting when competitor finished the race - to = markPassings.last().getTimePoint(); - } else { - to = timePoint; - } - com.sap.sse.common.Util.Pair maxSpeed = trackedRace.getTrack(competitor).getMaximumSpeedOverGround(from, to); - if (result == null || result.getB() == null || - (maxSpeed != null && maxSpeed.getB() != null && maxSpeed.getB().compareTo(result.getB()) > 0)) { - result = maxSpeed; - } - } - } - } - return result; - } - - @Override - public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) { - Speed result = null; - for (TrackedRace trackedRace : getTrackedRaces()) { - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { - NavigableSet markPassings = trackedRace.getMarkPassings(competitor); - if (!markPassings.isEmpty()) { - TimePoint from = markPassings.first().getTimePoint(); - TimePoint to; - if (timePoint.after(markPassings.last().getTimePoint()) && - markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) { - // stop counting when competitor finished the race - to = markPassings.last().getTimePoint(); - } else { - if (markPassings.last().getWaypoint() != trackedRace.getRace().getCourse().getLastWaypoint() && - timePoint.after(markPassings.last().getTimePoint())) { - result = null; - break; - } - to = timePoint; - } - Distance distanceTraveled = trackedRace.getDistanceTraveled(competitor, timePoint); - if (distanceTraveled != null) { - result = distanceTraveled.inTime(to.asMillis()-from.asMillis()); - } - } - } - } - return result; - } - - @Override - public Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint) throws NoWindException { - return getTotalTimeSailedInLegType(competitor, legType, timePoint, new HashMap()); - } - - private Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint, Map legTypeCache) throws NoWindException { - Duration result = null; - // TODO should we ensure that competitor participated in all race columns? - outerLoop: - for (TrackedRace trackedRace : getTrackedRaces()) { - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { - trackedRace.getRace().getCourse().lockForRead(); - try { - for (Leg leg : trackedRace.getRace().getCourse().getLegs()) { - TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, leg); - if (trackedLegOfCompetitor.hasStartedLeg(timePoint)) { - // find out leg type at the time the competitor started the leg - try { - final TrackedLeg trackedLeg = trackedRace.getTrackedLeg(leg); - LegType trackedLegType = legTypeCache.get(trackedLeg); - if (trackedLegType == null) { - final TimePoint startTime = trackedLegOfCompetitor.getStartTime(); - TimePoint finishTime = trackedLegOfCompetitor.getFinishTime(); - if (finishTime == null) { - finishTime = timePoint; - } - trackedLegType = trackedLeg.getLegType(startTime.plus(startTime.until(finishTime).divide(2))); // middle of the leg - legTypeCache.put(trackedLeg, trackedLegType); - } - if (legType == trackedLegType) { - Duration timeSpentOnDownwind = trackedLegOfCompetitor.getTime(timePoint); - if (timeSpentOnDownwind != null) { - if (result == null) { - result = timeSpentOnDownwind; - } else { - result = result.plus(timeSpentOnDownwind); - } - } else { - // Although the competitor has started the leg, no value was produced. This - // means that the competitor didn't finish the leg before tracking ended. No useful value - // can be obtained for this competitor anymore. - result = null; - break outerLoop; - } - } - } catch (NoWindException nwe) { - // without wind there is no leg type and hence there is no reasonable value for this: - result = null; - break outerLoop; - } - } - } - } finally { - trackedRace.getRace().getCourse().unlockAfterRead(); - } - } - } - return result; - } - - @Override - public Duration getTotalTimeSailed(Competitor competitor, TimePoint timePoint) { - Duration result = null; - for (TrackedRace trackedRace : getTrackedRaces()) { - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { - NavigableSet markPassings = trackedRace.getMarkPassings(competitor); - if (!markPassings.isEmpty()) { - TimePoint from = trackedRace.getStartOfRace(); // start counting at race start, not when the competitor passed the line - if (from != null && !timePoint.before(from)) { // but only if the race started after timePoint - TimePoint to; - if (timePoint.after(markPassings.last().getTimePoint()) - && markPassings.last().getWaypoint() == trackedRace.getRace().getCourse() - .getLastWaypoint()) { - // stop counting when competitor finished the race - to = markPassings.last().getTimePoint(); - } else { - if (trackedRace.getEndOfTracking() != null - && timePoint.after(trackedRace.getEndOfTracking())) { - result = null; // race not finished until end of tracking; no reasonable value can be - // computed for competitor - break; - } else { - to = timePoint; - } - } - Duration timeSpent = from.until(to); - if (result == null) { - result = timeSpent; - } else { - result=result.plus(timeSpent); - } - } - } - } - } - return result; - } - - @Override - public Distance getTotalDistanceTraveled(Competitor competitor, TimePoint timePoint) { - Distance result = null; - for (TrackedRace trackedRace : getTrackedRaces()) { - TimePoint startOfRace; - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor) && - (startOfRace=trackedRace.getStartOfRace()) != null && - !startOfRace.after(timePoint)) { - Distance distanceSailedInRace = trackedRace.getDistanceTraveled(competitor, timePoint); - if (distanceSailedInRace != null) { - if (result == null) { - result = distanceSailedInRace; - } else { - result = result.add(distanceSailedInRace); - } - } else { - // if competitor has not finished one single race in the whole - // series then we can not return a meaningful value for all - // all races - return null; - } - } - } - return result; - } - - protected RaceColumnListeners getRaceColumnListeners() { - return raceColumnListeners; - } - - @Override - public Iterable getCompetitors() { - final Iterable result; - // mostly the set of suppressed competitors is empty; in this case, avoid having to loop over the - // potentially large set of competitors - if (Util.isEmpty(getSuppressedCompetitors())) { - result = getAllCompetitors(); - } else { - final Iterable allCompetitors = getAllCompetitors(); - final Set suppressed = new HashSet<>(); - Util.addAll(getSuppressedCompetitors(), suppressed); - result = getCompetitorIterableSkippingSuppressed(allCompetitors, suppressed); - } - return result; - } - - @Override - public Iterable getCompetitors(RaceColumn raceColumn, Fleet fleet) { - return getCompetitorIterableSkippingSuppressed(getAllCompetitors(raceColumn, fleet), getSuppressedCompetitors()); - } - - /** - * return an iterable with a smart iterator that filters out the suppressed elements on demand - */ - protected Iterable getCompetitorIterableSkippingSuppressed(final Iterable allCompetitors, - final Iterable suppressed) { - final Iterable result; - result = new Iterable() { - @Override - public Iterator iterator() { - return new Iterator() { - private final Iterator allIter = allCompetitors.iterator(); - private Competitor next = advance(); - - private Competitor advance() { - next = null; - while (allIter.hasNext() && next == null) { - next = allIter.next(); - if (Util.contains(suppressed, next)) { - next = null; - } - } - return next; - } - - @Override - public boolean hasNext() { - return next != null; - } - - @Override - public Competitor next() { - if (next == null) { - throw new NoSuchElementException(); - } - final Competitor result = next; - advance(); - return result; - } - }; - } - }; - return result; - } - - @Override - public Iterable getSuppressedCompetitors() { - LockUtil.lockForRead(suppressedCompetitorsLock); - try { - return new HashSet(suppressedCompetitors); - } finally { - LockUtil.unlockAfterRead(suppressedCompetitorsLock); - } - } - - @Override - public void setSuppressed(Competitor competitor, boolean suppressed) { - LockUtil.lockForWrite(suppressedCompetitorsLock); - try { - if (suppressed) { - suppressedCompetitors.add(competitor); - } else { - suppressedCompetitors.remove(competitor); - } - } finally { - LockUtil.unlockAfterWrite(suppressedCompetitorsLock); - } - getScoreCorrection().notifyListenersAboutIsSuppressedChange(competitor, suppressed); - } - - @Override - public TimePoint getNowMinusDelay() { - final TimePoint now = MillisecondsTimePoint.now(); - final Long delayToLiveInMillis = getDelayToLiveInMillis(); - TimePoint timePoint = delayToLiveInMillis == null ? now : now.minus(delayToLiveInMillis); - return timePoint; - } - - @Override - public void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) { - getRaceColumnListeners().notifyListenersAboutRaceLogEventAdded(raceColumn, raceLogIdentifier, event); - if (event instanceof InvalidatesLeaderboardCache) { - // make sure to invalidate the cache as this event indicates that - // it changes values the cache could still hold - if (leaderboardDTOCache != null) { - leaderboardDTOCache.invalidate(this); - } - } - } - - @Override - public LeaderboardDTO computeDTO(final TimePoint timePoint, - final Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, - final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory, - final boolean fillNetPointsUncorrected) - throws NoWindException { - long startOfRequestHandling = System.currentTimeMillis(); - final LeaderboardDTOCalculationReuseCache cache = new LeaderboardDTOCalculationReuseCache(timePoint); - final LeaderboardDTO result = new LeaderboardDTO(this.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null ? null - : this.getScoreCorrection().getTimePointOfLastCorrectionsValidity().asDate(), - this.getScoreCorrection() == null ? null : this.getScoreCorrection().getComment(), - this.getScoringScheme() == null ? null : this.getScoringScheme().getType(), this - .getScoringScheme().isHigherBetter(), new UUIDGenerator(), addOverallDetails); - result.competitors = new ArrayList(); - result.name = this.getName(); - result.competitorDisplayNames = new HashMap(); - for (Competitor suppressedCompetitor : this.getSuppressedCompetitors()) { - result.setSuppressed(baseDomainFactory.convertToCompetitorDTO(suppressedCompetitor), true); - } - // Now create the race columns and, as a future task, set their competitorsFromBestToWorst, then wait for all these - // futures to finish: - Map>> competitorsFromBestToWorstTasks = new HashMap<>(); - for (final RaceColumn raceColumn : this.getRaceColumns()) { - result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(), - raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, - raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null); - for (Fleet fleet : raceColumn.getFleets()) { - RegattaAndRaceIdentifier raceIdentifier = null; - RaceDTO race = null; - TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); - final FleetDTO fleetDTO = baseDomainFactory.convertToFleetDTO(fleet); - if (trackedRace != null) { - raceIdentifier = new RegattaNameAndRaceName(trackedRace.getTrackedRegatta().getRegatta().getName(), - trackedRace.getRace().getName()); - race = baseDomainFactory.createRaceDTO(trackedRegattaRegistry, /* withGeoLocationData */ false, raceIdentifier, trackedRace); - } - // Note: the RaceColumnDTO won't be created by the following addRace call because it has been created - // above by the result.createEmptyRaceColumn call - result.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(), - raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, - raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, - fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race); - } - FutureTask> task = new FutureTask>( - () -> baseDomainFactory.getCompetitorDTOList(AbstractSimpleLeaderboardImpl.this.getCompetitorsFromBestToWorst(raceColumn, timePoint))); - executor.execute(task); - competitorsFromBestToWorstTasks.put(raceColumn, task); - } - // wait for the competitor orderings to have been computed for all race columns before continuing; subsequent tasks may depend on these data - for (Map.Entry>> raceColumnAndTaskToJoin : competitorsFromBestToWorstTasks.entrySet()) { - try { - result.setCompetitorsFromBestToWorst(raceColumnAndTaskToJoin.getKey().getName(), raceColumnAndTaskToJoin.getValue().get()); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept - // null values. - // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So - // this helps robustness from a user's perspective. - logger.log( - Level.SEVERE, - AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", " - + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails - + "): exception during computing competitor ordering for race column "+raceColumnAndTaskToJoin.getKey().getName(), e); - } - } - result.setDelayToLiveInMillisForLatestRace(this.getDelayToLiveInMillis()); - result.rows = new HashMap(); - result.hasCarriedPoints = this.hasCarriedPoints(); - if (this.getResultDiscardingRule() instanceof ThresholdBasedResultDiscardingRule) { - result.discardThresholds = ((ThresholdBasedResultDiscardingRule) this.getResultDiscardingRule()) - .getDiscardIndexResultsStartingWithHowManyRaces(); - } else { - result.discardThresholds = null; - } - // Computing the competitor leg ranks is expensive, especially in live mode, in case new events keep - // invalidating the ranks cache in TrackedLegImpl. The problem then is that the sorting based on wind data is repeated for - // each competitor, leading to square effort. We therefore need to compute the leg ranks for those races where leg - // details are requested only once and pass them into getLeaderboardEntryDTO - final Map> legRanksCache = new HashMap>(); - for (final RaceColumn raceColumn : this.getRaceColumns()) { - // if details for the column are requested, cache the leg's ranks - if (namesOfRaceColumnsForWhichToLoadLegDetails != null - && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn.getName())) { - for (Fleet fleet : raceColumn.getFleets()) { - TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); - if (trackedRace != null) { - trackedRace.getRace().getCourse().lockForRead(); - try { - for (TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) { - legRanksCache.put(trackedLeg.getLeg(), trackedLeg.getRanks(timePoint, cache)); - } - } finally { - trackedRace.getRace().getCourse().unlockAfterRead(); - } - } - } - } - } - for (final Competitor competitor : this.getCompetitorsFromBestToWorst(timePoint)) { - CompetitorDTO competitorDTO = baseDomainFactory.convertToCompetitorDTO(competitor); - LeaderboardRowDTO row = new LeaderboardRowDTO(); - row.competitor = competitorDTO; - row.fieldsByRaceColumnName = new HashMap(); - row.carriedPoints = this.hasCarriedPoints(competitor) ? this.getCarriedPoints(competitor) : null; - row.totalPoints = this.getTotalPoints(competitor, timePoint); - if (addOverallDetails) { - addOverallDetailsToRow(timePoint, competitor, row); - } - result.competitors.add(competitorDTO); - Map> futuresForColumnName = new HashMap>(); - final Set discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); - for (final RaceColumn raceColumn : this.getRaceColumns()) { - RunnableFuture future = new FutureTask(() -> { - Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint, discardedRaceColumns); - return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint, - namesOfRaceColumnsForWhichToLoadLegDetails != null - && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn - .getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory, - fillNetPointsUncorrected, cache); - }); - executor.execute(future); - futuresForColumnName.put(raceColumn.getName(), future); - } - for (Map.Entry> raceColumnNameAndFuture : futuresForColumnName.entrySet()) { - try { - row.fieldsByRaceColumnName.put(raceColumnNameAndFuture.getKey(), raceColumnNameAndFuture.getValue().get()); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept - // null values. - // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So - // this helps robustness from a user's perspective. - logger.log( - Level.SEVERE, - AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", " - + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails - + "): exception during computing leaderboard entry for competitor " - + competitor.getName() + " in race column " + raceColumnNameAndFuture.getKey() - + ". Leaving empty.", e); - } - } - result.rows.put(competitorDTO, row); - String displayName = this.getDisplayName(competitor); - if (displayName != null) { - result.competitorDisplayNames.put(competitorDTO, displayName); - } - } - logger.info("computeLeaderboardByName(" + this.getName() + ", " + timePoint + ", " - + namesOfRaceColumnsForWhichToLoadLegDetails + ", addOverallDetails=" + addOverallDetails + ") took " - + (System.currentTimeMillis() - startOfRequestHandling) + "ms"); - return result; - } - - private void addOverallDetailsToRow(final TimePoint timePoint, - final Competitor competitor, LeaderboardRowDTO row) throws NoWindException { - final com.sap.sse.common.Util.Pair maximumSpeedOverGround = this.getMaximumSpeedOverGround(competitor, timePoint); - if (maximumSpeedOverGround != null && maximumSpeedOverGround.getB() != null) { - row.maximumSpeedOverGroundInKnots = maximumSpeedOverGround.getB().getKnots(); - row.whenMaximumSpeedOverGroundWasAchieved = maximumSpeedOverGround.getA().getTimePoint().asDate(); - } - Map legTypeCache = new HashMap<>(); - final Duration totalTimeSailedDownwind = this.getTotalTimeSailedInLegType(competitor, LegType.DOWNWIND, timePoint, legTypeCache); - row.totalTimeSailedDownwindInSeconds = totalTimeSailedDownwind==null?null:totalTimeSailedDownwind.asSeconds(); - final Duration totalTimeSailedUpwind = this.getTotalTimeSailedInLegType(competitor, LegType.UPWIND, timePoint, legTypeCache); - row.totalTimeSailedUpwindInSeconds = totalTimeSailedUpwind==null?null:totalTimeSailedUpwind.asSeconds(); - final Duration totalTimeSailedReaching = this.getTotalTimeSailedInLegType(competitor, LegType.REACHING, timePoint, legTypeCache); - row.totalTimeSailedReachingInSeconds = totalTimeSailedReaching==null?null:totalTimeSailedReaching.asSeconds(); - final Duration totalTimeSailed = this.getTotalTimeSailed(competitor, timePoint); - row.totalTimeSailedInSeconds = totalTimeSailed==null?null:totalTimeSailed.asSeconds(); - final Distance totalDistanceTraveledInMeters = this.getTotalDistanceTraveled(competitor, timePoint); - row.totalDistanceTraveledInMeters = totalDistanceTraveledInMeters==null?null:totalDistanceTraveledInMeters.getMeters(); - } - - /** - * @param waitForLatestAnalyses - * if false, this method is allowed to read the maneuver analysis results from a cache that - * may not reflect all data already received; otherwise, the method will always block for the latest - * cache updates to have happened before returning. - * @param fillNetPointsUncorrected - * tells if {@link LeaderboardEntryDTO#netPointsUncorrected} shall be filled; filling it is rather - * expensive, especially when compared to simply retrieving a score correction, and particularly if in a - * larger fleet a number of competitors haven't properly finished the race. This should only be used for - * leaderboard editing where a user needs to see what the uncorrected score was that would be used when - * the correction was removed. - */ - private LeaderboardEntryDTO getLeaderboardEntryDTO(Entry entry, RaceColumn raceColumn, Competitor competitor, - TimePoint timePoint, boolean addLegDetails, boolean waitForLatestAnalyses, - Map> legRanksCache, DomainFactory baseDomainFactory, - boolean fillNetPointsUncorrected, WindLegTypeAndLegBearingCache cache) { - LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO(); - TrackedRace trackedRace = raceColumn.getTrackedRace(competitor); - entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier(); - entryDTO.netPoints = entry.getNetPoints(); - if (fillNetPointsUncorrected) { - entryDTO.netPointsUncorrected = entry.getNetPointsUncorrected(); - } - entryDTO.netPointsCorrected = entry.isNetPointsCorrected(); - entryDTO.totalPoints = entry.getTotalPoints(); - entryDTO.reasonForMaxPoints = entry.getMaxPointsReason(); - entryDTO.discarded = entry.isDiscarded(); - final GPSFixTrack track = trackedRace == null ? null : trackedRace.getTrack(competitor); - if (trackedRace != null) { - Date timePointOfLastPositionFixAtOrBeforeQueryTimePoint = getTimePointOfLastFixAtOrBefore(competitor, trackedRace, timePoint); - if (track != null) { - entryDTO.averageSamplingInterval = track.getAverageIntervalBetweenRawFixes(); - } - if (timePointOfLastPositionFixAtOrBeforeQueryTimePoint != null) { - long timeDifferenceInMs = timePoint.asMillis() - timePointOfLastPositionFixAtOrBeforeQueryTimePoint.getTime(); - entryDTO.timeSinceLastPositionFixInSeconds = timeDifferenceInMs == 0 ? 0.0 : timeDifferenceInMs / 1000.0; - } else { - entryDTO.timeSinceLastPositionFixInSeconds = null; - } - } - if (addLegDetails && trackedRace != null) { - try { - final RankingInfo rankingInfo = trackedRace.getRankingMetric().getRankingInfo(timePoint, cache); - RaceDetails raceDetails = getRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, - legRanksCache, rankingInfo, cache); - entryDTO.legDetails = raceDetails.getLegDetails(); - entryDTO.windwardDistanceToCompetitorFarthestAheadInMeters = raceDetails.getWindwardDistanceToCompetitorFarthestAhead() == null ? null - : raceDetails.getWindwardDistanceToCompetitorFarthestAhead().getMeters(); - entryDTO.gapToLeaderInOwnTime = trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache); - entryDTO.averageAbsoluteCrossTrackErrorInMeters = raceDetails.getAverageAbsoluteCrossTrackError() == null ? null - : raceDetails.getAverageAbsoluteCrossTrackError().getMeters(); - entryDTO.averageSignedCrossTrackErrorInMeters = raceDetails.getAverageSignedCrossTrackError() == null ? null - : raceDetails.getAverageSignedCrossTrackError().getMeters(); - entryDTO.calculatedTime = raceDetails.getCorrectedTime(); - entryDTO.calculatedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = raceDetails.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead(); - entryDTO.gapToLeaderInOwnTime = raceDetails.getGapToLeaderInOwnTime(); - final TimePoint startOfRace = trackedRace.getStartOfRace(); - if (startOfRace != null) { - Waypoint startWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint(); - NavigableSet competitorMarkPassings = trackedRace.getMarkPassings(competitor); - trackedRace.lockForRead(competitorMarkPassings); - try { - if (!competitorMarkPassings.isEmpty()) { - final MarkPassing firstMarkPassing = competitorMarkPassings.iterator().next(); - if (firstMarkPassing.getWaypoint() == startWaypoint) { - Distance distanceToStartLineFiveSecondsBeforeStartOfRace = trackedRace.getDistanceToStartLine(competitor, /*milliseconds before start*/ 5000); - entryDTO.distanceToStartLineFiveSecondsBeforeStartInMeters = distanceToStartLineFiveSecondsBeforeStartOfRace == null ? null - : distanceToStartLineFiveSecondsBeforeStartOfRace.getMeters(); - Speed speedFiveSecondsBeforeStartOfRace = trackedRace.getSpeed(competitor, /*milliseconds before start*/ 5000); - entryDTO.speedOverGroundFiveSecondsBeforeStartInKnots = speedFiveSecondsBeforeStartOfRace == null ? null - : speedFiveSecondsBeforeStartOfRace.getKnots(); - Distance distanceToStartLineAtStartOfRace = trackedRace.getDistanceToStartLine( - competitor, startOfRace); - entryDTO.distanceToStartLineAtStartOfRaceInMeters = distanceToStartLineAtStartOfRace == null ? null - : distanceToStartLineAtStartOfRace.getMeters(); - Speed speedAtStartTime = track == null ? null : track.getEstimatedSpeed(startOfRace); - entryDTO.speedOverGroundAtStartOfRaceInKnots = speedAtStartTime == null ? null - : speedAtStartTime.getKnots(); - TimePoint competitorStartTime = firstMarkPassing.getTimePoint(); - entryDTO.timeBetweenRaceStartAndCompetitorStartInSeconds = startOfRace.until(competitorStartTime).asSeconds(); - Speed competitorSpeedWhenPassingStart = track == null ? null : track - .getEstimatedSpeed(competitorStartTime); - entryDTO.speedOverGroundAtPassingStartWaypointInKnots = competitorSpeedWhenPassingStart == null ? null - : competitorSpeedWhenPassingStart.getKnots(); - try { - entryDTO.startTack = trackedRace.getTack(competitor, competitorStartTime); - } catch (NoWindException nwe) { - entryDTO.startTack = null; // leave empty in case no wind information is available - } - Distance distanceFromStarboardSideOfStartLineWhenPassingStart = trackedRace - .getDistanceFromStarboardSideOfStartLineWhenPassingStart(competitor); - entryDTO.distanceToStarboardSideOfStartLineInMeters = distanceFromStarboardSideOfStartLineWhenPassingStart == null ? null - : distanceFromStarboardSideOfStartLineWhenPassingStart.getMeters(); - } - } - } finally { - trackedRace.unlockAfterRead(competitorMarkPassings); - } - } - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (ExecutionException e) { - throw new RuntimeException(e); // the future used to calculate the leg details was interrupted; escalate as runtime exception - } - } - final Fleet fleet = entry.getFleet(); - entryDTO.fleet = fleet == null ? null : baseDomainFactory.convertToFleetDTO(fleet); - return entryDTO; - } - - /** - * Determines the time point of the last raw fix (with outliers not removed) for competitor in - * trackedRace. If the competitor's track is null or empty, null is returned. - * @param trackedRace must not be null - * @param atOrBefore find the last fix at or before the time point specified - */ - private Date getTimePointOfLastFixAtOrBefore(Competitor competitor, TrackedRace trackedRace, TimePoint atOrBefore) { - assert trackedRace != null; - final Date timePointOfLastPositionFix; - GPSFixTrack track = trackedRace.getTrack(competitor); - if (track == null) { - timePointOfLastPositionFix = null; - } else { - GPSFixMoving lastFix = track.getLastFixAtOrBefore(atOrBefore); - if (lastFix == null) { - timePointOfLastPositionFix = null; - } else { - timePointOfLastPositionFix = lastFix.getTimePoint().asDate(); - } - } - return timePointOfLastPositionFix; - } - - private static class RaceDetails { - private final List legDetails; - private final Distance windwardDistanceToCompetitorFarthestAhead; - private final Distance averageAbsoluteCrossTrackError; - private final Distance averageSignedCrossTrackError; - private final Duration gapToLeaderInOwnTime; - private final Duration correctedTime; - private final Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; - - public RaceDetails(List legDetails, Distance windwardDistanceToCompetitorFarthestAhead, - Distance averageAbsoluteCrossTrackError, Distance averageSignedCrossTrackError, - Duration gapToLeaderInOwnTime, Duration correctedTime, - Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead) { - super(); - this.legDetails = legDetails; - this.windwardDistanceToCompetitorFarthestAhead = windwardDistanceToCompetitorFarthestAhead; - this.averageAbsoluteCrossTrackError = averageAbsoluteCrossTrackError; - this.averageSignedCrossTrackError = averageSignedCrossTrackError; - this.gapToLeaderInOwnTime = gapToLeaderInOwnTime; - this.correctedTime = correctedTime; - this.correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; - } - public List getLegDetails() { - return legDetails; - } - public Distance getWindwardDistanceToCompetitorFarthestAhead() { - return windwardDistanceToCompetitorFarthestAhead; - } - public Distance getAverageAbsoluteCrossTrackError() { - return averageAbsoluteCrossTrackError; - } - public Distance getAverageSignedCrossTrackError() { - return averageSignedCrossTrackError; - } - public Duration getGapToLeaderInOwnTime() { - return gapToLeaderInOwnTime; - } - public Duration getCorrectedTime() { - return correctedTime; - } - public Duration getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead() { - return correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; - } - } - - /** - * If timePoint is after the end of the race's tracking the query will be adjusted to obtain the values - * at the end of the {@link TrackedRace#getEndOfTracking() race's tracking time}. If the time point adjusted this - * way equals the end of the tracking time, the query results will be looked up in a cache first and if not found, - * they will be stored to the cache after calculating them. A cache invalidation {@link RaceChangeListener listener} - * will be registered with the race which will be triggered for any event received by the race. - * @param waitForLatestAnalyses - * if false, this method is allowed to read the maneuver analysis results from a cache that - * may not reflect all data already received; otherwise, the method will always block for the latest - * cache updates to have happened before returning. - */ - private RaceDetails getRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint, - boolean waitForLatestAnalyses, Map> legRanksCache, - RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException { - final RaceDetails raceDetails; - if (trackedRace.getEndOfTracking() != null && trackedRace.getEndOfTracking().compareTo(timePoint) < 0) { - raceDetails = getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(trackedRace, competitor, legRanksCache, rankingInfo, cache); - } else { - raceDetails = calculateRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, legRanksCache, cache, rankingInfo); - } - return raceDetails; - } - - private RaceDetails getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(final TrackedRace trackedRace, - final Competitor competitor, final Map> legRanksCache, - RankingInfo rankingInfo, final WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException { - final com.sap.sse.common.Util.Pair key = new com.sap.sse.common.Util.Pair(trackedRace, competitor); - RunnableFuture raceDetails; - synchronized (raceDetailsAtEndOfTrackingCache) { - raceDetails = raceDetailsAtEndOfTrackingCache.get(key); - if (raceDetails == null) { - raceDetails = new FutureTask(new Callable() { - @Override - public RaceDetails call() throws Exception { - TimePoint end = trackedRace.getEndOfRace(); - if (end == null) { - end = trackedRace.getEndOfTracking(); - } - return calculateRaceDetails(trackedRace, competitor, end, - // TODO see bug 1358: for now, use waitForLatest==false until we've switched to optimistic locking for the course read lock - /* TODO old comment when it was still true: "because this is done only once after end of tracking" */ - /* waitForLatestAnalyses (maneuver and cross track error) */ false, - legRanksCache, cache, rankingInfo); - } - }); - raceDetailsExecutor.execute(raceDetails); - raceDetailsAtEndOfTrackingCache.put(key, raceDetails); - final CacheInvalidationListener cacheInvalidationListener = new CacheInvalidationListener(trackedRace, competitor); - trackedRace.addListener(cacheInvalidationListener); - cacheInvalidationListeners.add(cacheInvalidationListener); - } - } - return raceDetails.get(); - } - - private RaceDetails calculateRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint, - boolean waitForLatestAnalyses, Map> legRanksCache, - WindLegTypeAndLegBearingCache cache, final RankingInfo rankingInfo) { - final List legDetails = new ArrayList(); - final Course course = trackedRace.getRace().getCourse(); - course.lockForRead(); // hold back any course re-configurations while looping over the legs - try { - for (Leg leg : course.getLegs()) { - LegEntryDTO legEntry; - // We loop over a copy of the course's legs; during a course change, legs may become "stale," even with - // regard to the leg/trackedLeg structures inside the tracked race which is updated by the course change - // immediately. That's why we've acquired a read lock for the course above. - TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, leg); - if (trackedLeg != null && trackedLeg.hasStartedLeg(timePoint)) { - legEntry = createLegEntry(trackedLeg, timePoint, waitForLatestAnalyses, legRanksCache, rankingInfo, cache); - } else { - legEntry = null; - } - legDetails.add(legEntry); - } - final Distance windwardDistanceToCompetitorFarthestAhead = trackedRace == null ? null : trackedRace - .getWindwardDistanceToCompetitorFarthestAhead(competitor, timePoint, WindPositionMode.LEG_MIDDLE, rankingInfo, cache); - Distance averageAbsoluteCrossTrackError; - try { - averageAbsoluteCrossTrackError = trackedRace == null ? null : trackedRace.getAverageAbsoluteCrossTrackError( - competitor, timePoint, waitForLatestAnalyses, cache); - } catch (NoWindException nwe) { - // without wind information, use null meaning "unknown" - averageAbsoluteCrossTrackError = null; - } - Distance averageSignedCrossTrackError; - try { - averageSignedCrossTrackError = trackedRace == null ? null : trackedRace.getAverageSignedCrossTrackError( - competitor, timePoint, waitForLatestAnalyses, cache); - } catch (NoWindException nwe) { - // without wind information, use null meaning "unknown" - averageSignedCrossTrackError = null; - } - final CompetitorRankingInfo competitorRankingInfo = rankingInfo.getCompetitorRankingInfo().apply(competitor); - return new RaceDetails(legDetails, windwardDistanceToCompetitorFarthestAhead, averageAbsoluteCrossTrackError, averageSignedCrossTrackError, - trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache), - trackedRace.getRankingMetric().getCorrectedTime(competitor, timePoint), - competitorRankingInfo == null ? null : competitorRankingInfo.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead()); - } finally { - course.unlockAfterRead(); - } - } - - private LegEntryDTO createLegEntry(TrackedLegOfCompetitor trackedLeg, TimePoint timePoint, - boolean waitForLatestAnalyses, Map> legRanksCache, - RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) { - LegEntryDTO result; - final Duration time = trackedLeg.getTime(timePoint); - if (trackedLeg == null || time == null) { - result = null; - } else { - result = new LegEntryDTO(); - try { - result.legType = trackedLeg.getTrackedLeg().getLegType(timePoint); - } catch (NoWindException nwe) { - result.legType = null; // can't determine leg type without wind data - } - final Speed averageSpeedOverGround = trackedLeg.getAverageSpeedOverGround(timePoint); - result.averageSpeedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots(); - Distance averageAbsoluteCrossTrackError; - try { - averageAbsoluteCrossTrackError = trackedLeg.getAverageAbsoluteCrossTrackError(timePoint, waitForLatestAnalyses); - } catch (NoWindException nwe) { - // leave averageAbsoluteCrossTrackError as null, meaning "unknown" - averageAbsoluteCrossTrackError = null; - } - result.averageAbsoluteCrossTrackErrorInMeters = averageAbsoluteCrossTrackError == null ? null : averageAbsoluteCrossTrackError.getMeters(); - Distance averageSignedCrossTrackError; - try { - averageSignedCrossTrackError = trackedLeg.getAverageSignedCrossTrackError(timePoint, waitForLatestAnalyses); - } catch (NoWindException nwe) { - // leave averageSignedCrossTrackError as null, meaning "unknown" - averageSignedCrossTrackError = null; - } - result.averageSignedCrossTrackErrorInMeters = averageSignedCrossTrackError == null ? null : averageSignedCrossTrackError.getMeters(); - Double speedOverGroundInKnots; - if (trackedLeg.hasFinishedLeg(timePoint)) { - speedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots(); - } else { - final SpeedWithBearing speedOverGround = trackedLeg.getSpeedOverGround(timePoint); - speedOverGroundInKnots = speedOverGround == null ? null : speedOverGround.getKnots(); - } - result.currentSpeedOverGroundInKnots = speedOverGroundInKnots == null ? null : speedOverGroundInKnots; - Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint); - result.distanceTraveledInMeters = distanceTraveled == null ? null : distanceTraveled.getMeters(); - Distance distanceTraveledConsideringGateStart = trackedLeg.getDistanceTraveledConsideringGateStart(timePoint); - result.distanceTraveledIncludingGateStartInMeters = distanceTraveledConsideringGateStart == null ? null : distanceTraveledConsideringGateStart.getMeters(); - final Duration estimatedTimeToNextMarkInSeconds = trackedLeg.getEstimatedTimeToNextMark(timePoint, WindPositionMode.EXACT, cache); - result.estimatedTimeToNextWaypointInSeconds = estimatedTimeToNextMarkInSeconds==null?null:estimatedTimeToNextMarkInSeconds.asSeconds(); - result.timeInMilliseconds = time.asMillis(); - result.finished = trackedLeg.hasFinishedLeg(timePoint); - final TimePoint legFinishTime = trackedLeg.getFinishTime(); - result.correctedTotalTime = trackedLeg.hasStartedLeg(timePoint) ? trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getCorrectedTime(trackedLeg.getCompetitor(), - trackedLeg.hasFinishedLeg(timePoint) ? legFinishTime : timePoint, cache) : null; - // fetch the leg gap in own corrected time from the ranking metric - final Duration gapToLeaderInOwnTime = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric(). - getLegGapToLegLeaderInOwnTime(trackedLeg, timePoint, rankingInfo, cache); - result.gapToLeaderInSeconds = gapToLeaderInOwnTime == null ? null : gapToLeaderInOwnTime.asSeconds(); - if (result.gapToLeaderInSeconds != null) { - final Duration gapAtEndOfPreviousLeg = getGapAtEndOfPreviousLeg(trackedLeg, rankingInfo, cache); - if (gapAtEndOfPreviousLeg != null) { - result.gapChangeSinceLegStartInSeconds = result.gapToLeaderInSeconds - gapAtEndOfPreviousLeg.asSeconds(); - } - } - LinkedHashMap legRanks = legRanksCache.get(trackedLeg.getLeg()); - if (legRanks != null) { - result.rank = legRanks.get(trackedLeg.getCompetitor()); - } else { - result.rank = trackedLeg.getRank(timePoint, cache); - } - result.started = trackedLeg.hasStartedLeg(timePoint); - Speed velocityMadeGood; - if (trackedLeg.hasFinishedLeg(timePoint)) { - velocityMadeGood = trackedLeg.getAverageVelocityMadeGood(timePoint); - } else { - velocityMadeGood = trackedLeg.getVelocityMadeGood(timePoint, WindPositionMode.EXACT); - } - result.velocityMadeGoodInKnots = velocityMadeGood == null ? null : velocityMadeGood.getKnots(); - Distance windwardDistanceToGo = trackedLeg.getWindwardDistanceToGo(timePoint, WindPositionMode.LEG_MIDDLE); - result.windwardDistanceToGoInMeters = windwardDistanceToGo == null ? null : windwardDistanceToGo - .getMeters(); - final TimePoint startOfRace = trackedLeg.getTrackedLeg().getTrackedRace().getStartOfRace(); - if (startOfRace != null && trackedLeg.hasStartedLeg(timePoint)) { - // not using trackedLeg.getManeuvers(...) because it may not catch the mark passing maneuver starting this leg - // because that may have been detected as slightly before the mark passing time, hence associated with the previous leg - List maneuvers = trackedLeg.getTrackedLeg().getTrackedRace() - .getManeuvers(trackedLeg.getCompetitor(), startOfRace, timePoint, waitForLatestAnalyses); - if (maneuvers != null) { - result.numberOfManeuvers = new HashMap(); - result.numberOfManeuvers.put(ManeuverType.TACK, 0); - result.numberOfManeuvers.put(ManeuverType.JIBE, 0); - result.numberOfManeuvers.put(ManeuverType.PENALTY_CIRCLE, 0); - Map totalManeuverLossInMeters = new HashMap(); - totalManeuverLossInMeters.put(ManeuverType.TACK, 0.0); - totalManeuverLossInMeters.put(ManeuverType.JIBE, 0.0); - totalManeuverLossInMeters.put(ManeuverType.PENALTY_CIRCLE, 0.0); - TimePoint startOfLeg = trackedLeg.getStartTime(); - for (Maneuver maneuver : maneuvers) { - // don't count maneuvers that were in previous legs - switch (maneuver.getType()) { - case TACK: - case JIBE: - case PENALTY_CIRCLE: - if (!maneuver.getTimePoint().before(startOfLeg) && (legFinishTime == null || legFinishTime.after(timePoint) || - maneuver.getTimePoint().before(legFinishTime))) { - if (maneuver.getManeuverLoss() != null) { - result.numberOfManeuvers.put(maneuver.getType(), - result.numberOfManeuvers.get(maneuver.getType()) + 1); - totalManeuverLossInMeters.put(maneuver.getType(), - totalManeuverLossInMeters.get(maneuver.getType()) - + maneuver.getManeuverLoss().getMeters()); - } - } - break; - case MARK_PASSING: - // analyze all mark passings, not only those after this leg's start, to catch the mark passing - // maneuver starting this leg, even if its time point is slightly before the mark passing starting this leg - MarkPassingManeuver mpm = (MarkPassingManeuver) maneuver; - if (mpm.getWaypointPassed() == trackedLeg.getLeg().getFrom()) { - result.sideToWhichMarkAtLegStartWasRounded = mpm.getSide(); - } - break; - default: - /* Do nothing here. - * Throwing an exception destroys the toggling (and maybe other behaviour) of the leaderboard. - */ - } - } - result.averageManeuverLossInMeters = new HashMap(); - for (ManeuverType maneuverType : new ManeuverType[] { ManeuverType.TACK, ManeuverType.JIBE, - ManeuverType.PENALTY_CIRCLE }) { - if (result.numberOfManeuvers.get(maneuverType) != 0) { - result.averageManeuverLossInMeters.put( - maneuverType, - totalManeuverLossInMeters.get(maneuverType) - / result.numberOfManeuvers.get(maneuverType)); - } - } - } - } - } - return result; - } - - private Duration getGapAtEndOfPreviousLeg(TrackedLegOfCompetitor trackedLeg, final RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) { - final Duration result; - final Course course = trackedLeg.getTrackedLeg().getTrackedRace().getRace().getCourse(); - // if trackedLeg is the first leg, compute the gap at the start of this leg; otherwise, compute gap - // at the end of the previous leg - final TimePoint timePoint = trackedLeg.getStartTime(); - final TrackedLegOfCompetitor tloc; - if (course.getFirstWaypoint() == trackedLeg.getLeg().getFrom()) { - tloc = trackedLeg; - } else { - tloc = trackedLeg.getTrackedLeg().getTrackedRace().getTrackedLegFinishingAt(trackedLeg.getLeg().getFrom()) - .getTrackedLeg(trackedLeg.getCompetitor()); - } - result = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getLegGapToLegLeaderInOwnTime(tloc, timePoint, rankingInfo, cache); - return result; - } - - private LeaderboardDTO getLiveLeaderboard(Collection namesOfRaceColumnsForWhichToLoadLegDetails, - boolean addOverallDetails, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException, ExecutionException { - LiveLeaderboardUpdater liveLeaderboardUpdater = getLiveLeaderboardUpdater(trackedRegattaRegistry, - baseDomainFactory); - return liveLeaderboardUpdater.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails); - } - - private LiveLeaderboardUpdater getLiveLeaderboardUpdater(TrackedRegattaRegistry trackedRegattaRegistry, - DomainFactory baseDomainFactory) { - LiveLeaderboardUpdater result = this.liveLeaderboardUpdater; - if (result == null) { - synchronized (this) { - result = this.liveLeaderboardUpdater; - if (result == null) { - this.liveLeaderboardUpdater = new LiveLeaderboardUpdater(this, trackedRegattaRegistry, baseDomainFactory); - result = this.liveLeaderboardUpdater; - } - } - } - return result; - } - - private LeaderboardDTOCache getLeaderboardDTOCache() { - LeaderboardDTOCache result = this.leaderboardDTOCache; - if (result == null) { - synchronized (this) { - result = this.leaderboardDTOCache; - if (result == null) { - // The leaderboard cache is invalidated upon all competitor and mark position changes; some analyzes - // are pretty expensive, such as the maneuver re-calculation. Waiting for the latest analysis after only a - // single fix was updated is too expensive if users use the replay feature while a race is still running. - // Therefore, using waitForLatestAnalyses==false seems appropriate here. - this.leaderboardDTOCache = new LeaderboardDTOCache(/* waitForLatestAnalyses */false, this); - result = this.leaderboardDTOCache; - } - } - } - return result; - } - - @Override - public LeaderboardDTO getLeaderboardDTO(TimePoint timePoint, - Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, - TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory, boolean fillNetPointsUncorrected) throws NoWindException, - InterruptedException, ExecutionException { - LeaderboardDTO result = null; - if (timePoint == null) { - // timePoint==null means live mode; however, if we're after the end of all races and after all score - // corrections, don't use the live leaderboard updater which would keep re-calculating over and over again, but map - // this to a usual non-live call which uses the regular LeaderboardDTOCache which is invalidated properly - // when the tracked race associations or score corrections or tracked race contents changes: - final TimePoint nowMinusDelay = this.getNowMinusDelay(); - final TimePoint timePointOfLatestModification = this.getTimePointOfLatestModification(); - if (fillNetPointsUncorrected || (timePointOfLatestModification != null && !nowMinusDelay.before(timePointOfLatestModification))) { - // if there hasn't been any modification to the leaderboard since nowMinusDelay, use non-live mode - // and pull the result from the regular leaderboard cache: - timePoint = timePointOfLatestModification; - } else { - // don't use the regular leaderboard cache; the race still seems to be on; use the live leaderboard updater instead: - timePoint = null; - result = this.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, trackedRegattaRegistry, baseDomainFactory); - } - } - if (timePoint != null) { - if (fillNetPointsUncorrected) { - // explicitly filling the uncorrected net points requires uncached recalculation - result = computeDTO(timePoint, namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, /* waitForLatestAnalyses */ true, - trackedRegattaRegistry, baseDomainFactory, fillNetPointsUncorrected); - } else { - // in replay we'd like up-to-date results; they are still cached - // which is OK because the cache is invalidated whenever any of the tracked races attached to the - // leaderboard changes. - result = getLeaderboardDTOCache().getLeaderboardByName(timePoint, - namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, baseDomainFactory, - trackedRegattaRegistry); - } - } - return result; - } - - public String toString() { - return getName() + " " + (getDefaultCourseArea() != null ? getDefaultCourseArea().getName() : "") + " " + (getScoringScheme() != null ? getScoringScheme().getType().name() : ""); - } - - @Override - public NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher() { - return new NumberOfCompetitorsFetcherImpl(); - } -} +package com.sap.sailing.domain.leaderboard.impl; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RunnableFuture; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sailing.domain.abstractlog.race.InvalidatesLeaderboardCache; +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Course; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.Leg; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.base.RaceColumnInSeries; +import com.sap.sailing.domain.base.RaceColumnListener; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.LeaderboardType; +import com.sap.sailing.domain.common.LegType; +import com.sap.sailing.domain.common.ManeuverType; +import com.sap.sailing.domain.common.MaxPointsReason; +import com.sap.sailing.domain.common.NoWindError; +import com.sap.sailing.domain.common.NoWindException; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.RegattaNameAndRaceName; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.dto.BasicRaceDTO; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.domain.common.dto.LeaderboardDTO; +import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO; +import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; +import com.sap.sailing.domain.common.dto.LegEntryDTO; +import com.sap.sailing.domain.common.dto.MetaLeaderboardRaceColumnDTO; +import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.domain.common.dto.RaceDTO; +import com.sap.sailing.domain.common.tracking.GPSFixMoving; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher; +import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; +import com.sap.sailing.domain.leaderboard.ScoreCorrection.Result; +import com.sap.sailing.domain.leaderboard.SettableScoreCorrection; +import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; +import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCache; +import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache; +import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater; +import com.sap.sailing.domain.leaderboard.meta.MetaLeaderboardColumn; +import com.sap.sailing.domain.racelog.RaceLogIdentifier; +import com.sap.sailing.domain.ranking.RankingMetric.CompetitorRankingInfo; +import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; +import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.tracking.Maneuver; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.MarkPassingManeuver; +import com.sap.sailing.domain.tracking.RaceChangeListener; +import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; +import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache; +import com.sap.sailing.domain.tracking.WindPositionMode; +import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener; +import com.sap.sailing.util.impl.RaceColumnListeners; +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.concurrent.LockUtil; +import com.sap.sse.concurrent.NamedReentrantReadWriteLock; +import com.sap.sse.util.impl.ThreadFactoryWithPriority; + +/** + * Base implementation for various types of leaderboards. The {@link RaceColumnListener} implementation forwards events + * received to all {@link RaceColumnListener} subscribed with this leaderboard. To which objects this leaderboard subscribes + * as {@link RaceColumnListener} is left to the concrete subclasses to implement, but the race columns seem like useful + * candidates. + * + * @author Axel Uhl (d043530) + * + */ +public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, RaceColumnListener { + private static final long serialVersionUID = 330156778603279333L; + + private static final Logger logger = Logger.getLogger(AbstractSimpleLeaderboardImpl.class.getName()); + + static final Double DOUBLE_0 = new Double(0); + + private final SettableScoreCorrection scoreCorrection; + + private ThresholdBasedResultDiscardingRule crossLeaderboardResultDiscardingRule; + + /** + * The optional display name mappings for competitors. This allows a user to override the tracking-provided + * competitor names for display in a leaderboard. + */ + private final Map displayNames; + + /** the display name of the leaderboard */ + private String displayName; + + /** + * Backs the {@link #getCarriedPoints(Competitor)} API with data. Can be used to prime this leaderboard + * with aggregated results of races not tracked / displayed by this leaderboard in detail. The points + * provided by this map are considered by {@link #getTotalPoints(Competitor, TimePoint)}. + */ + private final Map carriedPoints; + + private final RaceColumnListeners raceColumnListeners; + + /** + * A set that manages the difference between {@link #getCompetitors()} and {@link #getAllCompetitors()}. Access + * is controlled by the {@link #suppressedCompetitorsLock} lock. + */ + private final Set suppressedCompetitors; + private final NamedReentrantReadWriteLock suppressedCompetitorsLock; + + private transient Map, RunnableFuture> raceDetailsAtEndOfTrackingCache; + + /** + * This executor needs to be a different one than {@link #executor} because the tasks run by {@link #executor} + * can depend on the results of the tasks run by {@link #raceDetailsExecutor}, and an {@link Executor} doesn't + * move a task that is blocked by waiting for another {@link FutureTask} to the side but blocks permanently, + * ending in a deadlock (one that cannot easily be detected by the Eclipse debugger either). + */ + private transient Executor raceDetailsExecutor; + + /** + * Used to remove all these listeners from their tracked races when this servlet is {@link #destroy() destroyed}. + */ + private transient Set cacheInvalidationListeners; + + private transient ThreadPoolExecutor executor; + + private transient LiveLeaderboardUpdater liveLeaderboardUpdater; + + private transient LeaderboardDTOCache leaderboardDTOCache; + + /** + * A leaderboard entry representing a snapshot of a cell at a given time point for a single race/competitor. + * + * @author Axel Uhl (d043530) + * + */ + class EntryImpl implements Entry { + private final Callable trackedRankProvider; + private final Double netPoints; + private final Callable netPointsUncorrectedProvider; + private final boolean isNetPointsCorrected; + private final Double totalPoints; + private final MaxPointsReason maxPointsReason; + private final boolean discarded; + private final Fleet fleet; + + private EntryImpl(Callable trackedRankProvider, Double netPoints, + Callable netPointsUncorrectedProvider, boolean isNetPointsCorrected, + Double totalPoints, MaxPointsReason maxPointsReason, boolean discarded, Fleet fleet) { + super(); + this.trackedRankProvider = trackedRankProvider; + this.netPoints = netPoints; + this.netPointsUncorrectedProvider = netPointsUncorrectedProvider; + this.isNetPointsCorrected = isNetPointsCorrected; + this.totalPoints = totalPoints; + this.maxPointsReason = maxPointsReason; + this.discarded = discarded; + this.fleet = fleet; + } + @Override + public int getTrackedRank() { + try { + return trackedRankProvider.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + @Override + public Double getNetPoints() { + return netPoints; + } + @Override + public boolean isNetPointsCorrected() { + return isNetPointsCorrected; + } + @Override + public Double getTotalPoints() { + return totalPoints; + } + @Override + public MaxPointsReason getMaxPointsReason() { + return maxPointsReason; + } + @Override + public boolean isDiscarded() { + return discarded; + } + @Override + public Fleet getFleet() { + return fleet; + } + @Override + public Double getNetPointsUncorrected() { + try { + return netPointsUncorrectedProvider.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + /** + * Computing the competitors can be a bit expensive, particularly if the fleet is large and there may be suppressed + * competitors, and the leaderboard may be a meta-leaderboard that refers to other leaderboards which each have + * several tracked races attached from where the competitors need to be retrieved. Ideally, the competitors list + * would be cached, but that is again difficult because we would have to monitor all changes in all dependent + * leaderboards and columns and tracked races properly. + *

+ * + * As it turns out, one of the most frequent uses of the {@link AbstractSimpleLeaderboardImpl#getCompetitors} + * competitors list is to determine their number which in turn is only required for high-point scoring systems and + * for computing the default score for penalties. Again, the most frequently used low-point family of scoring schemes + * does not require this number. Yet, the scoring scheme requires an argument for polymorphic use by those that + * need it. Instead of computing it for each call, this interface lets us defer the actual calculation until the + * point when it's really needed. Once asked, this object will cache the result. Therefore, a new one should be + * constructed each time the number shall be computed. + * + * @author Axel Uhl (D043530) + * + */ + public class NumberOfCompetitorsFetcherImpl implements NumberOfCompetitorsInLeaderboardFetcher { + private int numberOfCompetitors = -1; + + @Override + public int getNumberOfCompetitorsInLeaderboard() { + if (numberOfCompetitors == -1) { + numberOfCompetitors = Util.size(getCompetitors()); + } + return numberOfCompetitors; + } + } + + /** + * Handles the invalidation of the {@link SailingServiceImpl#raceDetailsAtEndOfTrackingCache} entries if the tracked + * race changes in any way. In particular, for {@link #statusChanged}, when the status changes away from LOADING, + * calculations may start or resume, making it necessary to clear the cache. + * + * @author Axel Uhl (D043530) + * + */ + private class CacheInvalidationListener extends AbstractRaceChangeListener { + private final TrackedRace trackedRace; + private final Competitor competitor; + + public CacheInvalidationListener(TrackedRace trackedRace, Competitor competitor) { + this.trackedRace = trackedRace; + this.competitor = competitor; + } + + public TrackedRace getTrackedRace() { + return trackedRace; + } + + public void removeFromTrackedRace() { + trackedRace.removeListener(this); + } + + private void invalidateCacheAndRemoveThisListenerFromTrackedRace() { + synchronized (raceDetailsAtEndOfTrackingCache) { + raceDetailsAtEndOfTrackingCache.remove(new com.sap.sse.common.Util.Pair(trackedRace, competitor)); + removeFromTrackedRace(); + } + } + + @Override + protected void defaultAction() { + invalidateCacheAndRemoveThisListenerFromTrackedRace(); + } + } + + private static class UUIDGenerator implements LeaderboardDTO.UUIDGenerator { + @Override + public String generateRandomUUID() { + return UUID.randomUUID().toString(); + } + } + + public AbstractSimpleLeaderboardImpl(ThresholdBasedResultDiscardingRule resultDiscardingRule) { + this.carriedPoints = new HashMap(); + this.scoreCorrection = createScoreCorrection(); + this.displayNames = new HashMap(); + this.crossLeaderboardResultDiscardingRule = resultDiscardingRule; + this.suppressedCompetitors = new HashSet(); + this.suppressedCompetitorsLock = new NamedReentrantReadWriteLock("suppressedCompetitorsLock", /* fair */ false); + this.raceColumnListeners = new RaceColumnListeners(); + this.raceDetailsAtEndOfTrackingCache = new HashMap, RunnableFuture>(); + initTransientFields(); + } + + /** + * Produces the score correction object to use in this leaderboard. Used by the constructor. Subclasses may override + * this method to create a more specific type of score correction. This implementation produces an object of type + * {@link ScoreCorrectionImpl}. + */ + protected SettableScoreCorrection createScoreCorrection() { + return new ScoreCorrectionImpl(this); + } + + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + initTransientFields(); + } + + private void initTransientFields() { + this.raceDetailsAtEndOfTrackingCache = new HashMap, RunnableFuture>(); + this.raceDetailsExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + this.cacheInvalidationListeners = new HashSet(); + // When many updates are triggered in a short period of time by a single thread, ensure that the single thread + // providing the updates is not outperformed by all the re-calculations happening here. Leave at least one + // core to other things, but by using at least three threads ensure that no simplistic deadlocks may occur. + final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3); + executor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE, + /* maximumPoolSize */ THREAD_POOL_SIZE, + /* keepAliveTime */ 60, TimeUnit.SECONDS, + /* workQueue */ new LinkedBlockingQueue(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY-1, /* daemon */ true)); + } + + @Override + public void destroy() { + for (CacheInvalidationListener cacheInvalidationListener : cacheInvalidationListeners) { + cacheInvalidationListener.removeFromTrackedRace(); + } + } + + @Override + public SettableScoreCorrection getScoreCorrection() { + return scoreCorrection; + } + + @Override + public String getDisplayName(Competitor competitor) { + return displayNames.get(competitor); + } + + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + @Override + public ResultDiscardingRule getResultDiscardingRule() { + return crossLeaderboardResultDiscardingRule; + } + + @Override + public void setCarriedPoints(Competitor competitor, double carriedPoints) { + Double oldCarriedPoints = this.carriedPoints.put(competitor, carriedPoints); + if (!Util.equalsWithNull(oldCarriedPoints, carriedPoints)) { + getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, carriedPoints); + } + } + + @Override + public double getCarriedPoints(Competitor competitor) { + Double result = carriedPoints.get(competitor); + return result == null ? 0 : result; + } + + @Override + public Map getCompetitorsForWhichThereAreCarriedPoints() { + return Collections.unmodifiableMap(carriedPoints); + } + + @Override + public void unsetCarriedPoints(Competitor competitor) { + Double oldCarriedPoints = carriedPoints.remove(competitor); + if (oldCarriedPoints != null) { + getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, null); + } + } + + @Override + public boolean hasCarriedPoints() { + return !carriedPoints.isEmpty(); + } + + @Override + public boolean hasCarriedPoints(Competitor competitor) { + return carriedPoints.containsKey(competitor); + } + + @Override + public void setDisplayName(Competitor competitor, String displayName) { + String oldDisplayName = displayNames.get(competitor); + displayNames.put(competitor, displayName); + if (!Util.equalsWithNull(oldDisplayName, displayName)) { + getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName); + } + } + + @Override + public void setCrossLeaderboardResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule) { + ResultDiscardingRule oldDiscardingRule = getResultDiscardingRule(); + this.crossLeaderboardResultDiscardingRule = discardingRule; + getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, discardingRule); + } + + @Override + public Double getNetPoints(final Competitor competitor, final RaceColumn raceColumn, final TimePoint timePoint) { + return getScoreCorrection().getCorrectedScore( + new Callable() { + public Integer call() throws NoWindException { + return getTrackedRank(competitor, raceColumn, timePoint); + } + }, competitor, + raceColumn, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()).getCorrectedScore(); + } + + @Override + public MaxPointsReason getMaxPointsReason(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { + return getScoreCorrection().getMaxPointsReason(competitor, raceColumn, timePoint); + } + + @Override + public boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { + return isDiscarded(competitor, raceColumn, getRaceColumns(), timePoint); + } + + private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, + Iterable raceColumnsToConsider, TimePoint timePoint) { + final Set discardedRaceColumns = getResultDiscardingRule() + .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); + return isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns); + } + + /** + * Same as {@link #isDiscarded(Competitor, RaceColumn, TimePoint)}, only that the set of discarded race columns can + * be specified which is useful when total points are to be computed for more than one column for the same + * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done + * once and not again for each column (which would lead to quadratic effort). + * + * @param discardedRaceColumns + * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}. + * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint) + * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}. + */ + private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, + final Set discardedRaceColumns) { + return !raceColumn.isMedalRace() + && getMaxPointsReason(competitor, raceColumn, timePoint).isDiscardable() + && discardedRaceColumns.contains(raceColumn); + } + + @Override + public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { + return getTotalPoints(competitor, raceColumn, getRaceColumns(), timePoint); + } + + @Override + public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, + Iterable raceColumnsToConsider, TimePoint timePoint) throws NoWindException { + final Set discardedRaceColumns = getResultDiscardingRule() + .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); + return getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns); + } + + /** + * Same as {@link #getTotalPoints(Competitor, RaceColumn, Iterable, TimePoint)}, only that the set of discarded race columns can + * be specified which is useful when total points are to be computed for more than one column for the same + * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done + * once and not again for each column (which would lead to quadratic effort). + * + * @param discardedRaceColumns + * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}. + * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint) + * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}. + */ + @Override + public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, Set discardedRaceColumns) { + Double result; + if (isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns)) { + result = 0.0; + } else { + final Double netPoints = getNetPoints(competitor, raceColumn, timePoint); + if (netPoints == null) { + result = null; + } else { + result = raceColumn.getFactor() * netPoints; + } + } + return result; + } + + @Override + public Double getTotalPoints(Competitor competitor, TimePoint timePoint) { + return getTotalPoints(competitor, getRaceColumns(), timePoint); + } + + @Override + public Double getTotalPoints(Competitor competitor, final Iterable raceColumnsToConsider, + TimePoint timePoint) { + // when a column with isStartsWithZeroScore() is found, only reset score if the competitor scored in any race from there on + boolean needToResetScoreUponNextNonEmptyEntry = false; + double result = getCarriedPoints(competitor); + final Set discardedRaceColumns = getResultDiscardingRule() + .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); + for (RaceColumn raceColumn : raceColumnsToConsider) { + if (raceColumn.isStartsWithZeroScore()) { + needToResetScoreUponNextNonEmptyEntry = true; + } + if (getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint)) { + final Double totalPoints = getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns); + if (totalPoints != null) { + if (needToResetScoreUponNextNonEmptyEntry) { + result = 0; + needToResetScoreUponNextNonEmptyEntry = false; + } + result += totalPoints; + } + } + } + return result; + } + + /** + * All competitors with non-null net points are added to the result which is then sorted by net points in ascending + * order. The fleet, if ordered, is the primary ordering criterion, followed by the net points. + */ + @Override + public List getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { + final Map> netPointsAndFleet = new HashMap>(); + for (Competitor competitor : getCompetitors()) { + Double netPoints = getNetPoints(competitor, raceColumn, timePoint); + if (netPoints != null) { + netPointsAndFleet.put(competitor, new com.sap.sse.common.Util.Pair(netPoints, raceColumn.getFleetOfCompetitor(competitor))); + } + } + List result = new ArrayList(netPointsAndFleet.keySet()); + Collections.sort(result, new Comparator() { + @Override + public int compare(Competitor o1, Competitor o2) { + int comparisonResult; + if (o1 == o2) { + comparisonResult = 0; + } else { + if (raceColumn.hasSplitFleets() && !raceColumn.hasSplitFleetContiguousScoring()) { + // only check fleets if there are more than one and the column is not to be contiguously scored even in case + // of split fleets + final Fleet o1Fleet = netPointsAndFleet.get(o1).getB(); + final Fleet o2Fleet = netPointsAndFleet.get(o2).getB(); + if (o1Fleet == null) { + if (o2Fleet == null) { + comparisonResult = 0; + } else { + comparisonResult = 1; // o1 ranks "worse" because it doesn't have a fleet set while o2 + // has + } + } else { + if (o2Fleet == null) { + comparisonResult = -1; // o1 ranks "better" because it has a fleet set while o2 hasn't + } else { + comparisonResult = o1Fleet.compareTo(o2Fleet); + } + } + } else { + // either there are no split fleets or the split isn't relevant for scoring as for ordered fleets + // the scoring runs contiguously from top to bottom + comparisonResult = 0; + } + if (comparisonResult == 0) { + comparisonResult = getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare( + netPointsAndFleet.get(o1).getA(), netPointsAndFleet.get(o2).getA()); + } + } + return comparisonResult; + } + }); + return result; + } + + @Override + public List getCompetitorsFromBestToWorst(TimePoint timePoint) { + return getCompetitorsFromBestToWorst(getRaceColumns(), timePoint); + } + + private List getCompetitorsFromBestToWorst(Iterable raceColumnsToConsider, TimePoint timePoint) { + List result = new ArrayList(); + for (Competitor competitor : getCompetitors()) { + result.add(competitor); + } + Collections.sort(result, getTotalRankComparator(raceColumnsToConsider, timePoint)); + return result; + } + + @Override + public int getTotalRankOfCompetitor(Competitor competitor, TimePoint timePoint) throws NoWindException { + List competitorsFromBestToWorst = getCompetitorsFromBestToWorst(timePoint); + return competitorsFromBestToWorst.indexOf(competitor) + 1; + } + + protected Comparator getTotalRankComparator(Iterable raceColumnsToConsider, TimePoint timePoint) { + return new LeaderboardTotalRankComparator(this, timePoint, getScoringScheme(), /* nullScoresAreBetter */ false, raceColumnsToConsider); + } + + @Override + public RaceColumn getRaceColumnByName(String columnName) { + RaceColumn result = null; + for (RaceColumn r : getRaceColumns()) { + if (r.getName().equals(columnName)) { + result = r; + break; + } + } + return result; + } + + @Override + public Competitor getCompetitorByName(String competitorName) { + for (Competitor competitor : getAllCompetitors()) { + if (competitor.getName().equals(competitorName)) { + return competitor; + } + } + return null; + } + + @Override + public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { + TrackedRace trackedRaceForCompetitorInColumn; + return getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint) && + (getScoreCorrection().isScoreCorrected(competitor, raceColumn, timePoint) || + ((trackedRaceForCompetitorInColumn=raceColumn.getTrackedRace(competitor)) != null && + trackedRaceForCompetitorInColumn.hasStarted(timePoint) && + trackedRaceForCompetitorInColumn.getRank(competitor, timePoint) != 0)); + } + + @Override + public void addRaceColumnListener(RaceColumnListener listener) { + getRaceColumnListeners().addRaceColumnListener(listener); + } + + @Override + public void removeRaceColumnListener(RaceColumnListener listener) { + getRaceColumnListeners().removeRaceColumnListener(listener); + } + + @Override + public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint) throws NoWindException { + final Set discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); + return getEntry(competitor, race, timePoint, discardedRaceColumns); + } + + @Override + public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint, + Set discardedRaceColumns) throws NoWindException { + Callable trackedRankProvider = new Callable() { + public Integer call() throws NoWindException { + return getTrackedRank(competitor, race, timePoint); + } + }; + final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, race, + timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); + boolean discarded = isDiscarded(competitor, race, timePoint, discardedRaceColumns); + final Double correctedScore = correctedResults.getCorrectedScore(); + return new EntryImpl(trackedRankProvider, correctedScore, new Callable() { + @Override + public Double call() { + return correctedResults.getUncorrectedScore(); + } + }, correctedResults.isCorrected(), discarded ? DOUBLE_0 : correctedScore == null ? null + : Double.valueOf(correctedScore * race.getFactor()), correctedResults.getMaxPointsReason(), discarded, + race.getFleetOfCompetitor(competitor)); + } + + @Override + public Map> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint) throws NoWindException { + Map> result = new LinkedHashMap<>(); + List raceColumnsToConsider = new ArrayList<>(); + for (RaceColumn raceColumn : getRaceColumns()) { + raceColumnsToConsider.add(raceColumn); + result.put(raceColumn, getCompetitorsFromBestToWorst(raceColumnsToConsider, timePoint)); + } + return result; + } + + @Override + public Map> getTotalPointsSumAfterRaceColumn(final TimePoint timePoint) + throws NoWindException { + final Map> result = new LinkedHashMap<>(); + List raceColumnsToConsider = new ArrayList<>(); + Map>> futures = new HashMap<>(); + for (final RaceColumn raceColumn : getRaceColumns()) { + raceColumnsToConsider.add(raceColumn); + final Iterable finalRaceColumnsToConsider = new ArrayList<>(raceColumnsToConsider); + futures.put(raceColumn, executor.submit(new Callable>() { + @Override + public Map call() { + Map totalPointsSumPerCompetitorInColumn = new HashMap<>(); + for (Competitor competitor : getCompetitors()) { + totalPointsSumPerCompetitorInColumn.put(competitor, getTotalPoints(competitor, finalRaceColumnsToConsider, timePoint)); + } + synchronized (result) { + return totalPointsSumPerCompetitorInColumn; + } + } + })); + } + for (RaceColumn raceColumn : getRaceColumns()) { + try { + result.put(raceColumn, futures.get(raceColumn).get()); + } catch (InterruptedException | ExecutionException e) { + if (e.getCause() instanceof NoWindError) { + throw ((NoWindError) e.getCause()).getCause(); + } else { + throw new RuntimeException(e); // no caught exceptions occur in the futures executed + } + } + } + return result; + } + + @Override + public Map, Entry> getContent(final TimePoint timePoint) throws NoWindException { + Map, Entry> result = new HashMap, Entry>(); + Map> discardedRaces = new HashMap>(); + for (final RaceColumn raceColumn : getRaceColumns()) { + for (final Competitor competitor : getCompetitors()) { + Callable trackedRankProvider = new Callable() { + @Override + public Integer call() throws Exception { + return getTrackedRank(competitor, raceColumn, timePoint); + } + }; + final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, raceColumn, + timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); + Set discardedRacesForCompetitor = discardedRaces.get(competitor); + if (discardedRacesForCompetitor == null) { + discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); + discardedRaces.put(competitor, discardedRacesForCompetitor); + } + boolean discarded = discardedRacesForCompetitor.contains(raceColumn); + final Double correctedScore = correctedResults.getCorrectedScore(); + Entry entry = new EntryImpl(trackedRankProvider, correctedScore, + new Callable() { @Override public Double call() { return correctedResults.getUncorrectedScore(); } }, + correctedResults.isCorrected(), + discarded ? DOUBLE_0 : (correctedScore==null?null: + Double.valueOf((correctedScore * raceColumn.getFactor()))), correctedResults.getMaxPointsReason(), + discarded, raceColumn.getFleetOfCompetitor(competitor)); + result.put(new com.sap.sse.common.Util.Pair(competitor, raceColumn), entry); + } + } + return result; + } + + @Override + public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { + getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace); + } + + @Override + public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { + getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace); + // It's generally possible that a leaderboard links to the same tracked race in multiple columns / fleets; + // only if it no longer references the trackedRace currently unlinked from one column/fleet, also unlink + // all cache invalidation listeners for said trackedRace + if (!Util.contains(getTrackedRaces(), trackedRace)) { + synchronized (cacheInvalidationListeners) { + for (Iterator cacheInvalidationListenerIter=cacheInvalidationListeners.iterator(); + cacheInvalidationListenerIter.hasNext(); ) { + CacheInvalidationListener cacheInvalidationListener = cacheInvalidationListenerIter.next(); + if (cacheInvalidationListener.getTrackedRace() == trackedRace) { + cacheInvalidationListener.removeFromTrackedRace(); + cacheInvalidationListenerIter.remove(); + } + } + } + } + } + + @Override + public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { + getRaceColumnListeners().notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace); + } + + @Override + public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { + getRaceColumnListeners().notifyListenersAboutIsStartsWithZeroScoreChanged(raceColumn, newIsStartsWithZeroScore); + } + + @Override + public void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, boolean firstColumnIsNonDiscardableCarryForward) { + getRaceColumnListeners().notifyListenersAboutIsFirstColumnIsNonDiscardableCarryForwardChanged(raceColumn, firstColumnIsNonDiscardableCarryForward); + } + + @Override + public void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) { + getRaceColumnListeners().notifyListenersAboutHasSplitFleetContiguousScoringChanged(raceColumn, hasSplitFleetContiguousScoring); + } + + @Override + public void raceColumnMoved(RaceColumn raceColumn, int newIndex) { + getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(raceColumn, newIndex); + } + + @Override + public void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) { + getRaceColumnListeners().notifyListenersAboutFactorChanged(raceColumn, oldFactor, newFactor); + } + + /** + * A leaderboard will only accept the addition of a race column if the column's name is unique across the leaderboard. + */ + @Override + public boolean canAddRaceColumnToContainer(RaceColumn newRaceColumn) { + boolean result = true; + for (RaceColumn raceColumn : getRaceColumns()) { + if (raceColumn.getName().equals(newRaceColumn.getName())) { + result = false; + break; + } + } + return result; + } + + @Override + public void raceColumnAddedToContainer(RaceColumn raceColumn) { + getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(raceColumn); + } + + @Override + public void raceColumnRemovedFromContainer(RaceColumn raceColumn) { + getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn); + } + + @Override + public void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) { + getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName); + } + + @Override + public void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule, ResultDiscardingRule newDiscardingRule) { + getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, newDiscardingRule); + } + + @Override + public boolean isTransient() { + return false; + } + + /** + * Finds out the time point when any of the {@link Leaderboard#getTrackedRaces() tracked races currently attached to + * the leaderboard} and the {@link Leaderboard#getScoreCorrection() score corrections} have last been + * modified. If no tracked race is attached and no time-stamped score corrections have been applied to the leaderboard, + * null is returned. The time point computed this way is a good choice for normalizing queries for later time + * points in an attempt to achieve more cache hits.

+ * + * Note, however, that the result does not tell about structural changes to the leaderboard and therefore cannot be used + * to determine the need for cache invalidation. For example, if a column is added to a leaderboard after the time point + * returned by this method but that column's attached tracked race has finished before the time point returned by this method, + * the result of this method won't change. Still, the contents of the leaderboard will change by a change in column structure. + * A different means to determine the possibility of changes that happened to this leaderboard must be used for cache + * management. Such a facility has to listen for score correction changes, tracked races being attached or detached and + * the column structure changing. + * + * @see TrackedRace#getTimePointOfNewestEvent() + * @see SettableScoreCorrection#getTimePointOfLastCorrectionsValidity() + */ + @Override + public TimePoint getTimePointOfLatestModification() { + TimePoint result = null; + for (TrackedRace trackedRace : getTrackedRaces()) { + if (result == null || (trackedRace.getTimePointOfNewestEvent() != null && trackedRace.getTimePointOfNewestEvent().after(result))) { + result = trackedRace.getTimePointOfNewestEvent(); + } + } + TimePoint timePointOfLastScoreCorrection = getScoreCorrection().getTimePointOfLastCorrectionsValidity(); + if (timePointOfLastScoreCorrection != null && (result == null || timePointOfLastScoreCorrection.after(result))) { + result = timePointOfLastScoreCorrection; + } + return result; + } + + @Override + public com.sap.sse.common.Util.Pair getMaximumSpeedOverGround(Competitor competitor, TimePoint timePoint) { + com.sap.sse.common.Util.Pair result = null; + // TODO should we ensure that competitor participated in all race columns? + for (TrackedRace trackedRace : getTrackedRaces()) { + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { + NavigableSet markPassings = trackedRace.getMarkPassings(competitor); + if (!markPassings.isEmpty()) { + TimePoint from = markPassings.first().getTimePoint(); + TimePoint to; + if (timePoint.after(markPassings.last().getTimePoint()) && + markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) { + // stop counting when competitor finished the race + to = markPassings.last().getTimePoint(); + } else { + to = timePoint; + } + com.sap.sse.common.Util.Pair maxSpeed = trackedRace.getTrack(competitor).getMaximumSpeedOverGround(from, to); + if (result == null || result.getB() == null || + (maxSpeed != null && maxSpeed.getB() != null && maxSpeed.getB().compareTo(result.getB()) > 0)) { + result = maxSpeed; + } + } + } + } + return result; + } + + @Override + public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) { + Speed result = null; + for (TrackedRace trackedRace : getTrackedRaces()) { + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { + NavigableSet markPassings = trackedRace.getMarkPassings(competitor); + if (!markPassings.isEmpty()) { + TimePoint from = markPassings.first().getTimePoint(); + TimePoint to; + if (timePoint.after(markPassings.last().getTimePoint()) && + markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) { + // stop counting when competitor finished the race + to = markPassings.last().getTimePoint(); + } else { + if (markPassings.last().getWaypoint() != trackedRace.getRace().getCourse().getLastWaypoint() && + timePoint.after(markPassings.last().getTimePoint())) { + result = null; + break; + } + to = timePoint; + } + Distance distanceTraveled = trackedRace.getDistanceTraveled(competitor, timePoint); + if (distanceTraveled != null) { + result = distanceTraveled.inTime(to.asMillis()-from.asMillis()); + } + } + } + } + return result; + } + + @Override + public Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint) throws NoWindException { + return getTotalTimeSailedInLegType(competitor, legType, timePoint, new HashMap()); + } + + private Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint, Map legTypeCache) throws NoWindException { + Duration result = null; + // TODO should we ensure that competitor participated in all race columns? + outerLoop: + for (TrackedRace trackedRace : getTrackedRaces()) { + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { + trackedRace.getRace().getCourse().lockForRead(); + try { + for (Leg leg : trackedRace.getRace().getCourse().getLegs()) { + TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, leg); + if (trackedLegOfCompetitor.hasStartedLeg(timePoint)) { + // find out leg type at the time the competitor started the leg + try { + final TrackedLeg trackedLeg = trackedRace.getTrackedLeg(leg); + LegType trackedLegType = legTypeCache.get(trackedLeg); + if (trackedLegType == null) { + final TimePoint startTime = trackedLegOfCompetitor.getStartTime(); + TimePoint finishTime = trackedLegOfCompetitor.getFinishTime(); + if (finishTime == null) { + finishTime = timePoint; + } + trackedLegType = trackedLeg.getLegType(startTime.plus(startTime.until(finishTime).divide(2))); // middle of the leg + legTypeCache.put(trackedLeg, trackedLegType); + } + if (legType == trackedLegType) { + Duration timeSpentOnDownwind = trackedLegOfCompetitor.getTime(timePoint); + if (timeSpentOnDownwind != null) { + if (result == null) { + result = timeSpentOnDownwind; + } else { + result = result.plus(timeSpentOnDownwind); + } + } else { + // Although the competitor has started the leg, no value was produced. This + // means that the competitor didn't finish the leg before tracking ended. No useful value + // can be obtained for this competitor anymore. + result = null; + break outerLoop; + } + } + } catch (NoWindException nwe) { + // without wind there is no leg type and hence there is no reasonable value for this: + result = null; + break outerLoop; + } + } + } + } finally { + trackedRace.getRace().getCourse().unlockAfterRead(); + } + } + } + return result; + } + + @Override + public Duration getTotalTimeSailed(Competitor competitor, TimePoint timePoint) { + Duration result = null; + for (TrackedRace trackedRace : getTrackedRaces()) { + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { + NavigableSet markPassings = trackedRace.getMarkPassings(competitor); + if (!markPassings.isEmpty()) { + TimePoint from = trackedRace.getStartOfRace(); // start counting at race start, not when the competitor passed the line + if (from != null && !timePoint.before(from)) { // but only if the race started after timePoint + TimePoint to; + if (timePoint.after(markPassings.last().getTimePoint()) + && markPassings.last().getWaypoint() == trackedRace.getRace().getCourse() + .getLastWaypoint()) { + // stop counting when competitor finished the race + to = markPassings.last().getTimePoint(); + } else { + if (trackedRace.getEndOfTracking() != null + && timePoint.after(trackedRace.getEndOfTracking())) { + result = null; // race not finished until end of tracking; no reasonable value can be + // computed for competitor + break; + } else { + to = timePoint; + } + } + Duration timeSpent = from.until(to); + if (result == null) { + result = timeSpent; + } else { + result=result.plus(timeSpent); + } + } + } + } + } + return result; + } + + @Override + public Distance getTotalDistanceTraveled(Competitor competitor, TimePoint timePoint) { + Distance result = null; + for (TrackedRace trackedRace : getTrackedRaces()) { + TimePoint startOfRace; + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor) && + (startOfRace=trackedRace.getStartOfRace()) != null && + !startOfRace.after(timePoint)) { + Distance distanceSailedInRace = trackedRace.getDistanceTraveled(competitor, timePoint); + if (distanceSailedInRace != null) { + if (result == null) { + result = distanceSailedInRace; + } else { + result = result.add(distanceSailedInRace); + } + } else { + // if competitor has not finished one single race in the whole + // series then we can not return a meaningful value for all + // all races + return null; + } + } + } + return result; + } + + protected RaceColumnListeners getRaceColumnListeners() { + return raceColumnListeners; + } + + @Override + public Iterable getCompetitors() { + final Iterable result; + // mostly the set of suppressed competitors is empty; in this case, avoid having to loop over the + // potentially large set of competitors + if (Util.isEmpty(getSuppressedCompetitors())) { + result = getAllCompetitors(); + } else { + final Iterable allCompetitors = getAllCompetitors(); + final Set suppressed = new HashSet<>(); + Util.addAll(getSuppressedCompetitors(), suppressed); + result = getCompetitorIterableSkippingSuppressed(allCompetitors, suppressed); + } + return result; + } + + @Override + public Iterable getCompetitors(RaceColumn raceColumn, Fleet fleet) { + return getCompetitorIterableSkippingSuppressed(getAllCompetitors(raceColumn, fleet), getSuppressedCompetitors()); + } + + /** + * return an iterable with a smart iterator that filters out the suppressed elements on demand + */ + protected Iterable getCompetitorIterableSkippingSuppressed(final Iterable allCompetitors, + final Iterable suppressed) { + final Iterable result; + result = new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + private final Iterator allIter = allCompetitors.iterator(); + private Competitor next = advance(); + + private Competitor advance() { + next = null; + while (allIter.hasNext() && next == null) { + next = allIter.next(); + if (Util.contains(suppressed, next)) { + next = null; + } + } + return next; + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public Competitor next() { + if (next == null) { + throw new NoSuchElementException(); + } + final Competitor result = next; + advance(); + return result; + } + }; + } + }; + return result; + } + + @Override + public Iterable getSuppressedCompetitors() { + LockUtil.lockForRead(suppressedCompetitorsLock); + try { + return new HashSet(suppressedCompetitors); + } finally { + LockUtil.unlockAfterRead(suppressedCompetitorsLock); + } + } + + @Override + public void setSuppressed(Competitor competitor, boolean suppressed) { + LockUtil.lockForWrite(suppressedCompetitorsLock); + try { + if (suppressed) { + suppressedCompetitors.add(competitor); + } else { + suppressedCompetitors.remove(competitor); + } + } finally { + LockUtil.unlockAfterWrite(suppressedCompetitorsLock); + } + getScoreCorrection().notifyListenersAboutIsSuppressedChange(competitor, suppressed); + } + + @Override + public TimePoint getNowMinusDelay() { + final TimePoint now = MillisecondsTimePoint.now(); + final Long delayToLiveInMillis = getDelayToLiveInMillis(); + TimePoint timePoint = delayToLiveInMillis == null ? now : now.minus(delayToLiveInMillis); + return timePoint; + } + + @Override + public void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) { + getRaceColumnListeners().notifyListenersAboutRaceLogEventAdded(raceColumn, raceLogIdentifier, event); + if (event instanceof InvalidatesLeaderboardCache) { + // make sure to invalidate the cache as this event indicates that + // it changes values the cache could still hold + if (leaderboardDTOCache != null) { + leaderboardDTOCache.invalidate(this); + } + } + } + + @Override + public LeaderboardDTO computeDTO(final TimePoint timePoint, + final Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, + final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory, + final boolean fillNetPointsUncorrected) + throws NoWindException { + long startOfRequestHandling = System.currentTimeMillis(); + final LeaderboardDTOCalculationReuseCache cache = new LeaderboardDTOCalculationReuseCache(timePoint); + final LeaderboardDTO result = new LeaderboardDTO(this.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null ? null + : this.getScoreCorrection().getTimePointOfLastCorrectionsValidity().asDate(), + this.getScoreCorrection() == null ? null : this.getScoreCorrection().getComment(), + this.getScoringScheme() == null ? null : this.getScoringScheme().getType(), this + .getScoringScheme().isHigherBetter(), new UUIDGenerator(), addOverallDetails); + result.type = getLeaderboardType(); + result.competitors = new ArrayList(); + result.name = this.getName(); + result.competitorDisplayNames = new HashMap(); + for (Competitor suppressedCompetitor : this.getSuppressedCompetitors()) { + result.setSuppressed(baseDomainFactory.convertToCompetitorDTO(suppressedCompetitor), true); + } + // Now create the race columns and, as a future task, set their competitorsFromBestToWorst, then wait for all these + // futures to finish: + Map>> competitorsFromBestToWorstTasks = new HashMap<>(); + for (final RaceColumn raceColumn : this.getRaceColumns()) { + boolean isMetaLeaderboardColumn = raceColumn instanceof MetaLeaderboardColumn; + RaceColumnDTO raceColumnDTO = result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(), + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, + isMetaLeaderboardColumn); + if (isMetaLeaderboardColumn && raceColumnDTO instanceof MetaLeaderboardRaceColumnDTO) { + calculateRacesMetadata((MetaLeaderboardColumn) raceColumn, (MetaLeaderboardRaceColumnDTO) raceColumnDTO, + trackedRegattaRegistry, baseDomainFactory); + } + for (Fleet fleet : raceColumn.getFleets()) { + RegattaAndRaceIdentifier raceIdentifier = null; + RaceDTO race = null; + TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); + final FleetDTO fleetDTO = baseDomainFactory.convertToFleetDTO(fleet); + if (trackedRace != null) { + raceIdentifier = new RegattaNameAndRaceName(trackedRace.getTrackedRegatta().getRegatta().getName(), + trackedRace.getRace().getName()); + race = baseDomainFactory.createRaceDTO(trackedRegattaRegistry, /* withGeoLocationData */ false, raceIdentifier, trackedRace); + } + // Note: the RaceColumnDTO won't be created by the following addRace call because it has been created + // above by the result.createEmptyRaceColumn call + result.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(), + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, + fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race, isMetaLeaderboardColumn); + } + FutureTask> task = new FutureTask>( + () -> baseDomainFactory.getCompetitorDTOList(AbstractSimpleLeaderboardImpl.this.getCompetitorsFromBestToWorst(raceColumn, timePoint))); + executor.execute(task); + competitorsFromBestToWorstTasks.put(raceColumn, task); + } + // wait for the competitor orderings to have been computed for all race columns before continuing; subsequent tasks may depend on these data + for (Map.Entry>> raceColumnAndTaskToJoin : competitorsFromBestToWorstTasks.entrySet()) { + try { + result.setCompetitorsFromBestToWorst(raceColumnAndTaskToJoin.getKey().getName(), raceColumnAndTaskToJoin.getValue().get()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept + // null values. + // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So + // this helps robustness from a user's perspective. + logger.log( + Level.SEVERE, + AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", " + + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails + + "): exception during computing competitor ordering for race column "+raceColumnAndTaskToJoin.getKey().getName(), e); + } + } + result.setDelayToLiveInMillisForLatestRace(this.getDelayToLiveInMillis()); + result.rows = new HashMap(); + result.hasCarriedPoints = this.hasCarriedPoints(); + if (this.getResultDiscardingRule() instanceof ThresholdBasedResultDiscardingRule) { + result.discardThresholds = ((ThresholdBasedResultDiscardingRule) this.getResultDiscardingRule()) + .getDiscardIndexResultsStartingWithHowManyRaces(); + } else { + result.discardThresholds = null; + } + // Computing the competitor leg ranks is expensive, especially in live mode, in case new events keep + // invalidating the ranks cache in TrackedLegImpl. The problem then is that the sorting based on wind data is repeated for + // each competitor, leading to square effort. We therefore need to compute the leg ranks for those races where leg + // details are requested only once and pass them into getLeaderboardEntryDTO + final Map> legRanksCache = new HashMap>(); + for (final RaceColumn raceColumn : this.getRaceColumns()) { + // if details for the column are requested, cache the leg's ranks + if (namesOfRaceColumnsForWhichToLoadLegDetails != null + && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn.getName())) { + for (Fleet fleet : raceColumn.getFleets()) { + TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); + if (trackedRace != null) { + trackedRace.getRace().getCourse().lockForRead(); + try { + for (TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) { + legRanksCache.put(trackedLeg.getLeg(), trackedLeg.getRanks(timePoint, cache)); + } + } finally { + trackedRace.getRace().getCourse().unlockAfterRead(); + } + } + } + } + } + for (final Competitor competitor : this.getCompetitorsFromBestToWorst(timePoint)) { + CompetitorDTO competitorDTO = baseDomainFactory.convertToCompetitorDTO(competitor); + LeaderboardRowDTO row = new LeaderboardRowDTO(); + row.competitor = competitorDTO; + row.fieldsByRaceColumnName = new HashMap(); + row.carriedPoints = this.hasCarriedPoints(competitor) ? this.getCarriedPoints(competitor) : null; + row.totalPoints = this.getTotalPoints(competitor, timePoint); + if (addOverallDetails) { + addOverallDetailsToRow(timePoint, competitor, row); + } + result.competitors.add(competitorDTO); + Map> futuresForColumnName = new HashMap>(); + final Set discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); + for (final RaceColumn raceColumn : this.getRaceColumns()) { + RunnableFuture future = new FutureTask(() -> { + Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint, discardedRaceColumns); + return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint, + namesOfRaceColumnsForWhichToLoadLegDetails != null + && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn + .getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory, + fillNetPointsUncorrected, cache); + }); + executor.execute(future); + futuresForColumnName.put(raceColumn.getName(), future); + } + for (Map.Entry> raceColumnNameAndFuture : futuresForColumnName.entrySet()) { + try { + row.fieldsByRaceColumnName.put(raceColumnNameAndFuture.getKey(), raceColumnNameAndFuture.getValue().get()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept + // null values. + // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So + // this helps robustness from a user's perspective. + logger.log( + Level.SEVERE, + AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", " + + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails + + "): exception during computing leaderboard entry for competitor " + + competitor.getName() + " in race column " + raceColumnNameAndFuture.getKey() + + ". Leaving empty.", e); + } + } + result.rows.put(competitorDTO, row); + String displayName = this.getDisplayName(competitor); + if (displayName != null) { + result.competitorDisplayNames.put(competitorDTO, displayName); + } + } + logger.info("computeLeaderboardByName(" + this.getName() + ", " + timePoint + ", " + + namesOfRaceColumnsForWhichToLoadLegDetails + ", addOverallDetails=" + addOverallDetails + ") took " + + (System.currentTimeMillis() - startOfRequestHandling) + "ms"); + return result; + } + + private void calculateRacesMetadata(MetaLeaderboardColumn metaLeaderboardColumn, MetaLeaderboardRaceColumnDTO columnDTO, + TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory) { + for (final RaceColumn raceColumn : metaLeaderboardColumn.getLeaderboard().getRaceColumns()) { + for (Fleet fleet : raceColumn.getFleets()) { + TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); + if (trackedRace != null) { + String regattaName = trackedRace.getTrackedRegatta().getRegatta().getName(); + String raceName = trackedRace.getRace().getName(); + RegattaAndRaceIdentifier raceIdentifier = new RegattaNameAndRaceName(regattaName, raceName); + columnDTO.addRace(new BasicRaceDTO(raceIdentifier, baseDomainFactory.createTrackedRaceDTO(trackedRace))); + } + } + } + } + + private void addOverallDetailsToRow(final TimePoint timePoint, + final Competitor competitor, LeaderboardRowDTO row) throws NoWindException { + final com.sap.sse.common.Util.Pair maximumSpeedOverGround = this.getMaximumSpeedOverGround(competitor, timePoint); + if (maximumSpeedOverGround != null && maximumSpeedOverGround.getB() != null) { + row.maximumSpeedOverGroundInKnots = maximumSpeedOverGround.getB().getKnots(); + row.whenMaximumSpeedOverGroundWasAchieved = maximumSpeedOverGround.getA().getTimePoint().asDate(); + } + Map legTypeCache = new HashMap<>(); + final Duration totalTimeSailedDownwind = this.getTotalTimeSailedInLegType(competitor, LegType.DOWNWIND, timePoint, legTypeCache); + row.totalTimeSailedDownwindInSeconds = totalTimeSailedDownwind==null?null:totalTimeSailedDownwind.asSeconds(); + final Duration totalTimeSailedUpwind = this.getTotalTimeSailedInLegType(competitor, LegType.UPWIND, timePoint, legTypeCache); + row.totalTimeSailedUpwindInSeconds = totalTimeSailedUpwind==null?null:totalTimeSailedUpwind.asSeconds(); + final Duration totalTimeSailedReaching = this.getTotalTimeSailedInLegType(competitor, LegType.REACHING, timePoint, legTypeCache); + row.totalTimeSailedReachingInSeconds = totalTimeSailedReaching==null?null:totalTimeSailedReaching.asSeconds(); + final Duration totalTimeSailed = this.getTotalTimeSailed(competitor, timePoint); + row.totalTimeSailedInSeconds = totalTimeSailed==null?null:totalTimeSailed.asSeconds(); + final Distance totalDistanceTraveledInMeters = this.getTotalDistanceTraveled(competitor, timePoint); + row.totalDistanceTraveledInMeters = totalDistanceTraveledInMeters==null?null:totalDistanceTraveledInMeters.getMeters(); + } + + /** + * @param waitForLatestAnalyses + * if false, this method is allowed to read the maneuver analysis results from a cache that + * may not reflect all data already received; otherwise, the method will always block for the latest + * cache updates to have happened before returning. + * @param fillNetPointsUncorrected + * tells if {@link LeaderboardEntryDTO#netPointsUncorrected} shall be filled; filling it is rather + * expensive, especially when compared to simply retrieving a score correction, and particularly if in a + * larger fleet a number of competitors haven't properly finished the race. This should only be used for + * leaderboard editing where a user needs to see what the uncorrected score was that would be used when + * the correction was removed. + */ + private LeaderboardEntryDTO getLeaderboardEntryDTO(Entry entry, RaceColumn raceColumn, Competitor competitor, + TimePoint timePoint, boolean addLegDetails, boolean waitForLatestAnalyses, + Map> legRanksCache, DomainFactory baseDomainFactory, + boolean fillNetPointsUncorrected, WindLegTypeAndLegBearingCache cache) { + LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO(); + TrackedRace trackedRace = raceColumn.getTrackedRace(competitor); + entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier(); + entryDTO.netPoints = entry.getNetPoints(); + if (fillNetPointsUncorrected) { + entryDTO.netPointsUncorrected = entry.getNetPointsUncorrected(); + } + entryDTO.netPointsCorrected = entry.isNetPointsCorrected(); + entryDTO.totalPoints = entry.getTotalPoints(); + entryDTO.reasonForMaxPoints = entry.getMaxPointsReason(); + entryDTO.discarded = entry.isDiscarded(); + final GPSFixTrack track = trackedRace == null ? null : trackedRace.getTrack(competitor); + if (trackedRace != null) { + Date timePointOfLastPositionFixAtOrBeforeQueryTimePoint = getTimePointOfLastFixAtOrBefore(competitor, trackedRace, timePoint); + if (track != null) { + entryDTO.averageSamplingInterval = track.getAverageIntervalBetweenRawFixes(); + } + if (timePointOfLastPositionFixAtOrBeforeQueryTimePoint != null) { + long timeDifferenceInMs = timePoint.asMillis() - timePointOfLastPositionFixAtOrBeforeQueryTimePoint.getTime(); + entryDTO.timeSinceLastPositionFixInSeconds = timeDifferenceInMs == 0 ? 0.0 : timeDifferenceInMs / 1000.0; + } else { + entryDTO.timeSinceLastPositionFixInSeconds = null; + } + } + if (addLegDetails && trackedRace != null) { + try { + final RankingInfo rankingInfo = trackedRace.getRankingMetric().getRankingInfo(timePoint, cache); + RaceDetails raceDetails = getRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, + legRanksCache, rankingInfo, cache); + entryDTO.legDetails = raceDetails.getLegDetails(); + entryDTO.windwardDistanceToCompetitorFarthestAheadInMeters = raceDetails.getWindwardDistanceToCompetitorFarthestAhead() == null ? null + : raceDetails.getWindwardDistanceToCompetitorFarthestAhead().getMeters(); + entryDTO.gapToLeaderInOwnTime = trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache); + entryDTO.averageAbsoluteCrossTrackErrorInMeters = raceDetails.getAverageAbsoluteCrossTrackError() == null ? null + : raceDetails.getAverageAbsoluteCrossTrackError().getMeters(); + entryDTO.averageSignedCrossTrackErrorInMeters = raceDetails.getAverageSignedCrossTrackError() == null ? null + : raceDetails.getAverageSignedCrossTrackError().getMeters(); + entryDTO.calculatedTime = raceDetails.getCorrectedTime(); + entryDTO.calculatedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = raceDetails.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead(); + entryDTO.gapToLeaderInOwnTime = raceDetails.getGapToLeaderInOwnTime(); + final TimePoint startOfRace = trackedRace.getStartOfRace(); + if (startOfRace != null) { + Waypoint startWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint(); + NavigableSet competitorMarkPassings = trackedRace.getMarkPassings(competitor); + trackedRace.lockForRead(competitorMarkPassings); + try { + if (!competitorMarkPassings.isEmpty()) { + final MarkPassing firstMarkPassing = competitorMarkPassings.iterator().next(); + if (firstMarkPassing.getWaypoint() == startWaypoint) { + Distance distanceToStartLineFiveSecondsBeforeStartOfRace = trackedRace.getDistanceToStartLine(competitor, /*milliseconds before start*/ 5000); + entryDTO.distanceToStartLineFiveSecondsBeforeStartInMeters = distanceToStartLineFiveSecondsBeforeStartOfRace == null ? null + : distanceToStartLineFiveSecondsBeforeStartOfRace.getMeters(); + Speed speedFiveSecondsBeforeStartOfRace = trackedRace.getSpeed(competitor, /*milliseconds before start*/ 5000); + entryDTO.speedOverGroundFiveSecondsBeforeStartInKnots = speedFiveSecondsBeforeStartOfRace == null ? null + : speedFiveSecondsBeforeStartOfRace.getKnots(); + Distance distanceToStartLineAtStartOfRace = trackedRace.getDistanceToStartLine( + competitor, startOfRace); + entryDTO.distanceToStartLineAtStartOfRaceInMeters = distanceToStartLineAtStartOfRace == null ? null + : distanceToStartLineAtStartOfRace.getMeters(); + Speed speedAtStartTime = track == null ? null : track.getEstimatedSpeed(startOfRace); + entryDTO.speedOverGroundAtStartOfRaceInKnots = speedAtStartTime == null ? null + : speedAtStartTime.getKnots(); + TimePoint competitorStartTime = firstMarkPassing.getTimePoint(); + entryDTO.timeBetweenRaceStartAndCompetitorStartInSeconds = startOfRace.until(competitorStartTime).asSeconds(); + Speed competitorSpeedWhenPassingStart = track == null ? null : track + .getEstimatedSpeed(competitorStartTime); + entryDTO.speedOverGroundAtPassingStartWaypointInKnots = competitorSpeedWhenPassingStart == null ? null + : competitorSpeedWhenPassingStart.getKnots(); + try { + entryDTO.startTack = trackedRace.getTack(competitor, competitorStartTime); + } catch (NoWindException nwe) { + entryDTO.startTack = null; // leave empty in case no wind information is available + } + Distance distanceFromStarboardSideOfStartLineWhenPassingStart = trackedRace + .getDistanceFromStarboardSideOfStartLineWhenPassingStart(competitor); + entryDTO.distanceToStarboardSideOfStartLineInMeters = distanceFromStarboardSideOfStartLineWhenPassingStart == null ? null + : distanceFromStarboardSideOfStartLineWhenPassingStart.getMeters(); + } + } + } finally { + trackedRace.unlockAfterRead(competitorMarkPassings); + } + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); // the future used to calculate the leg details was interrupted; escalate as runtime exception + } + } + final Fleet fleet = entry.getFleet(); + entryDTO.fleet = fleet == null ? null : baseDomainFactory.convertToFleetDTO(fleet); + return entryDTO; + } + + /** + * Determines the time point of the last raw fix (with outliers not removed) for competitor in + * trackedRace. If the competitor's track is null or empty, null is returned. + * @param trackedRace must not be null + * @param atOrBefore find the last fix at or before the time point specified + */ + private Date getTimePointOfLastFixAtOrBefore(Competitor competitor, TrackedRace trackedRace, TimePoint atOrBefore) { + assert trackedRace != null; + final Date timePointOfLastPositionFix; + GPSFixTrack track = trackedRace.getTrack(competitor); + if (track == null) { + timePointOfLastPositionFix = null; + } else { + GPSFixMoving lastFix = track.getLastFixAtOrBefore(atOrBefore); + if (lastFix == null) { + timePointOfLastPositionFix = null; + } else { + timePointOfLastPositionFix = lastFix.getTimePoint().asDate(); + } + } + return timePointOfLastPositionFix; + } + + private static class RaceDetails { + private final List legDetails; + private final Distance windwardDistanceToCompetitorFarthestAhead; + private final Distance averageAbsoluteCrossTrackError; + private final Distance averageSignedCrossTrackError; + private final Duration gapToLeaderInOwnTime; + private final Duration correctedTime; + private final Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; + + public RaceDetails(List legDetails, Distance windwardDistanceToCompetitorFarthestAhead, + Distance averageAbsoluteCrossTrackError, Distance averageSignedCrossTrackError, + Duration gapToLeaderInOwnTime, Duration correctedTime, + Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead) { + super(); + this.legDetails = legDetails; + this.windwardDistanceToCompetitorFarthestAhead = windwardDistanceToCompetitorFarthestAhead; + this.averageAbsoluteCrossTrackError = averageAbsoluteCrossTrackError; + this.averageSignedCrossTrackError = averageSignedCrossTrackError; + this.gapToLeaderInOwnTime = gapToLeaderInOwnTime; + this.correctedTime = correctedTime; + this.correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; + } + public List getLegDetails() { + return legDetails; + } + public Distance getWindwardDistanceToCompetitorFarthestAhead() { + return windwardDistanceToCompetitorFarthestAhead; + } + public Distance getAverageAbsoluteCrossTrackError() { + return averageAbsoluteCrossTrackError; + } + public Distance getAverageSignedCrossTrackError() { + return averageSignedCrossTrackError; + } + public Duration getGapToLeaderInOwnTime() { + return gapToLeaderInOwnTime; + } + public Duration getCorrectedTime() { + return correctedTime; + } + public Duration getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead() { + return correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; + } + } + + /** + * If timePoint is after the end of the race's tracking the query will be adjusted to obtain the values + * at the end of the {@link TrackedRace#getEndOfTracking() race's tracking time}. If the time point adjusted this + * way equals the end of the tracking time, the query results will be looked up in a cache first and if not found, + * they will be stored to the cache after calculating them. A cache invalidation {@link RaceChangeListener listener} + * will be registered with the race which will be triggered for any event received by the race. + * @param waitForLatestAnalyses + * if false, this method is allowed to read the maneuver analysis results from a cache that + * may not reflect all data already received; otherwise, the method will always block for the latest + * cache updates to have happened before returning. + */ + private RaceDetails getRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint, + boolean waitForLatestAnalyses, Map> legRanksCache, + RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException { + final RaceDetails raceDetails; + if (trackedRace.getEndOfTracking() != null && trackedRace.getEndOfTracking().compareTo(timePoint) < 0) { + raceDetails = getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(trackedRace, competitor, legRanksCache, rankingInfo, cache); + } else { + raceDetails = calculateRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, legRanksCache, cache, rankingInfo); + } + return raceDetails; + } + + private RaceDetails getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(final TrackedRace trackedRace, + final Competitor competitor, final Map> legRanksCache, + RankingInfo rankingInfo, final WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException { + final com.sap.sse.common.Util.Pair key = new com.sap.sse.common.Util.Pair(trackedRace, competitor); + RunnableFuture raceDetails; + synchronized (raceDetailsAtEndOfTrackingCache) { + raceDetails = raceDetailsAtEndOfTrackingCache.get(key); + if (raceDetails == null) { + raceDetails = new FutureTask(new Callable() { + @Override + public RaceDetails call() throws Exception { + TimePoint end = trackedRace.getEndOfRace(); + if (end == null) { + end = trackedRace.getEndOfTracking(); + } + return calculateRaceDetails(trackedRace, competitor, end, + // TODO see bug 1358: for now, use waitForLatest==false until we've switched to optimistic locking for the course read lock + /* TODO old comment when it was still true: "because this is done only once after end of tracking" */ + /* waitForLatestAnalyses (maneuver and cross track error) */ false, + legRanksCache, cache, rankingInfo); + } + }); + raceDetailsExecutor.execute(raceDetails); + raceDetailsAtEndOfTrackingCache.put(key, raceDetails); + final CacheInvalidationListener cacheInvalidationListener = new CacheInvalidationListener(trackedRace, competitor); + trackedRace.addListener(cacheInvalidationListener); + cacheInvalidationListeners.add(cacheInvalidationListener); + } + } + return raceDetails.get(); + } + + private RaceDetails calculateRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint, + boolean waitForLatestAnalyses, Map> legRanksCache, + WindLegTypeAndLegBearingCache cache, final RankingInfo rankingInfo) { + final List legDetails = new ArrayList(); + final Course course = trackedRace.getRace().getCourse(); + course.lockForRead(); // hold back any course re-configurations while looping over the legs + try { + for (Leg leg : course.getLegs()) { + LegEntryDTO legEntry; + // We loop over a copy of the course's legs; during a course change, legs may become "stale," even with + // regard to the leg/trackedLeg structures inside the tracked race which is updated by the course change + // immediately. That's why we've acquired a read lock for the course above. + TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, leg); + if (trackedLeg != null && trackedLeg.hasStartedLeg(timePoint)) { + legEntry = createLegEntry(trackedLeg, timePoint, waitForLatestAnalyses, legRanksCache, rankingInfo, cache); + } else { + legEntry = null; + } + legDetails.add(legEntry); + } + final Distance windwardDistanceToCompetitorFarthestAhead = trackedRace == null ? null : trackedRace + .getWindwardDistanceToCompetitorFarthestAhead(competitor, timePoint, WindPositionMode.LEG_MIDDLE, rankingInfo, cache); + Distance averageAbsoluteCrossTrackError; + try { + averageAbsoluteCrossTrackError = trackedRace == null ? null : trackedRace.getAverageAbsoluteCrossTrackError( + competitor, timePoint, waitForLatestAnalyses, cache); + } catch (NoWindException nwe) { + // without wind information, use null meaning "unknown" + averageAbsoluteCrossTrackError = null; + } + Distance averageSignedCrossTrackError; + try { + averageSignedCrossTrackError = trackedRace == null ? null : trackedRace.getAverageSignedCrossTrackError( + competitor, timePoint, waitForLatestAnalyses, cache); + } catch (NoWindException nwe) { + // without wind information, use null meaning "unknown" + averageSignedCrossTrackError = null; + } + final CompetitorRankingInfo competitorRankingInfo = rankingInfo.getCompetitorRankingInfo().apply(competitor); + return new RaceDetails(legDetails, windwardDistanceToCompetitorFarthestAhead, averageAbsoluteCrossTrackError, averageSignedCrossTrackError, + trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache), + trackedRace.getRankingMetric().getCorrectedTime(competitor, timePoint), + competitorRankingInfo == null ? null : competitorRankingInfo.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead()); + } finally { + course.unlockAfterRead(); + } + } + + private LegEntryDTO createLegEntry(TrackedLegOfCompetitor trackedLeg, TimePoint timePoint, + boolean waitForLatestAnalyses, Map> legRanksCache, + RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) { + LegEntryDTO result; + final Duration time = trackedLeg.getTime(timePoint); + if (trackedLeg == null || time == null) { + result = null; + } else { + result = new LegEntryDTO(); + try { + result.legType = trackedLeg.getTrackedLeg().getLegType(timePoint); + } catch (NoWindException nwe) { + result.legType = null; // can't determine leg type without wind data + } + final Speed averageSpeedOverGround = trackedLeg.getAverageSpeedOverGround(timePoint); + result.averageSpeedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots(); + Distance averageAbsoluteCrossTrackError; + try { + averageAbsoluteCrossTrackError = trackedLeg.getAverageAbsoluteCrossTrackError(timePoint, waitForLatestAnalyses); + } catch (NoWindException nwe) { + // leave averageAbsoluteCrossTrackError as null, meaning "unknown" + averageAbsoluteCrossTrackError = null; + } + result.averageAbsoluteCrossTrackErrorInMeters = averageAbsoluteCrossTrackError == null ? null : averageAbsoluteCrossTrackError.getMeters(); + Distance averageSignedCrossTrackError; + try { + averageSignedCrossTrackError = trackedLeg.getAverageSignedCrossTrackError(timePoint, waitForLatestAnalyses); + } catch (NoWindException nwe) { + // leave averageSignedCrossTrackError as null, meaning "unknown" + averageSignedCrossTrackError = null; + } + result.averageSignedCrossTrackErrorInMeters = averageSignedCrossTrackError == null ? null : averageSignedCrossTrackError.getMeters(); + Double speedOverGroundInKnots; + if (trackedLeg.hasFinishedLeg(timePoint)) { + speedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots(); + } else { + final SpeedWithBearing speedOverGround = trackedLeg.getSpeedOverGround(timePoint); + speedOverGroundInKnots = speedOverGround == null ? null : speedOverGround.getKnots(); + } + result.currentSpeedOverGroundInKnots = speedOverGroundInKnots == null ? null : speedOverGroundInKnots; + Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint); + result.distanceTraveledInMeters = distanceTraveled == null ? null : distanceTraveled.getMeters(); + Distance distanceTraveledConsideringGateStart = trackedLeg.getDistanceTraveledConsideringGateStart(timePoint); + result.distanceTraveledIncludingGateStartInMeters = distanceTraveledConsideringGateStart == null ? null : distanceTraveledConsideringGateStart.getMeters(); + final Duration estimatedTimeToNextMarkInSeconds = trackedLeg.getEstimatedTimeToNextMark(timePoint, WindPositionMode.EXACT, cache); + result.estimatedTimeToNextWaypointInSeconds = estimatedTimeToNextMarkInSeconds==null?null:estimatedTimeToNextMarkInSeconds.asSeconds(); + result.timeInMilliseconds = time.asMillis(); + result.finished = trackedLeg.hasFinishedLeg(timePoint); + final TimePoint legFinishTime = trackedLeg.getFinishTime(); + result.correctedTotalTime = trackedLeg.hasStartedLeg(timePoint) ? trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getCorrectedTime(trackedLeg.getCompetitor(), + trackedLeg.hasFinishedLeg(timePoint) ? legFinishTime : timePoint, cache) : null; + // fetch the leg gap in own corrected time from the ranking metric + final Duration gapToLeaderInOwnTime = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric(). + getLegGapToLegLeaderInOwnTime(trackedLeg, timePoint, rankingInfo, cache); + result.gapToLeaderInSeconds = gapToLeaderInOwnTime == null ? null : gapToLeaderInOwnTime.asSeconds(); + if (result.gapToLeaderInSeconds != null) { + final Duration gapAtEndOfPreviousLeg = getGapAtEndOfPreviousLeg(trackedLeg, rankingInfo, cache); + if (gapAtEndOfPreviousLeg != null) { + result.gapChangeSinceLegStartInSeconds = result.gapToLeaderInSeconds - gapAtEndOfPreviousLeg.asSeconds(); + } + } + LinkedHashMap legRanks = legRanksCache.get(trackedLeg.getLeg()); + if (legRanks != null) { + result.rank = legRanks.get(trackedLeg.getCompetitor()); + } else { + result.rank = trackedLeg.getRank(timePoint, cache); + } + result.started = trackedLeg.hasStartedLeg(timePoint); + Speed velocityMadeGood; + if (trackedLeg.hasFinishedLeg(timePoint)) { + velocityMadeGood = trackedLeg.getAverageVelocityMadeGood(timePoint); + } else { + velocityMadeGood = trackedLeg.getVelocityMadeGood(timePoint, WindPositionMode.EXACT); + } + result.velocityMadeGoodInKnots = velocityMadeGood == null ? null : velocityMadeGood.getKnots(); + Distance windwardDistanceToGo = trackedLeg.getWindwardDistanceToGo(timePoint, WindPositionMode.LEG_MIDDLE); + result.windwardDistanceToGoInMeters = windwardDistanceToGo == null ? null : windwardDistanceToGo + .getMeters(); + final TimePoint startOfRace = trackedLeg.getTrackedLeg().getTrackedRace().getStartOfRace(); + if (startOfRace != null && trackedLeg.hasStartedLeg(timePoint)) { + // not using trackedLeg.getManeuvers(...) because it may not catch the mark passing maneuver starting this leg + // because that may have been detected as slightly before the mark passing time, hence associated with the previous leg + List maneuvers = trackedLeg.getTrackedLeg().getTrackedRace() + .getManeuvers(trackedLeg.getCompetitor(), startOfRace, timePoint, waitForLatestAnalyses); + if (maneuvers != null) { + result.numberOfManeuvers = new HashMap(); + result.numberOfManeuvers.put(ManeuverType.TACK, 0); + result.numberOfManeuvers.put(ManeuverType.JIBE, 0); + result.numberOfManeuvers.put(ManeuverType.PENALTY_CIRCLE, 0); + Map totalManeuverLossInMeters = new HashMap(); + totalManeuverLossInMeters.put(ManeuverType.TACK, 0.0); + totalManeuverLossInMeters.put(ManeuverType.JIBE, 0.0); + totalManeuverLossInMeters.put(ManeuverType.PENALTY_CIRCLE, 0.0); + TimePoint startOfLeg = trackedLeg.getStartTime(); + for (Maneuver maneuver : maneuvers) { + // don't count maneuvers that were in previous legs + switch (maneuver.getType()) { + case TACK: + case JIBE: + case PENALTY_CIRCLE: + if (!maneuver.getTimePoint().before(startOfLeg) && (legFinishTime == null || legFinishTime.after(timePoint) || + maneuver.getTimePoint().before(legFinishTime))) { + if (maneuver.getManeuverLoss() != null) { + result.numberOfManeuvers.put(maneuver.getType(), + result.numberOfManeuvers.get(maneuver.getType()) + 1); + totalManeuverLossInMeters.put(maneuver.getType(), + totalManeuverLossInMeters.get(maneuver.getType()) + + maneuver.getManeuverLoss().getMeters()); + } + } + break; + case MARK_PASSING: + // analyze all mark passings, not only those after this leg's start, to catch the mark passing + // maneuver starting this leg, even if its time point is slightly before the mark passing starting this leg + MarkPassingManeuver mpm = (MarkPassingManeuver) maneuver; + if (mpm.getWaypointPassed() == trackedLeg.getLeg().getFrom()) { + result.sideToWhichMarkAtLegStartWasRounded = mpm.getSide(); + } + break; + default: + /* Do nothing here. + * Throwing an exception destroys the toggling (and maybe other behaviour) of the leaderboard. + */ + } + } + result.averageManeuverLossInMeters = new HashMap(); + for (ManeuverType maneuverType : new ManeuverType[] { ManeuverType.TACK, ManeuverType.JIBE, + ManeuverType.PENALTY_CIRCLE }) { + if (result.numberOfManeuvers.get(maneuverType) != 0) { + result.averageManeuverLossInMeters.put( + maneuverType, + totalManeuverLossInMeters.get(maneuverType) + / result.numberOfManeuvers.get(maneuverType)); + } + } + } + } + } + return result; + } + + private Duration getGapAtEndOfPreviousLeg(TrackedLegOfCompetitor trackedLeg, final RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) { + final Duration result; + final Course course = trackedLeg.getTrackedLeg().getTrackedRace().getRace().getCourse(); + // if trackedLeg is the first leg, compute the gap at the start of this leg; otherwise, compute gap + // at the end of the previous leg + final TimePoint timePoint = trackedLeg.getStartTime(); + final TrackedLegOfCompetitor tloc; + if (course.getFirstWaypoint() == trackedLeg.getLeg().getFrom()) { + tloc = trackedLeg; + } else { + tloc = trackedLeg.getTrackedLeg().getTrackedRace().getTrackedLegFinishingAt(trackedLeg.getLeg().getFrom()) + .getTrackedLeg(trackedLeg.getCompetitor()); + } + result = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getLegGapToLegLeaderInOwnTime(tloc, timePoint, rankingInfo, cache); + return result; + } + + private LeaderboardDTO getLiveLeaderboard(Collection namesOfRaceColumnsForWhichToLoadLegDetails, + boolean addOverallDetails, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException, ExecutionException { + LiveLeaderboardUpdater liveLeaderboardUpdater = getLiveLeaderboardUpdater(trackedRegattaRegistry, + baseDomainFactory); + return liveLeaderboardUpdater.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails); + } + + private LiveLeaderboardUpdater getLiveLeaderboardUpdater(TrackedRegattaRegistry trackedRegattaRegistry, + DomainFactory baseDomainFactory) { + LiveLeaderboardUpdater result = this.liveLeaderboardUpdater; + if (result == null) { + synchronized (this) { + result = this.liveLeaderboardUpdater; + if (result == null) { + this.liveLeaderboardUpdater = new LiveLeaderboardUpdater(this, trackedRegattaRegistry, baseDomainFactory); + result = this.liveLeaderboardUpdater; + } + } + } + return result; + } + + private LeaderboardDTOCache getLeaderboardDTOCache() { + LeaderboardDTOCache result = this.leaderboardDTOCache; + if (result == null) { + synchronized (this) { + result = this.leaderboardDTOCache; + if (result == null) { + // The leaderboard cache is invalidated upon all competitor and mark position changes; some analyzes + // are pretty expensive, such as the maneuver re-calculation. Waiting for the latest analysis after only a + // single fix was updated is too expensive if users use the replay feature while a race is still running. + // Therefore, using waitForLatestAnalyses==false seems appropriate here. + this.leaderboardDTOCache = new LeaderboardDTOCache(/* waitForLatestAnalyses */false, this); + result = this.leaderboardDTOCache; + } + } + } + return result; + } + + @Override + public LeaderboardDTO getLeaderboardDTO(TimePoint timePoint, + Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, + TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory, boolean fillNetPointsUncorrected) throws NoWindException, + InterruptedException, ExecutionException { + LeaderboardDTO result = null; + if (timePoint == null) { + // timePoint==null means live mode; however, if we're after the end of all races and after all score + // corrections, don't use the live leaderboard updater which would keep re-calculating over and over again, but map + // this to a usual non-live call which uses the regular LeaderboardDTOCache which is invalidated properly + // when the tracked race associations or score corrections or tracked race contents changes: + final TimePoint nowMinusDelay = this.getNowMinusDelay(); + final TimePoint timePointOfLatestModification = this.getTimePointOfLatestModification(); + if (fillNetPointsUncorrected || (timePointOfLatestModification != null && !nowMinusDelay.before(timePointOfLatestModification))) { + // if there hasn't been any modification to the leaderboard since nowMinusDelay, use non-live mode + // and pull the result from the regular leaderboard cache: + timePoint = timePointOfLatestModification; + } else { + // don't use the regular leaderboard cache; the race still seems to be on; use the live leaderboard updater instead: + timePoint = null; + result = this.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, trackedRegattaRegistry, baseDomainFactory); + } + } + if (timePoint != null) { + if (fillNetPointsUncorrected) { + // explicitly filling the uncorrected net points requires uncached recalculation + result = computeDTO(timePoint, namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, /* waitForLatestAnalyses */ true, + trackedRegattaRegistry, baseDomainFactory, fillNetPointsUncorrected); + } else { + // in replay we'd like up-to-date results; they are still cached + // which is OK because the cache is invalidated whenever any of the tracked races attached to the + // leaderboard changes. + result = getLeaderboardDTOCache().getLeaderboardByName(timePoint, + namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, baseDomainFactory, + trackedRegattaRegistry); + } + } + return result; + } + + public String toString() { + return getName() + " " + (getDefaultCourseArea() != null ? getDefaultCourseArea().getName() : "") + " " + (getScoringScheme() != null ? getScoringScheme().getType().name() : ""); + } + + @Override + public NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher() { + return new NumberOfCompetitorsFetcherImpl(); + } + + protected abstract LeaderboardType getLeaderboardType(); + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java index 9124f8bba72..c19f56c9ca3 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java @@ -1,337 +1,343 @@ -package com.sap.sailing.domain.leaderboard.impl; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.ObjectStreamException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Logger; - -import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.CourseArea; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.RaceColumn; -import com.sap.sailing.domain.base.RaceColumnListener; -import com.sap.sailing.domain.base.impl.AbstractRaceExecutionOrderProvider; -import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; -import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn; -import com.sap.sailing.domain.leaderboard.ScoringScheme; -import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; -import com.sap.sailing.domain.racelog.RaceLogStore; -import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; -import com.sap.sailing.domain.regattalike.BaseRegattaLikeImpl; -import com.sap.sailing.domain.regattalike.FlexibleLeaderboardAsRegattaLikeIdentifier; -import com.sap.sailing.domain.regattalike.IsRegattaLike; -import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier; -import com.sap.sailing.domain.regattalike.RegattaLikeListener; -import com.sap.sailing.domain.regattalog.RegattaLogStore; -import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore; -import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sse.common.Duration; - -/** - * A leaderboard implementation that allows users to flexibly configure which columns exist. No constraints need to be observed regarding - * the columns belonging to the same regatta or even boat class.

- * - * The flexible leaderboard listens as {@link RaceColumnListener} on all its {@link RaceColumn}s and forwards all events to - * all {@link RaceColumnListener}s subscribed with this leaderboard. - * - * @author Axel Uhl (D043530) - * - */ -public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements FlexibleLeaderboard { - private static Logger logger = Logger.getLogger(FlexibleLeaderboardImpl.class.getName()); - - protected static final DefaultFleetImpl defaultFleet = new DefaultFleetImpl(); - private static final long serialVersionUID = -5708971849158747846L; - private final List races; - private final ScoringScheme scoringScheme; - private String name; - private transient RaceLogStore raceLogStore; - private CourseArea courseArea; - private RaceExecutionOrderProvider raceExecutionOrderProvider; - - /** - * @see RegattaLog for the reason why the leaderboard manages a {@code RegattaLog} - */ - private final IsRegattaLike regattaLikeHelper; - - public FlexibleLeaderboardImpl(String name, ThresholdBasedResultDiscardingRule resultDiscardingRule, - ScoringScheme scoringScheme, CourseArea courseArea) { - this(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, - name, resultDiscardingRule, scoringScheme, courseArea); - } - - public FlexibleLeaderboardImpl(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, - String name, ThresholdBasedResultDiscardingRule resultDiscardingRule, - ScoringScheme scoringScheme, CourseArea courseArea) { - super(resultDiscardingRule); - this.scoringScheme = scoringScheme; - if (name == null) { - throw new IllegalArgumentException("A leaderboard's name must not be null"); - } - this.name = name; - this.races = new ArrayList<>(); - this.raceLogStore = raceLogStore; - this.courseArea = courseArea; - this.regattaLikeHelper = new BaseRegattaLikeImpl(new FlexibleLeaderboardAsRegattaLikeIdentifier(this), regattaLogStore) { - private static final long serialVersionUID = 4082392360832548953L; - - @Override - public RaceColumn getRaceColumnByName(String raceColumnName) { - return getRaceColumnByName(raceColumnName); - } - }; - this.raceExecutionOrderProvider = new RaceExecutionOrderCache(); - } - - /** - * Deserialization has to be maintained in lock-step with {@link #writeObject(ObjectOutputStream) serialization}. - * When de-serializing, a possibly remote {@link #raceLogStore} is ignored because it is transient. Instead, an - * {@link EmptyRaceLogStore} is used for the de-serialized instance. A new {@link RaceLogInformation} is - * assembled for this empty race log and applied to all columns. - */ - private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { - ois.defaultReadObject(); - raceLogStore = EmptyRaceLogStore.INSTANCE; - for (RaceColumn column : getRaceColumns()) { - column.setRaceLogInformation(raceLogStore, new FlexibleLeaderboardAsRegattaLikeIdentifier(this)); - } - } - - protected Object readResolve() throws ObjectStreamException { - raceExecutionOrderProvider.triggerUpdate(); - return this; - } - - @Override - public String getName() { - return name; - } - - /** - * @param newName must not be null - */ - public void setName(String newName) { - if (newName == null) { - throw new IllegalArgumentException("A leaderboard's name must not be null"); - } - this.name = newName; - } - - @Override - public RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace) { - FlexibleRaceColumn column = addRaceColumn(columnName, medalRace, /* logAlreadyExistingColumn */false); - column.setTrackedRace(defaultFleet, race); // triggers listeners because this object was registered above as - // race column listener on the column - return column; - } - - @Override - public FlexibleRaceColumn addRaceColumn(String name, boolean medalRace) { - return addRaceColumn(name, medalRace, /* logAlreadyExistingColumn */true); - } - - private FlexibleRaceColumn addRaceColumn(String name, boolean medalRace, boolean logAlreadyExistingColumn) { - FlexibleRaceColumn column = getRaceColumnByName(name); - if (column != null) { - if (logAlreadyExistingColumn) { - final String msg = "Trying to create race column with duplicate name " + name + " in leaderboard " + getName(); - logger.severe(msg); - } - } else { - column = createRaceColumn(name, medalRace); - column.addRaceColumnListener(this); - races.add(column); - column.setRaceLogInformation(raceLogStore, new FlexibleLeaderboardAsRegattaLikeIdentifier(this)); - column.setRegattaLikeHelper(regattaLikeHelper); - getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(column); - } - return column; - } - - @Override - public FlexibleRaceColumn getRaceColumnByName(String columnName) { - return (FlexibleRaceColumn) super.getRaceColumnByName(columnName); - } - - @Override - public Fleet getFleet(String fleetName) { - Fleet result; - if (fleetName == null) { - result = defaultFleet; - } else { - result = super.getFleet(fleetName); - } - return result; - } - - @Override - public void removeRaceColumn(String columnName) { - final FlexibleRaceColumn raceColumn = getRaceColumnByName(columnName); - if (raceColumn != null) { - for (Fleet fleet : raceColumn.getFleets()) { - raceLogStore.removeRaceLog(raceColumn.getRaceLogIdentifier(fleet)); - if (raceColumn.getTrackedRace(fleet) != null) { - raceColumn.getTrackedRace(fleet).detachRaceExecutionOrderProvider(raceExecutionOrderProvider); - } - } - races.remove(raceColumn); - getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn); - raceColumn.removeRaceColumnListener(this); - } - } - - @Override - public Iterable getRaceColumns() { - final Iterable result; - if (races != null) { - result = Collections.unmodifiableCollection(new ArrayList(races)); - } else { - result = null; - } - return result; - } - - protected RaceColumnImpl createRaceColumn(String column, boolean medalRace) { - return new RaceColumnImpl(column, medalRace, raceExecutionOrderProvider); - } - - protected Iterable turnNullOrEmptyFleetsIntoDefaultFleet(Fleet... fleets) { - Iterable theFleets; - if (fleets == null || fleets.length == 0) { - Fleet defaultfleetCasted = defaultFleet; - theFleets = Collections.singleton(defaultfleetCasted); - } else { - theFleets = Arrays.asList(fleets); - } - return theFleets; - } - - @Override - public void moveRaceColumnUp(String name) { - FlexibleRaceColumn race = null; - for (FlexibleRaceColumn r : races) { - if (r.getName().equals(name)) { - race = r; - } - } - if (race != null) { - int index = 0; - index = races.lastIndexOf(race); - index--; - if (index >= 0) { - races.remove(race); - races.add(index, race); - getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index); - } - } - } - - @Override - public void moveRaceColumnDown(String name) { - FlexibleRaceColumn race = null; - for (FlexibleRaceColumn r : races) { - if (r.getName().equals(name)) { - race = r; - } - } - if (race != null) { - int index = 0; - index = races.lastIndexOf(race); - if (index != -1) { - index++; - if (index < races.size()) { - races.remove(race); - races.add(index, race); - getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index); - } - } - } - } - - @Override - public void updateIsMedalRace(String raceName, boolean isMedalRace) { - FlexibleRaceColumn race = null; - for (FlexibleRaceColumn r : races) { - if (r.getName().equals(raceName)) - race = r; - } - if (race != null) { - race.setIsMedalRace(isMedalRace); - } - } - - @Override - public ScoringScheme getScoringScheme() { - return scoringScheme; - } - - @Override - public CourseArea getDefaultCourseArea() { - return courseArea; - } - - @Override - public void setDefaultCourseArea(CourseArea newCourseArea) { - this.courseArea = newCourseArea; - } - - @Override - public IsRegattaLike getRegattaLike() { - return regattaLikeHelper; - } - - @Override - public RegattaLog getRegattaLog() { - return regattaLikeHelper.getRegattaLog(); - } - - @Override - public RegattaLikeIdentifier getRegattaLikeIdentifier() { - return regattaLikeHelper.getRegattaLikeIdentifier(); - } - - @Override - public void addListener(RegattaLikeListener listener) { - regattaLikeHelper.addListener(listener); - } - - @Override - public void removeListener(RegattaLikeListener listener) { - regattaLikeHelper.removeListener(listener); - } - - @Override - public Double getTimeOnTimeFactor(Competitor competitor) { - return regattaLikeHelper.getTimeOnTimeFactor(competitor); - } - - @Override - public Duration getTimeOnDistanceAllowancePerNauticalMile(Competitor competitor) { - return regattaLikeHelper.getTimeOnDistanceAllowancePerNauticalMile(competitor); - } - - private class RaceExecutionOrderCache extends AbstractRaceExecutionOrderProvider { - private static final long serialVersionUID = 652833386555762661L; - - public RaceExecutionOrderCache() { - super(); - addRaceColumnListener(this); - } - - @Override - protected Map> getRaceColumnsOfSeries() { - final Map> result = new HashMap<>(); - result.put(FlexibleLeaderboardImpl.defaultFleet, getRaceColumns()); - return result; - } - } -} +package com.sap.sailing.domain.leaderboard.impl; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.CourseArea; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.base.RaceColumnListener; +import com.sap.sailing.domain.base.impl.AbstractRaceExecutionOrderProvider; +import com.sap.sailing.domain.common.LeaderboardType; +import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; +import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn; +import com.sap.sailing.domain.leaderboard.ScoringScheme; +import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; +import com.sap.sailing.domain.racelog.RaceLogStore; +import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; +import com.sap.sailing.domain.regattalike.BaseRegattaLikeImpl; +import com.sap.sailing.domain.regattalike.FlexibleLeaderboardAsRegattaLikeIdentifier; +import com.sap.sailing.domain.regattalike.IsRegattaLike; +import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier; +import com.sap.sailing.domain.regattalike.RegattaLikeListener; +import com.sap.sailing.domain.regattalog.RegattaLogStore; +import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore; +import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.Duration; + +/** + * A leaderboard implementation that allows users to flexibly configure which columns exist. No constraints need to be observed regarding + * the columns belonging to the same regatta or even boat class.

+ * + * The flexible leaderboard listens as {@link RaceColumnListener} on all its {@link RaceColumn}s and forwards all events to + * all {@link RaceColumnListener}s subscribed with this leaderboard. + * + * @author Axel Uhl (D043530) + * + */ +public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements FlexibleLeaderboard { + private static Logger logger = Logger.getLogger(FlexibleLeaderboardImpl.class.getName()); + + protected static final DefaultFleetImpl defaultFleet = new DefaultFleetImpl(); + private static final long serialVersionUID = -5708971849158747846L; + private final List races; + private final ScoringScheme scoringScheme; + private String name; + private transient RaceLogStore raceLogStore; + private CourseArea courseArea; + private RaceExecutionOrderProvider raceExecutionOrderProvider; + + /** + * @see RegattaLog for the reason why the leaderboard manages a {@code RegattaLog} + */ + private final IsRegattaLike regattaLikeHelper; + + public FlexibleLeaderboardImpl(String name, ThresholdBasedResultDiscardingRule resultDiscardingRule, + ScoringScheme scoringScheme, CourseArea courseArea) { + this(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, + name, resultDiscardingRule, scoringScheme, courseArea); + } + + public FlexibleLeaderboardImpl(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, + String name, ThresholdBasedResultDiscardingRule resultDiscardingRule, + ScoringScheme scoringScheme, CourseArea courseArea) { + super(resultDiscardingRule); + this.scoringScheme = scoringScheme; + if (name == null) { + throw new IllegalArgumentException("A leaderboard's name must not be null"); + } + this.name = name; + this.races = new ArrayList<>(); + this.raceLogStore = raceLogStore; + this.courseArea = courseArea; + this.regattaLikeHelper = new BaseRegattaLikeImpl(new FlexibleLeaderboardAsRegattaLikeIdentifier(this), regattaLogStore) { + private static final long serialVersionUID = 4082392360832548953L; + + @Override + public RaceColumn getRaceColumnByName(String raceColumnName) { + return getRaceColumnByName(raceColumnName); + } + }; + this.raceExecutionOrderProvider = new RaceExecutionOrderCache(); + } + + /** + * Deserialization has to be maintained in lock-step with {@link #writeObject(ObjectOutputStream) serialization}. + * When de-serializing, a possibly remote {@link #raceLogStore} is ignored because it is transient. Instead, an + * {@link EmptyRaceLogStore} is used for the de-serialized instance. A new {@link RaceLogInformation} is + * assembled for this empty race log and applied to all columns. + */ + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + raceLogStore = EmptyRaceLogStore.INSTANCE; + for (RaceColumn column : getRaceColumns()) { + column.setRaceLogInformation(raceLogStore, new FlexibleLeaderboardAsRegattaLikeIdentifier(this)); + } + } + + protected Object readResolve() throws ObjectStreamException { + raceExecutionOrderProvider.triggerUpdate(); + return this; + } + + @Override + public String getName() { + return name; + } + + /** + * @param newName must not be null + */ + public void setName(String newName) { + if (newName == null) { + throw new IllegalArgumentException("A leaderboard's name must not be null"); + } + this.name = newName; + } + + @Override + public RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace) { + FlexibleRaceColumn column = addRaceColumn(columnName, medalRace, /* logAlreadyExistingColumn */false); + column.setTrackedRace(defaultFleet, race); // triggers listeners because this object was registered above as + // race column listener on the column + return column; + } + + @Override + public FlexibleRaceColumn addRaceColumn(String name, boolean medalRace) { + return addRaceColumn(name, medalRace, /* logAlreadyExistingColumn */true); + } + + private FlexibleRaceColumn addRaceColumn(String name, boolean medalRace, boolean logAlreadyExistingColumn) { + FlexibleRaceColumn column = getRaceColumnByName(name); + if (column != null) { + if (logAlreadyExistingColumn) { + final String msg = "Trying to create race column with duplicate name " + name + " in leaderboard " + getName(); + logger.severe(msg); + } + } else { + column = createRaceColumn(name, medalRace); + column.addRaceColumnListener(this); + races.add(column); + column.setRaceLogInformation(raceLogStore, new FlexibleLeaderboardAsRegattaLikeIdentifier(this)); + column.setRegattaLikeHelper(regattaLikeHelper); + getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(column); + } + return column; + } + + @Override + public FlexibleRaceColumn getRaceColumnByName(String columnName) { + return (FlexibleRaceColumn) super.getRaceColumnByName(columnName); + } + + @Override + public Fleet getFleet(String fleetName) { + Fleet result; + if (fleetName == null) { + result = defaultFleet; + } else { + result = super.getFleet(fleetName); + } + return result; + } + + @Override + public void removeRaceColumn(String columnName) { + final FlexibleRaceColumn raceColumn = getRaceColumnByName(columnName); + if (raceColumn != null) { + for (Fleet fleet : raceColumn.getFleets()) { + raceLogStore.removeRaceLog(raceColumn.getRaceLogIdentifier(fleet)); + if (raceColumn.getTrackedRace(fleet) != null) { + raceColumn.getTrackedRace(fleet).detachRaceExecutionOrderProvider(raceExecutionOrderProvider); + } + } + races.remove(raceColumn); + getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn); + raceColumn.removeRaceColumnListener(this); + } + } + + @Override + public Iterable getRaceColumns() { + final Iterable result; + if (races != null) { + result = Collections.unmodifiableCollection(new ArrayList(races)); + } else { + result = null; + } + return result; + } + + protected RaceColumnImpl createRaceColumn(String column, boolean medalRace) { + return new RaceColumnImpl(column, medalRace, raceExecutionOrderProvider); + } + + protected Iterable turnNullOrEmptyFleetsIntoDefaultFleet(Fleet... fleets) { + Iterable theFleets; + if (fleets == null || fleets.length == 0) { + Fleet defaultfleetCasted = defaultFleet; + theFleets = Collections.singleton(defaultfleetCasted); + } else { + theFleets = Arrays.asList(fleets); + } + return theFleets; + } + + @Override + public void moveRaceColumnUp(String name) { + FlexibleRaceColumn race = null; + for (FlexibleRaceColumn r : races) { + if (r.getName().equals(name)) { + race = r; + } + } + if (race != null) { + int index = 0; + index = races.lastIndexOf(race); + index--; + if (index >= 0) { + races.remove(race); + races.add(index, race); + getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index); + } + } + } + + @Override + public void moveRaceColumnDown(String name) { + FlexibleRaceColumn race = null; + for (FlexibleRaceColumn r : races) { + if (r.getName().equals(name)) { + race = r; + } + } + if (race != null) { + int index = 0; + index = races.lastIndexOf(race); + if (index != -1) { + index++; + if (index < races.size()) { + races.remove(race); + races.add(index, race); + getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index); + } + } + } + } + + @Override + public void updateIsMedalRace(String raceName, boolean isMedalRace) { + FlexibleRaceColumn race = null; + for (FlexibleRaceColumn r : races) { + if (r.getName().equals(raceName)) + race = r; + } + if (race != null) { + race.setIsMedalRace(isMedalRace); + } + } + + @Override + public ScoringScheme getScoringScheme() { + return scoringScheme; + } + + @Override + public CourseArea getDefaultCourseArea() { + return courseArea; + } + + @Override + public void setDefaultCourseArea(CourseArea newCourseArea) { + this.courseArea = newCourseArea; + } + + @Override + public IsRegattaLike getRegattaLike() { + return regattaLikeHelper; + } + + @Override + public RegattaLog getRegattaLog() { + return regattaLikeHelper.getRegattaLog(); + } + + @Override + public RegattaLikeIdentifier getRegattaLikeIdentifier() { + return regattaLikeHelper.getRegattaLikeIdentifier(); + } + + @Override + public void addListener(RegattaLikeListener listener) { + regattaLikeHelper.addListener(listener); + } + + @Override + public void removeListener(RegattaLikeListener listener) { + regattaLikeHelper.removeListener(listener); + } + + @Override + public Double getTimeOnTimeFactor(Competitor competitor) { + return regattaLikeHelper.getTimeOnTimeFactor(competitor); + } + + @Override + public Duration getTimeOnDistanceAllowancePerNauticalMile(Competitor competitor) { + return regattaLikeHelper.getTimeOnDistanceAllowancePerNauticalMile(competitor); + } + + private class RaceExecutionOrderCache extends AbstractRaceExecutionOrderProvider { + private static final long serialVersionUID = 652833386555762661L; + + public RaceExecutionOrderCache() { + super(); + addRaceColumnListener(this); + } + + @Override + protected Map> getRaceColumnsOfSeries() { + final Map> result = new HashMap<>(); + result.put(FlexibleLeaderboardImpl.defaultFleet, getRaceColumns()); + return result; + } + } + + @Override + protected LeaderboardType getLeaderboardType() { + return LeaderboardType.FlexibleLeaderboard; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java index a6df854049f..d5af239cf6a 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java @@ -1,117 +1,123 @@ -package com.sap.sailing.domain.leaderboard.impl; - -import java.util.ArrayList; -import java.util.List; - -import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.CourseArea; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.RaceColumn; -import com.sap.sailing.domain.base.RaceColumnInSeries; -import com.sap.sailing.domain.base.RaceColumnListener; -import com.sap.sailing.domain.base.RaceDefinition; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.base.impl.RaceColumnInSeriesImpl; -import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; -import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; -import com.sap.sailing.domain.leaderboard.ScoringScheme; -import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; -import com.sap.sailing.domain.regattalike.IsRegattaLike; -import com.sap.sailing.domain.tracking.TrackedRace; - -/** - * A leaderboard that is based on the definition of a {@link Regatta} with its {@link Series} and {@link Fleet}. The regatta - * leaderboard listens to its {@link Regatta} as a {@link RaceColumnListener} and forwards all link/unlink events for - * {@link TrackedRace}s being linked to / unlinked from race columns to all {@link RaceColumnListener}s subscribed with - * this leaderboard. - * - * @author Axel Uhl (D043530) - * - */ -public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements RegattaLeaderboard { - private static final long serialVersionUID = 2370461218294770084L; - private final Regatta regatta; - - public RegattaLeaderboardImpl(Regatta regatta, ThresholdBasedResultDiscardingRule resultDiscardingRule) { - super(resultDiscardingRule); - this.regatta = regatta; - regatta.addRaceColumnListener(this); - } - - /** - * Updates the display name of this regatta leaderboard so that the regatta's name is no longer used as the default name. - */ - @Override - public void setName(String newName) { - setDisplayName(newName); - } - - @Override - public Regatta getRegatta() { - return regatta; - } - - @Override - public String getName() { - return getRegatta().getName(); - } - - @Override - public Iterable getRaceColumns() { - List result = new ArrayList(); - for (Series series : getRegatta().getSeries()) { - for (RaceColumn raceColumn : series.getRaceColumns()) { - result.add(raceColumn); - } - } - return result; - } - - @Override - public RaceColumnInSeries getRaceColumnByName(String columnName) { - return (RaceColumnInSeriesImpl) super.getRaceColumnByName(columnName); - } - - @Override - public ScoringScheme getScoringScheme() { - return regatta.getScoringScheme(); - } - - @Override - public CourseArea getDefaultCourseArea() { - return regatta.getDefaultCourseArea(); - } - - /** - * If the regatta' series {@link Regatta#definesSeriesDiscardThresholds() define} their own result discarding rules, this leaderboard uses - * a composite result discarding rule of type {@link PerSeriesResultDiscardingRuleImpl} that contains discards within each series according - * to the series' discarding rules. Otherwise, the default cross-leaderboard result discarding rule is obtained from the super class - * implementation. - */ - @Override - public ResultDiscardingRule getResultDiscardingRule() { - if (regatta.definesSeriesDiscardThresholds()) { - return new PerSeriesResultDiscardingRuleImpl(regatta); - } else { - return super.getResultDiscardingRule(); - } - } - - /** - * Delegates to {@link Regatta#getAllCompetitors()} which is expected to deliver all competitors from this - * leaderboard's regatta which includes those belonging to {@link RaceDefinition races} - * {@link Regatta#getAllRaces() belonging to the regatta} as well as competitors listed on the regatta's - * {@link RegattaLog}. - */ - @Override - public Iterable getAllCompetitors() { - return regatta.getAllCompetitors(); - } - - @Override - public IsRegattaLike getRegattaLike() { - return regatta; - } -} +package com.sap.sailing.domain.leaderboard.impl; + +import java.util.ArrayList; +import java.util.List; + +import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.CourseArea; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.base.RaceColumnInSeries; +import com.sap.sailing.domain.base.RaceColumnListener; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.impl.RaceColumnInSeriesImpl; +import com.sap.sailing.domain.common.LeaderboardType; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; +import com.sap.sailing.domain.leaderboard.ScoringScheme; +import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; +import com.sap.sailing.domain.regattalike.IsRegattaLike; +import com.sap.sailing.domain.tracking.TrackedRace; + +/** + * A leaderboard that is based on the definition of a {@link Regatta} with its {@link Series} and {@link Fleet}. The regatta + * leaderboard listens to its {@link Regatta} as a {@link RaceColumnListener} and forwards all link/unlink events for + * {@link TrackedRace}s being linked to / unlinked from race columns to all {@link RaceColumnListener}s subscribed with + * this leaderboard. + * + * @author Axel Uhl (D043530) + * + */ +public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements RegattaLeaderboard { + private static final long serialVersionUID = 2370461218294770084L; + private final Regatta regatta; + + public RegattaLeaderboardImpl(Regatta regatta, ThresholdBasedResultDiscardingRule resultDiscardingRule) { + super(resultDiscardingRule); + this.regatta = regatta; + regatta.addRaceColumnListener(this); + } + + /** + * Updates the display name of this regatta leaderboard so that the regatta's name is no longer used as the default name. + */ + @Override + public void setName(String newName) { + setDisplayName(newName); + } + + @Override + public Regatta getRegatta() { + return regatta; + } + + @Override + public String getName() { + return getRegatta().getName(); + } + + @Override + public Iterable getRaceColumns() { + List result = new ArrayList(); + for (Series series : getRegatta().getSeries()) { + for (RaceColumn raceColumn : series.getRaceColumns()) { + result.add(raceColumn); + } + } + return result; + } + + @Override + public RaceColumnInSeries getRaceColumnByName(String columnName) { + return (RaceColumnInSeriesImpl) super.getRaceColumnByName(columnName); + } + + @Override + public ScoringScheme getScoringScheme() { + return regatta.getScoringScheme(); + } + + @Override + public CourseArea getDefaultCourseArea() { + return regatta.getDefaultCourseArea(); + } + + /** + * If the regatta' series {@link Regatta#definesSeriesDiscardThresholds() define} their own result discarding rules, this leaderboard uses + * a composite result discarding rule of type {@link PerSeriesResultDiscardingRuleImpl} that contains discards within each series according + * to the series' discarding rules. Otherwise, the default cross-leaderboard result discarding rule is obtained from the super class + * implementation. + */ + @Override + public ResultDiscardingRule getResultDiscardingRule() { + if (regatta.definesSeriesDiscardThresholds()) { + return new PerSeriesResultDiscardingRuleImpl(regatta); + } else { + return super.getResultDiscardingRule(); + } + } + + /** + * Delegates to {@link Regatta#getAllCompetitors()} which is expected to deliver all competitors from this + * leaderboard's regatta which includes those belonging to {@link RaceDefinition races} + * {@link Regatta#getAllRaces() belonging to the regatta} as well as competitors listed on the regatta's + * {@link RegattaLog}. + */ + @Override + public Iterable getAllCompetitors() { + return regatta.getAllCompetitors(); + } + + @Override + public IsRegattaLike getRegattaLike() { + return regatta; + } + + @Override + protected LeaderboardType getLeaderboardType() { + return LeaderboardType.RegattaLeaderboard; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/FlexibleMetaLeaderboard.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/FlexibleMetaLeaderboard.java index a7de5eb4c40..e7f9c04de2d 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/FlexibleMetaLeaderboard.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/FlexibleMetaLeaderboard.java @@ -1,64 +1,70 @@ -package com.sap.sailing.domain.leaderboard.meta; - -import java.util.ArrayList; -import java.util.List; - -import com.sap.sailing.domain.base.CourseArea; -import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.domain.leaderboard.ScoringScheme; -import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; -import com.sap.sse.concurrent.LockUtil; -import com.sap.sse.concurrent.NamedReentrantReadWriteLock; - -public class FlexibleMetaLeaderboard extends AbstractMetaLeaderboard { - private static final long serialVersionUID = 789076326144062944L; - - private final List leaderboards; - - private final NamedReentrantReadWriteLock leaderboardsLock; - - public FlexibleMetaLeaderboard(String name, ScoringScheme scoringScheme, - ThresholdBasedResultDiscardingRule resultDiscardingRule) { - super(name, scoringScheme, resultDiscardingRule); - leaderboards = new ArrayList(); - leaderboardsLock = new NamedReentrantReadWriteLock("leaderboards collection of "+FlexibleMetaLeaderboard.class.getSimpleName()+" "+getName(), - /* fair */ false); - } - - @Override - public Iterable getLeaderboards() { - LockUtil.lockForRead(leaderboardsLock); - try { - return new ArrayList(leaderboards); - } finally { - LockUtil.unlockAfterRead(leaderboardsLock); - } - } - - public void addLeaderboard(Leaderboard leaderboard) { - LockUtil.lockForWrite(leaderboardsLock); - try { - leaderboards.add(leaderboard); - registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard); - getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard)); - } finally { - LockUtil.unlockAfterWrite(leaderboardsLock); - } - } - - public void removeLeaderboard(Leaderboard leaderboard) { - LockUtil.lockForWrite(leaderboardsLock); - try { - leaderboards.remove(leaderboard); - getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(getColumnForLeaderboard(leaderboard)); - unregisterScoreCorrectionChangeForwarder(leaderboard); - } finally { - LockUtil.unlockAfterWrite(leaderboardsLock); - } - } - - @Override - public CourseArea getDefaultCourseArea() { - return null; - } -} +package com.sap.sailing.domain.leaderboard.meta; + +import java.util.ArrayList; +import java.util.List; + +import com.sap.sailing.domain.base.CourseArea; +import com.sap.sailing.domain.common.LeaderboardType; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.ScoringScheme; +import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; +import com.sap.sse.concurrent.LockUtil; +import com.sap.sse.concurrent.NamedReentrantReadWriteLock; + +public class FlexibleMetaLeaderboard extends AbstractMetaLeaderboard { + private static final long serialVersionUID = 789076326144062944L; + + private final List leaderboards; + + private final NamedReentrantReadWriteLock leaderboardsLock; + + public FlexibleMetaLeaderboard(String name, ScoringScheme scoringScheme, + ThresholdBasedResultDiscardingRule resultDiscardingRule) { + super(name, scoringScheme, resultDiscardingRule); + leaderboards = new ArrayList(); + leaderboardsLock = new NamedReentrantReadWriteLock("leaderboards collection of "+FlexibleMetaLeaderboard.class.getSimpleName()+" "+getName(), + /* fair */ false); + } + + @Override + public Iterable getLeaderboards() { + LockUtil.lockForRead(leaderboardsLock); + try { + return new ArrayList(leaderboards); + } finally { + LockUtil.unlockAfterRead(leaderboardsLock); + } + } + + public void addLeaderboard(Leaderboard leaderboard) { + LockUtil.lockForWrite(leaderboardsLock); + try { + leaderboards.add(leaderboard); + registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard); + getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard)); + } finally { + LockUtil.unlockAfterWrite(leaderboardsLock); + } + } + + public void removeLeaderboard(Leaderboard leaderboard) { + LockUtil.lockForWrite(leaderboardsLock); + try { + leaderboards.remove(leaderboard); + getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(getColumnForLeaderboard(leaderboard)); + unregisterScoreCorrectionChangeForwarder(leaderboard); + } finally { + LockUtil.unlockAfterWrite(leaderboardsLock); + } + } + + @Override + public CourseArea getDefaultCourseArea() { + return null; + } + + @Override + protected LeaderboardType getLeaderboardType() { + return LeaderboardType.FlexibleMetaLeaderboard; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/LeaderboardGroupMetaLeaderboard.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/LeaderboardGroupMetaLeaderboard.java index 73860657959..673e1ee5120 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/LeaderboardGroupMetaLeaderboard.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/LeaderboardGroupMetaLeaderboard.java @@ -1,92 +1,98 @@ -package com.sap.sailing.domain.leaderboard.meta; - -import com.sap.sailing.domain.base.CourseArea; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.RaceColumn; -import com.sap.sailing.domain.base.RaceColumnListener; -import com.sap.sailing.domain.base.impl.TrackedRaces; -import com.sap.sailing.domain.common.LeaderboardNameConstants; -import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.domain.leaderboard.LeaderboardGroup; -import com.sap.sailing.domain.leaderboard.LeaderboardGroupListener; -import com.sap.sailing.domain.leaderboard.ScoringScheme; -import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; -import com.sap.sailing.domain.tracking.TrackedRace; - -/** - * A meta leaderboard which considers all regular leaderboards of a {@link LeaderboardGroup}. To stay up to date, this - * meta leaderboard registers itself as a {@link LeaderboardGroupListener} on the leaderboard group it represents. This - * way, whenever a leaderboard is added to or removed from the group, this meta leaderboard can in turn inform its - * {@link RaceColumnListener}s about the impact this change has on the set of {@link TrackedRaces} and therefore the - * competitors attached to this meta leaderboard. - *

- * - * After an object of this type has been de-serialized, and after all objects referenced by it (leaderboard, leaderboard - * group) have been initialized, {@link #registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards()} must be called to - * ensure that score corrections are propagated as desired. This is because score correction listeners are "transient" - * and are as such not serialized. Note: calling {@link #registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards()} - * during readObject or readResolve is not possible because the object graph hasn't been fully - * initialized yet when those methods are called. Hence, the structures for registering the score correction listeners - * are not yet in place at that time. - * - * @author Axel Uhl (d043530) - * - */ -public class LeaderboardGroupMetaLeaderboard extends AbstractMetaLeaderboard implements LeaderboardGroupListener, RaceColumnListener { - private static final long serialVersionUID = 8087872002175528002L; - - private final LeaderboardGroup leaderboardGroup; - - public LeaderboardGroupMetaLeaderboard(LeaderboardGroup leaderboardGroup, ScoringScheme scoringScheme, - ThresholdBasedResultDiscardingRule resultDiscardingRule) { - super(leaderboardGroup.getName() + " " + LeaderboardNameConstants.OVERALL, scoringScheme, resultDiscardingRule); - this.leaderboardGroup = leaderboardGroup; - leaderboardGroup.addLeaderboardGroupListener(this); - registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards(); - } - - public void registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards() { - for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) { - registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard); - } - } - - @Override - public Iterable getLeaderboards() { - return leaderboardGroup.getLeaderboards(); - } - - @Override - public void leaderboardAdded(LeaderboardGroup group, Leaderboard leaderboard) { - registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard); - for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { - for (Fleet fleet : raceColumn.getFleets()) { - TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); - if (trackedRace != null) { - getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace); - } - } - } - getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard)); - } - - @Override - public void leaderboardRemoved(LeaderboardGroup group, Leaderboard leaderboard) { - leaderboard.removeRaceColumnListener(this); - unregisterScoreCorrectionChangeForwarder(leaderboard); - for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { - for (Fleet fleet : raceColumn.getFleets()) { - TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); - if (trackedRace != null) { - getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace); - } - } - } - getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(getColumnForLeaderboard(leaderboard)); - } - - @Override - public CourseArea getDefaultCourseArea() { - return null; - } -} +package com.sap.sailing.domain.leaderboard.meta; + +import com.sap.sailing.domain.base.CourseArea; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.base.RaceColumnListener; +import com.sap.sailing.domain.base.impl.TrackedRaces; +import com.sap.sailing.domain.common.LeaderboardNameConstants; +import com.sap.sailing.domain.common.LeaderboardType; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.leaderboard.LeaderboardGroupListener; +import com.sap.sailing.domain.leaderboard.ScoringScheme; +import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; +import com.sap.sailing.domain.tracking.TrackedRace; + +/** + * A meta leaderboard which considers all regular leaderboards of a {@link LeaderboardGroup}. To stay up to date, this + * meta leaderboard registers itself as a {@link LeaderboardGroupListener} on the leaderboard group it represents. This + * way, whenever a leaderboard is added to or removed from the group, this meta leaderboard can in turn inform its + * {@link RaceColumnListener}s about the impact this change has on the set of {@link TrackedRaces} and therefore the + * competitors attached to this meta leaderboard. + *

+ * + * After an object of this type has been de-serialized, and after all objects referenced by it (leaderboard, leaderboard + * group) have been initialized, {@link #registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards()} must be called to + * ensure that score corrections are propagated as desired. This is because score correction listeners are "transient" + * and are as such not serialized. Note: calling {@link #registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards()} + * during readObject or readResolve is not possible because the object graph hasn't been fully + * initialized yet when those methods are called. Hence, the structures for registering the score correction listeners + * are not yet in place at that time. + * + * @author Axel Uhl (d043530) + * + */ +public class LeaderboardGroupMetaLeaderboard extends AbstractMetaLeaderboard implements LeaderboardGroupListener, RaceColumnListener { + private static final long serialVersionUID = 8087872002175528002L; + + private final LeaderboardGroup leaderboardGroup; + + public LeaderboardGroupMetaLeaderboard(LeaderboardGroup leaderboardGroup, ScoringScheme scoringScheme, + ThresholdBasedResultDiscardingRule resultDiscardingRule) { + super(leaderboardGroup.getName() + " " + LeaderboardNameConstants.OVERALL, scoringScheme, resultDiscardingRule); + this.leaderboardGroup = leaderboardGroup; + leaderboardGroup.addLeaderboardGroupListener(this); + registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards(); + } + + public void registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards() { + for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) { + registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard); + } + } + + @Override + public Iterable getLeaderboards() { + return leaderboardGroup.getLeaderboards(); + } + + @Override + public void leaderboardAdded(LeaderboardGroup group, Leaderboard leaderboard) { + registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard); + for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { + for (Fleet fleet : raceColumn.getFleets()) { + TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); + if (trackedRace != null) { + getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace); + } + } + } + getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard)); + } + + @Override + public void leaderboardRemoved(LeaderboardGroup group, Leaderboard leaderboard) { + leaderboard.removeRaceColumnListener(this); + unregisterScoreCorrectionChangeForwarder(leaderboard); + for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { + for (Fleet fleet : raceColumn.getFleets()) { + TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); + if (trackedRace != null) { + getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace); + } + } + } + getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(getColumnForLeaderboard(leaderboard)); + } + + @Override + public CourseArea getDefaultCourseArea() { + return null; + } + + @Override + protected LeaderboardType getLeaderboardType() { + return LeaderboardType.RegattaMetaLeaderboard; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java index 0925c09f7ba..c06766e9bf3 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java @@ -234,10 +234,12 @@ DynamicTrackedRace, GPSTrackListener { } /** - * In addition to creating the track which is performed by the superclass implementation, this implementation registers - * a {@link GPSTrackListener} with the mark's track and {@link #notifyListeners(GPSFix, Mark, boolean) notifies the listeners} - * about updates. The {@link #updated(TimePoint)} method is not called with the mark fix's time point because - * mark fixes may be received also from marks that don't belong to this race. + * In addition to creating the track which is performed by the superclass implementation, this implementation + * registers a {@link GPSTrackListener} with the mark's track and {@link #notifyListeners(GPSFix, Mark, boolean) + * notifies the listeners} about updates. In previous versions the {@link #updated(TimePoint)} method was + * not called with the mark fix's time point because mark fixes could have been received also from marks + * that don't belong to this race. However, we don't support any connector anymore that works this way. Therefore, + * it is now considered safe to call {@link #updated(TimePoint)} for the mark fix's time point. */ @Override protected DynamicGPSFixTrackImpl createMarkTrack(Mark mark) { @@ -247,6 +249,7 @@ DynamicTrackedRace, GPSTrackListener { @Override public void gpsFixReceived(GPSFix fix, Mark mark, boolean firstFixInTrack) { + updated(fix.getTimePoint()); triggerManeuverCacheRecalculationForAllCompetitors(); notifyListeners(fix, mark, firstFixInTrack); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java old mode 100755 new mode 100644 diff --git a/java/com.sap.sailing.feature.p2build/raceanalysis.product b/java/com.sap.sailing.feature.p2build/raceanalysis.product index 1dead91f56d..df90b607443 100755 --- a/java/com.sap.sailing.feature.p2build/raceanalysis.product +++ b/java/com.sap.sailing.feature.p2build/raceanalysis.product @@ -55,8 +55,6 @@ - - diff --git a/java/com.sap.sailing.feature/feature.xml b/java/com.sap.sailing.feature/feature.xml index fc426ce383c..ec021a0fddb 100755 --- a/java/com.sap.sailing.feature/feature.xml +++ b/java/com.sap.sailing.feature/feature.xml @@ -87,13 +87,6 @@ Computes leaderboard information for sailing races and offers RESTful APIs to al version="0.0.0" unpack="false"/> - - newPlacemarks) { if (cachedPoint != null) { synchronized (cache) { - cache.replace(cachedPoint, new Util.Triple>(cachedPoint, newRadius, newPlacemarks)); + cache.put(cachedPoint, new Util.Triple>(cachedPoint, newRadius, newPlacemarks)); } } } diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LeaderboardPanelMock.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LeaderboardPanelMock.java index 4999b3d2c9f..38c0fbdb061 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LeaderboardPanelMock.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LeaderboardPanelMock.java @@ -20,7 +20,7 @@ public class LeaderboardPanelMock extends LeaderboardPanel { String leaderboardName, ErrorReporter errorReporter, StringMessages stringMessages) { super(sailingService, new AsyncActionsExecutor(), LeaderboardSettingsFactory.getInstance().createNewDefaultSettings( - /* racesToShow */null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true), new CompetitorSelectionModel( + /* racesToShow */null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true), new CompetitorSelectionModel( /* hasMultiSelection */true), leaderboardName, errorReporter, stringMessages, new UserAgentDetails("gecko1_8"), /* showRaceDetails */ true); } diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestColumnSwapping.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestColumnSwapping.java index 38860207a43..a6123d06096 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestColumnSwapping.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestColumnSwapping.java @@ -1,162 +1,162 @@ -package com.sap.sailing.gwt.ui.test; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.UUID; - -import org.junit.Before; -import org.junit.Test; - -import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sailing.domain.common.dto.FleetDTO; -import com.sap.sailing.domain.common.dto.LeaderboardDTO; -import com.sap.sailing.domain.common.dto.RaceColumnDTO; -import com.sap.sailing.gwt.ui.server.SailingServiceImpl; - -public class TestColumnSwapping { - - private LeaderboardDTO lb = null; - private static final String LEADERBOARDNAME = "test"; - private static final String DEFAULT_FLEET_NAME = "Default"; - private static final FleetDTO DEFAULT_FLEET = new FleetDTO(DEFAULT_FLEET_NAME, /* ordering */ 0, /* color */ null); - private SailingServiceImpl service; - private LeaderboardDTO leaderboardOriginalDTO; - private LeaderboardDTO leaderboardDTO; - private Collection leglist; - private Date leaderboardCreationDate; - private SailingServiceImpl sailingService = null; - - // Test-Data - private final String TEST_LEADERBOARD_NAME = "test_board"; - private final String[] races = { "1", "2", "3" }; - private final boolean[] isMedalRace = { false, false, true }; - - @Before - public void prepareColumnSwapping() { - service = new SailingServiceImplMock(); - int[] disc = { 5, 8, 9, 0, 7, 5, 43 }; - service.removeLeaderboard(LEADERBOARDNAME); - service.createFlexibleLeaderboard(LEADERBOARDNAME, null, disc, ScoringSchemeType.LOW_POINT, null); - service.addColumnToLeaderboard("Race1", LEADERBOARDNAME, true); - service.addColumnToLeaderboard("Race2", LEADERBOARDNAME, true); - service.addColumnToLeaderboard("Race3", LEADERBOARDNAME, true); - leglist = new ArrayList(); - leglist.add("Race1"); - leglist.add("Race2"); - leglist.add("Race3"); - leaderboardCreationDate = new Date(); - try { - // get Leaderboard with name and current date - leaderboardOriginalDTO = new LeaderboardDTO(null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() { - @Override - public String generateRandomUUID() { - return UUID.randomUUID().toString(); - } - }, /* hasOverallDetails */ false); - leaderboardOriginalDTO.addRace("Race1", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null); - leaderboardOriginalDTO.addRace("Race3", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null); - leaderboardOriginalDTO.addRace("Race2", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null); - } catch (Exception e) { - e.printStackTrace(); - } - } - - @Test - public void testColumnSwapping() { - testColumnSwappingFabian(); - testLeaderBoardDTOMethods(); - testSailingService(); - } - - @Test - public void testSailingService() { - sailingService = new SailingServiceImplMock(); - assertNotNull("Sailingservice != NULL", sailingService); - int td[] = { 5, 8 }; - sailingService.removeLeaderboard(TEST_LEADERBOARD_NAME); - sailingService.createFlexibleLeaderboard(TEST_LEADERBOARD_NAME, null, td, ScoringSchemeType.LOW_POINT, null); - for (int i = 0; i < races.length; i++) - sailingService.addColumnToLeaderboard(races[i], TEST_LEADERBOARD_NAME, isMedalRace[i]); - sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]); - sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]); - sailingService.moveLeaderboardColumnUp(TEST_LEADERBOARD_NAME, races[2]); - sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true); - sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false); - try { - for (int i = 0; i < races.length; i++) - sailingService.addColumnToLeaderboard(races[i], TEST_LEADERBOARD_NAME, isMedalRace[i]); - sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]); - sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]); - sailingService.moveLeaderboardColumnUp(TEST_LEADERBOARD_NAME, races[2]); - sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true); - sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false); - lb = sailingService.getLeaderboardByName(TEST_LEADERBOARD_NAME, new Date(), /* races to load */ null, /* addOverallDetails */ true, /* previous leaderboard ID */ null, - /* fillNetPointsUncorrected */ false).getLeaderboardDTO(/* previousVersion */ null); - } catch (Exception e) { - // e.printStackTrace(); - fail(e.getLocalizedMessage()); - } - assertNotNull("LeaderboardDTO != NULL", lb); - - // asserted data - String[] raceNames = new String[] { "3", "2", "1" }; - boolean[] medalRace = { true, false, false }; - - for (int i = 0; i < lb.getRaceList().size(); i++) { - assertEquals("Race[" + i + "] == " + raceNames[i], raceNames[i], lb.getRaceList().get(i).getName()); - assertTrue("Race[" + i + "] is " + (medalRace[i] ? "" : "no ") + "medalrace.", - lb.raceIsMedalRace(races[i]) == medalRace[i]); - } - } - - @Test - public void testLeaderBoardDTOMethods() { - lb = new LeaderboardDTO(null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() { - @Override - public String generateRandomUUID() { - return UUID.randomUUID().toString(); - } - }, /* hasOverallDetails */ false); - assertNotNull("Leaderboard != NULL", lb); - lb.addRace("1", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, false, null, null); - lb.addRace("2", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, false, null, null); - lb.addRace("3", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null); - lb.moveRaceDown("1"); - String[] s = new String[] { "2", "1", "3" }; - for (int i = 0; i < lb.getRaceList().size(); i++) { - assertEquals("Race[" + i + "] = " + s[i], s[i], lb.getRaceList().get(i).getName()); - } - } - - @Test - public void testColumnSwappingFabian() { - service.moveLeaderboardColumnUp(LEADERBOARDNAME, "Race3"); - try { - leaderboardDTO = service.getLeaderboardByName(LEADERBOARDNAME, leaderboardCreationDate, leglist, /* addOverallDetails */ - true, /* previous leaderboard ID */null, /* fillNetPointsUncorrected */false).getLeaderboardDTO(/* previousVersion */null); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - // check if leaderboardDTO an dleaderboardOriginalDTO same - List leaderboardList = leaderboardDTO.getRaceList(); - List leaderboardOriginalList = leaderboardOriginalDTO.getRaceList(); - - // ??????? assert races in list - assertArrayEquals(leaderboardList.toArray(), leaderboardOriginalList.toArray()); - - for (RaceColumnDTO raceDTO : leaderboardOriginalList) { - assert leaderboardDTO.raceIsMedalRace(raceDTO.getRaceColumnName()) == leaderboardOriginalDTO.raceIsMedalRace(raceDTO.getRaceColumnName()); - assert leaderboardDTO.raceIsTracked(raceDTO.getRaceColumnName()) == leaderboardOriginalDTO.raceIsTracked(raceDTO.getRaceColumnName()); - } - } -} +package com.sap.sailing.gwt.ui.test; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.domain.common.dto.LeaderboardDTO; +import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.gwt.ui.server.SailingServiceImpl; + +public class TestColumnSwapping { + + private LeaderboardDTO lb = null; + private static final String LEADERBOARDNAME = "test"; + private static final String DEFAULT_FLEET_NAME = "Default"; + private static final FleetDTO DEFAULT_FLEET = new FleetDTO(DEFAULT_FLEET_NAME, /* ordering */ 0, /* color */ null); + private SailingServiceImpl service; + private LeaderboardDTO leaderboardOriginalDTO; + private LeaderboardDTO leaderboardDTO; + private Collection leglist; + private Date leaderboardCreationDate; + private SailingServiceImpl sailingService = null; + + // Test-Data + private final String TEST_LEADERBOARD_NAME = "test_board"; + private final String[] races = { "1", "2", "3" }; + private final boolean[] isMedalRace = { false, false, true }; + + @Before + public void prepareColumnSwapping() { + service = new SailingServiceImplMock(); + int[] disc = { 5, 8, 9, 0, 7, 5, 43 }; + service.removeLeaderboard(LEADERBOARDNAME); + service.createFlexibleLeaderboard(LEADERBOARDNAME, null, disc, ScoringSchemeType.LOW_POINT, null); + service.addColumnToLeaderboard("Race1", LEADERBOARDNAME, true); + service.addColumnToLeaderboard("Race2", LEADERBOARDNAME, true); + service.addColumnToLeaderboard("Race3", LEADERBOARDNAME, true); + leglist = new ArrayList(); + leglist.add("Race1"); + leglist.add("Race2"); + leglist.add("Race3"); + leaderboardCreationDate = new Date(); + try { + // get Leaderboard with name and current date + leaderboardOriginalDTO = new LeaderboardDTO(null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() { + @Override + public String generateRandomUUID() { + return UUID.randomUUID().toString(); + } + }, /* hasOverallDetails */ false); + leaderboardOriginalDTO.addRace("Race1", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null, false); + leaderboardOriginalDTO.addRace("Race3", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null, false); + leaderboardOriginalDTO.addRace("Race2", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null, false); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void testColumnSwapping() { + testColumnSwappingFabian(); + testLeaderBoardDTOMethods(); + testSailingService(); + } + + @Test + public void testSailingService() { + sailingService = new SailingServiceImplMock(); + assertNotNull("Sailingservice != NULL", sailingService); + int td[] = { 5, 8 }; + sailingService.removeLeaderboard(TEST_LEADERBOARD_NAME); + sailingService.createFlexibleLeaderboard(TEST_LEADERBOARD_NAME, null, td, ScoringSchemeType.LOW_POINT, null); + for (int i = 0; i < races.length; i++) + sailingService.addColumnToLeaderboard(races[i], TEST_LEADERBOARD_NAME, isMedalRace[i]); + sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]); + sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]); + sailingService.moveLeaderboardColumnUp(TEST_LEADERBOARD_NAME, races[2]); + sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true); + sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false); + try { + for (int i = 0; i < races.length; i++) + sailingService.addColumnToLeaderboard(races[i], TEST_LEADERBOARD_NAME, isMedalRace[i]); + sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]); + sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]); + sailingService.moveLeaderboardColumnUp(TEST_LEADERBOARD_NAME, races[2]); + sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true); + sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false); + lb = sailingService.getLeaderboardByName(TEST_LEADERBOARD_NAME, new Date(), /* races to load */ null, /* addOverallDetails */ true, /* previous leaderboard ID */ null, + /* fillNetPointsUncorrected */ false).getLeaderboardDTO(/* previousVersion */ null); + } catch (Exception e) { + // e.printStackTrace(); + fail(e.getLocalizedMessage()); + } + assertNotNull("LeaderboardDTO != NULL", lb); + + // asserted data + String[] raceNames = new String[] { "3", "2", "1" }; + boolean[] medalRace = { true, false, false }; + + for (int i = 0; i < lb.getRaceList().size(); i++) { + assertEquals("Race[" + i + "] == " + raceNames[i], raceNames[i], lb.getRaceList().get(i).getName()); + assertTrue("Race[" + i + "] is " + (medalRace[i] ? "" : "no ") + "medalrace.", + lb.raceIsMedalRace(races[i]) == medalRace[i]); + } + } + + @Test + public void testLeaderBoardDTOMethods() { + lb = new LeaderboardDTO(null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() { + @Override + public String generateRandomUUID() { + return UUID.randomUUID().toString(); + } + }, /* hasOverallDetails */ false); + assertNotNull("Leaderboard != NULL", lb); + lb.addRace("1", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, false, null, null, false); + lb.addRace("2", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, false, null, null, false); + lb.addRace("3", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null, false); + lb.moveRaceDown("1"); + String[] s = new String[] { "2", "1", "3" }; + for (int i = 0; i < lb.getRaceList().size(); i++) { + assertEquals("Race[" + i + "] = " + s[i], s[i], lb.getRaceList().get(i).getName()); + } + } + + @Test + public void testColumnSwappingFabian() { + service.moveLeaderboardColumnUp(LEADERBOARDNAME, "Race3"); + try { + leaderboardDTO = service.getLeaderboardByName(LEADERBOARDNAME, leaderboardCreationDate, leglist, /* addOverallDetails */ + true, /* previous leaderboard ID */null, /* fillNetPointsUncorrected */false).getLeaderboardDTO(/* previousVersion */null); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + // check if leaderboardDTO an dleaderboardOriginalDTO same + List leaderboardList = leaderboardDTO.getRaceList(); + List leaderboardOriginalList = leaderboardOriginalDTO.getRaceList(); + + // ??????? assert races in list + assertArrayEquals(leaderboardList.toArray(), leaderboardOriginalList.toArray()); + + for (RaceColumnDTO raceDTO : leaderboardOriginalList) { + assert leaderboardDTO.raceIsMedalRace(raceDTO.getRaceColumnName()) == leaderboardOriginalDTO.raceIsMedalRace(raceDTO.getRaceColumnName()); + assert leaderboardDTO.raceIsTracked(raceDTO.getRaceColumnName()) == leaderboardOriginalDTO.raceIsTracked(raceDTO.getRaceColumnName()); + } + } +} diff --git a/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs b/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs index d6863f1498a..fef4a78a7dc 100755 --- a/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs +++ b/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs @@ -3,7 +3,21 @@ encoding//src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages.prop encoding//src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_de.properties=UTF-8 encoding//src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_ru.properties=UTF-8 encoding//src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_zh.properties=UTF-8 +encoding//src/main/java/com/sap/sailing/gwt/home/mobile/partials/sectionHeader/SectionHeaderContent.java=utf-8 +encoding//src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaRaceStatesComponent.java=utf-8 +encoding//src/main/java/com/sap/sailing/gwt/server/HomeServiceUtil.java=utf-8 +encoding//src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDetailsComposite.java=utf-8 +encoding//src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java=utf-8 +encoding//src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java=utf-8 encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties=UTF-8 encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties=UTF-8 encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties=UTF-8 encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties=UTF-8 +encoding//src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java=utf-8 +encoding//src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/AbstractResultsPresenter.java=utf-8 +encoding//src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/PlainResultsPresenter.java=utf-8 +encoding//src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompetitorFilterPanel.java=utf-8 +encoding//src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java=utf-8 +encoding//src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiLeaderboardPanel.java=utf-8 +encoding//src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java=utf-8 +encoding//src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java=utf-8 diff --git a/java/com.sap.sailing.gwt.ui/Leaderboard.css b/java/com.sap.sailing.gwt.ui/Leaderboard.css index 8ab68acf9b9..ca7ab51f790 100755 --- a/java/com.sap.sailing.gwt.ui/Leaderboard.css +++ b/java/com.sap.sailing.gwt.ui/Leaderboard.css @@ -98,13 +98,13 @@ body, table td, select, button { padding: 49px 0 0 80px; width: 300px; color: #7c7c7c; - background: url(images/important-message.png) left center no-repeat; + background: url(images/important-message.png) left center no-repeat; } .LeaderboardHeader { position: relative; margin: 0 auto; - width: 400px; + width: 100%; text-align: center; font-family: 'Open Sans', Arial, Verdana, sans-serif; font-size: 18px; diff --git a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF index 5eeb8e17321..43c9489242f 100644 --- a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF @@ -72,6 +72,15 @@ TrackLifecycle: true Export-Package: com.google.gwt.user.client.rpc.core.com.sap.sailing.domain.common.confidence.impl, com.google.gwt.user.client.rpc.core.com.sap.sailing.domain.common.impl, com.google.gwt.user.client.rpc.core.com.sap.sailing.domain.common.tracking.impl, + com.sap.sailing.gwt.dispatch.client, + com.sap.sailing.gwt.dispatch.client.batching, + com.sap.sailing.gwt.dispatch.client.caching, + com.sap.sailing.gwt.dispatch.client.exceptions, + com.sap.sailing.gwt.dispatch.client.impl, + com.sap.sailing.gwt.dispatch.client.rpcimpl, + com.sap.sailing.gwt.home.communication, + com.sap.sailing.gwt.home.server, + com.sap.sailing.gwt.server, com.sap.sailing.gwt.ui.actions, com.sap.sailing.gwt.ui.client, com.sap.sailing.gwt.ui.client.shared.charts, diff --git a/java/com.sap.sailing.gwt.ui/RaceBoard.css b/java/com.sap.sailing.gwt.ui/RaceBoard.css index 0e7cd4f163c..0175c2a97dd 100644 --- a/java/com.sap.sailing.gwt.ui/RaceBoard.css +++ b/java/com.sap.sailing.gwt.ui/RaceBoard.css @@ -6,6 +6,13 @@ height: 0; clear: both; visibility: hidden; + + RegattaRaceInformation-Header + RaceName-Label + RaceSeriesAndFleet-Label + + RegattaAndRaceTime-Header??? + RegattaName-Anchor */ @@ -87,6 +94,14 @@ table { padding-top: 10px; padding-left: 10px; } +.CombinedWindPanelParentDiv.indentsmall, +.TrueNorthIndicatorPanelParentDiv.indentsmall { + padding-left: 30px; +} +.CombinedWindPanelParentDiv.indentbig, +.TrueNorthIndicatorPanelParentDiv.indentbig { + padding-left: 45px; +} .MapSimulationLegendParentDiv { padding-bottom: 10px; @@ -573,7 +588,8 @@ img.openColumn { url(images/viewicons/@1x/img/SAP_RV_Leaderboard_GHOSTED.png), url(images/arrows_top_black_small.png); background-repeat: no-repeat, no-repeat; - background-position: 4% 50%, 96% 50% + background-position: 4% 50%, 96% 50%; + background-position: 5px center, calc(100% - 6px) center; } .gwt-SplitLayoutPanel-EastToggleButton-Closed-leaderboard { @@ -582,7 +598,8 @@ img.openColumn { url(images/viewicons/@1x/img/SAP_RV_Leaderboard_INACTIVE.png), url(images/arrows_bottom_black_small.png); background-repeat: no-repeat, no-repeat; - background-position: 4% 50%, 96% 50% + background-position: 4% 50%, 96% 50%; + background-position: 5px center, calc(100% - 6px) center; } .gwt-SplitLayoutPanel-NorthSouthToggleButton-Closed-media, @@ -664,6 +681,32 @@ img.openColumn { margin-top: 5px; } +.compactHeader .RegattaRaceInformation-Header { + left: 110px; +} +.compactHeader .RegattaRaceInformation-Header .RaceName-Label { + display: block; + font-size: 16px; + padding: 2px 0; +} +.compactHeader .RegattaRaceInformation-Header .RaceSeriesAndFleet-Label { + display: block; + margin-top: 5px; + white-space: nowrap; +} +.compactHeader .RegattaAndRaceTime-Header { + top: 11px; +} +.compactHeader .RegattaAndRaceTime-Header .RegattaName-Anchor { + width: 115px; + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + white-space: nowrap; + padding: 2px 0 2px 20px; + background-position: 4px -1px; +} + .VideoPopup-Close-Button { border: none; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/player/AutoPlayController.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/player/AutoPlayController.java index 5cfe4fe6697..3dc87cce2bc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/player/AutoPlayController.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/player/AutoPlayController.java @@ -1,9 +1,7 @@ package com.sap.sailing.gwt.autoplay.client.place.player; import java.util.ArrayList; -import java.util.Collections; import java.util.Date; -import java.util.List; import java.util.Map; import com.google.gwt.core.client.Scheduler; @@ -29,7 +27,6 @@ import com.sap.sailing.gwt.common.client.FullscreenUtil; import com.sap.sailing.gwt.common.client.i18n.TextMessages; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; import com.sap.sailing.gwt.ui.client.MediaServiceAsync; -import com.sap.sailing.gwt.ui.client.RaceSelectionModel; import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -107,7 +104,7 @@ public class AutoPlayController implements RaceTimesInfoProviderListener { asyncActionsExecutor = new AsyncActionsExecutor(); showWindChart = false; leaderboard = null; - leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true); + leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true); leaderboardTimer = new Timer(PlayModes.Live, /* delayBetweenAutoAdvancesInMilliseconds */1000l); leaderboardTimer.setLivePlayDelayInMillis(delayToLiveInMillis); @@ -167,13 +164,10 @@ public class AutoPlayController implements RaceTimesInfoProviderListener { } private RaceBoardPanel createRaceBoardPanel(String leaderboardName, RegattaAndRaceIdentifier raceToShow, Map competitorsAndTheirBoats) { - RaceSelectionModel raceSelectionModel = new RaceSelectionModel(); - List singletonList = Collections.singletonList(raceToShow); - raceSelectionModel.setSelection(singletonList); - RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, userService, asyncActionsExecutor, - competitorsAndTheirBoats, raceboardTimer, raceSelectionModel, leaderboardName, null, /* event */null, raceboardViewConfig, - errorReporter, StringMessages.INSTANCE, userAgent, raceTimesInfoProvider, /* showMapControls */false); + competitorsAndTheirBoats, raceboardTimer, raceToShow, leaderboardName, null, /* event */null, raceboardViewConfig, + errorReporter, StringMessages.INSTANCE, userAgent, raceTimesInfoProvider, /* showMapControls */ false, + /* isScreenLargeEnoughToOfferChartSupport */ true); return raceBoardPanel; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/batching/AutomaticBatchingDispatch.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/batching/AutomaticBatchingDispatch.java index 59a28fa0249..05da5d64a75 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/batching/AutomaticBatchingDispatch.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/batching/AutomaticBatchingDispatch.java @@ -18,6 +18,8 @@ public class AutomaticBatchingDispatch implements D private DispatchCallStack currentCallstack; + private boolean processResultsScheduled; + /** * Creates a batching dispatch service using the given dispatch implementation. * @@ -28,7 +30,21 @@ public class AutomaticBatchingDispatch implements D * */ public AutomaticBatchingDispatch(DispatchAsync service) { + this(service, false); + } + + /** + * Creates a batching dispatch service using the given dispatch implementation. + * + * The underlying dispatch service is responsible for the context propagation. + * + * @param service + * the underlying service implementation to use + * + */ + public AutomaticBatchingDispatch(DispatchAsync service, boolean processResultsScheduled) { this.executionContext = service; + this.processResultsScheduled = processResultsScheduled; } /** @@ -73,7 +89,7 @@ public class AutomaticBatchingDispatch implements D @Override public void onSuccess(BatchResult result) { - callStackInExecution.processResult(result); + callStackInExecution.processResult(result, processResultsScheduled); } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/batching/DispatchCallStack.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/batching/DispatchCallStack.java index 2bec8747ffb..9f41dd26cf5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/batching/DispatchCallStack.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/batching/DispatchCallStack.java @@ -2,6 +2,7 @@ package com.sap.sailing.gwt.dispatch.client.batching; import java.util.LinkedList; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sailing.gwt.dispatch.client.Action; import com.sap.sailing.gwt.dispatch.client.DispatchContext; @@ -70,22 +71,44 @@ public final class DispatchCallStack { * * @param batchResult */ - @SuppressWarnings("unchecked") - public void processResult(BatchResult batchResult) { + public void processResult(BatchResult batchResult, boolean processResultsScheduled) { assert (batchResult != null); int loop = 0; for (@SuppressWarnings("rawtypes") DispatchCall call : this.actionQueue) { Result result = batchResult.getResult(loop); + Throwable exception = batchResult.getException(loop); - if (result == null) { - Throwable exception = batchResult.getException(loop); - call.getCallback().onFailure(exception); + CallbackCommand callbackCommand = new CallbackCommand(call, result, exception); + if(processResultsScheduled) { + SplitScheduler.get().schedule(callbackCommand); } else { - call.getCallback().onSuccess(result); + callbackCommand.execute(); } loop++; } } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private class CallbackCommand implements ScheduledCommand { + private final DispatchCall call; + private final Result result; + private final Throwable exception; + public CallbackCommand(DispatchCall call, Result result, Throwable exception) { + this.call = call; + this.result = result; + this.exception = exception; + } + + @Override + public void execute() { + if (result == null) { + call.getCallback().onFailure(exception); + } else { + call.getCallback().onSuccess(result); + } + } + + } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/batching/SplitScheduler.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/batching/SplitScheduler.java new file mode 100644 index 00000000000..c92b460b3db --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/batching/SplitScheduler.java @@ -0,0 +1,52 @@ +package com.sap.sailing.gwt.dispatch.client.batching; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.RepeatingCommand; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; + +public class SplitScheduler { + + private static final SplitScheduler INSTANCE = new SplitScheduler(); + + private List pendingCommands = new ArrayList(); + + private boolean scheduled = false; + + private final RepeatingCommand repeatingCommand = new RepeatingCommand() { + @Override + public boolean execute() { + ScheduledCommand commandToExecute = pendingCommands.remove(0); + + try { + commandToExecute.execute(); + } catch (Exception e) { + if(GWT.getUncaughtExceptionHandler() != null) { + GWT.getUncaughtExceptionHandler().onUncaughtException(e); + } + } + scheduled = !pendingCommands.isEmpty(); + return scheduled; + } + }; + + private SplitScheduler() { + } + + public static SplitScheduler get() { + return INSTANCE; + } + + public void schedule(ScheduledCommand command) { + pendingCommands.add(command); + + if(!scheduled) { + Scheduler.get().scheduleIncremental(repeatingCommand); + scheduled = true; + } + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/impl/DispatchSystemImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/impl/DispatchSystemImpl.java index 5bb331f988f..b90d0b900f5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/impl/DispatchSystemImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/impl/DispatchSystemImpl.java @@ -15,10 +15,19 @@ import com.sap.sailing.gwt.dispatch.client.rpcimpl.SimpleDispatch; public class DispatchSystemImpl implements DispatchSystem { - private final SimpleDispatch simpleDispatch = new SimpleDispatch(null); - private final DispatchAsync dispatch = new CachingDispatch(new AutomaticBatchingDispatch( - simpleDispatch)); + private final SimpleDispatch simpleDispatch; + private final DispatchAsync dispatch; + public DispatchSystemImpl(String dispatchRPCPath) { + this(dispatchRPCPath, false); + } + + public DispatchSystemImpl(String dispatchRPCPath, boolean processResultsScheduled) { + simpleDispatch = new SimpleDispatch(dispatchRPCPath); + dispatch = new CachingDispatch(new AutomaticBatchingDispatch( + simpleDispatch, processResultsScheduled)); + } + @Override public > void execute(final A action, final AsyncCallback callback) { // TODO: client side execution time logging diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/rpcimpl/SimpleDispatch.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/rpcimpl/SimpleDispatch.java index 63b564c4e04..49ab4df3730 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/rpcimpl/SimpleDispatch.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/dispatch/client/rpcimpl/SimpleDispatch.java @@ -9,8 +9,6 @@ import com.sap.sailing.gwt.dispatch.client.DispatchAsync; import com.sap.sailing.gwt.dispatch.client.DispatchContext; import com.sap.sailing.gwt.dispatch.client.Result; import com.sap.sailing.gwt.dispatch.client.ResultWrapper; -import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; -import com.sap.sailing.gwt.ui.client.SailingClientFactory; public class SimpleDispatch implements DispatchAsync { @@ -18,8 +16,8 @@ public class SimpleDispatch implements DispatchAsyn private long clientServerOffset = 0; - public SimpleDispatch(SailingClientFactory clientFactory) { - ((ServiceDefTarget) dispatchRPC).setServiceEntryPoint(RemoteServiceMappingConstants.dispatchServiceRemotePath); + public SimpleDispatch(String dispatchRPCPath) { + ((ServiceDefTarget) dispatchRPC).setServiceEntryPoint(dispatchRPCPath); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/SailingDispatchSystemImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/SailingDispatchSystemImpl.java index f6a83a87e13..db62ef9885d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/SailingDispatchSystemImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/SailingDispatchSystemImpl.java @@ -1,7 +1,12 @@ package com.sap.sailing.gwt.home.communication; import com.sap.sailing.gwt.dispatch.client.impl.DispatchSystemImpl; +import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; public class SailingDispatchSystemImpl extends DispatchSystemImpl implements SailingDispatchSystem { + + public SailingDispatchSystemImpl() { + super(RemoteServiceMappingConstants.dispatchServiceRemotePath); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetRegattasAndLiveRacesForEventAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetRegattasAndLiveRacesForEventAction.java index e6c9cf92821..b70f6c18a2c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetRegattasAndLiveRacesForEventAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetRegattasAndLiveRacesForEventAction.java @@ -43,7 +43,7 @@ public class GetRegattasAndLiveRacesForEventAction implements SailingAction extends FullscreenContainer implements LeaderboardDelegate { @@ -15,14 +19,18 @@ public abstract class AbstractLeaderboardFullscreenViewer exte private final Image autoRefreshControl = new Image("images/home/reload.svg"); private final Image settingsControl = new Image("images/home/settings.svg"); + private final static BusyIndicatorResources RESOURCES = GWT.create(BusyIndicatorResources.class); + private final Label lastScoringUpdateTime = new Label(); private final Label lastScoringUpdateText = new Label(); protected final Label lastScoringComment = new Label(); protected final Label scoringScheme = new Label(); + protected final BusyIndicator busyIndicator = new SimpleBusyIndicator(false, 0.9f, RESOURCES.busyIndicatorCircleInverted()); public AbstractLeaderboardFullscreenViewer() { showLogo(); showBorder(); + addToolbarBusyIndicator(busyIndicator); addToolbarInfo(createPanel(lastScoringUpdateText, lastScoringUpdateTime)); addToolbarAction(autoRefreshControl); addToolbarAction(settingsControl); @@ -88,4 +96,13 @@ public abstract class AbstractLeaderboardFullscreenViewer exte return scoringScheme.getElement(); } + @Override + public Element getBusyIndicatorElement() { + return busyIndicator.getElement(); + } + + @Override + public void setBusyState(boolean isBusy) { + busyIndicator.setBusy(isBusy); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/EventRegattaLeaderboard.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/EventRegattaLeaderboard.gss index 7aee44185c8..0eaa3b013d7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/EventRegattaLeaderboard.gss +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/EventRegattaLeaderboard.gss @@ -106,6 +106,11 @@ background-color: #f0ab00; } +.regattaleaderboard_meta_busyindicator { + display: inline-block; + margin: 0 0.666666666666667em; +} + .regattaleaderboard_meta_liverace { color: red; font-weight: 600; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/EventRegattaLeaderboardResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/EventRegattaLeaderboardResources.java index fee597cac95..90bef62da36 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/EventRegattaLeaderboardResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/EventRegattaLeaderboardResources.java @@ -30,6 +30,7 @@ public interface EventRegattaLeaderboardResources extends ClientBundle { String regattaleaderboard_meta_update_type(); String regattaleaderboard_meta_scoring_timestamp(); String regattaleaderboard_meta_update_timestamp(); + String regattaleaderboard_meta_busyindicator(); String regattaleaderboard_meta_scoring_text(); String regattaleaderboard_meta_update_text(); String regattaleaderboard_meta_reload(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/LeaderboardDelegate.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/LeaderboardDelegate.java index c1523e63036..fd6ee999728 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/LeaderboardDelegate.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/LeaderboardDelegate.java @@ -10,6 +10,8 @@ public interface LeaderboardDelegate { void setLeaderboardPanel(T leaderboardPanel); + void setBusyState(boolean isBusy); + Widget getAutoRefreshControl(); Widget getSettingsControl(); @@ -24,4 +26,5 @@ public interface LeaderboardDelegate { Element getScoringSchemeElement(); + Element getBusyIndicatorElement(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/leaderboard/OldLeaderboard.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/leaderboard/OldLeaderboard.java index cc8fecd8c30..2fb55ac6429 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/leaderboard/OldLeaderboard.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/leaderboard/OldLeaderboard.java @@ -31,12 +31,14 @@ import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter; +import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator; +import com.sap.sse.gwt.client.controls.busyindicator.BusyStateChangeListener; import com.sap.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.player.Timer.PlayModes; import com.sap.sse.gwt.client.player.Timer.PlayStates; import com.sap.sse.gwt.client.shared.components.SettingsDialog; -public class OldLeaderboard extends Composite { +public class OldLeaderboard extends Composite implements BusyStateChangeListener { private static OldLeaderboardUiBinder uiBinder = GWT.create(OldLeaderboardUiBinder.class); interface OldLeaderboardUiBinder extends UiBinder { @@ -52,6 +54,7 @@ public class OldLeaderboard extends Composite { @UiField DivElement lastScoringCommentDiv; @UiField DivElement scoringSchemeDiv; @UiField DivElement hasLiveRaceDiv; + @UiField BusyIndicator busyIndicator; @UiField EventRegattaLeaderboardResources local_res; private LeaderboardPanel leaderboardPanel; @@ -166,6 +169,7 @@ public class OldLeaderboard extends Composite { this.autoRefreshTimer = timer; this.leaderboardPanel = leaderboardPanel; oldLeaderboardPanel.add(leaderboardPanel); + leaderboardPanel.addBusyStateChangeListener(this); } public void updatedLeaderboard(LeaderboardDTO leaderboard) { @@ -217,8 +221,17 @@ public class OldLeaderboard extends Composite { } lastScoringUpdateTimeDiv.getStyle().setVisibility(Visibility.HIDDEN); } - + + @Override + public void onBusyStateChange(boolean busyState) { + busyIndicator.setBusy(busyState); + if(delegate != null) { + delegate.setBusyState(busyState); + } + } + public interface OldLeaderboardDelegate extends LeaderboardDelegate { Element getHasLiveRaceElement(); } + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/leaderboard/OldLeaderboard.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/leaderboard/OldLeaderboard.ui.xml index 593f26846a6..a65cf15c133 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/leaderboard/OldLeaderboard.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/leaderboard/OldLeaderboard.ui.xml @@ -1,16 +1,18 @@ - +

-
+
-
+
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/multileaderboard/OldMultiLeaderboard.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/multileaderboard/OldMultiLeaderboard.java index 0d822d22293..0cb578288a0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/multileaderboard/OldMultiLeaderboard.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/multileaderboard/OldMultiLeaderboard.java @@ -3,7 +3,7 @@ package com.sap.sailing.gwt.home.desktop.partials.old.multileaderboard; import java.util.Date; import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.ParagraphElement; +import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Style.Overflow; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.Style.Visibility; @@ -27,15 +27,19 @@ import com.sap.sailing.gwt.home.desktop.partials.old.LeaderboardDelegate; import com.sap.sailing.gwt.ui.client.DebugIdHelper; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; import com.sap.sailing.gwt.ui.leaderboard.MultiLeaderboardPanel; import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter; +import com.sap.sailing.gwt.ui.leaderboard.SelectedLeaderboardChangeListener; +import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator; +import com.sap.sse.gwt.client.controls.busyindicator.BusyStateChangeListener; import com.sap.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.player.Timer.PlayModes; import com.sap.sse.gwt.client.player.Timer.PlayStates; import com.sap.sse.gwt.client.shared.components.SettingsDialog; -public class OldMultiLeaderboard extends Composite { +public class OldMultiLeaderboard extends Composite implements SelectedLeaderboardChangeListener, BusyStateChangeListener { private static OldMultiLeaderboardUiBinder uiBinder = GWT.create(OldMultiLeaderboardUiBinder.class); interface OldMultiLeaderboardUiBinder extends UiBinder { @@ -46,16 +50,18 @@ public class OldMultiLeaderboard extends Composite { @UiField Anchor settingsAnchor; @UiField Anchor autoRefreshAnchor; @UiField Anchor fullscreenAnchor; - @UiField ParagraphElement lastScoringUpdateTimeDiv; - @UiField ParagraphElement lastScoringUpdateTextDiv; - @UiField ParagraphElement lastScoringCommentDiv; - @UiField ParagraphElement scoringSchemeDiv; + @UiField DivElement lastScoringUpdateTimeDiv; + @UiField DivElement lastScoringUpdateTextDiv; + @UiField DivElement lastScoringCommentDiv; + @UiField DivElement scoringSchemeDiv; + @UiField BusyIndicator busyIndicator; @UiField EventRegattaLeaderboardResources local_res; private MultiLeaderboardPanel multiLeaderboardPanel; private Timer autoRefreshTimer; private final OldMultiLeaderboardDelegate delegate; - + private LeaderboardPanel lastSelectedLeaderboardPanel; + public OldMultiLeaderboard() { this(null); } @@ -69,6 +75,7 @@ public class OldMultiLeaderboard extends Composite { fullscreenAnchor.setTitle(StringMessages.INSTANCE.openFullscreenView()); this.delegate = delegate; this.setupFullscreenDelegate(); + lastSelectedLeaderboardPanel = null; } private void setupFullscreenDelegate() { @@ -163,6 +170,7 @@ public class OldMultiLeaderboard extends Composite { public void setMultiLeaderboard(MultiLeaderboardPanel multiLeaderboardPanel, final Timer timer) { this.autoRefreshTimer = timer; this.multiLeaderboardPanel = multiLeaderboardPanel; + this.multiLeaderboardPanel.addSelectedLeaderboardChangeListener(this); oldMultiLeaderboardPanel.add(multiLeaderboardPanel); } @@ -198,6 +206,23 @@ public class OldMultiLeaderboard extends Composite { } } } + + @Override + public void onBusyStateChange(boolean busyState) { + busyIndicator.setBusy(busyState); + if(delegate != null) { + delegate.setBusyState(busyState); + } + } + + @Override + public void onSelectedLeaderboardChanged(LeaderboardPanel selectedLeaderboard) { + if(lastSelectedLeaderboardPanel != null) { + lastSelectedLeaderboardPanel.removeBusyStateChangeListener(this); + } + selectedLeaderboard.addBusyStateChangeListener(this); + lastSelectedLeaderboardPanel = selectedLeaderboard; + } public interface OldMultiLeaderboardDelegate extends LeaderboardDelegate{ } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/multileaderboard/OldMultiLeaderboard.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/multileaderboard/OldMultiLeaderboard.ui.xml index 737bef4a01d..55a4cf060f9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/multileaderboard/OldMultiLeaderboard.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/old/multileaderboard/OldMultiLeaderboard.ui.xml @@ -1,23 +1,25 @@ - +
-

-

+
+
+
-

-

+
+
-
+
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/utils/EventParamUtils.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/utils/EventParamUtils.java index d10d5917c86..e2c3f612111 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/utils/EventParamUtils.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/utils/EventParamUtils.java @@ -108,7 +108,7 @@ public class EventParamUtils { .createNewDefaultSettings(null, null, /* overallDetails */overallDetails, null, /* autoExpandFirstRace */false, refreshIntervalMillis, numberOfLastRacesToShow, - raceColumnSelectionStrategy); + raceColumnSelectionStrategy, /* showCompetitorSailIdColumns */ true, /*showCompetitorFullNameColumn*/ true); } return result; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventActivity.java index d0867a28588..4edafe69a6b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventActivity.java @@ -44,7 +44,6 @@ import com.sap.sailing.gwt.home.shared.places.fakeseries.SeriesDefaultPlace; import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.refresh.ErrorAndBusyClientFactory; -import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration; import com.sap.sailing.gwt.ui.shared.util.NullSafeComparableComparator; public abstract class AbstractEventActivity extends AbstractActivity implements Presenter { @@ -248,7 +247,6 @@ public abstract class AbstractEventActivity ex } linkParams.put("raceName", trackedRaceName); linkParams.put("regattaName", regattaName); - linkParams.put(RaceBoardViewConfiguration.PARAM_VIEW_MODE, "simple"); // TODO this must only be forwarded if there is a logged-on user // linkParams.put(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, "true"); return linkParams; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/ExperimentalFeatures.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/ExperimentalFeatures.java index dcfbd043171..53366e8b6d0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/ExperimentalFeatures.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/ExperimentalFeatures.java @@ -54,7 +54,7 @@ public class ExperimentalFeatures { /** * Enables/disables the link to the race board on mobile races view (competition format), in case of a tracked race */ - public static final boolean ENABLE_RACE_VIEWER_LINK_ON_MOBILE = false; + public static final boolean ENABLE_RACE_VIEWER_LINK_ON_MOBILE = true; /** * Provide selection to filter regattas by boat category on mobile multiregatta event overview */ diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/busy/BusyViewImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/busy/BusyViewImpl.java index 27c28089e2c..fb3434000bb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/busy/BusyViewImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/busy/BusyViewImpl.java @@ -8,7 +8,8 @@ public class BusyViewImpl extends Composite implements BusyView { public BusyViewImpl() { BusyViewResources.INSTANCE.css().ensureInjected(); - initWidget(new SimpleBusyIndicator(true, 1.0f, "", BusyViewResources.INSTANCE.css().busy())); + SimpleBusyIndicator busyIndicator = new SimpleBusyIndicator(true, 1.0f); + busyIndicator.setImageStyleClass(BusyViewResources.INSTANCE.css().busy()); + initWidget(busyIndicator); } - } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/fullscreen/FullscreenContainer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/fullscreen/FullscreenContainer.java index 998b211f053..8315428bfd4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/fullscreen/FullscreenContainer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/fullscreen/FullscreenContainer.java @@ -37,6 +37,7 @@ public class FullscreenContainer { String popup(); String toolbarInfo(); String toolbarAction(); + String toolbarBusyIndicator(); String content(); String contentBorder(); } @@ -88,7 +89,12 @@ public class FullscreenContainer { widget.addStyleName(style.toolbarAction()); toolbarUi.add(widget); } - + + public void addToolbarBusyIndicator(Widget widget) { + widget.addStyleName(style.toolbarBusyIndicator()); + toolbarUi.add(widget); + } + public void addToolbarInfo(Widget widget) { widget.addStyleName(style.toolbarInfo()); toolbarUi.add(widget); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/fullscreen/FullscreenContainer.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/fullscreen/FullscreenContainer.ui.xml index bc1f63ed53d..2fcfd38df77 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/fullscreen/FullscreenContainer.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/fullscreen/FullscreenContainer.ui.xml @@ -50,6 +50,13 @@ cursor: pointer; padding: 10px; } + .toolbarBusyIndicator { + display: inline-block; + height: 50px; + width: auto; + padding: 10px; + vertical-align: top; + } .toolbarAction:hover { background-color: #d79900; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaOverviewPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaOverviewPanel.java index 4d4de5230a4..fc2180d3170 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaOverviewPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaOverviewPanel.java @@ -237,7 +237,7 @@ public class RegattaOverviewPanel extends SimplePanel { if (leaderboardsTabPanel != null) { if (showLeaderboard) { final CompetitorSelectionModel competitorSelectionProvider = new CompetitorSelectionModel(/* hasMultiSelection */ true); - final LeaderboardSettings leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true); + final LeaderboardSettings leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true); sailingService.getLeaderboardsByEvent(eventDTO, new MarkedAsyncCallback>( new AsyncCallback>() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/AbstractGetMapRelatedDataAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/AbstractGetMapRelatedDataAction.java new file mode 100644 index 00000000000..c2e4aeab9f8 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/AbstractGetMapRelatedDataAction.java @@ -0,0 +1,47 @@ +package com.sap.sailing.gwt.ui.actions; + +import java.util.Date; +import java.util.Map; + +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sse.gwt.client.async.AsyncAction; + +public abstract class AbstractGetMapRelatedDataAction implements AsyncAction { + private final SailingServiceAsync sailingService; + private final RegattaAndRaceIdentifier raceIdentifier; + private final Map from; + private final Map to; + private final boolean extrapolate; + + public AbstractGetMapRelatedDataAction(SailingServiceAsync sailingService, + RegattaAndRaceIdentifier raceIdentifier, Map from, + Map to, boolean extrapolate) { + this.sailingService = sailingService; + this.raceIdentifier = raceIdentifier; + this.from = from; + this.to = to; + this.extrapolate = extrapolate; + } + + protected SailingServiceAsync getSailingService() { + return sailingService; + } + + protected RegattaAndRaceIdentifier getRaceIdentifier() { + return raceIdentifier; + } + + protected Map getFrom() { + return from; + } + + protected Map getTo() { + return to; + } + + protected boolean isExtrapolate() { + return extrapolate; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetBoatPositionsAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetBoatPositionsAction.java new file mode 100644 index 00000000000..74bc77702d4 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetBoatPositionsAction.java @@ -0,0 +1,43 @@ +package com.sap.sailing.gwt.ui.actions; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO; + +public class GetBoatPositionsAction extends AbstractGetMapRelatedDataAction { + public GetBoatPositionsAction(SailingServiceAsync sailingService, + RegattaAndRaceIdentifier raceIdentifier, Map from, + Map to, boolean extrapolate) { + super(sailingService, raceIdentifier, from, to, extrapolate); + } + + @Override + public void execute(final AsyncCallback callback) { + Map fromByCompetitorIdAsString = new HashMap(); + for (Map.Entry fromEntry : getFrom().entrySet()) { + fromByCompetitorIdAsString.put(fromEntry.getKey().getIdAsString(), fromEntry.getValue()); + } + Map toByCompetitorIdAsString = new HashMap(); + for (Map.Entry toEntry : getTo().entrySet()) { + toByCompetitorIdAsString.put(toEntry.getKey().getIdAsString(), toEntry.getValue()); + } + getSailingService().getBoatPositions(getRaceIdentifier(), fromByCompetitorIdAsString, toByCompetitorIdAsString, + isExtrapolate(), new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + callback.onFailure(caught); + } + + @Override + public void onSuccess(CompactBoatPositionsDTO result) { + callback.onSuccess(result); + } + }); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceMapDataAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceMapDataAction.java index ac35428be2a..aa983200a8a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceMapDataAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceMapDataAction.java @@ -11,45 +11,37 @@ import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO; import com.sap.sailing.gwt.ui.shared.RaceMapDataDTO; -import com.sap.sse.gwt.client.async.AsyncAction; -public class GetRaceMapDataAction implements AsyncAction { - private final SailingServiceAsync sailingService; - private final Iterable allCompetitors; - private final RegattaAndRaceIdentifier raceIdentifier; - private final Map from; - private final Map to; - private final boolean extrapolate; +public class GetRaceMapDataAction extends AbstractGetMapRelatedDataAction { + private final Map competitorsByIdAsString; private final Date date; private final LegIdentifier simulationLegIdentifier; + private final byte[] md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID; - public GetRaceMapDataAction(SailingServiceAsync sailingService, Iterable allCompetitors, + public GetRaceMapDataAction(SailingServiceAsync sailingService, Map competitorsByIdAsString, RegattaAndRaceIdentifier raceIdentifier, Date date, Map from, - Map to, boolean extrapolate, LegIdentifier simulationLegIdentifier) { - this.allCompetitors = allCompetitors; - this.sailingService = sailingService; - this.raceIdentifier = raceIdentifier; + Map to, boolean extrapolate, LegIdentifier simulationLegIdentifier, + byte[] md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID) { + super(sailingService, raceIdentifier, from, to, extrapolate); + this.competitorsByIdAsString = competitorsByIdAsString; this.date = date; - this.from = from; - this.to = to; - this.extrapolate = extrapolate; this.simulationLegIdentifier = simulationLegIdentifier; + this.md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID = md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID; } @Override public void execute(final AsyncCallback callback) { Map fromByCompetitorIdAsString = new HashMap(); - for (Map.Entry fromEntry : from.entrySet()) { + for (Map.Entry fromEntry : getFrom().entrySet()) { fromByCompetitorIdAsString.put(fromEntry.getKey().getIdAsString(), fromEntry.getValue()); } - Map toByCompetitorIdAsString = new HashMap(); - for (Map.Entry toEntry : to.entrySet()) { + for (Map.Entry toEntry : getTo().entrySet()) { toByCompetitorIdAsString.put(toEntry.getKey().getIdAsString(), toEntry.getValue()); } - - sailingService.getRaceMapData(raceIdentifier, date, fromByCompetitorIdAsString, toByCompetitorIdAsString, - extrapolate, simulationLegIdentifier, new AsyncCallback() { + getSailingService().getRaceMapData(getRaceIdentifier(), date, fromByCompetitorIdAsString, toByCompetitorIdAsString, + isExtrapolate(), simulationLegIdentifier, md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID, + new AsyncCallback() { @Override public void onFailure(Throwable caught) { callback.onFailure(caught); @@ -57,7 +49,7 @@ public class GetRaceMapDataAction implements AsyncAction { @Override public void onSuccess(CompactRaceMapDataDTO result) { - callback.onSuccess(result.getRaceMapDataDTO(allCompetitors)); + callback.onSuccess(result.getRaceMapDataDTO(competitorsByIdAsString)); } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractCompetitorRegistrationsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractCompetitorRegistrationsDialog.java index 5be3482a392..437056b3696 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractCompetitorRegistrationsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractCompetitorRegistrationsDialog.java @@ -17,18 +17,18 @@ import com.google.gwt.user.client.ui.HasVerticalAlignment; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; -import com.google.gwt.view.client.MultiSelectionModel; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.CompetitorDTOImpl; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sse.common.Util; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog; public abstract class AbstractCompetitorRegistrationsDialog extends DataEntryDialog> { - protected CompetitorTableWrapper> allCompetitorsTable; - protected CompetitorTableWrapper> registeredCompetitorsTable; + protected CompetitorTableWrapper> allCompetitorsTable; + protected CompetitorTableWrapper> registeredCompetitorsTable; protected final boolean filterByLeaderBoardInitially = false; final StringMessages stringMessages; protected final SailingServiceAsync sailingService; @@ -168,8 +168,8 @@ public abstract class AbstractCompetitorRegistrationsDialog extends DataEntryDia } } - private void moveSelected(CompetitorTableWrapper> from, - CompetitorTableWrapper> to) { + private void moveSelected(CompetitorTableWrapper> from, + CompetitorTableWrapper> to) { move(from, to, from.getSelectionModel().getSelectedSet()); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractEventManagementPanel.java index 326230751e8..d0d0dad3dba 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractEventManagementPanel.java @@ -15,7 +15,6 @@ import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.ListBox; import com.sap.sailing.domain.common.BoatClassMasterdata; import com.sap.sailing.gwt.ui.client.AbstractRegattaPanel; -import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -30,7 +29,7 @@ public abstract class AbstractEventManagementPanel extends AbstractRegattaPanel private final ListBox availableRegattasListBox; public AbstractEventManagementPanel(SailingServiceAsync sailingService, RegattaRefresher regattaRefresher, - ErrorReporter errorReporter, RaceSelectionProvider raceSelectionProvider, boolean actionButtonsEnabled, StringMessages stringMessages) { + ErrorReporter errorReporter, boolean actionButtonsEnabled, StringMessages stringMessages) { super(sailingService, regattaRefresher, errorReporter, stringMessages); this.availableRegattas = new ArrayList(); @@ -39,7 +38,7 @@ public abstract class AbstractEventManagementPanel extends AbstractRegattaPanel // TrackedEventsComposite should exist in every *ManagementPanel. trackedRacesListComposite = new TrackedRacesListComposite(sailingService, errorReporter, regattaRefresher, - raceSelectionProvider, stringMessages, /* multiselection */ true, actionButtonsEnabled); + stringMessages, /* multiselection */ true, actionButtonsEnabled); trackedRacesListComposite.ensureDebugId("TrackedRacesListComposite"); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java index ecf918e0008..dda1524caac 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java @@ -8,8 +8,11 @@ import java.util.Map; import java.util.Set; import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; import com.google.gwt.user.client.Window; @@ -23,26 +26,23 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.view.client.ListDataProvider; -import com.google.gwt.view.client.MultiSelectionModel; import com.google.gwt.view.client.SelectionChangeEvent; -import com.google.gwt.view.client.SetSelectionModel; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.NamedDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.domain.common.dto.RaceDTO; import com.sap.sailing.gwt.ui.adminconsole.RaceColumnInLeaderboardDialog.RaceColumnDescriptor; import com.sap.sailing.gwt.ui.client.LeaderboardsDisplayer; import com.sap.sailing.gwt.ui.client.LeaderboardsRefresher; import com.sap.sailing.gwt.ui.client.ParallelExecutionCallback; import com.sap.sailing.gwt.ui.client.ParallelExecutionHolder; -import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; -import com.sap.sailing.gwt.ui.client.RaceSelectionModel; -import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.RegattasDisplayer; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.FlushableCellTable; import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sailing.gwt.ui.shared.RaceLogDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; @@ -51,11 +51,14 @@ import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.common.Util; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; +import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; public abstract class AbstractLeaderboardConfigPanel extends FormPanel implements SelectedLeaderboardProvider, - RegattasDisplayer, RaceSelectionChangeListener, TrackedRaceChangedListener, LeaderboardsDisplayer { + RegattasDisplayer, TrackedRaceChangedListener, LeaderboardsDisplayer { protected final VerticalPanel mainPanel; protected final TrackedRacesListComposite trackedRacesListComposite; @@ -68,10 +71,10 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement protected final ErrorReporter errorReporter; - protected final CellTable leaderboardTable; + protected final FlushableCellTable leaderboardTable; - protected final RaceTableWrapper> raceColumnTable; - protected final SetSelectionModel raceColumnTableSelectionModel; + protected final RaceTableWrapper> raceColumnTable; + protected final RefreshableSelectionModel raceColumnTableSelectionModel; protected RaceColumnDTOAndFleetDTOWithNameBasedEquality selectedRaceInLeaderboard; @@ -83,12 +86,14 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement protected List availableLeaderboardList; - protected final MultiSelectionModel leaderboardSelectionModel; - - protected final RaceSelectionProvider raceSelectionProvider; + protected final RefreshableMultiSelectionModel leaderboardSelectionModel; + protected final RefreshableSelectionModel refreshableTrackedRaceSelectionModel; + protected final SelectionChangeEvent.Handler trackedRaceListHandler; + protected HandlerRegistration trackedRaceListHandlerRegistration; + private final LeaderboardsRefresher leaderboardsRefresher; - + public static class RaceColumnDTOAndFleetDTOWithNameBasedEquality extends Util.Pair { private static final long serialVersionUID = -8742476113296862662L; @@ -157,7 +162,7 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement leaderboardsPanel.add(leaderboardControlsPanel); AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); - leaderboardTable = new CellTable(/* pageSize */10000, tableRes); + leaderboardTable = new FlushableCellTable(/* pageSize */10000, tableRes); filterLeaderboardPanel = new LabeledAbstractFilterablePanel(lblFilterEvents, availableLeaderboardList, leaderboardTable, leaderboardList) { @Override @@ -172,9 +177,9 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement leaderboardsPanel.add(filterLeaderboardPanel); leaderboardTable.ensureDebugId("AvailableLeaderboardsTable"); - addColumnsToLeaderboardTableAndSetSelectionModel(leaderboardTable, tableRes); + addColumnsToLeaderboardTableAndSetSelectionModel(leaderboardTable, tableRes, leaderboardList); @SuppressWarnings("unchecked") - MultiSelectionModel multiSelectionModel = (MultiSelectionModel) leaderboardTable.getSelectionModel(); + RefreshableMultiSelectionModel multiSelectionModel = (RefreshableMultiSelectionModel) leaderboardTable.getSelectionModel(); leaderboardSelectionModel = multiSelectionModel; leaderboardTable.setWidth("100%"); leaderboardSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { @@ -210,13 +215,34 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement trackedRacesCaptionPanel.setContentWidget(trackedRacesPanel); trackedRacesCaptionPanel.setStyleName("bold"); - raceSelectionProvider = new RaceSelectionModel(); trackedRacesListComposite = new TrackedRacesListComposite(sailingService, errorReporter, regattaRefresher, - raceSelectionProvider, stringMessages, /* multiselection */false, isActionButtonsEnabled()); + stringMessages, /* multiselection */false, isActionButtonsEnabled()); + refreshableTrackedRaceSelectionModel = trackedRacesListComposite.getSelectionModel(); trackedRacesListComposite.ensureDebugId("TrackedRacesListComposite"); trackedRacesPanel.add(trackedRacesListComposite); trackedRacesListComposite.addTrackedRaceChangeListener(this); - raceSelectionProvider.addRaceSelectionChangeListener(this); + trackedRaceListHandler = new SelectionChangeEvent.Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + Set selectedRaces = refreshableTrackedRaceSelectionModel.getSelectedSet(); + // if no leaderboard column is selected, ignore the race selection change + RaceColumnDTOAndFleetDTOWithNameBasedEquality selectedRaceColumnAndFleetName = getSelectedRaceColumnWithFleet(); + if (selectedRaceColumnAndFleetName != null) { + if (selectedRaces.isEmpty()) { + if (selectedRaceColumnAndFleetName.getA() + .getRaceIdentifier(selectedRaceColumnAndFleetName.getB()) != null) { + unlinkRaceColumnFromTrackedRace(selectedRaceColumnAndFleetName.getA().getRaceColumnName(), + selectedRaceColumnAndFleetName.getB()); + } + } else { + linkTrackedRaceToSelectedRaceColumn(selectedRaceColumnAndFleetName.getA(), + selectedRaceColumnAndFleetName.getB(), + selectedRaces.iterator().next().getRaceIdentifier()); + } + } + } + }; + trackedRaceListHandlerRegistration = refreshableTrackedRaceSelectionModel.addSelectionChangeHandler(trackedRaceListHandler); Button reloadAllRaceLogs = new Button(stringMessages.reloadAllRaceLogs()); reloadAllRaceLogs.ensureDebugId("ReloadAllRaceLogsButton"); @@ -235,13 +261,18 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement vPanel.add(reloadAllRaceLogs); Label lblRaceNamesIn = new Label(stringMessages.races()); vPanel.add(lblRaceNamesIn); - raceColumnTable = new RaceTableWrapper>( + raceColumnTable = new RaceTableWrapper>( sailingService, stringMessages, errorReporter, multiSelection); + raceColumnTable.asWidget().ensureDebugId("RaceColumnTable"); raceColumnTable.getTable().setWidth("100%"); addColumnsToRacesTable(raceColumnTable.getTable()); this.raceColumnTableSelectionModel = raceColumnTable.getSelectionModel(); raceColumnTableSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { public void onSelectionChange(SelectionChangeEvent event) { + // If the selection on the raceColumnTable changes, + // you don't want to link or unlink raceColumns with the + // trackedRaceListHandler. + removeTrackedRaceListHandlerTemporarily(); leaderboardRaceColumnSelectionChanged(); } }); @@ -260,19 +291,26 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement protected abstract void addLeaderboardControls(Panel controlsPanel); protected abstract void addSelectedLeaderboardRacesControls(Panel racesPanel); - protected abstract void addColumnsToLeaderboardTableAndSetSelectionModel(CellTable leaderboardTable, AdminConsoleTableResources tableRes); + protected abstract void addColumnsToLeaderboardTableAndSetSelectionModel(FlushableCellTable leaderboardTable, + AdminConsoleTableResources tableRes, ListDataProvider listDataProvider); protected abstract void addColumnsToRacesTable(CellTable racesTable); protected SelectionCheckboxColumn createSortableSelectionCheckboxColumn( - final CellTable leaderboardTable, AdminConsoleTableResources tableResources, - ListHandler leaderboardColumnListHandler) { - SelectionCheckboxColumn selectionCheckboxColumn = new SelectionCheckboxColumn(tableResources.cellTableStyle().cellTableCheckboxSelected(), - tableResources.cellTableStyle().cellTableCheckboxDeselected(), tableResources.cellTableStyle().cellTableCheckboxColumnCell()) { - @Override - protected ListDataProvider getListDataProvider() { - return leaderboardList; - } - }; + final FlushableCellTable leaderboardTable, AdminConsoleTableResources tableResources, + ListHandler leaderboardColumnListHandler, ListDataProvider listDataProvider) { + SelectionCheckboxColumn selectionCheckboxColumn = new SelectionCheckboxColumn( + tableResources.cellTableStyle().cellTableCheckboxSelected(), + tableResources.cellTableStyle().cellTableCheckboxDeselected(), + tableResources.cellTableStyle().cellTableCheckboxColumnCell(), new EntityIdentityComparator() { + @Override + public boolean representSameEntity(StrippedLeaderboardDTO dto1, StrippedLeaderboardDTO dto2) { + return dto1.name.equals(dto2.name); + } + @Override + public int hashCode(StrippedLeaderboardDTO t) { + return t.name.hashCode(); + } + }, listDataProvider, leaderboardTable); selectionCheckboxColumn.setSortable(true); leaderboardColumnListHandler.setComparator(selectionCheckboxColumn, new Comparator() { @Override @@ -407,9 +445,11 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement @Override public void onSuccess(Map regattaAndRaceNamesPerFleet) { + // This method should select the linked trackedRace. + // So you don't want to link or unlink it again throw the trackedRaceListHandler. + removeTrackedRaceListHandlerTemporarily(); if (regattaAndRaceNamesPerFleet != null && !regattaAndRaceNamesPerFleet.isEmpty()) { RegattaAndRaceIdentifier raceIdentifier = regattaAndRaceNamesPerFleet.get(selectedFleetName); - if (raceIdentifier != null) { selectRaceInList(raceIdentifier.getRegattaName(), raceIdentifier.getRaceName()); } else { @@ -443,6 +483,7 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement @Override public void fillRegattas(Iterable regattas) { + removeTrackedRaceListHandlerTemporarily(); trackedRacesListComposite.fillRegattas(regattas); allRegattas.clear(); Util.addAll(regattas, allRegattas); @@ -479,22 +520,6 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement raceColumnAndFleetName.getA().setRaceIdentifier(raceColumnAndFleetName.getB(), null); } - @Override - public void onRaceSelectionChange(List selectedRaces) { - // if no leaderboard column is selected, ignore the race selection change - RaceColumnDTOAndFleetDTOWithNameBasedEquality selectedRaceColumnAndFleetName = getSelectedRaceColumnWithFleet(); - if (selectedRaceColumnAndFleetName != null) { - if (selectedRaces.isEmpty()) { - if (selectedRaceColumnAndFleetName.getA().getRaceIdentifier(selectedRaceColumnAndFleetName.getB()) != null) { - unlinkRaceColumnFromTrackedRace(selectedRaceColumnAndFleetName.getA().getRaceColumnName(), selectedRaceColumnAndFleetName.getB()); - } - } else { - linkTrackedRaceToSelectedRaceColumn(selectedRaceColumnAndFleetName.getA(), selectedRaceColumnAndFleetName.getB(), - selectedRaces.iterator().next()); - } - } - } - private void linkTrackedRaceToSelectedRaceColumn(final RaceColumnDTO selectedRaceInLeaderboard, final FleetDTO fleet, final RegattaAndRaceIdentifier selectedRace) { sailingService.connectTrackedRaceToLeaderboardColumn(getSelectedLeaderboardName(), selectedRaceInLeaderboard @@ -650,4 +675,32 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement }); dialog.show(); } + + /** + * Removes the {@link SelectionChangeEvent.Handler} until the browser regains control. The handler will be added + * again using {@link Scheduler#scheduleDeferred(ScheduledCommand)} method. + *

+ * Use this method if you change the {@link ListDataProvider} or {@link RefreshableSelectionModel} of + * {@link TrackedRacesListComposite} and you don't want to trigger the + * {@link SelectionChangeEvent.Handler#onSelectionChange(SelectionChangeEvent)}. + */ + private void removeTrackedRaceListHandlerTemporarily() { + if (trackedRaceListHandlerRegistration == null) { + return; + } + trackedRaceListHandlerRegistration.removeHandler(); + trackedRaceListHandlerRegistration = null; + // It is necessary to do this with the ScheduleDeferred() method, + // because the SelectionChangeEvent isn't fired directly after + // selection changes. So an remove of SelectionChangeHandler before + // the selection change and and new registration directly after it + // isn't possible. + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + trackedRaceListHandlerRegistration = refreshableTrackedRaceSelectionModel + .addSelectionChangeHandler(trackedRaceListHandler); + } + }); + } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLogImportFixesAndAddMappingsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLogImportFixesAndAddMappingsDialog.java index 5ddaf3893a3..1c841b5e8f4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLogImportFixesAndAddMappingsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLogImportFixesAndAddMappingsDialog.java @@ -21,13 +21,14 @@ import com.sap.sailing.gwt.ui.shared.DeviceMappingDTO; import com.sap.sailing.gwt.ui.shared.MarkDTO; import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog; public abstract class AbstractLogImportFixesAndAddMappingsDialog extends DataEntryDialog> { TrackFileImportWidget importWidget; private TrackFileImportDeviceIdentifierTableWrapper deviceIdTable; - protected final CompetitorTableWrapper> competitorTable; - protected final MarkTableWrapper> markTable; + protected final CompetitorTableWrapper> competitorTable; + protected final MarkTableWrapper> markTable; private final StringMessages stringMessages; private final Map mappings = new HashMap<>(); @@ -56,7 +57,7 @@ public abstract class AbstractLogImportFixesAndAddMappingsDialog extends DataEnt } }); competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, /* multiSelection */ false, true); - markTable = new MarkTableWrapper>( + markTable = new MarkTableWrapper>( /* multiSelection */ false, sailingService, stringMessages, errorReporter); competitorTable.getSelectionModel().addSelectionChangeHandler(new SelectionChangeEvent.Handler() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractRaceManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractRaceManagementPanel.java index a6104f78418..283b8d9196b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractRaceManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractRaceManagementPanel.java @@ -1,20 +1,21 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.List; +import java.util.Set; import com.google.gwt.user.client.ui.CaptionPanel; import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.view.client.SelectionChangeEvent; +import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.dto.RaceDTO; -import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; -import com.sap.sailing.gwt.ui.client.RaceSelectionModel; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sse.gwt.client.ErrorReporter; -public abstract class AbstractRaceManagementPanel extends AbstractEventManagementPanel implements RaceSelectionChangeListener { +public abstract class AbstractRaceManagementPanel extends AbstractEventManagementPanel { protected RegattaAndRaceIdentifier singleSelectedRace; protected RaceDTO selectedRaceDTO; @@ -27,22 +28,39 @@ public abstract class AbstractRaceManagementPanel extends AbstractEventManagemen public AbstractRaceManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter, RegattaRefresher regattaRefresher, boolean actionButtonsEnabled, StringMessages stringMessages) { - super(sailingService, regattaRefresher, errorReporter, new RaceSelectionModel(), actionButtonsEnabled, stringMessages); - + super(sailingService, regattaRefresher, errorReporter, actionButtonsEnabled, stringMessages); VerticalPanel mainPanel = new VerticalPanel(); this.setWidget(mainPanel); mainPanel.setWidth("100%"); - mainPanel.add(trackedRacesListComposite); - - trackedRacesListComposite.addRaceSelectionChangeListener(this); - + trackedRacesListComposite.getSelectionModel().addSelectionChangeHandler(new Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + Set selectedRaces = trackedRacesListComposite.getSelectionModel().getSelectedSet(); + if (selectedRaces.size() == 1) { + singleSelectedRace = selectedRaces.iterator().next().getRaceIdentifier(); + selectedCaptionRacePanel.setCaptionText(singleSelectedRace.getRaceName()); + selectedCaptionRacePanel.setVisible(true); + for (RegattaDTO regatta : getAvailableRegattas()) { + for (RaceDTO race : regatta.races) { + if (race != null && race.getRaceIdentifier().equals(singleSelectedRace)) { + AbstractRaceManagementPanel.this.selectedRaceDTO = race; + refreshSelectedRaceData(); + break; + } + } + } + } else { + selectedCaptionRacePanel.setCaptionText(""); + singleSelectedRace = null; + selectedCaptionRacePanel.setVisible(false); + } + } + }); singleSelectedRace = null; - selectedCaptionRacePanel = new CaptionPanel(stringMessages.race()); selectedCaptionRacePanel.setWidth("100%"); mainPanel.add(selectedCaptionRacePanel); - selectedRaceContentPanel = new VerticalPanel(); selectedRaceContentPanel.setWidth("100%"); selectedCaptionRacePanel.setContentWidget(selectedRaceContentPanel); @@ -50,26 +68,4 @@ public abstract class AbstractRaceManagementPanel extends AbstractEventManagemen } abstract void refreshSelectedRaceData(); - - @Override - public void onRaceSelectionChange(List selectedRaces) { - if (selectedRaces.size() == 1) { - singleSelectedRace = selectedRaces.get(0); - selectedCaptionRacePanel.setCaptionText(singleSelectedRace.getRaceName()); - selectedCaptionRacePanel.setVisible(true); - for (RegattaDTO regatta : getAvailableRegattas()) { - for (RaceDTO race : regatta.races) { - if (race != null && race.getRaceIdentifier().equals(singleSelectedRace)) { - this.selectedRaceDTO = race; - refreshSelectedRaceData(); - break; - } - } - } - } else { - selectedCaptionRacePanel.setCaptionText(""); - singleSelectedRace = null; - selectedCaptionRacePanel.setVisible(false); - } - } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractRegattaWithSeriesAndFleetsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractRegattaWithSeriesAndFleetsDialog.java index b18109f4e2f..07a068228fc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractRegattaWithSeriesAndFleetsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractRegattaWithSeriesAndFleetsDialog.java @@ -21,6 +21,7 @@ import com.sap.sailing.domain.common.RankingMetrics; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.gwt.ui.client.DataEntryDialogWithBootstrap; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; import com.sap.sailing.gwt.ui.leaderboard.RankingMetricTypeFormatter; import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter; import com.sap.sailing.gwt.ui.shared.BetterDateTimeBox; @@ -134,20 +135,23 @@ public abstract class AbstractRegattaWithSeriesAndFleetsDialog extends DataEn if (additionalWidget != null) { panel.add(additionalWidget); } - Grid formGrid = new Grid(6, 2); + Grid formGrid = new Grid(7, 2); panel.add(formGrid); - formGrid.setWidget(0, 0, new Label(stringMessages.startDate() + ":")); - formGrid.setWidget(0, 1, startDateBox); - formGrid.setWidget(1, 0, new Label(stringMessages.endDate() + ":")); - formGrid.setWidget(1, 1, endDateBox); - formGrid.setWidget(2, 0, new Label(stringMessages.scoringSystem() + ":")); - formGrid.setWidget(2, 1, scoringSchemeListBox); - formGrid.setWidget(3, 0, new Label(stringMessages.event() + ":")); - formGrid.setWidget(3, 1, sailingEventsListBox); - formGrid.setWidget(4, 0, new Label(stringMessages.courseArea() + ":")); - formGrid.setWidget(4, 1, courseAreaListBox); - formGrid.setWidget(5, 0, new Label(stringMessages.useStartTimeInference() + ":")); - formGrid.setWidget(5, 1, useStartTimeInferenceCheckBox); + + formGrid.setWidget(0, 0, new Label(stringMessages.timeZone() + ":")); + formGrid.setWidget(0, 1, new Label(DateAndTimeFormatterUtil.getClientTimeZoneAsGMTString())); + formGrid.setWidget(1, 0, new Label(stringMessages.startDate() + ":")); + formGrid.setWidget(1, 1, startDateBox); + formGrid.setWidget(2, 0, new Label(stringMessages.endDate() + ":")); + formGrid.setWidget(2, 1, endDateBox); + formGrid.setWidget(3, 0, new Label(stringMessages.scoringSystem() + ":")); + formGrid.setWidget(3, 1, scoringSchemeListBox); + formGrid.setWidget(4, 0, new Label(stringMessages.event() + ":")); + formGrid.setWidget(4, 1, sailingEventsListBox); + formGrid.setWidget(5, 0, new Label(stringMessages.courseArea() + ":")); + formGrid.setWidget(5, 1, courseAreaListBox); + formGrid.setWidget(6, 0, new Label(stringMessages.useStartTimeInference() + ":")); + formGrid.setWidget(6, 1, useStartTimeInferenceCheckBox); setupAdditionalWidgetsOnPanel(panel, formGrid); return panel; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractTrackedRacesListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractTrackedRacesListComposite.java index ba07f527a4f..c57c9e9ed49 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractTrackedRacesListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractTrackedRacesListComposite.java @@ -1,9 +1,9 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Set; import com.google.gwt.cell.client.AbstractCell; import com.google.gwt.core.client.GWT; @@ -12,7 +12,6 @@ import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.safehtml.shared.SafeHtmlUtils; -import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; import com.google.gwt.user.cellview.client.TextColumn; @@ -27,18 +26,15 @@ import com.google.gwt.user.client.ui.Widget; import com.google.gwt.view.client.ListDataProvider; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; -import com.google.gwt.view.client.SelectionModel; -import com.google.gwt.view.client.SingleSelectionModel; import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sailing.domain.common.dto.RaceDTO; -import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; -import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.RegattasDisplayer; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.FlushableCellTable; import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; import com.sap.sailing.gwt.ui.shared.RegattaDTO; @@ -46,24 +42,25 @@ import com.sap.sse.common.Util; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel; +import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; import com.sap.sse.gwt.client.shared.components.Component; import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; public abstract class AbstractTrackedRacesListComposite extends SimplePanel implements Component, - RegattasDisplayer, RaceSelectionChangeListener { + RegattasDisplayer { protected final long DEFAULT_LIVE_DELAY_IN_MILLISECONDS = 5000; private final boolean multiSelection; - private boolean dontFireNextSelectionChangeEvent; - - private SelectionModel selectionModel; + protected RefreshableSelectionModel refreshableSelectionModel; private SelectionCheckboxColumn selectionCheckboxColumn; - private CellTable raceTable; + private FlushableCellTable raceTable; private ListDataProvider raceList; @@ -74,7 +71,6 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl protected final SailingServiceAsync sailingService; protected final ErrorReporter errorReporter; protected final RegattaRefresher regattaRefresher; - private final RaceSelectionProvider raceSelectionProvider; protected final StringMessages stringMessages; private Button btnRefresh; @@ -92,37 +88,21 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl public AbstractTrackedRacesListComposite(final SailingServiceAsync sailingService, final ErrorReporter errorReporter, final RegattaRefresher regattaRefresher, - final RaceSelectionProvider raceSelectionProvider, final StringMessages stringMessages, boolean hasMultiSelection) { + final StringMessages stringMessages, boolean hasMultiSelection) { this.sailingService = sailingService; this.errorReporter = errorReporter; this.regattaRefresher = regattaRefresher; this.multiSelection = hasMultiSelection; - this.raceSelectionProvider = raceSelectionProvider; this.stringMessages = stringMessages; } protected void createUI() { AdminConsoleTableResources tableResources = GWT.create(AdminConsoleTableResources.class); - this.selectionCheckboxColumn = new SelectionCheckboxColumn(tableResources.cellTableStyle().cellTableCheckboxSelected(), - tableResources.cellTableStyle().cellTableCheckboxDeselected(), tableResources.cellTableStyle().cellTableCheckboxColumnCell()) { - @Override - protected ListDataProvider getListDataProvider() { - return raceList; - } - - @Override - public Boolean getValue(RaceDTO row) { - return raceTable.getSelectionModel().isSelected(row); - } - }; - raceList = new ListDataProvider(); settings = new TrackedRacesSettings(); settings.setDelayToLiveInSeconds(DEFAULT_LIVE_DELAY_IN_MILLISECONDS / 1000l); - VerticalPanel panel = new VerticalPanel(); setWidget(panel); - HorizontalPanel filterPanel = new HorizontalPanel(); panel.add(filterPanel); Label lblFilterRaces = new Label(stringMessages.filterRacesByName() + ":"); @@ -133,19 +113,35 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl noTrackedRacesLabel = new Label(stringMessages.noRacesYet()); noTrackedRacesLabel.setWordWrap(false); panel.add(noTrackedRacesLabel); - AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); - raceTable = new CellTable(/* pageSize */10000, tableRes); + raceTable = new FlushableCellTable(/* pageSize */10000, tableRes); raceTable.ensureDebugId("TrackedRacesCellTable"); + final EntityIdentityComparator entityIdentityComparator = new EntityIdentityComparator() { + @Override + public boolean representSameEntity(RaceDTO dto1, RaceDTO dto2) { + return dto1.getRaceIdentifier().equals(dto2.getRaceIdentifier()); + } + @Override + public int hashCode(RaceDTO t) { + return t.getRaceIdentifier().hashCode(); + } + }; + if (multiSelection) { + this.selectionCheckboxColumn = new SelectionCheckboxColumn( + tableResources.cellTableStyle().cellTableCheckboxSelected(), + tableResources.cellTableStyle().cellTableCheckboxDeselected(), + tableResources.cellTableStyle().cellTableCheckboxColumnCell(), + entityIdentityComparator, raceList, raceTable); + refreshableSelectionModel = selectionCheckboxColumn.getSelectionModel(); + raceTable.setSelectionModel(refreshableSelectionModel, this.selectionCheckboxColumn.getSelectionManager()); + } else { + refreshableSelectionModel = new RefreshableSingleSelectionModel(entityIdentityComparator, raceList); + raceTable.setSelectionModel(refreshableSelectionModel); + } + ListHandler columnSortHandler = setupTableColumns(stringMessages); raceTable.setWidth("300px"); - if (multiSelection) { - selectionModel = this.selectionCheckboxColumn.getSelectionModel(); - raceTable.setSelectionModel(selectionModel, this.selectionCheckboxColumn.getSelectionManager()); - } else { - selectionModel = new SingleSelectionModel(); - raceTable.setSelectionModel(selectionModel); - } + raceTable.setVisible(false); panel.add(raceTable); raceList.addDataDisplay(raceTable); @@ -153,18 +149,8 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl raceTable.getSelectionModel().addSelectionChangeHandler(new Handler() { @Override public void onSelectionChange(SelectionChangeEvent event) { - List selectedRaces = getSelectedRaces(); + Set selectedRaces = refreshableSelectionModel.getSelectedSet(); makeControlsReactToSelectionChange(selectedRaces); - if (dontFireNextSelectionChangeEvent) { - dontFireNextSelectionChangeEvent = false; - } else { - List selectedRaceIdentifiers = new ArrayList(); - for (RaceDTO selectedRace : selectedRaces) { - selectedRaceIdentifiers.add(selectedRace.getRaceIdentifier()); - } - AbstractTrackedRacesListComposite.this.raceSelectionProvider.setSelection(selectedRaceIdentifiers, - AbstractTrackedRacesListComposite.this); - } } }); filterablePanelRaces = new LabeledAbstractFilterablePanel(lblFilterRaces, allRaces, raceTable, raceList) { @@ -196,7 +182,7 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl addControlButtons(trackedRacesButtonPanel); } - abstract protected void makeControlsReactToSelectionChange(List selectedRaces); + abstract protected void makeControlsReactToSelectionChange(Set selectedRaces); abstract protected void makeControlsReactToFillRegattas(Iterable regattas); @@ -381,14 +367,6 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl return columnSortHandler; } - - @Override - public void onRaceSelectionChange(List selectedRaces) { - for (RaceDTO raceFromAllRaces : raceList.getList()) { - selectionModel.setSelected(raceFromAllRaces, selectedRaces.contains(raceFromAllRaces.getRaceIdentifier())); - } - } - @Override public boolean hasSettings() { return true; @@ -405,7 +383,7 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl // set the new delay to all selected races List raceIdentifiersToUpdate = new ArrayList(); - for (RaceDTO raceDTO : getSelectedRaces()) { + for (RaceDTO raceDTO : refreshableSelectionModel.getSelectedSet()) { raceIdentifiersToUpdate.add(raceDTO.getRaceIdentifier()); } @@ -439,8 +417,8 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl return this; } - public void addRaceSelectionChangeListener(RaceSelectionChangeListener listener) { - this.raceSelectionProvider.addRaceSelectionChangeListener(listener); + public void addRaceSelectionChangeHandler(Handler handler) { + refreshableSelectionModel.addSelectionChangeHandler(handler); } public RaceDTO getRaceByIdentifier(RaceIdentifier raceIdentifier) { @@ -456,26 +434,13 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl return result; } - List getSelectedRaces() { - List result = new ArrayList(); - if (raceList != null) { - for (RaceDTO race : raceList.getList()) { - if (selectionModel.isSelected(race)) { - result.add(race); - } - } - } - return result; - } - public void selectRaceByIdentifier(RegattaAndRaceIdentifier raceIdentifier) { if (raceList != null) { for (RaceDTO race : raceList.getList()) { String regattaName = race.getRegattaName(); if (regattaName.equals(raceIdentifier.getRegattaName()) && race.getName().equals(raceIdentifier.getRaceName())) { - dontFireNextSelectionChangeEvent = true; - selectionModel.setSelected(race, true); + refreshableSelectionModel.setSelected(race, true); break; } } @@ -483,13 +448,7 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl } public void clearSelection() { - List emptySelection = Collections.emptyList(); - raceSelectionProvider.setSelection(emptySelection, /* listenersNotToNotify */this); - if (raceList != null) { - for (RaceDTO race : raceList.getList()) { - selectionModel.setSelected(race, false); - } - } + refreshableSelectionModel.clear(); } @Override @@ -504,22 +463,18 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl } List newAllRaces = new ArrayList(); List newAllRegattas = new ArrayList(); - List newAllRaceIdentifiers = new ArrayList(); for (RegattaDTO regatta : regattas) { newAllRegattas.add(regatta); for (RaceDTO race : regatta.races) { if (race != null) { if (raceIsToBeAddedToList(race)) { newAllRaces.add(race); - newAllRaceIdentifiers.add(race.getRaceIdentifier()); } } } } allRaces = newAllRaces; filterablePanelRaces.updateAll(allRaces); - raceSelectionProvider.setAllRaces(newAllRaceIdentifiers); // have this object be notified; triggers - // onRaceSelectionChange } /** @@ -532,5 +487,8 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl protected boolean raceIsToBeAddedToList(RaceDTO race) { return true; } - + + public RefreshableSelectionModel getSelectionModel() { + return refreshableSelectionModel; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java index 6f1aa6e53ae..fed88bb39f8 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java @@ -181,6 +181,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements final SmartphoneTrackingEventManagementPanel raceLogTrackingEventManagementPanel = new SmartphoneTrackingEventManagementPanel( sailingService, this, this, this, getStringMessages()); + raceLogTrackingEventManagementPanel.ensureDebugId("SmartphoneTrackingPanel"); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(raceLogTrackingEventManagementPanel) { @Override public void refreshAfterBecomingVisible() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AssignRacesToMediaDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AssignRacesToMediaDialog.java index 2726be874cf..d4d4d033a97 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AssignRacesToMediaDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AssignRacesToMediaDialog.java @@ -1,7 +1,6 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.HashSet; -import java.util.List; import java.util.Set; import com.google.gwt.user.client.ui.Button; @@ -14,7 +13,6 @@ import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.dto.RaceDTO; import com.sap.sailing.domain.common.dto.TrackedRaceDTO; import com.sap.sailing.domain.common.media.MediaTrack; -import com.sap.sailing.gwt.ui.client.RaceSelectionModel; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.RegattasDisplayer; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -40,7 +38,7 @@ public class AssignRacesToMediaDialog extends DataEntryDialog selectedRaces) { - } - + @Override protected void makeControlsReactToFillRegattas(Iterable regattas) { } @@ -114,7 +108,7 @@ public class AssignRacesToMediaDialog extends DataEntryDialog getAssignedRaces() { - List races = trackedRacesListComposite.getSelectedRaces(); + Set races = trackedRacesListComposite.getSelectionModel().getSelectedSet(); Set assignedRaces = new HashSet(); for (RaceDTO race : races) { assignedRaces.add(race.getRaceIdentifier()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java index 245273a3680..79570bfeb05 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java @@ -14,7 +14,6 @@ import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.VerticalPanel; -import com.google.gwt.view.client.MultiSelectionModel; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.sap.sailing.domain.common.dto.CompetitorDTO; @@ -23,6 +22,7 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sse.common.Util; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; /** @@ -35,8 +35,8 @@ public class CompetitorPanel extends SimplePanel { private final SailingServiceAsync sailingService; private final StringMessages stringMessages; private final ErrorReporter errorReporter; - private final CompetitorTableWrapper> competitorTable; - private final MultiSelectionModel competitorSelectionModel; + private final CompetitorTableWrapper> competitorTable; + private final RefreshableMultiSelectionModel refreshableCompetitorSelectionModel; private final String leaderboardName; public CompetitorPanel(final SailingServiceAsync sailingService, final StringMessages stringMessages, @@ -52,7 +52,7 @@ public class CompetitorPanel extends SimplePanel { this.errorReporter = errorReporter; this.leaderboardName = leaderboardName; this.competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, /* multiSelection */ true, /* enablePager */ false); - this.competitorSelectionModel = competitorTable.getSelectionModel(); + this.refreshableCompetitorSelectionModel = (RefreshableMultiSelectionModel) competitorTable.getSelectionModel(); VerticalPanel mainPanel = new VerticalPanel(); this.setWidget(mainPanel); mainPanel.setWidth("100%"); @@ -68,12 +68,13 @@ public class CompetitorPanel extends SimplePanel { refreshCompetitorList(); } }); + refreshButton.ensureDebugId("RefreshButton"); buttonPanel.add(refreshButton); final Button allowReloadButton = new Button(stringMessages.allowReload()); allowReloadButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { - allowUpdate(competitorSelectionModel.getSelectedSet()); + allowUpdate(refreshableCompetitorSelectionModel.getSelectedSet()); } }); buttonPanel.add(allowReloadButton); @@ -92,7 +93,7 @@ public class CompetitorPanel extends SimplePanel { @Override public void onClick(ClickEvent event) { for (CompetitorDTO c : competitorTable.getDataProvider().getList()) { - competitorSelectionModel.setSelected(c, true); + refreshableCompetitorSelectionModel.setSelected(c, true); } } }); @@ -104,7 +105,7 @@ public class CompetitorPanel extends SimplePanel { inviteCompetitorsButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { - Set competitors = competitorSelectionModel.getSelectedSet(); + Set competitors = refreshableCompetitorSelectionModel.getSelectedSet(); CompetitorInvitationHelper helper = new CompetitorInvitationHelper(sailingService, stringMessages, errorReporter); helper.inviteCompetitors(competitors, leaderboardName); @@ -132,13 +133,13 @@ public class CompetitorPanel extends SimplePanel { competitorTable.getTable().addColumn(competitorActionColumn, stringMessages.actions()); mainPanel.add(competitorTable); - competitorSelectionModel.addSelectionChangeHandler(new Handler() { + refreshableCompetitorSelectionModel.addSelectionChangeHandler(new Handler() { @Override public void onSelectionChange(SelectionChangeEvent event) { - allowReloadButton.setEnabled(!competitorSelectionModel.getSelectedSet().isEmpty()); + allowReloadButton.setEnabled(!refreshableCompetitorSelectionModel.getSelectedSet().isEmpty()); } }); - allowReloadButton.setEnabled(!competitorSelectionModel.getSelectedSet().isEmpty()); + allowReloadButton.setEnabled(!refreshableCompetitorSelectionModel.getSelectedSet().isEmpty()); if (leaderboardName != null) { refreshCompetitorList(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java index 9b74a2a56cb..8461846d12d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java @@ -15,7 +15,6 @@ import com.google.gwt.user.cellview.client.TextColumn; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.ImageResourceRenderer; import com.google.gwt.user.client.ui.Label; -import com.google.gwt.view.client.SelectionModel; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.gwt.ui.adminconsole.ColorColumn.ColorRetriever; import com.sap.sailing.gwt.ui.client.FlagImageResolver; @@ -24,14 +23,26 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sse.common.Color; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; -public class CompetitorTableWrapper> extends TableWrapper { +public class CompetitorTableWrapper> extends TableWrapper { private final LabeledAbstractFilterablePanel filterField; public CompetitorTableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages,ErrorReporter errorReporter, boolean multiSelection, boolean enablePager) { - super(sailingService, stringMessages, errorReporter, multiSelection, enablePager); + super(sailingService, stringMessages, errorReporter, multiSelection, enablePager, + new EntityIdentityComparator() { + @Override + public boolean representSameEntity(CompetitorDTO dto1, CompetitorDTO dto2) { + return dto1.getIdAsString().equals(dto2.getIdAsString()); + } + @Override + public int hashCode(CompetitorDTO t) { + return t.getIdAsString().hashCode(); + } + }); ListHandler competitorColumnListHandler = getColumnSortHandler(); // competitors table diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ControlPointTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ControlPointTableWrapper.java index 3671f8d60f4..8fa2cf96e96 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ControlPointTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ControlPointTableWrapper.java @@ -1,18 +1,29 @@ package com.sap.sailing.gwt.ui.adminconsole; import com.google.gwt.user.cellview.client.TextColumn; -import com.google.gwt.view.client.SelectionModel; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.ControlPointDTO; import com.sap.sailing.gwt.ui.shared.GateDTO; import com.sap.sailing.gwt.ui.shared.MarkDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel; -public class ControlPointTableWrapper> extends TableWrapper { +public class ControlPointTableWrapper> extends TableWrapper { public ControlPointTableWrapper(boolean multiSelection, SailingServiceAsync sailingService, final StringMessages stringMessages, ErrorReporter errorReporter) { - super(sailingService, stringMessages, errorReporter, multiSelection, /* enablePager */ true); + super(sailingService, stringMessages, errorReporter, multiSelection, /* enablePager */ true, + new EntityIdentityComparator() { + @Override + public boolean representSameEntity(ControlPointDTO dto1, ControlPointDTO dto2) { + return dto1.getIdAsString().equals(dto2.getIdAsString()); + } + @Override + public int hashCode(ControlPointDTO t) { + return t.getIdAsString().hashCode(); + } + }); TextColumn nameColumn = new TextColumn() { @Override public String getValue(ControlPointDTO d) { @@ -35,4 +46,8 @@ public class ControlPointTableWrapper> }; table.addColumn(typeColumn, stringMessages.type()); } + @Override + public void refresh(Iterable controllPoints) { + super.refresh(controllPoints); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CopyCourseAndCompetitorsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CopyCourseAndCompetitorsDialog.java index 096b8f8b20b..24efd341018 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CopyCourseAndCompetitorsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CopyCourseAndCompetitorsDialog.java @@ -7,17 +7,17 @@ import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Widget; -import com.google.gwt.view.client.MultiSelectionModel; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.sap.sailing.gwt.ui.adminconsole.AbstractLeaderboardConfigPanel.RaceColumnDTOAndFleetDTOWithNameBasedEquality; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog; public class CopyCourseAndCompetitorsDialog extends DataEntryDialog { - private final RaceTableWrapper> + private final RaceTableWrapper> racesTable; private final CheckBox courseCheckBox; private final CheckBox competitorCheckBox; @@ -38,7 +38,7 @@ public class CopyCourseAndCompetitorsDialog extends DataEntryDialog>( + racesTable = new RaceTableWrapper>( sailingService, stringMessages, errorReporter, /* multiSelection */ true); racesTable.setSelectedLeaderboardName(leaderboardName); racesTable.getDataProvider().getList().addAll(races); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CourseManagementWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CourseManagementWidget.java index 088a236ceb0..d63cbf39034 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CourseManagementWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CourseManagementWidget.java @@ -16,11 +16,9 @@ import com.google.gwt.user.client.ui.HasVerticalAlignment; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.IsWidget; import com.google.gwt.user.client.ui.Widget; -import com.google.gwt.view.client.MultiSelectionModel; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.google.gwt.view.client.SetSelectionModel; -import com.google.gwt.view.client.SingleSelectionModel; import com.sap.sailing.domain.common.PassingInstruction; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -30,12 +28,14 @@ import com.sap.sailing.gwt.ui.shared.MarkDTO; import com.sap.sailing.gwt.ui.shared.RaceCourseDTO; import com.sap.sailing.gwt.ui.shared.WaypointDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; +import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog; public abstract class CourseManagementWidget implements IsWidget { - protected final MarkTableWrapper> marks; - protected final ControlPointTableWrapper> multiMarkControlPoints; - protected final WaypointTableWrapper> waypoints; + protected final MarkTableWrapper> marks; + protected final ControlPointTableWrapper> multiMarkControlPoints; + protected final WaypointTableWrapper> waypoints; protected final Grid mainPanel; @@ -67,11 +67,11 @@ public abstract class CourseManagementWidget implements IsWidget { mainPanel.setCellPadding(5); mainPanel.getRowFormatter().setVerticalAlign(0, HasVerticalAlignment.ALIGN_TOP); - waypoints = new WaypointTableWrapper>( + waypoints = new WaypointTableWrapper>( /* multiSelection */ false, sailingService, stringMessages, errorReporter); - multiMarkControlPoints = new ControlPointTableWrapper>( + multiMarkControlPoints = new ControlPointTableWrapper>( /* multiSelection */ false, sailingService, stringMessages, errorReporter); - marks = new MarkTableWrapper>( + marks = new MarkTableWrapper>( /* multiSelection */ true, sailingService, stringMessages, errorReporter); marks.getSelectionModel().addSelectionChangeHandler(new SelectionChangeEvent.Handler() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationListComposite.java index 5bab4bb5ab0..ad6323343d6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationListComposite.java @@ -1,6 +1,5 @@ package com.sap.sailing.gwt.ui.adminconsole; -import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -15,20 +14,19 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.view.client.ListDataProvider; -import com.google.gwt.view.client.MultiSelectionModel; -import com.google.gwt.view.client.SelectionChangeEvent; -import com.google.gwt.view.client.SelectionModel; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.SelectionProvider; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationMatcherDTO; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; public class DeviceConfigurationListComposite extends Composite { protected static AdminConsoleTableResources tableResource = GWT.create(AdminConsoleTableResources.class); - private final SelectionModel configurationSelectionModel; + private final RefreshableMultiSelectionModel refreshableConfigurationSelectionModel; private final CellTable configurationTable; protected ListDataProvider configurationsDataProvider; @@ -41,8 +39,7 @@ public class DeviceConfigurationListComposite extends Composite { private final ErrorReporter errorReporter; protected final StringMessages stringMessages; - public DeviceConfigurationListComposite(final SailingServiceAsync sailingService, - final SelectionProvider selectionProvider, + public DeviceConfigurationListComposite(final SailingServiceAsync sailingService, final ErrorReporter errorReporter, final StringMessages stringMessages) { this.sailingService = sailingService; this.errorReporter = errorReporter; @@ -61,15 +58,18 @@ public class DeviceConfigurationListComposite extends Composite { configurationTable = createConfigurationTable(); configurationTable.setVisible(true); - - configurationSelectionModel = new MultiSelectionModel(); - configurationSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + refreshableConfigurationSelectionModel = new RefreshableMultiSelectionModel<>(new EntityIdentityComparator() { @Override - public void onSelectionChange(SelectionChangeEvent event) { - selectionProvider.setSelection(getSelectedConfigurations()); + public boolean representSameEntity(DeviceConfigurationMatcherDTO dto1, DeviceConfigurationMatcherDTO dto2) { + return Util.equalsWithNull(dto1.type.name(), dto2.type.name()); } - }); - configurationTable.setSelectionModel(configurationSelectionModel); + + @Override + public int hashCode(DeviceConfigurationMatcherDTO t) { + return t.type.name() == null ? 0 : t.type.name().hashCode(); + } + }, configurationsDataProvider); + configurationTable.setSelectionModel(refreshableConfigurationSelectionModel); panel.add(configurationTable); initWidget(mainPanel); @@ -97,6 +97,7 @@ public class DeviceConfigurationListComposite extends Composite { noConfigurationsLabel.setVisible(true); configurationTable.setVisible(false); errorReporter.reportError("Error retrieving configuration data from server: " + caught.getMessage()); + refreshableConfigurationSelectionModel.clear(); } }); } @@ -145,16 +146,8 @@ public class DeviceConfigurationListComposite extends Composite { table.addColumn(identifierNameColumn, stringMessages.device()); return table; } - - private List getSelectedConfigurations() { - List result = new ArrayList(); - if (configurationsDataProvider != null) { - for (DeviceConfigurationMatcherDTO regatta : configurationsDataProvider.getList()) { - if (configurationSelectionModel.isSelected(regatta)) { - result.add(regatta); - } - } - } - return result; + + public RefreshableMultiSelectionModel getSelectionModel() { + return refreshableConfigurationSelectionModel; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationPanel.java index 70c24a77c22..ff483265f84 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationPanel.java @@ -1,7 +1,7 @@ package com.sap.sailing.gwt.ui.adminconsole; -import java.util.Arrays; import java.util.List; +import java.util.Set; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.ClickEvent; @@ -13,20 +13,20 @@ import com.google.gwt.user.client.ui.HasVerticalAlignment; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.view.client.SelectionChangeEvent; +import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.sap.sailing.gwt.ui.adminconsole.DeviceConfigurationDetailComposite.DeviceConfigurationCloneListener; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.SelectionChangeListener; -import com.sap.sailing.gwt.ui.client.SelectionProvider; -import com.sap.sailing.gwt.ui.client.SelectionProviderImpl; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationMatcherDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator; -public class DeviceConfigurationPanel extends SimplePanel implements SelectionChangeListener, DeviceConfigurationCloneListener { +public class DeviceConfigurationPanel extends SimplePanel implements DeviceConfigurationCloneListener { public static String renderIdentifiers(List clientIdentifiers, StringMessages stringMessages) { if (clientIdentifiers.size() == 1) { @@ -48,17 +48,30 @@ public class DeviceConfigurationPanel extends SimplePanel implements SelectionCh private DeviceConfigurationListComposite listComposite; private DeviceConfigurationDetailComposite detailComposite; - private final SelectionProvider selectionProvider; + private final RefreshableMultiSelectionModel refreshableMultiSelectionModel; public DeviceConfigurationPanel(SailingServiceAsync sailingService, StringMessages stringMessages, ErrorReporter reporter) { this.sailingService = sailingService; this.stringMessages = stringMessages; this.errorReporter = reporter; - - this.selectionProvider = new SelectionProviderImpl(true); - this.selectionProvider.addSelectionChangeListener(this); - setupUi(); + refreshableMultiSelectionModel = listComposite.getSelectionModel(); + refreshableMultiSelectionModel.addSelectionChangeHandler(new Handler() { + + @Override + public void onSelectionChange(SelectionChangeEvent event) { + Set selectedConfigurations = refreshableMultiSelectionModel + .getSelectedSet(); + if (selectedConfigurations.size() == 1 && selectedConfigurations.iterator().hasNext()) { + detailComposite.setConfiguration(selectedConfigurations.iterator().next()); + detailComposite.setVisible(true); + } else { + detailComposite.setConfiguration(null); + detailComposite.setVisible(false); + } + removeConfigurationButton.setEnabled(!selectedConfigurations.isEmpty()); + } + }); } private void setupUi() { @@ -96,8 +109,6 @@ public class DeviceConfigurationPanel extends SimplePanel implements SelectionCh @Override public void onClick(ClickEvent event) { - detailComposite.setConfiguration(null); - detailComposite.setVisible(false); listComposite.refreshTable(); } }); @@ -110,7 +121,7 @@ public class DeviceConfigurationPanel extends SimplePanel implements SelectionCh Grid grid = new Grid(1 ,2); mainPanel.add(grid); - listComposite = createListComposite(sailingService, selectionProvider, errorReporter, stringMessages); + listComposite = createListComposite(sailingService, errorReporter, stringMessages); grid.setWidget(0, 0, listComposite); grid.getRowFormatter().setVerticalAlign(0, HasVerticalAlignment.ALIGN_TOP); grid.getColumnFormatter().getElement(1).getStyle().setPaddingTop(2.0, Unit.EM); @@ -119,18 +130,6 @@ public class DeviceConfigurationPanel extends SimplePanel implements SelectionCh detailComposite.setVisible(false); grid.setWidget(0, 1, detailComposite); } - - @Override - public void onSelectionChange(List selectedConfigurations) { - if (selectedConfigurations.size() == 1 && selectedConfigurations.iterator().hasNext()) { - detailComposite.setConfiguration(selectedConfigurations.iterator().next()); - detailComposite.setVisible(true); - } else { - detailComposite.setConfiguration(null); - detailComposite.setVisible(false); - } - removeConfigurationButton.setEnabled(!selectedConfigurations.isEmpty()); - } private void createConfiguration() { createConfiguration(new DeviceConfigurationDTO()); @@ -161,7 +160,8 @@ public class DeviceConfigurationPanel extends SimplePanel implements SelectionCh @Override public void onSuccess(DeviceConfigurationMatcherDTO newMatcher) { listComposite.refreshTable(); - onSelectionChange(Arrays.asList(newMatcher)); + refreshableMultiSelectionModel.clear(); + refreshableMultiSelectionModel.setSelected(newMatcher, true); } @Override @@ -182,10 +182,9 @@ public class DeviceConfigurationPanel extends SimplePanel implements SelectionCh return new DeviceConfigurationCreateMatcherDialog(stringMessages, validator, callback); } - protected DeviceConfigurationListComposite createListComposite(SailingServiceAsync sailingService, SelectionProvider selectionProvider, + protected DeviceConfigurationListComposite createListComposite(SailingServiceAsync sailingService, ErrorReporter errorReporter, StringMessages stringMessages) { - return new DeviceConfigurationListComposite(sailingService, selectionProvider, - errorReporter, stringMessages); + return new DeviceConfigurationListComposite(sailingService, errorReporter, stringMessages); } protected DeviceConfigurationDetailComposite createDetailComposite(SailingServiceAsync sailingService, @@ -196,7 +195,7 @@ public class DeviceConfigurationPanel extends SimplePanel implements SelectionCh private void removeConfiguration() { detailComposite.setConfiguration(null); detailComposite.setVisible(false); - for (DeviceConfigurationMatcherDTO identifier : selectionProvider.getSelectedItems()) { + for (DeviceConfigurationMatcherDTO identifier : refreshableMultiSelectionModel.getSelectedSet()) { sailingService.removeDeviceConfiguration(identifier.type, identifier.clients, new AsyncCallback() { @Override public void onSuccess(Boolean result) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationUserListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationUserListComposite.java index 939ef660f8b..3e38ef265d2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationUserListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationUserListComposite.java @@ -6,17 +6,15 @@ import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; import com.google.gwt.user.cellview.client.TextColumn; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.SelectionProvider; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationMatcherDTO; import com.sap.sse.gwt.client.ErrorReporter; public class DeviceConfigurationUserListComposite extends DeviceConfigurationListComposite { - public DeviceConfigurationUserListComposite(SailingServiceAsync sailingService, - SelectionProvider selectionProvider, ErrorReporter errorReporter, + public DeviceConfigurationUserListComposite(SailingServiceAsync sailingService, ErrorReporter errorReporter, StringMessages stringMessages) { - super(sailingService, selectionProvider, errorReporter, stringMessages); + super(sailingService, errorReporter, stringMessages); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationUserPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationUserPanel.java index b79cbad36dc..38eed18e701 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationUserPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationUserPanel.java @@ -2,7 +2,6 @@ package com.sap.sailing.gwt.ui.adminconsole; import com.sap.sailing.gwt.ui.adminconsole.DeviceConfigurationDetailComposite.DeviceConfigurationCloneListener; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.SelectionProvider; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationMatcherDTO; import com.sap.sse.gwt.client.ErrorReporter; @@ -18,9 +17,9 @@ public class DeviceConfigurationUserPanel extends DeviceConfigurationPanel { } @Override - protected DeviceConfigurationListComposite createListComposite(SailingServiceAsync sailingService, SelectionProvider selectionProvider, + protected DeviceConfigurationListComposite createListComposite(SailingServiceAsync sailingService, ErrorReporter errorReporter, StringMessages stringMessages) { - return new DeviceConfigurationUserListComposite(sailingService, selectionProvider, errorReporter, stringMessages); + return new DeviceConfigurationUserListComposite(sailingService, errorReporter, stringMessages); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceIdentifierTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceIdentifierTableWrapper.java index 7024f8a3d6a..03ce7c9dabc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceIdentifierTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceIdentifierTableWrapper.java @@ -1,17 +1,28 @@ package com.sap.sailing.gwt.ui.adminconsole; import com.google.gwt.user.cellview.client.TextColumn; -import com.google.gwt.view.client.MultiSelectionModel; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.DeviceIdentifierDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; -public class DeviceIdentifierTableWrapper extends TableWrapper> { +public class DeviceIdentifierTableWrapper extends TableWrapper> { public DeviceIdentifierTableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages, ErrorReporter errorReporter) { - super(sailingService, stringMessages, errorReporter, /* multiSelection */ true, /* enablePager */ false); + super(sailingService, stringMessages, errorReporter, /* multiSelection */ true, /* enablePager */ false, + new EntityIdentityComparator() { + @Override + public boolean representSameEntity(DeviceIdentifierDTO dto1, DeviceIdentifierDTO dto2) { + return dto1.deviceId.equals(dto2.deviceId); + } + @Override + public int hashCode(DeviceIdentifierDTO t) { + return t.deviceId.hashCode(); + } + }); TextColumn typeColumn = new TextColumn() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceMappingTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceMappingTableWrapper.java index 339a10e99a1..0cd91bd17bb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceMappingTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceMappingTableWrapper.java @@ -11,15 +11,16 @@ import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; import com.google.gwt.user.cellview.client.TextColumn; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.Widget; -import com.google.gwt.view.client.SingleSelectionModel; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; import com.sap.sailing.gwt.ui.shared.DeviceMappingDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel; -public class DeviceMappingTableWrapper extends TableWrapper> { +public class DeviceMappingTableWrapper extends TableWrapper> { static interface FilterChangedHandler { void onFilterChanged(List filteredList); } @@ -30,7 +31,18 @@ public class DeviceMappingTableWrapper extends TableWrapper() { + @Override + public boolean representSameEntity(DeviceMappingDTO dto1, DeviceMappingDTO dto2) { + return dto1.deviceIdentifier.deviceId.equals(dto2.deviceIdentifier.deviceId) && + dto1.mappedTo.getIdAsString().equals(dto2.mappedTo.getIdAsString()); + } + @Override + public int hashCode(DeviceMappingDTO t) { + return t.deviceIdentifier.deviceId.concat(t.mappedTo.getIdAsString()).hashCode(); + } + }); showPingMappingsCb = new CheckBox(stringMessages.showPingMarkMappings()); showPingMappingsCb.addValueChangeHandler(new ValueChangeHandler() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDetailsComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDetailsComposite.java index 382674ef414..f2ab322c0d5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDetailsComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDetailsComposite.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import com.google.gwt.safehtml.shared.UriUtils; import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.CaptionPanel; import com.google.gwt.user.client.ui.Composite; @@ -139,7 +140,7 @@ public class EventDetailsComposite extends Composite { regattaOverviewURLParameters.put("event", event.id.toString()); String regattaOverviewLink = EntryPointLinkFactory.createRegattaOverviewLink(regattaOverviewURLParameters); eventOverviewURL.setText(regattaOverviewLink); - eventOverviewURL.setHref(regattaOverviewLink); + eventOverviewURL.setHref(UriUtils.fromString(regattaOverviewLink)); List courseAreaNames = new ArrayList<>(); if (event.venue.getCourseAreas() != null && event.venue.getCourseAreas().size() > 0) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java index 51bd281f3dd..5c548c58620 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java @@ -24,6 +24,7 @@ import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.ui.client.DataEntryDialogWithBootstrap; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; import com.sap.sailing.gwt.ui.shared.BetterDateTimeBox; import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; import com.sap.sailing.gwt.ui.shared.EventDTO; @@ -195,24 +196,25 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap if (additionalWidget != null) { panel.add(additionalWidget); } - - Grid formGrid = new Grid(8, 2); + Grid formGrid = new Grid(9, 2); formGrid.setWidget(0, 0, new Label(stringMessages.name() + ":")); formGrid.setWidget(0, 1, nameEntryField); formGrid.setWidget(1, 0, new Label(stringMessages.description() + ":")); formGrid.setWidget(1, 1, descriptionEntryField); formGrid.setWidget(2, 0, new Label(stringMessages.venue() + ":")); formGrid.setWidget(2, 1, venueEntryField); - formGrid.setWidget(3, 0, new Label(stringMessages.startDate() + ":")); - formGrid.setWidget(3, 1, startDateBox); - formGrid.setWidget(4, 0, new Label(stringMessages.endDate() + ":")); - formGrid.setWidget(4, 1, endDateBox); - formGrid.setWidget(5, 0, new Label(stringMessages.isPublic() + ":")); - formGrid.setWidget(5, 1, isPublicCheckBox); - formGrid.setWidget(6, 0, new Label(stringMessages.eventOfficialWebsiteURL() + ":")); - formGrid.setWidget(6, 1, officialWebsiteURLEntryField); - formGrid.setWidget(7, 0, new Label(stringMessages.eventSailorsInfoWebsiteURL() + ":")); - formGrid.setWidget(7, 1, sailorsInfoWebsiteURLEntryField); + formGrid.setWidget(3, 0, new Label(stringMessages.timeZone() + ":")); + formGrid.setWidget(3, 1, new Label(DateAndTimeFormatterUtil.getClientTimeZoneAsGMTString())); + formGrid.setWidget(4, 0, new Label(stringMessages.startDate() + ":")); + formGrid.setWidget(4, 1, startDateBox); + formGrid.setWidget(5, 0, new Label(stringMessages.endDate() + ":")); + formGrid.setWidget(5, 1, endDateBox); + formGrid.setWidget(6, 0, new Label(stringMessages.isPublic() + ":")); + formGrid.setWidget(6, 1, isPublicCheckBox); + formGrid.setWidget(7, 0, new Label(stringMessages.eventOfficialWebsiteURL() + ":")); + formGrid.setWidget(7, 1, officialWebsiteURLEntryField); + formGrid.setWidget(8, 0, new Label(stringMessages.eventSailorsInfoWebsiteURL() + ":")); + formGrid.setWidget(8, 1, sailorsInfoWebsiteURLEntryField); TabLayoutPanel tabPanel = new TabLayoutPanel(30, Unit.PX); tabPanel.setHeight("500px"); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java index 6a9820f3bf8..70012a52657 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java @@ -7,7 +7,6 @@ import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.UUID; import com.google.gwt.cell.client.AbstractCell; @@ -32,14 +31,13 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.view.client.ListDataProvider; -import com.google.gwt.view.client.MultiSelectionModel; import com.google.gwt.view.client.SelectionChangeEvent; import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory; -import com.sap.sailing.gwt.ui.client.EventSelectionProvider; import com.sap.sailing.gwt.ui.client.EventsRefresher; import com.sap.sailing.gwt.ui.client.LeaderboardGroupsDisplayer; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.FlushableCellTable; import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; @@ -50,6 +48,8 @@ import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; @@ -60,12 +60,11 @@ import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; */ public class EventListComposite extends Composite implements EventsRefresher, LeaderboardGroupsDisplayer { private final SailingServiceAsync sailingService; - private final EventSelectionProvider eventSelectionProvider; private final ErrorReporter errorReporter; private final StringMessages stringMessages; private CellTable eventTable; - private MultiSelectionModel eventSelectionModel; + private final RefreshableMultiSelectionModel refreshableEventSelectionModel; private ListDataProvider eventListDataProvider; private List allEvents; private LabeledAbstractFilterablePanel filterTextbox; @@ -93,10 +92,9 @@ public class EventListComposite extends Composite implements EventsRefresher, Le private final AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); - public EventListComposite(final SailingServiceAsync sailingService, final EventSelectionProvider eventSelectionProvider, - final ErrorReporter errorReporter, final StringMessages stringMessages) { + @SuppressWarnings("unchecked") + public EventListComposite(final SailingServiceAsync sailingService, final ErrorReporter errorReporter, final StringMessages stringMessages) { this.sailingService = sailingService; - this.eventSelectionProvider = eventSelectionProvider; this.stringMessages = stringMessages; this.errorReporter = errorReporter; availableLeaderboardGroups = Collections.emptyList(); @@ -134,7 +132,7 @@ public class EventListComposite extends Composite implements EventsRefresher, Le @Override public void onClick(ClickEvent event) { if (Window.confirm(stringMessages.doYouReallyWantToRemoveEvents())) { - removeEvents(eventSelectionModel.getSelectedSet()); + removeEvents(refreshableEventSelectionModel.getSelectedSet()); } } }); @@ -143,24 +141,14 @@ public class EventListComposite extends Composite implements EventsRefresher, Le eventListDataProvider = new ListDataProvider(); eventTable = createEventTable(); eventTable.ensureDebugId("EventsCellTable"); + refreshableEventSelectionModel = (RefreshableMultiSelectionModel) eventTable.getSelectionModel(); eventTable.setVisible(false); - @SuppressWarnings("unchecked") - MultiSelectionModel multiSelectionModel = (MultiSelectionModel) eventTable.getSelectionModel(); - eventSelectionModel = multiSelectionModel; - - eventSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + this.refreshableEventSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { @Override public void onSelectionChange(SelectionChangeEvent event) { - final boolean somethingSelected = !eventSelectionModel.getSelectedSet().isEmpty(); + final boolean somethingSelected = !refreshableEventSelectionModel.getSelectedSet().isEmpty(); removeEventsButton.setEnabled(somethingSelected); - - List selectedEvents = getSelectedEvents(); - List selectedEventUUIDs = new ArrayList(); - for (EventDTO selectedEvent : selectedEvents) { - selectedEventUUIDs.add(selectedEvent.id); - } - EventListComposite.this.eventSelectionProvider.setSelection(selectedEventUUIDs); } }); @@ -194,22 +182,23 @@ public class EventListComposite extends Composite implements EventsRefresher, Le } private CellTable createEventTable() { - CellTable table = new CellTable(/* pageSize */10000, tableRes); + FlushableCellTable table = new FlushableCellTable(/* pageSize */10000, tableRes); eventListDataProvider.addDataDisplay(table); table.setWidth("100%"); - SelectionCheckboxColumn eventSelectionCheckboxColumn = new SelectionCheckboxColumn(tableRes.cellTableStyle().cellTableCheckboxSelected(), - tableRes.cellTableStyle().cellTableCheckboxDeselected(), tableRes.cellTableStyle().cellTableCheckboxColumnCell()) { - @Override - protected ListDataProvider getListDataProvider() { - return eventListDataProvider; - } - - @Override - public Boolean getValue(EventDTO row) { - return eventTable.getSelectionModel().isSelected(row); - } - }; + SelectionCheckboxColumn eventSelectionCheckboxColumn = new SelectionCheckboxColumn( + tableRes.cellTableStyle().cellTableCheckboxSelected(), + tableRes.cellTableStyle().cellTableCheckboxDeselected(), + tableRes.cellTableStyle().cellTableCheckboxColumnCell(), new EntityIdentityComparator() { + @Override + public boolean representSameEntity(EventDTO dto1, EventDTO dto2) { + return dto1.id.equals(dto2.id); + } + @Override + public int hashCode(EventDTO t) { + return t.id.hashCode(); + } + },eventListDataProvider,table); AnchorCell anchorCell = new AnchorCell(); Column eventNameColumn = new Column(anchorCell) { @@ -601,20 +590,9 @@ public class EventListComposite extends Composite implements EventsRefresher, Le eventTable.setVisible(true); noEventsLabel.setVisible(false); } - - Set selectedEventUUIDs = new HashSet<>(); - for (EventDTO selectedEvent : eventSelectionModel.getSelectedSet()) { - selectedEventUUIDs.add(selectedEvent.id); - } - eventSelectionModel.clear(); allEvents.clear(); allEvents.addAll(events); filterTextbox.updateAll(allEvents); - for (EventDTO e : allEvents) { - if (selectedEventUUIDs.contains(e.id)) { - eventSelectionModel.setSelected(e, true); - } - } } }); } @@ -623,15 +601,7 @@ public class EventListComposite extends Composite implements EventsRefresher, Le return allEvents; } - private List getSelectedEvents() { - List result = new ArrayList(); - if (eventListDataProvider != null) { - for (EventDTO Event : eventListDataProvider.getList()) { - if (eventSelectionModel.isSelected(Event)) { - result.add(Event); - } - } - } - return result; + public RefreshableMultiSelectionModel getRefreshableMultiSelectionModel() { + return refreshableEventSelectionModel; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventManagementPanel.java index fd6c47395b5..24bf4f7ff9a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventManagementPanel.java @@ -1,14 +1,11 @@ package com.sap.sailing.gwt.ui.adminconsole; -import java.util.List; -import java.util.UUID; - +import java.util.Set; import com.google.gwt.user.client.ui.CaptionPanel; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.VerticalPanel; -import com.sap.sailing.gwt.ui.client.EventSelectionChangeListener; -import com.sap.sailing.gwt.ui.client.EventSelectionModel; -import com.sap.sailing.gwt.ui.client.EventSelectionProvider; +import com.google.gwt.view.client.SelectionChangeEvent; +import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.sap.sailing.gwt.ui.client.EventsRefresher; import com.sap.sailing.gwt.ui.client.LeaderboardGroupsDisplayer; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -16,6 +13,7 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; /** * Allows administrators to manage a sailing event. @@ -23,11 +21,11 @@ import com.sap.sse.gwt.client.ErrorReporter; * @author Frank Mittag (C5163974) * @author Axel Uhl (d043530) */ -public class EventManagementPanel extends SimplePanel implements EventsRefresher, LeaderboardGroupsDisplayer, EventSelectionChangeListener { +public class EventManagementPanel extends SimplePanel implements EventsRefresher, LeaderboardGroupsDisplayer { private EventListComposite eventListComposite; private EventDetailsComposite eventDetailsComposite; private final CaptionPanel eventsPanel; - private EventSelectionProvider eventSelectionProvider; + private final RefreshableMultiSelectionModel refreshableEventSelectionModel; public EventManagementPanel(final SailingServiceAsync sailingService, final ErrorReporter errorReporter, final StringMessages stringMessages) { @@ -40,10 +38,7 @@ public class EventManagementPanel extends SimplePanel implements EventsRefresher VerticalPanel eventsContentPanel = new VerticalPanel(); eventsPanel.setContentWidget(eventsContentPanel); - eventSelectionProvider = new EventSelectionModel(true); - eventSelectionProvider.addEventSelectionChangeListener(this); - - eventListComposite = new EventListComposite(sailingService, eventSelectionProvider, errorReporter, stringMessages); + eventListComposite = new EventListComposite(sailingService, errorReporter, stringMessages); eventListComposite.ensureDebugId("EventListComposite"); eventsContentPanel.add(eventListComposite); @@ -51,6 +46,27 @@ public class EventManagementPanel extends SimplePanel implements EventsRefresher eventDetailsComposite.ensureDebugId("EventDetailsComposite"); eventDetailsComposite.setVisible(false); mainPanel.add(eventDetailsComposite); + + refreshableEventSelectionModel = eventListComposite.getRefreshableMultiSelectionModel(); + refreshableEventSelectionModel.addSelectionChangeHandler(new Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + final Set selectedEvents = refreshableEventSelectionModel.getSelectedSet(); + if (selectedEvents.size() == 1 && eventListComposite.getAllEvents() != null) { + final EventDTO selectedEvent = selectedEvents.iterator().next(); + for (EventDTO eventDTO : eventListComposite.getAllEvents()) { + if (eventDTO.id.equals(selectedEvent.id)) { + eventDetailsComposite.setEvent(eventDTO); + eventDetailsComposite.setVisible(true); + break; + } + } + } else { + eventDetailsComposite.setEvent(null); + eventDetailsComposite.setVisible(false); + } + } + }); } @Override @@ -62,22 +78,4 @@ public class EventManagementPanel extends SimplePanel implements EventsRefresher public void fillLeaderboardGroups(Iterable leaderboardGroups) { eventListComposite.fillLeaderboardGroups(leaderboardGroups); } - - @Override - public void onEventSelectionChange(List selectedEvents) { - final UUID selectedEventUUID; - if (selectedEvents.size() == 1 && eventListComposite.getAllEvents() != null) { - selectedEventUUID = selectedEvents.get(0); - for (EventDTO eventDTO : eventListComposite.getAllEvents()) { - if (eventDTO.id.equals(selectedEventUUID)) { - eventDetailsComposite.setEvent(eventDTO); - eventDetailsComposite.setVisible(true); - break; - } - } - } else { - eventDetailsComposite.setEvent(null); - eventDetailsComposite.setVisible(false); - } - } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/GateCreationDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/GateCreationDialog.java index d7fee494249..adbc9f85ee6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/GateCreationDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/GateCreationDialog.java @@ -10,7 +10,6 @@ import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.Widget; -import com.google.gwt.view.client.MultiSelectionModel; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -18,10 +17,11 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.GateDTO; import com.sap.sailing.gwt.ui.shared.MarkDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog; public class GateCreationDialog extends DataEntryDialog { - private final MarkTableWrapper> marksWrapper; + private final MarkTableWrapper> marksWrapper; private final TextBox name; private final StringMessages stringMessages; @@ -42,7 +42,7 @@ public class GateCreationDialog extends DataEntryDialog { }, /* animationEnabled */ false, callback); this.stringMessages = stringMessages; name = createTextBox(""); - marksWrapper = new MarkTableWrapper>( + marksWrapper = new MarkTableWrapper>( /* multiSelection */ true, sailingService, stringMessages, errorReporter); marksWrapper.getDataProvider().getList().addAll(marks); marksWrapper.getSelectionModel().addSelectionChangeHandler(new Handler() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java index db01cb84507..7a2c891e806 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java @@ -9,6 +9,7 @@ import com.google.gwt.cell.client.FieldUpdater; 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.safehtml.shared.UriUtils; import com.google.gwt.text.shared.SafeHtmlRenderer; import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.Column; @@ -29,13 +30,13 @@ import com.google.gwt.user.client.ui.Widget; import com.google.gwt.view.client.ListDataProvider; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; -import com.google.gwt.view.client.SingleSelectionModel; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.controls.ImagesBarCell; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.IconResources; +import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; @@ -46,6 +47,7 @@ public class IgtimiAccountsPanel extends FlowPanel { private final ErrorReporter errorReporter; private final CellTable allAccounts; private final LabeledAbstractFilterablePanel filterAccountsPanel; + private final RefreshableSingleSelectionModel refreshableAccountsSelectionModel; public static class AccountImagesBarCell extends ImagesBarCell { public static final String ACTION_REMOVE = "ACTION_REMOVE"; @@ -75,9 +77,9 @@ public class IgtimiAccountsPanel extends FlowPanel { AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); allAccounts = new CellTable(/* pageSize */10000, tableRes); - final SingleSelectionModel accountsSelectionModel = new SingleSelectionModel(); - allAccounts.setSelectionModel(accountsSelectionModel); final ListDataProvider filteredAccounts = new ListDataProvider(); + refreshableAccountsSelectionModel = new RefreshableSingleSelectionModel(null, filteredAccounts); + allAccounts.setSelectionModel(refreshableAccountsSelectionModel); ListHandler accountColumnListHandler = new ListHandler(filteredAccounts.getList()); filteredAccounts.addDataDisplay(allAccounts); final List emptyList = Collections.emptyList(); @@ -94,17 +96,17 @@ public class IgtimiAccountsPanel extends FlowPanel { removeAccountButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { - if (accountsSelectionModel.getSelectedObject() != null) { + if (refreshableAccountsSelectionModel.getSelectedObject() != null) { if (Window.confirm("Do you really want to remove the leaderboards?")) { - removeAccount(accountsSelectionModel.getSelectedObject(), filteredAccounts); + removeAccount(refreshableAccountsSelectionModel.getSelectedObject(), filteredAccounts); } } } }); - accountsSelectionModel.addSelectionChangeHandler(new Handler() { + refreshableAccountsSelectionModel.addSelectionChangeHandler(new Handler() { @Override public void onSelectionChange(SelectionChangeEvent event) { - removeAccountButton.setEnabled(accountsSelectionModel.getSelectedObject() != null); + removeAccountButton.setEnabled(refreshableAccountsSelectionModel.getSelectedObject() != null); } }); controlsPanel.add(filterAccountsPanel); @@ -159,7 +161,7 @@ public class IgtimiAccountsPanel extends FlowPanel { @Override public void onSuccess(String result) { - Anchor addIgtimiUserLink = new Anchor(stringMessages.addIgtimiUser(), result); + Anchor addIgtimiUserLink = new Anchor(stringMessages.addIgtimiUser(), UriUtils.fromString(result).asString()); controlsPanel.add(addIgtimiUserLink); } }); @@ -250,7 +252,7 @@ public class IgtimiAccountsPanel extends FlowPanel { return new UserData(eMail.getText(), password.getText()); } } - + private static void updateAllAccounts(SailingServiceAsync sailingService, final LabeledAbstractFilterablePanel filterAccountsPanel, final StringMessages stringMessages, final ErrorReporter errorReporter) { sailingService.getAllIgtimiAccountEmailAddresses(new AsyncCallback>() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageDialog.java index dd67a567fda..1d8ed5ef1e8 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageDialog.java @@ -109,24 +109,30 @@ public abstract class ImageDialog extends DataEntryDialog { imageURLAndUploadComposite.addValueChangeHandler(new ValueChangeHandler() { @Override public void onValueChange(ValueChangeEvent event) { - busyIndicator.setBusy(true); String imageUrlAsString = event.getValue(); - ImageDialog.this.sailingService.resolveImageDimensions(imageUrlAsString, new AsyncCallback>() { - @Override - public void onSuccess(Pair imageSize) { - busyIndicator.setBusy(false); - if (imageSize != null) { - widthInPxBox.setValue(imageSize.getA()); - heightInPxBox.setValue(imageSize.getB()); - } - validate(); - } - - @Override - public void onFailure(Throwable caught) { - busyIndicator.setBusy(false); - } - }); + if (imageUrlAsString == null || imageUrlAsString.isEmpty()) { + widthInPxBox.setText(""); + heightInPxBox.setText(""); + } else { + busyIndicator.setBusy(true); + ImageDialog.this.sailingService.resolveImageDimensions(imageUrlAsString, + new AsyncCallback>() { + @Override + public void onSuccess(Pair imageSize) { + busyIndicator.setBusy(false); + if (imageSize != null) { + widthInPxBox.setValue(imageSize.getA()); + heightInPxBox.setValue(imageSize.getB()); + } + validate(); + } + + @Override + public void onFailure(Throwable caught) { + busyIndicator.setBusy(false); + } + }); + } validate(); } }); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ItemToMapToDeviceSelectionPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ItemToMapToDeviceSelectionPanel.java index 688aadeefd5..1e3c7928a86 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ItemToMapToDeviceSelectionPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ItemToMapToDeviceSelectionPanel.java @@ -16,10 +16,11 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.MarkDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel; public class ItemToMapToDeviceSelectionPanel implements IsWidget { - private final CompetitorTableWrapper> competitorTable; - private final MarkTableWrapper> markTable; + private final CompetitorTableWrapper> competitorTable; + private final MarkTableWrapper> markTable; private MappableToDevice selected; private final VerticalPanel mainPanel; private final ErrorReporter errorReporter; @@ -34,7 +35,7 @@ public class ItemToMapToDeviceSelectionPanel implements IsWidget { this.selected = selected; this.errorReporter = errorReporter; competitorTable = new CompetitorTableWrapper<>(sailingService, stringMessages, errorReporter, /* multiSelection */ false, /* enablePager */ true); - markTable = new MarkTableWrapper>(/* multiSelection */ false, sailingService, + markTable = new MarkTableWrapper>(/* multiSelection */ false, sailingService, stringMessages, errorReporter); final SingleSelectionModel markSelectionModel = markTable.getSelectionModel(); competitorTable.getSelectionModel().addSelectionChangeHandler(new SelectionChangeEvent.Handler() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java index 61f4c85bfb2..75e4b2735e5 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java @@ -13,13 +13,12 @@ import java.util.Set; import com.google.gwt.cell.client.AbstractCell; import com.google.gwt.cell.client.FieldUpdater; import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.safehtml.client.SafeHtmlTemplates; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +import com.google.gwt.safehtml.shared.UriUtils; import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; @@ -30,6 +29,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.HasHorizontalAlignment; import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.view.client.ListDataProvider; import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.RegattaName; import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO; @@ -40,13 +40,14 @@ import com.sap.sailing.gwt.ui.adminconsole.DisablableCheckboxCell.IsEnabled; import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory; import com.sap.sailing.gwt.ui.client.LeaderboardsDisplayer; import com.sap.sailing.gwt.ui.client.LeaderboardsRefresher; -import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.RegattasDisplayer; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.FlushableCellTable; import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardEntryPoint; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardUrlSettings; import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.RaceLogSetStartTimeAndProcedureDTO; @@ -54,11 +55,10 @@ import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.common.Util; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; -import com.sap.sse.gwt.client.URLEncoder; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; -public class LeaderboardConfigPanel extends AbstractLeaderboardConfigPanel implements SelectedLeaderboardProvider, RegattasDisplayer, RaceSelectionChangeListener, +public class LeaderboardConfigPanel extends AbstractLeaderboardConfigPanel implements SelectedLeaderboardProvider, RegattasDisplayer, TrackedRaceChangedListener, LeaderboardsDisplayer { private final AnchorTemplates ANCHORTEMPLATE = GWT.create(AnchorTemplates.class); @@ -127,20 +127,25 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { } @Override - protected void addColumnsToLeaderboardTableAndSetSelectionModel(final CellTable leaderboardTable, AdminConsoleTableResources tableResources) { + protected void addColumnsToLeaderboardTableAndSetSelectionModel(final FlushableCellTable leaderboardTable, + AdminConsoleTableResources tableResources, ListDataProvider listDataProvider) { ListHandler leaderboardColumnListHandler = new ListHandler( leaderboardList.getList()); SelectionCheckboxColumn selectionCheckboxColumn = createSortableSelectionCheckboxColumn( - leaderboardTable, tableResources, leaderboardColumnListHandler); + leaderboardTable, tableResources, leaderboardColumnListHandler, listDataProvider); AnchorCell anchorCell = new AnchorCell(); Column linkColumn = new Column(anchorCell) { @Override public SafeHtml getValue(StrippedLeaderboardDTO object) { - String debugParam = Window.Location.getParameter("gwt.codesvr"); - String link = URLEncoder.encode("/gwt/Leaderboard.html?name=" + object.name - + (showRaceDetails ? "&showRaceDetails=true" : "") - + (object.displayName != null ? "&displayName="+object.displayName : "") - + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : "")); + Map leaderboardUrlParams = new HashMap<>(); + leaderboardUrlParams.put("name", object.name); + if (showRaceDetails) { + leaderboardUrlParams.put(LeaderboardUrlSettings.PARAM_SHOW_RACE_DETAILS, "true"); + } + if (object.displayName != null) { + leaderboardUrlParams.put("displayName", object.displayName); + } + String link = EntryPointLinkFactory.createLeaderboardLink(leaderboardUrlParams); return ANCHORTEMPLATE.cell(link, object.name); } @@ -307,9 +312,10 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { } } } else if (LeaderboardConfigImagesBarCell.ACTION_EDIT_SCORES.equals(value)) { - String debugParam = Window.Location.getParameter("gwt.codesvr"); - Window.open("/gwt/LeaderboardEditing.html?name=" + leaderboardDTO.name - + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : ""), "_blank", null); + Map leaderboardEditingParams = new HashMap<>(); + leaderboardEditingParams.put("name", leaderboardDTO.name); + String leaderboardEditingUrl = EntryPointLinkFactory.createLeaderboardEditingLink(leaderboardEditingParams); + Window.open(leaderboardEditingUrl, "_blank", null); } else if (LeaderboardConfigImagesBarCell.ACTION_EDIT_COMPETITORS.equals(value)) { EditCompetitorsDialog editCompetitorsDialog = new EditCompetitorsDialog(sailingService, leaderboardDTO.name, stringMessages, errorReporter, new DialogCallback>() { @@ -326,7 +332,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { } else if (LeaderboardConfigImagesBarCell.ACTION_CONFIGURE_URL.equals(value)) { openLeaderboardUrlConfigDialog(leaderboardDTO, stringMessages); } else if (LeaderboardConfigImagesBarCell.ACTION_EXPORT_XML.equals(value)) { - Window.open("/export/xml?domain=leaderboard&name=" + leaderboardDTO.name, "", null); + Window.open(UriUtils.fromString("/export/xml?domain=leaderboard&name=" + leaderboardDTO.name).asString(), "", null); } else if (LeaderboardConfigImagesBarCell.ACTION_OPEN_COACH_DASHBOARD.equals(value)) { Map dashboardURLParameters = new HashMap(); dashboardURLParameters.put("leaderboardName", leaderboardDTO.name); @@ -672,17 +678,6 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { @Override protected void leaderboardSelectionChanged() { - // make sure that clearing the selection doesn't cause an unlinking of the selected tracked race - raceSelectionProvider.removeRaceSelectionChangeListener(this); - trackedRacesListComposite.clearSelection(); - // add listener again using a scheduled command which is executed when the browser's event loop re-gains - // control; we assume that at that point in time the selection updates have already been performed - Scheduler.get().scheduleFinally(new ScheduledCommand() { - @Override - public void execute() { - raceSelectionProvider.addRaceSelectionChangeListener(LeaderboardConfigPanel.this); - } - }); leaderboardRemoveButton.setEnabled(!leaderboardSelectionModel.getSelectedSet().isEmpty()); StrippedLeaderboardDTO selectedLeaderboard = getSelectedLeaderboard(); if (leaderboardSelectionModel.getSelectedSet().size() == 1 && selectedLeaderboard != null) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java index ddf2a0ad41e..268ae3f3907 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardGroupConfigPanel.java @@ -29,8 +29,6 @@ import com.google.gwt.user.client.ui.TextArea; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.view.client.ListDataProvider; -import com.google.gwt.view.client.MultiSelectionModel; -import com.google.gwt.view.client.ProvidesKey; import com.google.gwt.view.client.SelectionChangeEvent; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.gwt.ui.adminconsole.LeaderboardConfigPanel.AnchorCell; @@ -43,6 +41,7 @@ import com.sap.sailing.gwt.ui.client.LeaderboardsRefresher; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.FlushableCellTable; import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter; import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration; @@ -54,6 +53,8 @@ import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.URLEncoder; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; @@ -75,15 +76,15 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements private LabeledAbstractFilterablePanel groupsFilterablePanel; private Button removeButton; private CellTable groupsTable; - private MultiSelectionModel groupsSelectionModel; + private RefreshableMultiSelectionModel refreshableGroupsSelectionModel; /** * True, if only one group is selected in the {@link #groupsSelectionModel}. */ private boolean isSingleGroupSelected; private ListDataProvider groupsProvider; - private CellTable groupDetailsTable; - private MultiSelectionModel groupDetailsSelectionModel; + private FlushableCellTable groupDetailsTable; + private RefreshableMultiSelectionModel refreshableGroupDetailsSelectionModel; private ListDataProvider groupDetailsProvider; private Button editDescriptionButton; private Button abortDescriptionButton; @@ -93,8 +94,8 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements private Button leaderboardDownButton; private LabeledAbstractFilterablePanel leaderboardsFilterablePanel; - private CellTable leaderboardsTable; - private MultiSelectionModel leaderboardsSelectionModel; + private FlushableCellTable leaderboardsTable; + private RefreshableMultiSelectionModel refreshableLeaderboardsSelectionModel; private ListDataProvider leaderboardsProvider; private Button moveToLeaderboardsButton; @@ -187,18 +188,25 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements // Create leaderboards table leaderboardsProvider = new ListDataProvider(); ListHandler leaderboardsListHandler = new ListHandler(leaderboardsProvider.getList()); - leaderboardsTable = new CellTable(10000, tableRes); + leaderboardsTable = new FlushableCellTable(10000, tableRes); leaderboardsTable.ensureDebugId("LeaderboardsCellTable"); SelectionCheckboxColumn leaderboardTableSelectionColumn = new SelectionCheckboxColumn( - tableResources.cellTableStyle().cellTableCheckboxSelected(), tableResources.cellTableStyle().cellTableCheckboxDeselected(), tableResources.cellTableStyle().cellTableCheckboxColumnCell()) { - @Override - protected ListDataProvider getListDataProvider() { - return leaderboardsProvider; - } - }; - leaderboardsSelectionModel = leaderboardTableSelectionColumn.getSelectionModel(); - leaderboardsTable.setSelectionModel(leaderboardsSelectionModel, leaderboardTableSelectionColumn.getSelectionManager()); + tableResources.cellTableStyle().cellTableCheckboxSelected(), + tableResources.cellTableStyle().cellTableCheckboxDeselected(), + tableResources.cellTableStyle().cellTableCheckboxColumnCell(), + new EntityIdentityComparator() { + @Override + public boolean representSameEntity(StrippedLeaderboardDTO dto1, StrippedLeaderboardDTO dto2) { + return dto1.name.equals(dto2.name); + } + @Override + public int hashCode(StrippedLeaderboardDTO t) { + return t.name.hashCode(); + } + }, leaderboardsProvider, leaderboardsTable); + refreshableLeaderboardsSelectionModel = leaderboardTableSelectionColumn.getSelectionModel(); + leaderboardsTable.setSelectionModel(refreshableLeaderboardsSelectionModel, leaderboardTableSelectionColumn.getSelectionManager()); leaderboardsFilterablePanel = new LabeledAbstractFilterablePanel(filterLeaderboardsLabel, availableLeaderboards, leaderboardsTable, leaderboardsProvider) { @Override public Iterable getSearchableStrings(StrippedLeaderboardDTO t) { @@ -257,10 +265,10 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements leaderboardsTable.addColumn(leaderboardsNameColumn, stringMessages.leaderboardName()); leaderboardsTable.addColumn(leaderboardsRacesColumn, stringMessages.races()); - leaderboardsSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + refreshableLeaderboardsSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { @Override public void onSelectionChange(SelectionChangeEvent event) { - Set selectedLeaderboards = leaderboardsSelectionModel.getSelectedSet(); + Set selectedLeaderboards = refreshableLeaderboardsSelectionModel.getSelectedSet(); moveToGroupButton.setEnabled(selectedLeaderboards != null && selectedLeaderboards.size() > 0); } }); @@ -358,35 +366,41 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements } }; - groupDetailsTable = new CellTable(10000, tableRes); + groupDetailsTable = new FlushableCellTable(10000, tableRes); groupDetailsTable.ensureDebugId("LeaderboardGroupsCellTable"); groupDetailsProvider = new ListDataProvider(); groupDetailsProvider.addDataDisplay(groupDetailsTable); SelectionCheckboxColumn groupDetailsTableSelectionColumn = new SelectionCheckboxColumn( - tableResources.cellTableStyle().cellTableCheckboxSelected(), tableResources.cellTableStyle().cellTableCheckboxDeselected(), tableResources.cellTableStyle().cellTableCheckboxColumnCell()) { + tableResources.cellTableStyle().cellTableCheckboxSelected(), tableResources.cellTableStyle().cellTableCheckboxDeselected(), + tableResources.cellTableStyle().cellTableCheckboxColumnCell(), new EntityIdentityComparator() { @Override - protected ListDataProvider getListDataProvider() { - return groupDetailsProvider; + public boolean representSameEntity(StrippedLeaderboardDTO dto1, + StrippedLeaderboardDTO dto2) { + return dto1.name.equals(dto2.name); } - }; + @Override + public int hashCode(StrippedLeaderboardDTO t) { + return t.name.hashCode(); + } + }, groupDetailsProvider, groupDetailsTable); groupDetailsTable.setWidth("100%"); groupDetailsTable.addColumn(groupDetailsTableSelectionColumn, groupDetailsTableSelectionColumn.getHeader()); groupDetailsTable.addColumn(groupDetailsNameColumn, stringMessages.leaderboardName()); groupDetailsTable.addColumn(groupDetailsRacesColumn, stringMessages.races()); - groupDetailsSelectionModel = groupDetailsTableSelectionColumn.getSelectionModel(); - groupDetailsSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + refreshableGroupDetailsSelectionModel = groupDetailsTableSelectionColumn.getSelectionModel(); + refreshableGroupDetailsSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { @Override public void onSelectionChange(SelectionChangeEvent event) { - Set selectedLeaderboardsInGroup = groupDetailsSelectionModel.getSelectedSet(); + Set selectedLeaderboardsInGroup = refreshableGroupDetailsSelectionModel.getSelectedSet(); moveToLeaderboardsButton.setEnabled(selectedLeaderboardsInGroup != null && !selectedLeaderboardsInGroup.isEmpty()); leaderboardDownButton.setEnabled(selectedLeaderboardsInGroup != null && selectedLeaderboardsInGroup.size() == 1); leaderboardUpButton.setEnabled(selectedLeaderboardsInGroup != null && selectedLeaderboardsInGroup.size() == 1); } }); - groupDetailsTable.setSelectionModel(groupDetailsSelectionModel, groupDetailsTableSelectionColumn.getSelectionManager()); + groupDetailsTable.setSelectionModel(refreshableGroupDetailsSelectionModel, groupDetailsTableSelectionColumn.getSelectionManager()); groupDetailsPanel.add(groupDetailsTable); //Create details functionality @@ -465,12 +479,12 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements @Override public void onClick(ClickEvent event) { StringBuilder messageBuilder = new StringBuilder("Do you really want to remove the leaderboard groups:\n"); - for (LeaderboardGroupDTO group : groupsSelectionModel.getSelectedSet()) { + for (LeaderboardGroupDTO group : refreshableGroupsSelectionModel.getSelectedSet()) { messageBuilder.append("\n" + group.getName()); } if (Window.confirm(messageBuilder.toString())) { - removeLeaderboardGroups(groupsSelectionModel.getSelectedSet()); + removeLeaderboardGroups(refreshableGroupsSelectionModel.getSelectedSet()); } } }); @@ -480,8 +494,8 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements refreshButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { - leaderboardGroupsRefresher.fillLeaderboardGroups(); leaderboardsRefresher.fillLeaderboards(); + leaderboardGroupsRefresher.fillLeaderboardGroups(); } }); leaderboardGroupsControlsPanel.add(refreshButton); @@ -563,20 +577,24 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements groupsTable.addColumn(groupActionsColumn, stringMessages.actions()); groupsTable.addColumnSortHandler(leaderboardGroupsListHandler); - ProvidesKey groupsKeyProvider = new ProvidesKey() { - @Override - public Object getKey(LeaderboardGroupDTO group) { - return group.getName(); - } - }; - groupsSelectionModel = new MultiSelectionModel(groupsKeyProvider); - groupsSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + refreshableGroupsSelectionModel = new RefreshableMultiSelectionModel<>( + new EntityIdentityComparator() { + @Override + public boolean representSameEntity(LeaderboardGroupDTO dto1, LeaderboardGroupDTO dto2) { + return dto1.getId().equals(dto2.getId()); + } + @Override + public int hashCode(LeaderboardGroupDTO t) { + return t.getId().hashCode(); + } + }, groupsProvider); + refreshableGroupsSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { @Override public void onSelectionChange(SelectionChangeEvent event) { groupSelectionChanged(); } }); - groupsTable.setSelectionModel(groupsSelectionModel); + groupsTable.setSelectionModel(refreshableGroupsSelectionModel); groupsProvider.addDataDisplay(groupsTable); leaderboardGroupsContentPanel.add(groupsTable); @@ -610,7 +628,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements */ private void refreshLeaderboardsList() { final LeaderboardGroupDTO selectedGroup = getSelectedGroup(); - final Set selectedLeaderboards = leaderboardsSelectionModel.getSelectedSet(); + final Set selectedLeaderboards = refreshableLeaderboardsSelectionModel.getSelectedSet(); if (isSingleGroupSelected && selectedGroup != null) { sailingService.getLeaderboards(new MarkedAsyncCallback>( new AsyncCallback>() { @@ -630,9 +648,9 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements leaderboardsProvider.getList().removeAll(selectedGroup.leaderboards); leaderboardsFilterablePanel.getTextBox().setText(""); leaderboardsFilterablePanel.updateAll(availableLeaderboards); - leaderboardsSelectionModel.clear(); + refreshableLeaderboardsSelectionModel.clear(); for (StrippedLeaderboardDTO leaderboard : selectedLeaderboards) { - leaderboardsSelectionModel.setSelected(leaderboard, true); + refreshableLeaderboardsSelectionModel.setSelected(leaderboard, true); } } })); @@ -668,8 +686,8 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements public void onSuccess(LeaderboardGroupDTO newGroup) { availableLeaderboardGroups.add(newGroup); groupsFilterablePanel.updateAll(availableLeaderboardGroups); - groupsSelectionModel.clear(); - groupsSelectionModel.setSelected(newGroup, true); + refreshableGroupsSelectionModel.clear(); + refreshableGroupsSelectionModel.setSelected(newGroup, true); leaderboardGroupsRefresher.updateLeaderboardGroups(availableLeaderboardGroups, LeaderboardGroupConfigPanel.this); } })); @@ -699,7 +717,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements // if the leaderboard is selected, de-select equality/hashCode may change) and select again when done final boolean wasSelected = Util.equalsWithNull(groupToUpdate, getSelectedGroup()); if (wasSelected) { - groupsSelectionModel.setSelected(groupToUpdate, false); + refreshableGroupsSelectionModel.setSelected(groupToUpdate, false); } groupToUpdate.setName(updateDescriptor.getName()); groupToUpdate.description = updateDescriptor.getDescription(); @@ -713,7 +731,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements groupsProvider.getList().set(displayedIndex, groupToUpdate); } if (wasSelected) { - groupsSelectionModel.setSelected(groupToUpdate, true); + refreshableGroupsSelectionModel.setSelected(groupToUpdate, true); } } } @@ -793,13 +811,13 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements private void removeGroupFromTable(final LeaderboardGroupDTO group) { availableLeaderboardGroups.remove(group); groupsFilterablePanel.updateAll(availableLeaderboardGroups); - groupsSelectionModel.setSelected(group, false); + refreshableGroupsSelectionModel.setSelected(group, false); } private void groupSelectionChanged() { - isSingleGroupSelected = groupsSelectionModel.getSelectedSet().size() == 1; + isSingleGroupSelected = refreshableGroupsSelectionModel.getSelectedSet().size() == 1; final LeaderboardGroupDTO selectedGroup = getSelectedGroup(); removeButton.setEnabled(selectedGroup != null); splitPanel.setVisible(isSingleGroupSelected && selectedGroup != null); @@ -809,15 +827,18 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements descriptionTextArea.setText(selectedGroup.description); setDescriptionEditable(false); - groupDetailsSelectionModel.clear(); groupDetailsProvider.getList().clear(); groupDetailsProvider.getList().addAll(selectedGroup.leaderboards); + groupDetailsProvider.refresh(); // Tell leaderboards filter panel to use all available leaderboards except the leaderboards of the group from the list - leaderboardsSelectionModel.clear(); + refreshableLeaderboardsSelectionModel.clear(); ArrayList allLeaderboardsExceptThoseOfSelectedLeaderboardGroup = new ArrayList<>(availableLeaderboards); allLeaderboardsExceptThoseOfSelectedLeaderboardGroup.removeAll(selectedGroup.leaderboards); leaderboardsFilterablePanel.updateAll(allLeaderboardsExceptThoseOfSelectedLeaderboardGroup); + leaderboardsProvider.getList().clear(); + leaderboardsProvider.getList().addAll(allLeaderboardsExceptThoseOfSelectedLeaderboardGroup); + leaderboardsProvider.refresh(); } } @@ -825,12 +846,12 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements private void moveToLeaderboards() { LeaderboardGroupDTO selectedGroup = getSelectedGroup(); - Set selectedLeaderboards = groupDetailsSelectionModel.getSelectedSet(); + Set selectedLeaderboards = refreshableGroupDetailsSelectionModel.getSelectedSet(); if (isSingleGroupSelected && selectedGroup != null && selectedLeaderboards != null && selectedLeaderboards.size() > 0) { for (StrippedLeaderboardDTO leaderboard : selectedLeaderboards) { selectedGroup.leaderboards.remove(leaderboard); groupDetailsProvider.getList().remove(leaderboard); - groupDetailsSelectionModel.setSelected(leaderboard, false); + refreshableGroupDetailsSelectionModel.setSelected(leaderboard, false); leaderboardsProvider.getList().add(leaderboard); } updateGroup(selectedGroup); @@ -842,7 +863,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements private void moveToGroup() { final LeaderboardGroupDTO selectedGroup = getSelectedGroup(); - ArrayList selectedLeaderboards = new ArrayList(leaderboardsSelectionModel.getSelectedSet()); + ArrayList selectedLeaderboards = new ArrayList(refreshableLeaderboardsSelectionModel.getSelectedSet()); if (isSingleGroupSelected && selectedGroup != null && selectedLeaderboards != null && !selectedLeaderboards.isEmpty()) { Collections.sort(selectedLeaderboards, new Comparator() { @Override @@ -856,7 +877,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements selectedGroup.leaderboards.add(leaderboard); groupDetailsProvider.getList().add(leaderboard); leaderboardsProvider.getList().remove(leaderboard); - leaderboardsSelectionModel.setSelected(leaderboard, false); + refreshableLeaderboardsSelectionModel.setSelected(leaderboard, false); } } updateGroup(selectedGroup); @@ -865,7 +886,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements private void moveLeaderboardInGroupUp() { LeaderboardGroupDTO selectedGroup = getSelectedGroup(); - Set selectedLeaderboards = groupDetailsSelectionModel.getSelectedSet(); + Set selectedLeaderboards = refreshableGroupDetailsSelectionModel.getSelectedSet(); if (isSingleGroupSelected && selectedGroup != null && selectedLeaderboards != null && selectedLeaderboards.size() == 1) { StrippedLeaderboardDTO selectedLeaderboard = selectedLeaderboards.iterator().next(); moveLeaderboardInGroup(selectedGroup, selectedLeaderboard, -1); @@ -874,7 +895,7 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements private void moveLeaderboardInGroupDown() { LeaderboardGroupDTO selectedGroup = getSelectedGroup(); - Set selectedLeaderboards = groupDetailsSelectionModel.getSelectedSet(); + Set selectedLeaderboards = refreshableGroupDetailsSelectionModel.getSelectedSet(); if (isSingleGroupSelected && selectedGroup != null && selectedLeaderboards != null && selectedLeaderboards.size() == 1) { StrippedLeaderboardDTO selectedLeaderboard = selectedLeaderboards.iterator().next(); moveLeaderboardInGroup(selectedGroup, selectedLeaderboard, 1); @@ -928,6 +949,6 @@ public class LeaderboardGroupConfigPanel extends AbstractRegattaPanel implements } private LeaderboardGroupDTO getSelectedGroup() { - return groupsSelectionModel.getSelectedSet().isEmpty() ? null : groupsSelectionModel.getSelectedSet().iterator().next(); + return refreshableGroupsSelectionModel.getSelectedSet().isEmpty() ? null : refreshableGroupsSelectionModel.getSelectedSet().iterator().next(); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MarkTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MarkTableWrapper.java index b57c4937573..280ff80d3fc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MarkTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MarkTableWrapper.java @@ -6,7 +6,6 @@ import java.util.Comparator; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.cellview.client.TextColumn; -import com.google.gwt.view.client.SelectionModel; import com.sap.sailing.gwt.ui.adminconsole.ColorColumn.ColorRetriever; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -15,11 +14,23 @@ import com.sap.sse.common.Color; import com.sap.sse.common.impl.AbstractColor; import com.sap.sse.common.impl.RGBColor; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel; -public class MarkTableWrapper> extends TableWrapper { +public class MarkTableWrapper> extends TableWrapper { public MarkTableWrapper(boolean multiSelection, SailingServiceAsync sailingService, StringMessages stringMessages, ErrorReporter errorReporter) { - super(sailingService, stringMessages, errorReporter, multiSelection, true); + super(sailingService, stringMessages, errorReporter, multiSelection, true, + new EntityIdentityComparator() { + @Override + public boolean representSameEntity(MarkDTO dto1, MarkDTO dto2) { + return dto1.getIdAsString().equals(dto2.getIdAsString()); + } + @Override + public int hashCode(MarkDTO t) { + return t.getIdAsString().hashCode(); + } + }); TextColumn markNameColumn = new TextColumn() { @Override public String getValue(MarkDTO markDTO) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java index 3490ec93491..a1d37ab3563 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/MediaPanel.java @@ -37,7 +37,6 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.view.client.DefaultSelectionEventManager; import com.google.gwt.view.client.ListDataProvider; import com.google.gwt.view.client.SelectionModel; -import com.google.gwt.view.client.SingleSelectionModel; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.media.MediaTrack; import com.sap.sailing.domain.common.media.MediaUtil; @@ -53,6 +52,8 @@ import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; @@ -76,6 +77,7 @@ public class MediaPanel extends FlowPanel { private CellTable mediaTracksTable; private ListDataProvider mediaTrackListDataProvider = new ListDataProvider(); private Date latestDate; + private RefreshableSingleSelectionModel refreshableSelectionModel; public MediaPanel(Set regattasDisplayers, SailingServiceAsync sailingService, RegattaRefresher regattaRefresher, MediaServiceAsync mediaService, ErrorReporter errorReporter, @@ -164,12 +166,21 @@ public class MediaPanel extends FlowPanel { mediaTracksTable.addColumnSortHandler(sortHandler); // Add a selection model so we can select cells. - final SelectionModel selectionModel = new SingleSelectionModel(); - mediaTracksTable.setSelectionModel(selectionModel, + refreshableSelectionModel = new RefreshableSingleSelectionModel<>(new EntityIdentityComparator() { + @Override + public boolean representSameEntity(MediaTrack dto1, MediaTrack dto2) { + return dto1.dbId.equals(dto2.dbId); + } + @Override + public int hashCode(MediaTrack t) { + return t.dbId.hashCode(); + } + }, mediaTrackListDataProvider); + mediaTracksTable.setSelectionModel(refreshableSelectionModel, DefaultSelectionEventManager. createDefaultManager()); // Initialize the columns. - initTableColumns(selectionModel, sortHandler); + initTableColumns(refreshableSelectionModel, sortHandler); mediaTrackListDataProvider.addDataDisplay(mediaTracksTable); add(mediaTracksTable); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceSelection.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceSelection.java deleted file mode 100644 index 9ac6cd60f19..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceSelection.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.sap.sailing.gwt.ui.adminconsole; - -import com.google.gwt.core.client.JavaScriptObject; - -public class RaceSelection extends JavaScriptObject { - - protected RaceSelection() {}; - - public static final native RaceSelection create() /*-{return [];}-*/; - - public final native String toJson() /*-{return JSON.stringify(this);}-*/; - - public final native void addRace(RaceEntry race) /*-{ - - this.push(race); - - }-*/; - - public static class RaceEntry extends JavaScriptObject { - - protected RaceEntry() {}; - - public static final native RaceEntry create() /*-{return {};}-*/; - - public final native void setRaceName(String raceName) /*-{this.race = raceName;}-*/; - - public final native void setRegatteName(String regatteName) /*-{this.regatta = regatteName;}-*/; - } - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceTableWrapper.java index 5d1eb5fdeb5..805cfecd4e9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceTableWrapper.java @@ -9,7 +9,6 @@ import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.cellview.client.TextColumn; -import com.google.gwt.view.client.SelectionModel; import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.gwt.ui.adminconsole.AbstractLeaderboardConfigPanel.RaceColumnDTOAndFleetDTOWithNameBasedEquality; import com.sap.sailing.gwt.ui.adminconsole.LeaderboardConfigPanel.AnchorCell; @@ -19,15 +18,30 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel; -public class RaceTableWrapper> +public class RaceTableWrapper> extends TableWrapper { private final AnchorTemplates ANCHORTEMPLATE = GWT.create(AnchorTemplates.class); private String selectedLeaderboardName; public RaceTableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages, ErrorReporter errorReporter, boolean multiSelection) { - super(sailingService, stringMessages, errorReporter, multiSelection, /* enablePager */ false); + super(sailingService, stringMessages, errorReporter, multiSelection, /* enablePager */ false, + new EntityIdentityComparator() { + @Override + public boolean representSameEntity(RaceColumnDTOAndFleetDTOWithNameBasedEquality dto1, + RaceColumnDTOAndFleetDTOWithNameBasedEquality dto2) { + return dto1.getA().getName().toString().equals(dto2.getA().getName().toString()) && + dto1.getB().getName().toString().equals(dto2.getB().getName().toString()); + } + + @Override + public int hashCode(RaceColumnDTOAndFleetDTOWithNameBasedEquality t) { + return t.getA().getName().toString().concat(t.getB().getName().toString()).hashCode(); + } + }); Column raceNameColumn = new Column(new AnchorCell()) { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RacesListBoxPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RacesListBoxPanel.java deleted file mode 100755 index 706b564ebef..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RacesListBoxPanel.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.sap.sailing.gwt.ui.adminconsole; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.FormPanel; -import com.google.gwt.user.client.ui.HorizontalPanel; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.ListBox; -import com.google.gwt.user.client.ui.VerticalPanel; -import com.sap.sailing.domain.common.RaceIdentifier; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.domain.common.dto.RaceDTO; -import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; -import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; -import com.sap.sailing.gwt.ui.client.RegattaRefresher; -import com.sap.sailing.gwt.ui.client.RegattasDisplayer; -import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.shared.RegattaDTO; - -public class RacesListBoxPanel extends FormPanel implements RegattasDisplayer, RaceSelectionChangeListener { - private final List raceList; - private final ListBox raceListBox; - private final RegattaRefresher regattaRefresher; - private final StringMessages stringConstants; - private final RaceSelectionProvider raceSelectionProvider; - - public RacesListBoxPanel(RegattaRefresher regattaRefresher, RaceSelectionProvider raceSelectionProvider, StringMessages stringConstants) { - this.regattaRefresher = regattaRefresher; - this.stringConstants = stringConstants; - this.raceSelectionProvider = raceSelectionProvider; - raceListBox = new ListBox(); - raceListBox.addChangeHandler(new ChangeHandler() { - @Override - public void onChange(ChangeEvent event) { - RacesListBoxPanel.this.raceSelectionProvider.setSelection(getSelectedRaceIdentifiers(), RacesListBoxPanel.this); - } - }); - raceListBox.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - RacesListBoxPanel.this.raceSelectionProvider.setSelection(getSelectedRaceIdentifiers(), RacesListBoxPanel.this); - } - }); - raceList = new ArrayList(); - VerticalPanel vp = new VerticalPanel(); - HorizontalPanel labelAndRefreshButton = new HorizontalPanel(); - labelAndRefreshButton.setSpacing(20); - vp.add(labelAndRefreshButton); - labelAndRefreshButton.add(new Label(stringConstants.races())); - vp.add(raceListBox); - Button btnRefresh = new Button(stringConstants.refresh()); - btnRefresh.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - RacesListBoxPanel.this.regattaRefresher.fillRegattas(); - } - }); - labelAndRefreshButton.add(btnRefresh); - setWidget(vp); - } - - @Override - public void fillRegattas(Iterable result) { - raceList.clear(); - raceListBox.clear(); - for (RegattaDTO regatta : result) { - for (RaceDTO race : regatta.races) { - raceList.add(race); - } - } - Collections.sort(raceList, new Comparator() { - @Override - public int compare(RaceDTO o1, RaceDTO o2) { - String name1 = RacesListBoxPanel.this.toString(o1); - String name2 = RacesListBoxPanel.this.toString(o2); - return name1.compareTo(name2); - } - - }); - for (RaceDTO p : raceList) { - raceListBox.addItem(toString(p)); - } - raceSelectionProvider.setSelection(getSelectedRaceIdentifiers(), this); - } - - private List getSelectedRaceIdentifiers() { - List result = new ArrayList(); - for (RaceDTO selectedRace : getSelectedRaces()) { - result.add(selectedRace.getRaceIdentifier()); - } - return result; - } - - private List getSelectedRaces() { - int i=0; - List result = new ArrayList(); - for (RaceDTO triple : raceList) { - if (raceListBox.isItemSelected(i)) { - result.add(triple); - } - i++; - } - return result; - } - - private String toString(RaceDTO race) { - return race.getRegattaName()+" - "+race.getName()+(race.isTracked ? " ("+stringConstants.tracked()+")" : ""); - } - - @Override - public void onRaceSelectionChange(List selectedRaces) { - if (selectedRaces != null && !selectedRaces.isEmpty()) { - RaceDTO newSelection = getRace(selectedRaces.iterator().next()); - int index = raceList.indexOf(newSelection); - if (index >= 0) { - raceListBox.setSelectedIndex(index); - } else { - deselectAll(); - } - } else { - deselectAll(); - } - } - - private void deselectAll() { - for (int i=0; i getAllRaces() { - return Collections.unmodifiableList(raceList); - } - - public RaceDTO getRace(RaceIdentifier raceIdentifier) { - for (RaceDTO race : getAllRaces()) { - if (raceIdentifier.equals(race.getRaceIdentifier())) { - return race; - } - } - return null; - } - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java index d964d25e4e5..75e7359985c 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java @@ -23,15 +23,14 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.view.client.ListDataProvider; -import com.google.gwt.view.client.MultiSelectionModel; import com.google.gwt.view.client.SelectionChangeEvent; import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.RegattaName; import com.sap.sailing.gwt.ui.client.RegattaRefresher; -import com.sap.sailing.gwt.ui.client.RegattaSelectionProvider; import com.sap.sailing.gwt.ui.client.RegattasDisplayer; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.FlushableCellTable; import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; import com.sap.sailing.gwt.ui.leaderboard.RankingMetricTypeFormatter; @@ -42,6 +41,8 @@ import com.sap.sse.common.Util; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; @@ -51,8 +52,6 @@ import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; * */ public class RegattaListComposite extends Composite implements RegattasDisplayer { - - protected final MultiSelectionModel regattaSelectionModel; protected final CellTable regattaTable; protected final ListDataProvider regattaListDataProvider; private List allRegattas; @@ -62,7 +61,7 @@ public class RegattaListComposite extends Composite implements RegattasDisplayer private final Label noRegattasLabel; private final SailingServiceAsync sailingService; - protected final RegattaSelectionProvider regattaSelectionProvider; + protected final RefreshableMultiSelectionModel refreshableRegattaMultiSelectionModel; private final ErrorReporter errorReporter; private final RegattaRefresher regattaRefresher; protected final StringMessages stringMessages; @@ -78,11 +77,10 @@ public class RegattaListComposite extends Composite implements RegattasDisplayer } } - public RegattaListComposite(final SailingServiceAsync sailingService, - final RegattaSelectionProvider regattaSelectionProvider, RegattaRefresher regattaRefresher, + @SuppressWarnings("unchecked") + public RegattaListComposite(final SailingServiceAsync sailingService, RegattaRefresher regattaRefresher, final ErrorReporter errorReporter, final StringMessages stringMessages) { this.sailingService = sailingService; - this.regattaSelectionProvider = regattaSelectionProvider; this.regattaRefresher = regattaRefresher; this.errorReporter = errorReporter; this.stringMessages = stringMessages; @@ -101,6 +99,7 @@ public class RegattaListComposite extends Composite implements RegattasDisplayer regattaListDataProvider = new ListDataProvider(); regattaTable = createRegattaTable(); regattaTable.ensureDebugId("RegattasCellTable"); + refreshableRegattaMultiSelectionModel = (RefreshableMultiSelectionModel) regattaTable.getSelectionModel(); regattaTable.setVisible(false); filterablePanelRegattas = new LabeledAbstractFilterablePanel(filterRegattasLabel, allRegattas, @@ -118,46 +117,32 @@ public class RegattaListComposite extends Composite implements RegattasDisplayer filterablePanelRegattas.getTextBox().ensureDebugId("RegattasFilterTextBox"); panel.add(filterablePanelRegattas); - @SuppressWarnings("unchecked") - MultiSelectionModel multiSelectionModel = (MultiSelectionModel) regattaTable.getSelectionModel(); - regattaSelectionModel = multiSelectionModel; - - regattaSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { - @Override - public void onSelectionChange(SelectionChangeEvent event) { - List selectedRegattas = getSelectedRegattas(); - List selectedRaceIdentifiers = new ArrayList(); - for (RegattaDTO selectedRegatta : selectedRegattas) { - selectedRaceIdentifiers.add(selectedRegatta.getRegattaIdentifier()); - } - RegattaListComposite.this.regattaSelectionProvider.setSelection(selectedRaceIdentifiers); - } - }); panel.add(regattaTable); initWidget(mainPanel); } public HandlerRegistration addSelectionChangeHandler(SelectionChangeEvent.Handler handler) { - return regattaSelectionModel.addSelectionChangeHandler(handler); + return refreshableRegattaMultiSelectionModel.addSelectionChangeHandler(handler); } protected CellTable createRegattaTable() { - CellTable table = new CellTable(/* pageSize */10000, tableRes); + FlushableCellTable table = new FlushableCellTable(/* pageSize */10000, tableRes); regattaListDataProvider.addDataDisplay(table); table.setWidth("100%"); - SelectionCheckboxColumn regattaSelectionCheckboxColumn = new SelectionCheckboxColumn(tableRes.cellTableStyle().cellTableCheckboxSelected(), - tableRes.cellTableStyle().cellTableCheckboxDeselected(), tableRes.cellTableStyle().cellTableCheckboxColumnCell()) { - @Override - protected ListDataProvider getListDataProvider() { - return regattaListDataProvider; - } - - @Override - public Boolean getValue(RegattaDTO row) { - return regattaTable.getSelectionModel().isSelected(row); - } - }; + SelectionCheckboxColumn regattaSelectionCheckboxColumn = new SelectionCheckboxColumn( + tableRes.cellTableStyle().cellTableCheckboxSelected(), + tableRes.cellTableStyle().cellTableCheckboxDeselected(), + tableRes.cellTableStyle().cellTableCheckboxColumnCell(), new EntityIdentityComparator() { + @Override + public boolean representSameEntity(RegattaDTO dto1, RegattaDTO dto2) { + return dto1.getRegattaIdentifier().equals(dto2.getRegattaIdentifier()); + } + @Override + public int hashCode(RegattaDTO t) { + return t.getRegattaIdentifier().hashCode(); + } + },regattaListDataProvider, table); ListHandler columnSortHandler = new ListHandler(regattaListDataProvider.getList()); table.addColumnSortHandler(columnSortHandler); @@ -250,7 +235,6 @@ public class RegattaListComposite extends Composite implements RegattasDisplayer table.addColumn(rankingMetricColumn, stringMessages.rankingMetric()); table.addColumn(regattaActionColumn, stringMessages.actions()); table.setSelectionModel(regattaSelectionCheckboxColumn.getSelectionModel(), regattaSelectionCheckboxColumn.getSelectionManager()); - return table; } @@ -347,15 +331,7 @@ public class RegattaListComposite extends Composite implements RegattasDisplayer } protected List getSelectedRegattas() { - List result = new ArrayList(); - if (regattaListDataProvider != null) { - for (RegattaDTO regatta : regattaListDataProvider.getList()) { - if (regattaSelectionModel.isSelected(regatta)) { - result.add(regatta); - } - } - } - return result; + return new ArrayList(refreshableRegattaMultiSelectionModel.getSelectedSet()); } @Override @@ -369,17 +345,15 @@ public class RegattaListComposite extends Composite implements RegattasDisplayer } List newAllRegattas = new ArrayList(); Util.addAll(regattas, newAllRegattas); - List newAllRegattaIdentifiers = new ArrayList(); - for (RegattaDTO regatta : regattas) { - newAllRegattaIdentifiers.add(regatta.getRegattaIdentifier()); - } allRegattas = newAllRegattas; filterablePanelRegattas.updateAll(allRegattas); - regattaSelectionProvider.setAllRegattas(newAllRegattaIdentifiers); } public List getAllRegattas() { return allRegattas; } + public RefreshableMultiSelectionModel getRefreshableMultiSelectionModel() { + return refreshableRegattaMultiSelectionModel; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaLogTrackingDeviceMappingsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaLogTrackingDeviceMappingsDialog.java index fb0ad4b1207..e14daadfb47 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaLogTrackingDeviceMappingsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaLogTrackingDeviceMappingsDialog.java @@ -29,7 +29,6 @@ public class RegattaLogTrackingDeviceMappingsDialog extends AbstractLogTrackingD @Override public void onSuccess(List result) { mappings = result; - updateChart(); deviceMappingTable.refresh(mappings); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaManagementPanel.java index 236bce167ba..a8d35952281 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaManagementPanel.java @@ -1,5 +1,6 @@ package com.sap.sailing.gwt.ui.adminconsole; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -15,13 +16,12 @@ import com.google.gwt.user.client.ui.CaptionPanel; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.view.client.SelectionChangeEvent; +import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO; import com.sap.sailing.domain.common.dto.SeriesCreationParametersDTO; import com.sap.sailing.gwt.ui.client.RegattaRefresher; -import com.sap.sailing.gwt.ui.client.RegattaSelectionChangeListener; -import com.sap.sailing.gwt.ui.client.RegattaSelectionModel; -import com.sap.sailing.gwt.ui.client.RegattaSelectionProvider; import com.sap.sailing.gwt.ui.client.RegattasDisplayer; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -30,6 +30,7 @@ import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; /** @@ -39,13 +40,13 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; * @author Frank Mittag (C5163974) * */ -public class RegattaManagementPanel extends SimplePanel implements RegattasDisplayer, RegattaSelectionChangeListener { +public class RegattaManagementPanel extends SimplePanel implements RegattasDisplayer { private final SailingServiceAsync sailingService; private final ErrorReporter errorReporter; private final StringMessages stringMessages; private final RegattaRefresher regattaRefresher; - private RegattaSelectionProvider regattaSelectionProvider; + private final RefreshableMultiSelectionModel refreshableRegattaMultiSelectionModel; private Button removeRegattaButton; private RegattaListComposite regattaListComposite; @@ -92,8 +93,8 @@ public class RegattaManagementPanel extends SimplePanel implements RegattasDispl //Creating a new Collection, because getSelectedRegattas returns an //unmodifiable collection, which can't be sent to the server. Collection regattas = new HashSet(); - for (RegattaIdentifier regatta : regattaSelectionProvider.getSelectedRegattas()) { - regattas.add(regatta); + for (RegattaDTO regatta : refreshableRegattaMultiSelectionModel.getSelectedSet()) { + regattas.add(regatta.getRegattaIdentifier()); } removeRegattas(regattas); } @@ -101,12 +102,32 @@ public class RegattaManagementPanel extends SimplePanel implements RegattasDispl }); regattaManagementControlsPanel.add(removeRegattaButton); regattasContentPanel.add(regattaManagementControlsPanel); - - regattaSelectionProvider = new RegattaSelectionModel(true); - regattaSelectionProvider.addRegattaSelectionChangeListener(this); - - regattaListComposite = new RegattaListComposite(sailingService, regattaSelectionProvider, regattaRefresher, errorReporter, stringMessages); + regattaListComposite = new RegattaListComposite(sailingService, regattaRefresher, errorReporter, stringMessages); regattaListComposite.ensureDebugId("RegattaListComposite"); + refreshableRegattaMultiSelectionModel = regattaListComposite.getRefreshableMultiSelectionModel(); + refreshableRegattaMultiSelectionModel.addSelectionChangeHandler(new Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + List selectedRegattas = new ArrayList<>(refreshableRegattaMultiSelectionModel.getSelectedSet()); + final RegattaIdentifier selectedRegatta; + if (selectedRegattas.size() == 1) { + selectedRegatta = selectedRegattas.iterator().next().getRegattaIdentifier(); + if (selectedRegatta != null && regattaListComposite.getAllRegattas() != null) { + for (RegattaDTO regattaDTO : regattaListComposite.getAllRegattas()) { + if (regattaDTO.getRegattaIdentifier().equals(selectedRegatta)) { + regattaDetailsComposite.setRegatta(regattaDTO); + regattaDetailsComposite.setVisible(true); + break; + } + } + } + } else { + regattaDetailsComposite.setRegatta(null); + regattaDetailsComposite.setVisible(false); + } + removeRegattaButton.setEnabled(!selectedRegattas.isEmpty()); + } + }); regattasContentPanel.add(regattaListComposite); regattaDetailsComposite = new RegattaDetailsComposite(sailingService, regattaRefresher, errorReporter, stringMessages); @@ -221,25 +242,4 @@ public class RegattaManagementPanel extends SimplePanel implements RegattasDispl public void fillRegattas(Iterable regattas) { regattaListComposite.fillRegattas(regattas); } - - @Override - public void onRegattaSelectionChange(List selectedRegattas) { - final RegattaIdentifier selectedRegatta; - if (selectedRegattas.size() == 1 && selectedRegattas.iterator().hasNext()) { - selectedRegatta = selectedRegattas.iterator().next(); - if (selectedRegatta != null && regattaListComposite.getAllRegattas() != null) { - for (RegattaDTO regattaDTO : regattaListComposite.getAllRegattas()) { - if (regattaDTO.getRegattaIdentifier().equals(selectedRegatta)) { - regattaDetailsComposite.setRegatta(regattaDTO); - regattaDetailsComposite.setVisible(true); - break; - } - } - } - } else { - regattaDetailsComposite.setRegatta(null); - regattaDetailsComposite.setVisible(false); - } - removeRegattaButton.setEnabled(!selectedRegattas.isEmpty()); - } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RemoteServerInstancesManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RemoteServerInstancesManagementPanel.java index 70a30fdf3ba..8708162030e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RemoteServerInstancesManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RemoteServerInstancesManagementPanel.java @@ -25,12 +25,13 @@ import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.view.client.ListDataProvider; -import com.google.gwt.view.client.MultiSelectionModel; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.EventBaseDTO; import com.sap.sailing.gwt.ui.shared.RemoteSailingServerReferenceDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; @@ -40,7 +41,8 @@ public class RemoteServerInstancesManagementPanel extends SimplePanel { private final StringMessages stringMessages; private final AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); - private MultiSelectionModel serverSelectionModel; + private final ListDataProvider serverDataProvider; + private RefreshableMultiSelectionModel refreshableServerSelectionModel; private LabeledAbstractFilterablePanel filteredServerTablePanel; private final CaptionPanel remoteServersPanel; @@ -60,8 +62,8 @@ public class RemoteServerInstancesManagementPanel extends SimplePanel { VerticalPanel remoteServersContentPanel = new VerticalPanel(); remoteServersPanel.setContentWidget(remoteServersContentPanel); + serverDataProvider = new ListDataProvider(); CellTable remoteServersTable = createRemoteServersTable(); - ListDataProvider serverDataProvider = new ListDataProvider(); serverDataProvider.addDataDisplay(remoteServersTable); filteredServerTablePanel = new LabeledAbstractFilterablePanel( @@ -158,8 +160,19 @@ public class RemoteServerInstancesManagementPanel extends SimplePanel { serverTable.setEmptyTableWidget(new Label(stringMessages.noSailingServerInstancesYet())); - serverSelectionModel = new MultiSelectionModel(); - serverTable.setSelectionModel(serverSelectionModel); + refreshableServerSelectionModel = new RefreshableMultiSelectionModel( + new EntityIdentityComparator() { + @Override + public boolean representSameEntity(RemoteSailingServerReferenceDTO dto1, + RemoteSailingServerReferenceDTO dto2) { + return dto1.getUrl().equals(dto2.getUrl()); + } + @Override + public int hashCode(RemoteSailingServerReferenceDTO t) { + return t.getUrl().hashCode(); + } + }, serverDataProvider); + serverTable.setSelectionModel(refreshableServerSelectionModel); return serverTable; } @@ -180,7 +193,7 @@ public class RemoteServerInstancesManagementPanel extends SimplePanel { private void removeSelectedSailingServers() { Set toRemove = new HashSet(); - for (RemoteSailingServerReferenceDTO selectedServer: serverSelectionModel.getSelectedSet()) { + for (RemoteSailingServerReferenceDTO selectedServer: refreshableServerSelectionModel.getSelectedSet()) { toRemove.add(selectedServer.getName()); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java index 5f0bd5fff7a..71707b47540 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ReplicationPanel.java @@ -273,7 +273,8 @@ public class ReplicationPanel extends FlowPanel { errorReporter.reportPersistentInformation(stringMessages.warningServerIsReplica()); registeredMasters.insertRow(i); registeredMasters.setWidget(i, 0, new Label(stringMessages.replicatingFromMaster(replicatingFromMaster.getHostname(), - replicatingFromMaster.getMessagingPort(), replicatingFromMaster.getServletPort()))); + replicatingFromMaster.getMessagingPort(), replicatingFromMaster.getServletPort(), + replicatingFromMaster.getMessagingHostname(), replicatingFromMaster.getExchangeName()))); i++; addButton.setEnabled(false); stopReplicationButton.setEnabled(true); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java index ae00bc9551c..ce5cfa0c2d5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java @@ -19,6 +19,7 @@ import com.google.gwt.user.client.ui.CaptionPanel; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.view.client.ListDataProvider; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.sap.sailing.domain.common.PassingInstruction; @@ -31,6 +32,7 @@ import com.sap.sailing.gwt.ui.client.LeaderboardsRefresher; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.FlushableCellTable; import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sailing.gwt.ui.shared.ControlPointDTO; import com.sap.sailing.gwt.ui.shared.EventDTO; @@ -84,11 +86,12 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC } @Override - protected void addColumnsToLeaderboardTableAndSetSelectionModel(CellTable leaderboardTable, AdminConsoleTableResources tableResources) { + protected void addColumnsToLeaderboardTableAndSetSelectionModel(FlushableCellTable leaderboardTable, + AdminConsoleTableResources tableResources, ListDataProvider listDataProvider) { ListHandler leaderboardColumnListHandler = new ListHandler( leaderboardList.getList()); SelectionCheckboxColumn selectionCheckboxColumn = createSortableSelectionCheckboxColumn( - leaderboardTable, tableResources, leaderboardColumnListHandler); + leaderboardTable, tableResources, leaderboardColumnListHandler, listDataProvider); TextColumn leaderboardNameColumn = new TextColumn() { @Override public String getValue(StrippedLeaderboardDTO leaderboard) { @@ -318,6 +321,7 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC trackWind = new CheckBox(stringMessages.trackWind()); correctWindDirectionForDeclination = new CheckBox(stringMessages.declinationCheckbox()); startTrackingButton = new Button(stringMessages.startTracking()); + startTrackingButton.ensureDebugId("StartTrackingButton"); startTrackingButton.setEnabled(false); racesPanel.add(trackWind); racesPanel.add(correctWindDirectionForDeclination); @@ -380,7 +384,6 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC selectedLeaderBoardPanel.setVisible(false); trackedRacesCaptionPanel.setVisible(false); } - raceColumnTableSelectionModel.clear(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportListComposite.java index 427e9e40044..ee3571c54e5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportListComposite.java @@ -5,16 +5,16 @@ import java.util.Comparator; import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; import com.google.gwt.user.cellview.client.TextColumn; -import com.google.gwt.view.client.ListDataProvider; import com.sap.sailing.gwt.ui.client.RegattaRefresher; -import com.sap.sailing.gwt.ui.client.RegattaSelectionProvider; import com.sap.sailing.gwt.ui.client.RegattasDisplayer; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.FlushableCellTable; import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; public class StructureImportListComposite extends RegattaListComposite implements RegattasDisplayer { @@ -25,34 +25,33 @@ public class StructureImportListComposite extends RegattaListComposite implement RegattaStructure getRegattaStructure(RegattaDTO regatta); } - public StructureImportListComposite(final SailingServiceAsync sailingService, - final RegattaSelectionProvider regattaSelectionProvider, RegattaRefresher regattaRefresher, + public StructureImportListComposite(final SailingServiceAsync sailingService, RegattaRefresher regattaRefresher, RegattaStructureProvider regattaStructureProvider, final ErrorReporter errorReporter, final StringMessages stringMessages) { - super(sailingService, regattaSelectionProvider, regattaRefresher, errorReporter, stringMessages); + super(sailingService, regattaRefresher, errorReporter, stringMessages); this.regattaStructureProvider = regattaStructureProvider; } // create Regatta Table in StructureImportManagementPanel @Override protected CellTable createRegattaTable() { - CellTable table = new CellTable(/* pageSize */10000, tableRes); + FlushableCellTable table = new FlushableCellTable(/* pageSize */10000, tableRes); regattaListDataProvider.addDataDisplay(table); table.setWidth("100%"); - this.selectionCheckboxColumn = new SelectionCheckboxColumn(tableRes.cellTableStyle() - .cellTableCheckboxSelected(), tableRes.cellTableStyle().cellTableCheckboxDeselected(), - tableRes.cellTableStyle().cellTableCheckboxColumnCell()) { - @Override - protected ListDataProvider getListDataProvider() { - return regattaListDataProvider; - } - - @Override - public Boolean getValue(RegattaDTO row) { - return regattaTable.getSelectionModel().isSelected(row); - } - }; + this.selectionCheckboxColumn = new SelectionCheckboxColumn( + tableRes.cellTableStyle().cellTableCheckboxSelected(), + tableRes.cellTableStyle().cellTableCheckboxDeselected(), + tableRes.cellTableStyle().cellTableCheckboxColumnCell(), new EntityIdentityComparator() { + @Override + public boolean representSameEntity(RegattaDTO dto1, RegattaDTO dto2) { + return dto1.getRegattaIdentifier().equals(dto2.getRegattaIdentifier()); + } + @Override + public int hashCode(RegattaDTO t) { + return t.getRegattaIdentifier().hashCode(); + } + }, regattaListDataProvider, table); ListHandler columnSortHandler = new ListHandler(regattaListDataProvider.getList()); table.addColumnSortHandler(columnSortHandler); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java index 54aee742cbc..2c05ef7d624 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java @@ -36,7 +36,6 @@ import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.dto.BoatClassDTO; import com.sap.sailing.gwt.ui.adminconsole.StructureImportListComposite.RegattaStructureProvider; import com.sap.sailing.gwt.ui.client.RegattaRefresher; -import com.sap.sailing.gwt.ui.client.RegattaSelectionModel; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; @@ -103,14 +102,14 @@ public class StructureImportManagementPanel extends SimplePanel implements Regat this.errorReporter = errorReporter; this.stringMessages = stringMessages; this.regattaRefresher = regattaRefresher; - this.regattaListComposite = new StructureImportListComposite(this.sailingService, new RegattaSelectionModel( - true), this.regattaRefresher, this, this.errorReporter, this.stringMessages); + this.regattaListComposite = new StructureImportListComposite(this.sailingService, this.regattaRefresher, this, + this.errorReporter, this.stringMessages); regattaListComposite.ensureDebugId("RegattaListComposite"); VerticalPanel mainPanel = new VerticalPanel(); setWidget(mainPanel); mainPanel.setWidth("100%"); - + createUI(mainPanel); regattaListComposite.addSelectionChangeHandler(new Handler() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java index a4405fcaf29..3330314773d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java @@ -30,10 +30,8 @@ import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.view.client.ListDataProvider; -import com.google.gwt.view.client.MultiSelectionModel; import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.RegattaName; -import com.sap.sailing.gwt.ui.client.RaceSelectionModel; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -43,6 +41,8 @@ import com.sap.sailing.gwt.ui.shared.SwissTimingEventRecordDTO; import com.sap.sailing.gwt.ui.shared.SwissTimingRaceRecordDTO; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; /** @@ -66,7 +66,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane public SwissTimingEventManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter, RegattaRefresher regattaRefresher, StringMessages stringConstants) { - super(sailingService, regattaRefresher, errorReporter, new RaceSelectionModel(), true, stringConstants); + super(sailingService, regattaRefresher, errorReporter, true, stringConstants); this.errorReporter = errorReporter; VerticalPanel mainPanel = new VerticalPanel(); @@ -262,11 +262,21 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane raceTable.addColumn(genderColumn, "Gender"); raceTable.addColumn(raceStartTimeColumn, stringConstants.startTime()); raceTable.setWidth("300px"); - raceTable.setSelectionModel(new MultiSelectionModel() {}); + raceList = new ListDataProvider(); + raceTable.setSelectionModel(new RefreshableMultiSelectionModel( + new EntityIdentityComparator() { + @Override + public boolean representSameEntity(SwissTimingRaceRecordDTO dto1, SwissTimingRaceRecordDTO dto2) { + return dto1.raceId.equals(dto2.raceId); + } + @Override + public int hashCode(SwissTimingRaceRecordDTO t) { + return t.raceId.hashCode(); + } + }, raceList) { + }); trackableRacesPanel.add(raceTable); - - raceList = new ListDataProvider(); raceList.addDataDisplay(raceTable); Handler columnSortHandler = getRaceTableColumnSortHandler(raceList.getList(), regattaNameColumn, seriesNameColumn, raceNameColumn, raceStartTimeColumn, raceIdColumn, boatClassColumn, genderColumn, raceStatusColumn); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingReplayConnectorPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingReplayConnectorPanel.java index 341e4c17ac1..cab262120d0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingReplayConnectorPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingReplayConnectorPanel.java @@ -30,10 +30,8 @@ import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.view.client.ListDataProvider; -import com.google.gwt.view.client.MultiSelectionModel; import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.RegattaName; -import com.sap.sailing.gwt.ui.client.RaceSelectionModel; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -42,6 +40,8 @@ import com.sap.sailing.gwt.ui.shared.SwissTimingArchiveConfigurationDTO; import com.sap.sailing.gwt.ui.shared.SwissTimingReplayRaceDTO; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; /** @@ -64,7 +64,7 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane public SwissTimingReplayConnectorPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter, RegattaRefresher regattaRefresher, StringMessages stringMessages) { - super(sailingService, regattaRefresher, errorReporter, new RaceSelectionModel(), true, stringMessages); + super(sailingService, regattaRefresher, errorReporter, true, stringMessages); this.errorReporter = errorReporter; availableSwissTimingRaces = new ArrayList(); previousConfigurationsComboBox = new ListBox(); @@ -190,12 +190,22 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane raceTable.addColumn(boatClassNamesColumn, stringMessages.boatClass()); raceTable.addColumn(raceStartTrackingColumn, stringMessages.startTime()); raceTable.setWidth("300px"); - raceTable.setSelectionModel(new MultiSelectionModel() {}); + raceList = new ListDataProvider(); + raceTable.setSelectionModel(new RefreshableMultiSelectionModel( + new EntityIdentityComparator() { + @Override + public boolean representSameEntity(SwissTimingReplayRaceDTO dto1, SwissTimingReplayRaceDTO dto2) { + return dto1.race_id.equals(dto2.race_id); + } + @Override + public int hashCode(SwissTimingReplayRaceDTO t) { + return t.race_id.hashCode(); + } + }, raceList) { + }); racesHorizontalPanel.add(raceTable); racesHorizontalPanel.add(trackPanel); - - raceList = new ListDataProvider(); raceList.addDataDisplay(raceTable); Handler columnSortHandler = getRaceTableColumnSortHandler(raceList.getList(), raceNameColumn, boatClassNamesColumn, raceStartTrackingColumn); raceTable.addColumnSortHandler(columnSortHandler); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TableWrapper.java index 90c2038daa8..f4c8bc05f49 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TableWrapper.java @@ -8,21 +8,23 @@ import com.google.gwt.user.client.ui.IsWidget; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.view.client.ListDataProvider; -import com.google.gwt.view.client.SelectionModel; -import com.google.gwt.view.client.SingleSelectionModel; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.FlushableCellTable; import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sse.common.Util; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel; +import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel; /** * Implementing classes still have to add the table to the main panel. The table created and wrapped by this object * offers already a {@link ListHandler} for sorting. Subclasses can obtain the table's default column sort handler * created by this class's constructor by calling {@link #getColumnSortHandler}. */ -public abstract class TableWrapper> implements IsWidget { - protected final CellTable table; +public abstract class TableWrapper> implements IsWidget { + protected final FlushableCellTable table; private final S selectionModel; protected final ListDataProvider dataProvider; protected VerticalPanel mainPanel; @@ -36,12 +38,18 @@ public abstract class TableWrapper> implements Is public Widget asWidget() { return mainPanel; } + + /** + * @param entityIdentityComparator + * {@link EntityIdentityComparator} to create a {@link RefreshableSelectionModel} + */ public TableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages, ErrorReporter errorReporter, - boolean multiSelection, boolean enablePager) { + boolean multiSelection, boolean enablePager, EntityIdentityComparator entityIdentityComparator) { this.sailingService = sailingService; this.errorReporter = errorReporter; - table = new CellTable(10000, tableRes); + table = new FlushableCellTable(10000, tableRes); + table.ensureDebugId("WrappedTable"); this.dataProvider = new ListDataProvider(); this.columnSortHandler = new ListHandler(dataProvider.getList()); table.addColumnSortHandler(this.columnSortHandler); @@ -49,12 +57,7 @@ public abstract class TableWrapper> implements Is SelectionCheckboxColumn selectionCheckboxColumn = new SelectionCheckboxColumn( tableRes.cellTableStyle().cellTableCheckboxSelected(), tableRes.cellTableStyle().cellTableCheckboxDeselected(), - tableRes.cellTableStyle().cellTableCheckboxColumnCell()) { - @Override - protected ListDataProvider getListDataProvider() { - return dataProvider; - } - }; + tableRes.cellTableStyle().cellTableCheckboxColumnCell(), entityIdentityComparator, dataProvider, table); columnSortHandler.setComparator(selectionCheckboxColumn, selectionCheckboxColumn.getComparator()); @SuppressWarnings("unchecked") S typedSelectionModel = (S) selectionCheckboxColumn.getSelectionModel(); @@ -63,7 +66,7 @@ public abstract class TableWrapper> implements Is table.addColumn(selectionCheckboxColumn, selectionCheckboxColumn.getHeader()); } else { @SuppressWarnings("unchecked") - S typedSelectionModel = (S) new SingleSelectionModel(); + S typedSelectionModel = (S) new RefreshableSingleSelectionModel(entityIdentityComparator, dataProvider); selectionModel = typedSelectionModel; table.setSelectionModel(selectionModel); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java index b9f26f50610..60bf7508857 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java @@ -14,7 +14,6 @@ import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; import com.google.gwt.user.cellview.client.TextColumn; @@ -37,10 +36,10 @@ import com.google.gwt.view.client.ListDataProvider; import com.google.gwt.view.client.SelectionModel; import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.RegattaName; -import com.sap.sailing.gwt.ui.client.RaceSelectionModel; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.FlushableCellTable; import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.TracTracConfigurationDTO; @@ -49,6 +48,7 @@ import com.sap.sse.common.Duration; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; /** @@ -84,12 +84,12 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { private Label loadingMessageLabel; private LabeledAbstractFilterablePanel racesFilterablePanel; - private CellTable racesTable; + private FlushableCellTable racesTable; private static final String ZERO_AS_STRING = "0"; public TracTracEventManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter, RegattaRefresher regattaRefresher, StringMessages stringMessages) { - super(sailingService, regattaRefresher, errorReporter, new RaceSelectionModel(), true, stringMessages); + super(sailingService, regattaRefresher, errorReporter, true, stringMessages); this.errorReporter = errorReporter; this.previousConfigurations = new HashMap(); this.availableTracTracRaces = new ArrayList(); @@ -314,7 +314,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { // Filter Label racesFilterLabel = new Label(stringMessages.filterRacesByName() + ":"); AdminConsoleTableResources tableResources = GWT.create(AdminConsoleTableResources.class); - racesTable = new CellTable(10000, tableResources); + racesTable = new FlushableCellTable(10000, tableResources); racesTable.ensureDebugId("TrackableRacesCellTable"); this.racesFilterablePanel = new LabeledAbstractFilterablePanel(racesFilterLabel, availableTracTracRaces, racesTable, raceList) { @Override @@ -378,18 +378,20 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { } }; raceVisibilityColumn.setSortable(true); - SelectionCheckboxColumn selectionCheckboxColumn = new SelectionCheckboxColumn(tableResources.cellTableStyle().cellTableCheckboxSelected(), - tableResources.cellTableStyle().cellTableCheckboxDeselected(), tableResources.cellTableStyle().cellTableCheckboxColumnCell()) { + SelectionCheckboxColumn selectionCheckboxColumn = new SelectionCheckboxColumn( + tableResources.cellTableStyle().cellTableCheckboxSelected(), + tableResources.cellTableStyle().cellTableCheckboxDeselected(), + tableResources.cellTableStyle().cellTableCheckboxColumnCell(), + new EntityIdentityComparator() { @Override - public Boolean getValue(TracTracRaceRecordDTO row) { - return racesTable.getSelectionModel().isSelected(row); + public boolean representSameEntity(TracTracRaceRecordDTO dto1, TracTracRaceRecordDTO dto2) { + return dto1.id.equals(dto2.id); } - @Override - protected ListDataProvider getListDataProvider() { - return raceList; + public int hashCode(TracTracRaceRecordDTO t) { + return t.id.hashCode(); } - }; + }, raceList, racesTable); racesTable.addColumn(selectionCheckboxColumn, selectionCheckboxColumn.getHeader()); racesTable.addColumn(regattaNameColumn, stringMessages.event()); racesTable.addColumn(raceNameColumn, stringMessages.race()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackFileImportDeviceIdentifierTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackFileImportDeviceIdentifierTableWrapper.java index 9e6d3b47f9f..12d730da684 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackFileImportDeviceIdentifierTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackFileImportDeviceIdentifierTableWrapper.java @@ -1,19 +1,31 @@ package com.sap.sailing.gwt.ui.adminconsole; import com.google.gwt.user.cellview.client.TextColumn; -import com.google.gwt.view.client.SingleSelectionModel; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel; public class TrackFileImportDeviceIdentifierTableWrapper extends TableWrapper> { +RefreshableSingleSelectionModel> { public TrackFileImportDeviceIdentifierTableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages, ErrorReporter errorReporter) { - super(sailingService, stringMessages, errorReporter, /* multiSelection */ false, /* enablePager */ true); + super(sailingService, stringMessages, errorReporter, /* multiSelection */ false, /* enablePager */ true, + new EntityIdentityComparator() { + @Override + public boolean representSameEntity(TrackFileImportDeviceIdentifierDTO dto1, + TrackFileImportDeviceIdentifierDTO dto2) { + return dto1.uuidAsString.equals(dto2.uuidAsString); + } + @Override + public int hashCode(TrackFileImportDeviceIdentifierDTO t) { + return t.uuidAsString.hashCode(); + } + }); TextColumn uuidColumn = new TextColumn() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesListComposite.java index 1296571fbae..54d801f5eeb 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesListComposite.java @@ -13,7 +13,6 @@ import com.google.gwt.user.client.ui.HorizontalPanel; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.domain.common.dto.RaceDTO; -import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.RegattasDisplayer; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -38,9 +37,8 @@ public class TrackedRacesListComposite extends AbstractTrackedRacesListComposite private boolean actionButtonsEnabled; public TrackedRacesListComposite(final SailingServiceAsync sailingService, final ErrorReporter errorReporter, - final RegattaRefresher regattaRefresher, RaceSelectionProvider raceSelectionProvider, - final StringMessages stringMessages, boolean hasMultiSelection, boolean actionButtonsEnabled) { - super(sailingService, errorReporter, regattaRefresher, raceSelectionProvider, stringMessages, hasMultiSelection); + final RegattaRefresher regattaRefresher, final StringMessages stringMessages, boolean hasMultiSelection, boolean actionButtonsEnabled) { + super(sailingService, errorReporter, regattaRefresher, stringMessages, hasMultiSelection); this.raceIsTrackedRaceChangeListener = new HashSet(); this.actionButtonsEnabled = actionButtonsEnabled; createUI(); @@ -113,7 +111,7 @@ public class TrackedRacesListComposite extends AbstractTrackedRacesListComposite btnRemoveRace.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { - removeAndUntrackRaces(getSelectedRaces()); + removeAndUntrackRaces(refreshableSelectionModel.getSelectedSet()); } }); btnRemoveRace.setEnabled(false); @@ -124,7 +122,7 @@ public class TrackedRacesListComposite extends AbstractTrackedRacesListComposite btnUntrack.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent click) { - stopTrackingRaces(getSelectedRaces()); + stopTrackingRaces(refreshableSelectionModel.getSelectedSet()); } }); btnUntrack.setEnabled(false); @@ -146,7 +144,7 @@ public class TrackedRacesListComposite extends AbstractTrackedRacesListComposite btnExport.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { - exportPopup.center(getSelectedRaces()); + exportPopup.center(new ArrayList<>(refreshableSelectionModel.getSelectedSet())); } }); btnExport.setEnabled(false); @@ -155,8 +153,8 @@ public class TrackedRacesListComposite extends AbstractTrackedRacesListComposite } @Override - protected void makeControlsReactToSelectionChange(List selectedRaces) { - if(actionButtonsEnabled) { + protected void makeControlsReactToSelectionChange(Set selectedRaces) { + if (actionButtonsEnabled) { if (selectedRaces.isEmpty()) { btnRemoveRace.setEnabled(false); btnUntrack.setEnabled(false); @@ -166,7 +164,7 @@ public class TrackedRacesListComposite extends AbstractTrackedRacesListComposite btnUntrack.setEnabled(true); btnExport.setEnabled(true); } - } + } } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WaypointCreationDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WaypointCreationDialog.java index 42b98c2bc79..e103e0cae81 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WaypointCreationDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WaypointCreationDialog.java @@ -9,7 +9,6 @@ import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; -import com.google.gwt.view.client.SingleSelectionModel; import com.sap.sailing.domain.common.PassingInstruction; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -17,10 +16,11 @@ import com.sap.sailing.gwt.ui.shared.ControlPointDTO; import com.sap.sailing.gwt.ui.shared.WaypointDTO; import com.sap.sse.common.Util; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog; public class WaypointCreationDialog extends DataEntryDialog { - private final ControlPointTableWrapper> controlPointsWrapper; + private final ControlPointTableWrapper> controlPointsWrapper; private final ListBox passingInstructions; private final StringMessages stringMessages; @@ -39,7 +39,7 @@ public class WaypointCreationDialog extends DataEntryDialog { }, /* animationEnabled */ false, callback); this.stringMessages = stringMessages; - controlPointsWrapper = new ControlPointTableWrapper>( + controlPointsWrapper = new ControlPointTableWrapper>( /* multiSelection */ false, sailingService, stringMessages, errorReporter); controlPointsWrapper.getDataProvider().getList().addAll(controlPoints); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WaypointTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WaypointTableWrapper.java index b987b2512a9..be2756ef901 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WaypointTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WaypointTableWrapper.java @@ -1,7 +1,6 @@ package com.sap.sailing.gwt.ui.adminconsole; import com.google.gwt.user.cellview.client.TextColumn; -import com.google.gwt.view.client.SelectionModel; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.ControlPointDTO; @@ -9,11 +8,23 @@ import com.sap.sailing.gwt.ui.shared.GateDTO; import com.sap.sailing.gwt.ui.shared.MarkDTO; import com.sap.sailing.gwt.ui.shared.WaypointDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel; -public class WaypointTableWrapper> extends TableWrapper { +public class WaypointTableWrapper> extends TableWrapper { public WaypointTableWrapper(boolean multiSelection, SailingServiceAsync sailingService, final StringMessages stringMessages, ErrorReporter errorReporter) { - super(sailingService, stringMessages, errorReporter, multiSelection, true); + super(sailingService, stringMessages, errorReporter, multiSelection, true, + new EntityIdentityComparator() { + @Override + public boolean representSameEntity(WaypointDTO dto1, WaypointDTO dto2) { + return dto1.getName().equals(dto2.getName()); + } + @Override + public int hashCode(WaypointDTO t) { + return t.getName().hashCode(); + } + }); TextColumn nameColumn = new TextColumn() { @Override @@ -46,4 +57,9 @@ public class WaypointTableWrapper> extends }; table.addColumn(passingInstructionsColumn, stringMessages.passingInstructions()); } + + @Override + public void refresh(Iterable wayPoints) { + super.refresh(wayPoints); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java index adda238a59d..507a8f08b30 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java @@ -20,6 +20,9 @@ import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.i18n.client.NumberFormat; +import com.google.gwt.json.client.JSONArray; +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.json.client.JSONString; import com.google.gwt.regexp.shared.MatchResult; import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; @@ -45,15 +48,13 @@ import com.google.gwt.user.client.ui.TabPanel; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.view.client.ListDataProvider; +import com.google.gwt.view.client.SelectionChangeEvent; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.dto.RaceDTO; import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.gwt.ui.adminconsole.WindImportResult.RaceEntry; -import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; -import com.sap.sailing.gwt.ui.client.RaceSelectionModel; -import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.RegattasDisplayer; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -66,6 +67,7 @@ import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; /** @@ -76,7 +78,7 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; * @author Axel Uhl (d043530) * */ -public class WindPanel extends FormPanel implements RegattasDisplayer, WindShower, RaceSelectionChangeListener { +public class WindPanel extends FormPanel implements RegattasDisplayer, WindShower { private static final String EXPEDITON_IMPORT_PARAMETER_RACES = "races"; @@ -93,7 +95,7 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe private final TextColumn windDirectionInDegColumn; private final TextColumn positionColumn; private final TrackedRacesListComposite trackedRacesListComposite; - private final RaceSelectionProvider raceSelectionProvider; + private final RefreshableMultiSelectionModel refreshableRaceSelectionModel; private final WindSourcesToExcludeSelectorPanel windSourcesToExcludeSelectorPanel; private final CheckBox raceIsKnownToStartUpwindBox; private final CaptionPanel windCaptionPanel; @@ -108,17 +110,23 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe this.sailingService = sailingService; this.errorReporter = errorReporter; this.stringMessages = stringMessages; - this.raceSelectionProvider = new RaceSelectionModel(); windSourcesToExcludeSelectorPanel = new WindSourcesToExcludeSelectorPanel(sailingService, stringMessages, errorReporter); VerticalPanel mainPanel = new VerticalPanel(); mainPanel.setSize("100%", "100%"); this.setWidget(mainPanel); - trackedRacesListComposite = new TrackedRacesListComposite(sailingService, errorReporter, regattaRefresher, - raceSelectionProvider, stringMessages, /*multiselection*/true, /* actionButtonsEnabled */ false); + trackedRacesListComposite = new TrackedRacesListComposite(sailingService, errorReporter, regattaRefresher, + stringMessages, /*multiselection*/true, /* actionButtonsEnabled */ false); mainPanel.add(trackedRacesListComposite); - raceSelectionProvider.addRaceSelectionChangeListener(this); + refreshableRaceSelectionModel = (RefreshableMultiSelectionModel) trackedRacesListComposite.getSelectionModel(); + refreshableRaceSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + + @Override + public void onSelectionChange(SelectionChangeEvent event) { + updateWindDisplay(); + } + }); windCaptionPanel = new CaptionPanel(stringMessages.wind()); windCaptionPanel.setVisible(false); @@ -180,8 +188,8 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe removeColumn = new IdentityColumn(new ActionCell(stringMessages.remove(), new Delegate() { @Override public void execute(final WindDTO wind) { - List selectedRaces = raceSelectionProvider.getSelectedRaces(); - final RegattaAndRaceIdentifier raceIdentifier = selectedRaces.get(selectedRaces.size() - 1); + List selectedRaces = new ArrayList<>(refreshableRaceSelectionModel.getSelectedSet()); + final RegattaAndRaceIdentifier raceIdentifier = selectedRaces.get(selectedRaces.size() - 1).getRaceIdentifier(); sailingService.removeWind(raceIdentifier, wind, new AsyncCallback() { @Override public void onSuccess(Void result) { @@ -261,14 +269,14 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe @Override public void onClick(ClickEvent event) { final String warningMessage; - if (trackedRacesListComposite.getSelectedRaces().isEmpty()) { + if (refreshableRaceSelectionModel.getSelectedSet().isEmpty()) { warningMessage = stringMessages.windImport_AllRacesWarning(); } else { - warningMessage = stringMessages.windImport_SelectedRacesWarning(trackedRacesListComposite.getSelectedRaces().size()); + warningMessage = stringMessages.windImport_SelectedRacesWarning(refreshableRaceSelectionModel.getSelectedSet().size()); } if (Window.confirm(warningMessage)) { resultReport.setText(stringMessages.loading()); - sailingService.importWindFromIgtimi(trackedRacesListComposite.getSelectedRaces(), + sailingService.importWindFromIgtimi(new ArrayList<>(refreshableRaceSelectionModel.getSelectedSet()), correctByDeclination.getValue(), new AsyncCallback>() { @Override public void onFailure(Throwable caught) { @@ -335,24 +343,21 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe final FileUpload fileUpload = new FileUpload(); fileUpload.setName("upload"); fileUpload.addChangeHandler(new ChangeHandler() { - @Override public void onChange(ChangeEvent event) { importResultPanel.clear(); String fileName = fileUpload.getFilename(); boolean isValidFileName = (fileName != null) && (fileUpload.getFilename().trim().length() > 0); submitButton.setEnabled(isValidFileName); - String boatId = ""; if (isValidFileName) { RegExp EXPEDITION_EXPORT_FILE_PATTERN = RegExp.compile("^.*_([0-9]+)\\.csv"); //matches typical expedition log file names like "2013Jun26_0.csv" where 0 as group[1] indicates the boat id. MatchResult match = EXPEDITION_EXPORT_FILE_PATTERN.exec(fileName); - if (match.getGroupCount() > 0) { + if (match != null && match.getGroupCount() > 0) { boatId = match.getGroup(1); } } boatIdTextBox.setText(boatId); - } }); @@ -375,19 +380,19 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe form.addSubmitHandler(new FormPanel.SubmitHandler() { public void onSubmit(SubmitEvent event) { if ((fileUpload.getFilename() != null) && (fileUpload.getFilename().trim().length() > 0)) { - List selectedRaces = raceSelectionProvider.getSelectedRaces(); + Set selectedRaces = refreshableRaceSelectionModel.getSelectedSet(); String warningMessage; - if (selectedRaces.size() > 0) { + if (!selectedRaces.isEmpty()) { warningMessage = stringMessages.windImport_SelectedRacesWarning(selectedRaces.size()); - RaceSelection raceSelection = RaceSelection.create(); - for (int i = 0; i < selectedRaces.size(); i++) { - RegattaAndRaceIdentifier raceIdentifier = selectedRaces.get(i); - RaceSelection.RaceEntry raceEntry = RaceSelection.RaceEntry.create(); - raceEntry.setRaceName(raceIdentifier.getRaceName()); - raceEntry.setRegatteName(raceIdentifier.getRegattaName()); - raceSelection.addRace(raceEntry); + JSONArray raceSelection = new JSONArray(); + for (RaceDTO race : selectedRaces) { + RegattaAndRaceIdentifier raceIdentifier = race.getRaceIdentifier(); + JSONObject raceEntry = new JSONObject(); + raceEntry.put("race", new JSONString(raceIdentifier.getRaceName())); + raceEntry.put("regatta", new JSONString(raceIdentifier.getRegattaName())); + raceSelection.set(raceSelection.size(), raceEntry); } - hiddenRacesField.setValue(raceSelection.toJson()); + hiddenRacesField.setValue(raceSelection.toString()); } else { warningMessage = stringMessages.windImport_AllRacesWarning(); hiddenRacesField.setValue(null); @@ -557,12 +562,12 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe } private RegattaAndRaceIdentifier getSelectedRace() { - List selectedRaces = raceSelectionProvider.getSelectedRaces(); + Set selectedRaces = refreshableRaceSelectionModel.getSelectedSet(); if (selectedRaces.isEmpty() || selectedRaces.size() > 1) { return null; } - return selectedRaces.get(0); + return selectedRaces.iterator().next().getRaceIdentifier(); } private void clearWindSources() { @@ -626,9 +631,4 @@ public class WindPanel extends FormPanel implements RegattasDisplayer, WindShowe } } - - @Override - public void onRaceSelectionChange(List selectedRaces) { - updateWindDisplay(); - } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorColorProviderImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorColorProviderImpl.java index 07afa3256b4..4ec01e9cf03 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorColorProviderImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorColorProviderImpl.java @@ -13,7 +13,10 @@ import com.sap.sailing.domain.common.impl.ColorMapImpl; import com.sap.sse.common.Color; public class CompetitorColorProviderImpl implements CompetitorColorProvider { - private final ColorMap competitorsColorMap; + /** + * Uses the {@link CompetitorDTO#getIdAsString()} as the ID for the color map + */ + private final ColorMap competitorsColorMap; private final Map> competitorsBoatColorsPerRace; public CompetitorColorProviderImpl() { @@ -22,7 +25,7 @@ public class CompetitorColorProviderImpl implements CompetitorColorProvider { public CompetitorColorProviderImpl(RegattaAndRaceIdentifier raceIdentifier, Map competitorsAndTheirBoats) { - this.competitorsColorMap = new ColorMapImpl(); + this.competitorsColorMap = new ColorMapImpl<>(); this.competitorsBoatColorsPerRace = new HashMap>(); if (raceIdentifier != null) { for (Entry competitorAndBoat : competitorsAndTheirBoats.entrySet()) { @@ -61,7 +64,7 @@ public class CompetitorColorProviderImpl implements CompetitorColorProvider { } // fallback if (result == null) { - result = competitorsColorMap.getColorByID(competitor); + result = competitorsColorMap.getColorByID(competitor.getIdAsString()); } return result; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionModel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionModel.java index 62793384452..f6859a5b75b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionModel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/CompetitorSelectionModel.java @@ -3,9 +3,12 @@ package com.sap.sailing.gwt.ui.client; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.Map; import java.util.Set; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; @@ -18,7 +21,10 @@ import com.sap.sse.common.filter.FilterSet; public class CompetitorSelectionModel implements CompetitorSelectionProvider { private final Set allCompetitors; - private final LinkedHashSet selectedCompetitors; + /** + * Keys are {@link CompetitorDTO#getIdAsString()} of their values + */ + private final LinkedHashMap selectedCompetitors; private final Set listeners; @@ -42,7 +48,7 @@ public class CompetitorSelectionModel implements CompetitorSelectionProvider { this.competitorColorProvider = competitorColorProvider; this.competitorsFilterSet = competitorsFilterSet; this.allCompetitors = new LinkedHashSet(); - this.selectedCompetitors = new LinkedHashSet(); + this.selectedCompetitors = new LinkedHashMap<>(); this.listeners = new HashSet(); } @@ -115,12 +121,12 @@ public class CompetitorSelectionModel implements CompetitorSelectionProvider { @Override public Iterable getSelectedCompetitors() { - return Collections.unmodifiableCollection(selectedCompetitors); + return Collections.unmodifiableCollection(selectedCompetitors.values()); } @Override public Iterable getSelectedFilteredCompetitors() { - Set result = new HashSet<>(selectedCompetitors); + Set result = new HashSet<>(selectedCompetitors.values()); result.retainAll(getFilteredCompetitors()); return result; } @@ -148,13 +154,13 @@ public class CompetitorSelectionModel implements CompetitorSelectionProvider { public void setSelected(CompetitorDTO competitor, boolean selected, CompetitorSelectionChangeListener... listenersNotToNotify) { if (selected) { - if (allCompetitors.contains(competitor) && !selectedCompetitors.contains(competitor)) { - selectedCompetitors.add(competitor); + if (allCompetitors.contains(competitor) && !selectedCompetitors.containsKey(competitor.getIdAsString())) { + selectedCompetitors.put(competitor.getIdAsString(), competitor); fireAddedToSelection(competitor, listenersNotToNotify); } } else { - if (selectedCompetitors.contains(competitor)) { - selectedCompetitors.remove(competitor); + if (selectedCompetitors.containsKey(competitor.getIdAsString())) { + selectedCompetitors.remove(competitor.getIdAsString()); fireRemovedFromSelection(competitor, listenersNotToNotify); } } @@ -186,7 +192,7 @@ public class CompetitorSelectionModel implements CompetitorSelectionProvider { @Override public boolean isSelected(CompetitorDTO competitor) { - return selectedCompetitors.contains(competitor); + return selectedCompetitors.containsKey(competitor.getIdAsString()); } @Override @@ -201,7 +207,7 @@ public class CompetitorSelectionModel implements CompetitorSelectionProvider { @Override public void setSelection(Iterable newSelection, CompetitorSelectionChangeListener... listenersNotToNotify) { - Set competitorsToRemoveFromSelection = new HashSet(selectedCompetitors); + Set competitorsToRemoveFromSelection = new HashSet(selectedCompetitors.values()); for (CompetitorDTO newSelectedCompetitor : newSelection) { setSelected(newSelectedCompetitor, true, listenersNotToNotify); competitorsToRemoveFromSelection.remove(newSelectedCompetitor); @@ -214,17 +220,32 @@ public class CompetitorSelectionModel implements CompetitorSelectionProvider { @Override public void setCompetitors(Iterable newCompetitors, CompetitorSelectionChangeListener... listenersNotToNotify) { boolean changed = false; - - Set oldCompetitorsToRemove = new HashSet(allCompetitors); + Map oldCompetitorsToRemove = new HashMap(); + for (final CompetitorDTO c : allCompetitors) { + oldCompetitorsToRemove.put(c.getIdAsString(), c); + } for (CompetitorDTO newCompetitor : newCompetitors) { + // Search by equality, including all CompetitorDTO fields; if equal object is found, leave it in place; + // non-equal objects may still represent the same competitor entity; yet, replace the object to obtain the + // new, changed state; make sure the selection keeps unchanged regarding the selected competitor "entities"; + // see also bug 3413 for more background. if (allCompetitors.contains(newCompetitor)) { - oldCompetitorsToRemove.remove(newCompetitor); + oldCompetitorsToRemove.remove(newCompetitor.getIdAsString()); // the old competitor object remains in allCompetitors; it is equal but doesn't have to be identical to newCompetitor } else { - add(newCompetitor, false); + boolean selected = isSelected(newCompetitor); + if (selected) { + remove(oldCompetitorsToRemove.get(newCompetitor.getIdAsString())); + oldCompetitorsToRemove.remove(newCompetitor.getIdAsString()); + } + add(newCompetitor, false); // due to the equals definition of CompetitorDTOImpl that compares most fields, state changes lead to a replacement here + if (selected) { + // restore selection for replaced element + setSelected(newCompetitor, selected, listenersNotToNotify); + } changed = true; } } - for (CompetitorDTO oldCompetitorToRemove : oldCompetitorsToRemove) { + for (CompetitorDTO oldCompetitorToRemove : oldCompetitorsToRemove.values()) { remove(oldCompetitorToRemove); changed = true; } @@ -241,7 +262,7 @@ public class CompetitorSelectionModel implements CompetitorSelectionProvider { @Override public Color getColor(CompetitorDTO competitor, RegattaAndRaceIdentifier raceIdentfier) { - return allCompetitors.contains(competitor) ? competitorColorProvider.getColor(competitor, raceIdentfier) : null; + return allCompetitors.contains(competitor) ? competitorColorProvider.getColor(competitor, raceIdentfier) : Color.WHITE /* safe default */; } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EntryPointLinkFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EntryPointLinkFactory.java index 66840bcf631..0cbc204ee07 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EntryPointLinkFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EntryPointLinkFactory.java @@ -1,5 +1,6 @@ package com.sap.sailing.gwt.ui.client; +import java.util.Collections; import java.util.Map; import com.sap.sse.gwt.client.AbstractEntryPointLinkFactory; @@ -17,10 +18,21 @@ public class EntryPointLinkFactory extends AbstractEntryPointLinkFactory { return createEntryPointLink("/gwt/Leaderboard.html", parameters); } + public static String createLeaderboardEditingLink(Map parameters) { + return createEntryPointLink("/gwt/LeaderboardEditing.html", parameters); + } + public static String createLeaderboardTabLink(String eventId, String regattaId, Map parameters) { return createEntryPointLink("/gwt/Home.html#/regatta/leaderboard/:eventId=" + eventId + "®attaId=" + regattaId, parameters); } + public static String createLeaderboardPlaceLink(String eventId, String leaderboardName) { + final Map emptyParams = Collections.emptyMap(); + return createEntryPointLink("/gwt/Home.html#EventPlace:eventId="+eventId+ + "&navigationTab=Regatta&leaderboardName=" +leaderboardName, emptyParams); + + } + public static String createLeaderboardGroupLink(Map parameters) { return createEntryPointLink("/gwt/Spectator.html", parameters); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EventSelectionChangeListener.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EventSelectionChangeListener.java deleted file mode 100644 index 013b258e080..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EventSelectionChangeListener.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.sap.sailing.gwt.ui.client; - -import java.util.List; -import java.util.UUID; - - -/** - * Allows UI components to observe a saling event selector, such as a drop-down box showing a list of sailing events - * - * @author Axel Uhl (d043530) - * - */ -public interface EventSelectionChangeListener { - - /** - * The first element is the first one selected - * - * @param selectedEvents a non-null list which is empty if nothing is selected - */ - void onEventSelectionChange(List selectedEvents); - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EventSelectionModel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EventSelectionModel.java deleted file mode 100644 index 56a23c7202d..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EventSelectionModel.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.sap.sailing.gwt.ui.client; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.UUID; - -public class EventSelectionModel implements EventSelectionProvider { - private final List selection; - private final List allEvents; - private final Set listeners; - private final boolean hasMultiSelection; - - public EventSelectionModel(boolean hasMultiSelection) { - this.hasMultiSelection = hasMultiSelection; - this.selection = new ArrayList(); - this.allEvents = new ArrayList(); - listeners = new HashSet(); - } - - @Override - public List getSelectedEvents() { - return Collections.unmodifiableList(selection); - } - - @Override - public void addEventSelectionChangeListener(EventSelectionChangeListener listener) { - listeners.add(listener); - } - - @Override - public void removeEventSelectionChangeListener(EventSelectionChangeListener listener) { - listeners.remove(listener); - } - - @Override - public void setSelection(List newSelection, EventSelectionChangeListener... listenersNotToNotify) { - boolean notify = !selection.equals(newSelection); - selection.clear(); - selection.addAll(newSelection); - if (notify) { - notifyListeners(listenersNotToNotify); - } - } - - private void notifyListeners(EventSelectionChangeListener[] listenersNotToNotify) { - List selectedEvents = getSelectedEvents(); - for (EventSelectionChangeListener listener : listeners) { - if (listenersNotToNotify == null || !Arrays.asList(listenersNotToNotify).contains(listener)) { - listener.onEventSelectionChange(selectedEvents); - } - } - } - - /** - * Sets the universe of all (sailing) events from which this selection model may select. An equal list will subsequently be - * returned by {@link #getallEvents()}. Events from the {@link #getSelectedEvents() selection} not in - * newEvents are removed from the selection. If this happens, the selection listeners are notified. - */ - @Override - public void setAllEvents(List newEvents, EventSelectionChangeListener... listenersNotToNotify) { - allEvents.clear(); - for (UUID r : newEvents) { - allEvents.add(r); - } - for (Iterator i=selection.iterator(); i.hasNext(); ) { - UUID selectedRegatta = i.next(); - if (!allEvents.contains(selectedRegatta)) { - i.remove(); - } - } - // when setting all events, the actual EventDTOs will usually have changed their identity and maybe also their state; - // so notifying the selection listeners is necessary anyhow - notifyListeners(listenersNotToNotify); - } - - @Override - public List getAllEvents() { - return Collections.unmodifiableList(allEvents); - } - - @Override - public boolean hasMultiSelection() { - return hasMultiSelection; - } -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EventSelectionProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EventSelectionProvider.java deleted file mode 100644 index d4b9e197934..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/EventSelectionProvider.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.sap.sailing.gwt.ui.client; - -import java.util.List; -import java.util.UUID; - -public interface EventSelectionProvider { - /** - * @return a non-null list which may be empty - */ - List getSelectedEvents(); - - void addEventSelectionChangeListener(EventSelectionChangeListener listener); - - void removeEventSelectionChangeListener(EventSelectionChangeListener listener); - - void setSelection(List newSelection, EventSelectionChangeListener... listenersNotToNotify); - - boolean hasMultiSelection(); - - /** - * @return a non-null list which may be empty - */ - List getAllEvents(); - - void setAllEvents(List events, EventSelectionChangeListener... listenersNotToNotify); -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceSelectionChangeListener.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceSelectionChangeListener.java deleted file mode 100755 index de8556b6dc5..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceSelectionChangeListener.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.sap.sailing.gwt.ui.client; - -import java.util.List; - -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; - - -/** - * Allows UI components to observe a race selector, such as a drop-down box showing a list of races - * - * @author Axel Uhl (d043530) - * - */ -public interface RaceSelectionChangeListener { - - /** - * The first element is the first one selected - * - * @param selectedRaces a non-null list which is empty if nothing is selected - */ - void onRaceSelectionChange(List selectedRaces); - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceSelectionModel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceSelectionModel.java deleted file mode 100755 index 556bbf02538..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceSelectionModel.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.sap.sailing.gwt.ui.client; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import com.sap.sailing.domain.common.RaceIdentifier; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; - -public class RaceSelectionModel implements RaceSelectionProvider { - private final List selection; - - private final List allRaces; - - private final Set listeners; - - public RaceSelectionModel() { - this.selection = new ArrayList(); - this.allRaces = new ArrayList(); - listeners = new HashSet(); - } - - @Override - public List getSelectedRaces() { - return Collections.unmodifiableList(selection); - } - - @Override - public void addRaceSelectionChangeListener(RaceSelectionChangeListener listener) { - listeners.add(listener); - } - - @Override - public void removeRaceSelectionChangeListener(RaceSelectionChangeListener listener) { - listeners.remove(listener); - } - - @Override - public void setSelection(List newSelection, RaceSelectionChangeListener... listenersNotToNotify) { - boolean notify = !selection.equals(newSelection); - selection.clear(); - selection.addAll(newSelection); - if (notify) { - notifyListeners(listenersNotToNotify); - } - } - - private void notifyListeners(RaceSelectionChangeListener[] listenersNotToNotify) { - List selectedRaces = getSelectedRaces(); - for (RaceSelectionChangeListener listener : listeners) { - if (listenersNotToNotify == null || !Arrays.asList(listenersNotToNotify).contains(listener)) { - listener.onRaceSelectionChange(selectedRaces); - } - } - } - - /** - * Sets the universe of all races from which this selection model may select. An equal list will subsequently be - * returned by {@link #getAllRaces()}. Races from the {@link #getSelectedRaces() selection} not in - * newAllRaces are removed from the selection. If this happens, the selection listeners are notified. - */ - @Override - public void setAllRaces(List newAllRaces, RaceSelectionChangeListener... listenersNotToNotify) { - allRaces.clear(); - for (RegattaAndRaceIdentifier r : newAllRaces) { - allRaces.add(r); - } - boolean notify = false; - for (Iterator i=selection.iterator(); i.hasNext(); ) { - RaceIdentifier selectedRace = i.next(); - if (!allRaces.contains(selectedRace)) { - notify = true; - i.remove(); - } - } - if (notify) { - notifyListeners(listenersNotToNotify); - } - } - - @Override - public List getAllRaces() { - return Collections.unmodifiableList(allRaces); - } - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceSelectionProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceSelectionProvider.java deleted file mode 100755 index 5db6435e36b..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceSelectionProvider.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.sap.sailing.gwt.ui.client; - -import java.util.List; - -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; - -public interface RaceSelectionProvider { - /** - * @return a non-null list which may be empty - */ - List getSelectedRaces(); - - void addRaceSelectionChangeListener(RaceSelectionChangeListener listener); - - void removeRaceSelectionChangeListener(RaceSelectionChangeListener listener); - - void setSelection(List newSelection, RaceSelectionChangeListener... listenersNotToNotify); - - /** - * @return a non-null list which may be empty - */ - List getAllRaces(); - - void setAllRaces(List newAllRaces, RaceSelectionChangeListener... listenersNotToNotify); -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanel.java index ef6d167bb08..3a87de62bc8 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanel.java @@ -15,7 +15,7 @@ import com.sap.sse.gwt.client.player.Timer.PlayModes; import com.sap.sse.gwt.client.player.Timer.PlayStates; import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; -public class RaceTimePanel extends TimePanel implements RaceSelectionChangeListener, RaceTimesInfoProviderListener { +public class RaceTimePanel extends TimePanel implements RaceTimesInfoProviderListener { private final RaceTimesInfoProvider raceTimesInfoProvider; private RegattaAndRaceIdentifier selectedRace; @@ -30,11 +30,16 @@ public class RaceTimePanel extends TimePanel implements R private boolean redrawAllMarkersPendingForMinMaxBeingInitialized; public RaceTimePanel(Timer timer, TimeRangeWithZoomProvider timeRangeProvider, StringMessages stringMessages, - RaceTimesInfoProvider raceTimesInfoProvider, boolean canReplayWhileLiveIsPossible) { - super(timer, timeRangeProvider, stringMessages, canReplayWhileLiveIsPossible); + RaceTimesInfoProvider raceTimesInfoProvider, boolean canReplayWhileLiveIsPossible, boolean isScreenLargeEnoughToOfferChartSupport, + RegattaAndRaceIdentifier selectedRaceIdentifier) { + super(timer, timeRangeProvider, stringMessages, canReplayWhileLiveIsPossible, isScreenLargeEnoughToOfferChartSupport); this.raceTimesInfoProvider = raceTimesInfoProvider; selectedRace = null; autoAdjustPlayMode = true; + selectedRace = selectedRaceIdentifier; + if (!raceTimesInfoProvider.containsRaceIdentifier(selectedRace)) { + raceTimesInfoProvider.addRaceIdentifier(selectedRace, true); + } } /** @@ -148,16 +153,6 @@ public class RaceTimePanel extends TimePanel implements R (lastRaceTimesInfo.startOfRace != null && liveTimePointInMillis > lastRaceTimesInfo.startOfRace.getTime() - timeBeforeRaceStartTolerance)); } - @Override - public void onRaceSelectionChange(List selectedRaces) { - if (selectedRaces != null && !selectedRaces.isEmpty()) { - selectedRace = selectedRaces.iterator().next(); - if (!raceTimesInfoProvider.containsRaceIdentifier(selectedRace)) { - raceTimesInfoProvider.addRaceIdentifier(selectedRace, true); - } - } - } - @Override public void playStateChanged(PlayStates playState, PlayModes playMode) { super.playStateChanged(playState, playMode); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RegattaSelectionChangeListener.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RegattaSelectionChangeListener.java deleted file mode 100644 index df0cab75a6f..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RegattaSelectionChangeListener.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.sap.sailing.gwt.ui.client; - -import java.util.List; - -import com.sap.sailing.domain.common.RegattaIdentifier; - -/** - * Allows UI components to observe a regatta selector, such as a drop-down box showing a list of regattas - * - * @author Axel Uhl (d043530) - * - */ -public interface RegattaSelectionChangeListener { - - /** - * The first element is the first one selected - * - * @param selectedRegattas a non-null list which is empty if nothing is selected - */ - void onRegattaSelectionChange(List selectedRegattas); - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RegattaSelectionModel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RegattaSelectionModel.java deleted file mode 100644 index a416c372cff..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RegattaSelectionModel.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.sap.sailing.gwt.ui.client; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import com.sap.sailing.domain.common.RegattaIdentifier; - -public class RegattaSelectionModel implements RegattaSelectionProvider { - private final List selection; - - private final List allRegattas; - - private final Set listeners; - - private final boolean hasMultiSelection; - - public RegattaSelectionModel(boolean hasMultiSelection) { - this.hasMultiSelection = hasMultiSelection; - this.selection = new ArrayList(); - this.allRegattas = new ArrayList(); - listeners = new HashSet(); - } - - @Override - public List getSelectedRegattas() { - return Collections.unmodifiableList(selection); - } - - @Override - public void addRegattaSelectionChangeListener(RegattaSelectionChangeListener listener) { - listeners.add(listener); - } - - @Override - public void removeRegattaSelectionChangeListener(RegattaSelectionChangeListener listener) { - listeners.remove(listener); - } - - @Override - public void setSelection(List newSelection, RegattaSelectionChangeListener... listenersNotToNotify) { - boolean notify = !selection.equals(newSelection); - selection.clear(); - selection.addAll(newSelection); - if (notify) { - notifyListeners(listenersNotToNotify); - } - } - - private void notifyListeners(RegattaSelectionChangeListener[] listenersNotToNotify) { - List selectedRegattas = getSelectedRegattas(); - for (RegattaSelectionChangeListener listener : listeners) { - if (listenersNotToNotify == null || !Arrays.asList(listenersNotToNotify).contains(listener)) { - listener.onRegattaSelectionChange(selectedRegattas); - } - } - } - - /** - * Sets the universe of all regattas from which this selection model may select. An equal list will subsequently be - * returned by {@link #getAllRegattas()}. Regattas from the {@link #getSelectedRegattas() selection} not in - * newAllRegattas are removed from the selection. If this happens, the selection listeners are notified. - */ - @Override - public void setAllRegattas(List newAllRegattas, RegattaSelectionChangeListener... listenersNotToNotify) { - allRegattas.clear(); - for (RegattaIdentifier r : newAllRegattas) { - allRegattas.add(r); - } - for (Iterator i=selection.iterator(); i.hasNext(); ) { - RegattaIdentifier selectedRegatta = i.next(); - if (!allRegattas.contains(selectedRegatta)) { - i.remove(); - } - } - // when setting all regattas, the actual RegattaDTOs will usually have changed their identity and maybe also their state; - // so notifying the selection listeners is necessary anyhow - notifyListeners(listenersNotToNotify); - } - - @Override - public List getAllRegattas() { - return Collections.unmodifiableList(allRegattas); - } - - @Override - public boolean hasMultiSelection() { - return hasMultiSelection; - } -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RegattaSelectionProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RegattaSelectionProvider.java deleted file mode 100644 index b8f77a75335..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RegattaSelectionProvider.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.sap.sailing.gwt.ui.client; - -import java.util.List; - -import com.sap.sailing.domain.common.RegattaIdentifier; - -public interface RegattaSelectionProvider { - /** - * @return a non-null list which may be empty - */ - List getSelectedRegattas(); - - void addRegattaSelectionChangeListener(RegattaSelectionChangeListener listener); - - void removeRegattaSelectionChangeListener(RegattaSelectionChangeListener listener); - - void setSelection(List newSelection, RegattaSelectionChangeListener... listenersNotToNotify); - - boolean hasMultiSelection(); - - /** - * @return a non-null list which may be empty - */ - List getAllRegattas(); - - void setAllRegattas(List newAllRegattas, RegattaSelectionChangeListener... listenersNotToNotify); -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java old mode 100755 new mode 100644 index fc5c25987da..0802b3d2b72 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -48,6 +48,7 @@ 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.shared.BulkScoreCorrectionDTO; +import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO; import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO; import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO; import com.sap.sailing.gwt.ui.shared.ControlPointDTO; @@ -159,8 +160,13 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS Map getCompetitorBoats(RegattaAndRaceIdentifier raceIdentifier); CompactRaceMapDataDTO getRaceMapData(RegattaAndRaceIdentifier raceIdentifier, Date date, Map fromPerCompetitorIdAsString, - Map toPerCompetitorIdAsString, boolean extrapolate, LegIdentifier simulationLegIdentifier) throws NoWindException; + Map toPerCompetitorIdAsString, boolean extrapolate, LegIdentifier simulationLegIdentifier, + byte[] md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID) throws NoWindException; + CompactBoatPositionsDTO getBoatPositions(RegattaAndRaceIdentifier raceIdentifier, + Map fromPerCompetitorIdAsString, Map toPerCompetitorIdAsString, + boolean extrapolate) throws NoWindException; + RaceTimesInfoDTO getRaceTimesInfo(RegattaAndRaceIdentifier raceIdentifier); List getRaceTimesInfos(Collection raceIdentifiers); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java old mode 100755 new mode 100644 index de885604ebb..dd3cc3fefba --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -37,6 +37,7 @@ 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.shared.BulkScoreCorrectionDTO; +import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO; import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO; import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO; import com.sap.sailing.gwt.ui.shared.ControlPointDTO; @@ -363,10 +364,20 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan * @param date * use null to indicate "live" in which case the server live time stamp for the race * identified by raceIdentifier will be used, considering that race's delay. + * @param md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID + * used to decide whether the client requires an update to the race's competitor set. If the server + * has the same MD5 hash for the race's competitors, no competitor set is transmitted to the client. + * Otherwise, the full race competitor ID's as strings are sent to the client again for update. */ void getRaceMapData(RegattaAndRaceIdentifier raceIdentifier, Date date, Map fromPerCompetitorIdAsString, Map toPerCompetitorIdAsString, - boolean extrapolate, LegIdentifier simulationLegIdentifier, AsyncCallback callback); + boolean extrapolate, LegIdentifier simulationLegIdentifier, + byte[] md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID, + AsyncCallback callback); + + void getBoatPositions(RegattaAndRaceIdentifier raceIdentifier, Map fromPerCompetitorIdAsString, + Map toPerCompetitorIdAsString, boolean extrapolate, + AsyncCallback callback); void getReplicaInfo(AsyncCallback callback); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SelectionChangeListener.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SelectionChangeListener.java deleted file mode 100644 index 4ad12c87def..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SelectionChangeListener.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.sap.sailing.gwt.ui.client; - -import java.util.List; - -/** - * Allows UI components to observe a selector, such as a drop-down box showing a list of items. - * - * - */ -public interface SelectionChangeListener { - - /** - * The first element is the first one selected - * - * @param selectedItems a non-null list which is empty if nothing is selected - */ - void onSelectionChange(List selectedItems); - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SelectionProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SelectionProvider.java deleted file mode 100644 index d3fc2ff1af5..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SelectionProvider.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.sap.sailing.gwt.ui.client; - -import java.util.List; - -public interface SelectionProvider { - /** - * @return a non-null list which may be empty - */ - List getSelectedItems(); - - void addSelectionChangeListener(SelectionChangeListener listener); - - void removeSelectionChangeListener(SelectionChangeListener listener); - - void setSelection(List newSelection); - - void setSelection(List newSelection, SelectionChangeListener[] listenerNotToNotify); - - boolean hasMultiSelection(); - - /** - * @return a non-null list which may be empty - */ - List getAllItems(); - - void setAllItems(List newAllItems); -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SelectionProviderImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SelectionProviderImpl.java deleted file mode 100644 index 458379310b7..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SelectionProviderImpl.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.sap.sailing.gwt.ui.client; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -public class SelectionProviderImpl implements SelectionProvider { - private final List selection; - - private final List allItems; - - private final Set> listeners; - - private final boolean hasMultiSelection; - - public SelectionProviderImpl(boolean hasMultiSelection) { - this.hasMultiSelection = hasMultiSelection; - this.selection = new ArrayList(); - this.allItems = new ArrayList(); - listeners = new HashSet>(); - } - - @Override - public List getSelectedItems() { - return Collections.unmodifiableList(selection); - } - - @Override - public void addSelectionChangeListener(SelectionChangeListener listener) { - listeners.add(listener); - } - - @Override - public void removeSelectionChangeListener(SelectionChangeListener listener) { - listeners.remove(listener); - } - - @Override - public void setSelection(List newSelection) { - setSelection(newSelection, null); - } - - @Override - public void setSelection(List newSelection, SelectionChangeListener[] listenersNotToNotify) { - boolean notify = !selection.equals(newSelection); - selection.clear(); - selection.addAll(newSelection); - if (notify) { - notifyListeners(listenersNotToNotify); - } - } - - private void notifyListeners(SelectionChangeListener[] listenersNotToNotify) { - List selectedItems = getSelectedItems(); - for (SelectionChangeListener listener : listeners) { - if (listenersNotToNotify == null || !Arrays.asList(listenersNotToNotify).contains(listener)) { - listener.onSelectionChange(selectedItems); - } - } - } - - /** - * Sets the universe of all items from which this selection model may select. An equal list will subsequently be - * returned by {@link #getAllItems()}. Items from the {@link #getSelectedItems() selection} not in - * newAllItems are removed from the selection. If this happens, the selection listeners are notified. - */ - @Override - public void setAllItems(List newAllItems) { - allItems.clear(); - for (T r : newAllItems) { - allItems.add(r); - } - for (Iterator i = selection.iterator(); i.hasNext(); ) { - T selectedItem = i.next(); - if (!allItems.contains(selectedItem)) { - i.remove(); - } - } - // when setting all items, the underlying items will usually have changed their identity and maybe also their state; - // so notifying the selection listeners is necessary anyhow - notifyListeners(null); - } - - @Override - public List getAllItems() { - return Collections.unmodifiableList(allItems); - } - - @Override - public boolean hasMultiSelection() { - return hasMultiSelection; - } -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SmallWindHistoryPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SmallWindHistoryPanel.java deleted file mode 100755 index f5d3b5d2fe4..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SmallWindHistoryPanel.java +++ /dev/null @@ -1,159 +0,0 @@ -package com.sap.sailing.gwt.ui.client; - -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.Map; - -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.FormPanel; -import com.google.gwt.user.client.ui.Grid; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.VerticalPanel; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.domain.common.WindSource; -import com.sap.sailing.domain.common.WindSourceType; -import com.sap.sailing.gwt.ui.shared.WindDTO; -import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; -import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO; -import com.sap.sse.gwt.client.ErrorReporter; -import com.sap.sse.gwt.client.player.TimeListener; - -/** - * Displays a bit of wind history around the time notified to this time listener. - * - * @author Axel Uhl (d043530) - * - */ -public class SmallWindHistoryPanel extends FormPanel implements TimeListener, RaceSelectionChangeListener { - private final Position position; - private final WindIndicator[] windIndicators; - private final Label selectedWindSourceLabel; - private Date date; - private RegattaAndRaceIdentifier race; - private final SailingServiceAsync sailingService; - private final long millisecondStepsPerLabel; - private final ErrorReporter errorReporter; - private final StringMessages stringConstants; - - /** - * @param position - * the position to show the wind for; can, e.g., be the middle of the race course if an average wind - * display for a race is desired - * @param numberOfTimepoints - * the number of wind displays arranged in this panel; each display shows the estimated wind - * @param windTrack - * the wind track to visualize using this panel - */ - public SmallWindHistoryPanel(SailingServiceAsync sailingService, Position position, - int numberOfTimepoints, long millisecondStepsPerLabel, StringMessages stringConstants, ErrorReporter errorReporter) { - this.sailingService = sailingService; - this.position = position; - this.stringConstants = stringConstants; - this.millisecondStepsPerLabel = millisecondStepsPerLabel; - this.errorReporter = errorReporter; - windIndicators = new WindIndicator[numberOfTimepoints]; - VerticalPanel vp = new VerticalPanel(); - setWidget(vp); - Grid grid = new Grid(2, numberOfTimepoints); - selectedWindSourceLabel = new Label(); - vp.add(selectedWindSourceLabel); - for (int i=0; i() { - @Override - public void onSuccess(WindInfoForRaceDTO result) { - if (result != null) { - WindSource bestWindSource = getBestWindSource(result, windIndicators.length); - // expecting to find windIndicators.length fixes - if (result == null - || result.windTrackInfoByWindSource.get(bestWindSource).windFixes.size() != windIndicators.length) { - clearWindDisplay(); - } else { - setSelectedWindSource(bestWindSource); - int i = 0; - for (WindDTO fix : result.windTrackInfoByWindSource.get(bestWindSource).windFixes) { - updateWindIndicator(i, fix); - i++; - } - } - } - } - - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error trying to obtain wind information for race " - + race + ": " + caught.getMessage()); - } - }); - } - } - } - - /** - * Looks for the best wind source in windInfo that has length fixes. Uses the order of - * literals of {@link WindSourceType} to determine precedence. - * - * @return null if no wind source has the expected number of fixes; the best wind source with the - * expected number of fixes otherwise - */ - private WindSource getBestWindSource(WindInfoForRaceDTO windInfo, int expectedNumberOfFixes) { - List windSourceTypesInOrder = Arrays.asList(WindSourceType.values()); - WindSource result = null; - int bestIndexSoFar = Integer.MAX_VALUE; - for (Map.Entry e : windInfo.windTrackInfoByWindSource.entrySet()) { - if (e.getValue().windFixes.size() == expectedNumberOfFixes) { - int index = windSourceTypesInOrder.indexOf(e.getKey()); - if (index < bestIndexSoFar) { - bestIndexSoFar = index; - result = e.getKey(); - } - } - } - return result; - } - - private void clearWindDisplay() { - for (int i=0; i selectedRaces) { - if (!selectedRaces.isEmpty()) { - race = selectedRaces.get(selectedRaces.size() - 1); - updateWindDisplay(); - } - } - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 21b3b2c836e..5d9eb7082d7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -325,7 +325,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c String messagingPortNumber(); String servletPortNumber(); String registeredAt(String string); - String replicatingFromMaster(String hostname, int jmsPort, int servletPort); + String replicatingFromMaster(String hostname, int jmsPort, int servletPort, String messagingHostname, String exchangeName); String showWindSpeedSeries(); String showWindDirectionSeries(); String fleet(); @@ -505,6 +505,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c String sideToWhichMarkAtLegStartWasRounded(); String raceIsLive(String raceName); String racesAreLive(String raceNames); + String regattaIsLive(String regattaName); + String regattasAreLive(String regattaNames); String scoringSchemeHighPointFirstGetsOne(); String scoringSchemeHighPointFirstGetsTen(); String knotsUnit(); @@ -1109,6 +1111,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c String invalidSelection(); String advancePassId(); String pleaseSelectAScoringResult(); + String timeZone(); String eventOfficialWebsiteURL(); String eventSailorsInfoWebsiteURL(); String eventOverviewURL(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 1af7e410846..f06ec1fd184 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -336,7 +336,7 @@ courseMiddleLine=Course middle line messagingPortNumber=Messaging port number (0 for default) servletPortNumber=Servlet port number registeredAt=Registered at {0} -replicatingFromMaster=Replicating from master {0}, messaging port {1}, servlet port {2} +replicatingFromMaster=Replicating from master {0}:{2}, messaging port {3}:{1}, exchange {4} showWindSpeedSeries=Show wind speed data showWindDirectionSeries=Show wind directions data fleet=Fleet @@ -529,6 +529,8 @@ sideToWhichMarkAtLegStartWasRounded=Mark rounded to sideToWhichMarkAtLegStartWasRoundedTooltip=Side to which the mark at leg start was rounded. raceIsLive=Race {0} is live racesAreLive=Races {0} are live +regattaIsLive=Regatta {0} is live +regattasAreLive=Regattas {0} are live knotsUnit=kts knotsValue={0,number,#0.0} kts knotsRange={0,number,#0.0} - {1,number,#0.0} kts @@ -1080,6 +1082,7 @@ sponsorImageURLs=Sponsor Image URLs invalidSelection=The current selection isn''t valid. advancePassId=Advance Pass ID pleaseSelectAScoringResult=Please select a scoring result... +timeZone=Time zone eventOfficialWebsiteURL=Official Website URL eventSailorsInfoWebsiteURL=Sailors Info Website URL eventOverviewURL=Event Overview URL diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 10231b560ac..386a0843272 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -337,7 +337,7 @@ courseMiddleLine=Kursmittellinie messagingPortNumber=Messaging-Portnummer (0 für Default) servletPortNumber=Servlet Portnummer registeredAt=Registriert {0} -replicatingFromMaster=Repliziert von master {0}, Messaging Port {1}, Servlet Port {2} +replicatingFromMaster=Repliziert von master {0}:{2}, Messaging Port {3}:{1}, Exchange {4} showWindSpeedSeries=Daten für Windgeschwindigkeit zeigen showWindDirectionSeries=Daten für Windrichtung zeigen fleet=Flotte @@ -530,6 +530,8 @@ sideToWhichMarkAtLegStartWasRounded=Tonne gerundet an sideToWhichMarkAtLegStartWasRoundedTooltip=Seite, an der die Bahnmarke am Schenkelstart gerundet wurde raceIsLive=Rennen {0} ist live racesAreLive=Rennen {0} sind live +regattaIsLive=Regatta {0} ist live +regattasAreLive=Regatten {0} sind live scoringSchemeHighPointFirstGetsOne=High Point System, Gewinner erhält 1 Punkt scoringSchemeHighPointFirstGetsTen=High Point System, Gewinner erhält 10 Punkte knotsUnit=kn @@ -1075,6 +1077,7 @@ sponsorImageURLs=Sponsorenlogo-URLs invalidSelection=Die aktuelle Selektion ist nicht gültig. advancePassId=Pass ID erhöhen pleaseSelectAScoringResult=Bitte ein Ergebnis auswählen... +timeZone=Zeitzone eventOfficialWebsiteURL=URL der offiziellen Veranstaltungswebseite eventSailorsInfoWebsiteURL=URL zur Webseite mit Seglerinfos eventOverviewURL=URL der Veranstaltungsübersicht diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index a7d4cff59d0..a29a04b8fe7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -329,7 +329,7 @@ courseMiddleLine=Средняя линия курса messagingPortNumber=Номер порта обмена сообщениями ( по умолчанию 0) servletPortNumber=Номер порта сервлета registeredAt=Зарегистрирован в {0} -replicatingFromMaster=Дублирование с основного {0}, порт передачи обмена сообщениями {1}, порт сервлета {2} +replicatingFromMaster=Replicating from master {0}:{2}, messaging port {3}:{1}, exchange {4} showWindSpeedSeries=Показать данные о силе ветра showWindDirectionSeries=Показать данные о направлении ветра fleet=Флот @@ -513,6 +513,8 @@ sideToWhichMarkAtLegStartWasRounded=Отметка округленная до sideToWhichMarkAtLegStartWasRoundedTooltip=Сторона в которую округляется отметка на старте этапа. raceIsLive=Гонка {0} в прямом эфире racesAreLive=Гонки {0} в прямом эфире +regattaIsLive=Регата {0} в прямом эфире +regattasAreLive=Регаты {0} в прямом эфире knotsUnit=Узлы knotsValue={0,number,#0.0} Узлы knotsRange={0,number,#0.0} - {1,number,#0.0} Узлы @@ -1028,6 +1030,7 @@ imageURLs=Image URLs videoURLs=Video URLs sponsorImageURLs=Sponsor Image URLs pleaseSelectAScoringResult=Please select a scoring result... +timeZone=Time zone eventOfficialWebsiteURL=Official Website URL eventSailorsInfoWebsiteURL=Sailors Info Website URL eventOverviewURL=Event Overview URL diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties index 508e2ea36b3..dbd13fad0d2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties @@ -328,7 +328,7 @@ setFixedPassing=Set time as mark passing maximumSpeedOverGroundInKnotsTooltip=比赛中参赛队的最大实际速度 twoMarkControlPoint=2-mark-controlpoint earlyStarters=Early starters -replicatingFromMaster=Replicating from master {0}, messaging port {1}, servlet port {2} +replicatingFromMaster=Replicating from master {0}:{2}, messaging port {3}:{1}, exchange {4} latitude=Latitude filterByLeaderboard=Filter by Leaderboard fromDeg=从 (角度) @@ -840,12 +840,16 @@ refreshIntervalMustBeGreaterThanXSeconds=刷新间隔必须大于0秒 {0} authorName=Author name raceIsInStartphase=开始阶段 pleaseSelectAScoringResult=Please select a scoring result... +timeZone=Time zone portSide=左舷 explainReplicationHostname=Hostname where the replication master is running on. igtimiAccounts=Igtimi Accounts windChart=Wind Chart events=活动 +raceIsLive=Race {0} is live racesAreLive=Races {0} are live +regattaIsLive=Regatta {0} is live +regattasAreLive=Regattas {0} are live operatorNotContains=Not Contains windwardDistanceToCompetitorFarthestAheadInMetersTooltip=迎/顺风航段时离开上风/下风第一名的平行距离或横风时的实际距离name 名字 errorAddingResultImportUrl=Error adding result import URL: {0} @@ -1067,7 +1071,6 @@ polarSheetMinimumConfidenceMeasure=Minimum confidence measure operator=operator tractracUsername=TracTrac username webWindSourceTypeName=Manual -raceIsLive=Race {0} is live sapSailingAnalytics=航行分析 trackWind=Track Wind rawFixes=All fixes (including outliers) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java index b87c3903017..22f8ebbc100 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java @@ -89,7 +89,13 @@ public class TimePanel extends SimplePanel implemen private static ClientResources resources = GWT.create(ClientResources.class); protected static TimePanelCss timePanelCss = TimePanelCssResources.INSTANCE.css(); - public TimePanel(Timer timer, TimeRangeWithZoomProvider timeRangeProvider, StringMessages stringMessages, boolean canReplayWhileLiveIsPossible) { + /** + * @param isScreenLargeEnoughToOfferChartSupport + * if true, the right padding will be set such that the time panel lines up with charts such + * as the competitor chart or the wind chart shown above it + */ + public TimePanel(Timer timer, TimeRangeWithZoomProvider timeRangeProvider, StringMessages stringMessages, + boolean canReplayWhileLiveIsPossible, boolean isScreenLargeEnoughToOfferChartSupport) { this.timer = timer; this.timeRangeProvider = timeRangeProvider; this.stringMessages = stringMessages; @@ -105,7 +111,9 @@ public class TimePanel extends SimplePanel implemen timePanelSliderFlowWrapper = new FlowPanel(); timePanelSlider.setStyleName("timePanelSlider"); timePanelSlider.getElement().getStyle().setPaddingLeft(66, Unit.PX); - timePanelSlider.getElement().getStyle().setPaddingRight(66, Unit.PX); + if (isScreenLargeEnoughToOfferChartSupport) { + timePanelSlider.getElement().getStyle().setPaddingRight(66, Unit.PX); + } timePanelSliderFlowWrapper.add(timePanelSlider); playSpeedImg = resources.timesliderPlaySpeedIcon(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java index 843955e5fa2..b10c5671e99 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java @@ -15,6 +15,7 @@ import com.google.gwt.dom.client.MediaElement; import com.google.gwt.event.logical.shared.CloseEvent; import com.google.gwt.event.logical.shared.CloseHandler; import com.google.gwt.media.client.Audio; +import com.google.gwt.safehtml.shared.UriUtils; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.Window.ClosingEvent; import com.google.gwt.user.client.Window.ClosingHandler; @@ -124,7 +125,7 @@ public class MediaPlayerManagerComponent implements Component, AudioElement mediaReachableTester = audio.getAudioElement(); addLoadMetadataHandler(mediaReachableTester, mediaTrack); mediaReachableTester.setPreload(MediaElement.PRELOAD_METADATA); - mediaReachableTester.setSrc(mediaTrack.url); + mediaReachableTester.setSrc(UriUtils.fromString(mediaTrack.url).asString()); mediaReachableTester.load(); } else { mediaTrack.status = Status.CANNOT_PLAY; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorRaceChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorRaceChart.java index 58bfe7fe2aa..66cb9d0bd54 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorRaceChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorRaceChart.java @@ -45,7 +45,6 @@ import com.sap.sailing.gwt.ui.actions.GetCompetitorsRaceDataAction; import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; import com.sap.sailing.gwt.ui.client.DetailTypeFormatter; -import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.CompetitorRaceDataDTO; @@ -94,16 +93,16 @@ public abstract class AbstractCompetitorRaceChart selectedRaces) { - if (selectedRaces != null && !selectedRaces.isEmpty()) { - selectedRaceIdentifier = selectedRaces.iterator().next(); - } else { - selectedRaceIdentifier = null; - } - - clearChart(); - if (selectedRaceIdentifier != null) { - timeChanged(timer.getTime(), null); - } - } - /** * Checks the relation of the mark passings to the selection range. * diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractRaceChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractRaceChart.java index 64e2ddfae51..acfb723825b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractRaceChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractRaceChart.java @@ -18,7 +18,6 @@ import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; 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.ChartCssResources.ChartsCss; @@ -33,8 +32,7 @@ import com.sap.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.player.Timer.PlayModes; import com.sap.sse.gwt.client.shared.components.SettingsDialog; -public abstract class AbstractRaceChart extends AbsolutePanel implements RaceSelectionChangeListener, - TimeListener, TimeZoomChangeListener, TimeRangeChangeListener { +public abstract class AbstractRaceChart extends AbsolutePanel implements TimeListener, TimeZoomChangeListener, TimeRangeChangeListener { /** * Used as the turboThreshold for the Highcharts series; this is basically the maximum number of points in a series * to be displayed. Default is 1000. See also bug 1742. @@ -83,7 +81,9 @@ public abstract class AbstractRaceChart extends AbsolutePanel implements RaceSel timeRangeWithZoomProvider.addTimeZoomChangeListener(this); timeRangeWithZoomProvider.addTimeRangeChangeListener(this); chartsCss.ensureInjected(); - busyIndicator = new SimpleBusyIndicator(/* busy */ true, 2.0f, chartsCss.busyIndicatorStyle(), chartsCss.busyIndicatorImageStyle()); + busyIndicator = new SimpleBusyIndicator(/* busy */ true, 2.0f); + busyIndicator.setPanelStyleClass(chartsCss.busyIndicatorStyle()); + busyIndicator.setImageStyleClass(chartsCss.busyIndicatorImageStyle()); settingsButton = createSettingsButton(); settingsButton.setStyleName(chartsCss.settingsButtonStyle()); settingsButton.addStyleName(chartsCss.settingsButtonBackgroundImage()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPassingsPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPassingsPanel.java index a812cae69a8..6e060aec45b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPassingsPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPassingsPanel.java @@ -32,7 +32,6 @@ 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.RaceSelectionChangeListener; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.RaceCourseDTO; @@ -47,8 +46,7 @@ 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.SettingsDialogComponent; -public class EditMarkPassingsPanel extends AbsolutePanel implements Component, RaceSelectionChangeListener, - CompetitorSelectionChangeListener { +public class EditMarkPassingsPanel extends AbsolutePanel implements Component, CompetitorSelectionChangeListener { private static class AnchorCell extends AbstractCell { @Override public void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml safeHtml, SafeHtmlBuilder sb) { @@ -259,6 +257,7 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component selectedRaces) { - raceIdentifier = selectedRaces.iterator().next(); - refreshWaypoints(); - } - public void setLeaderboard(LeaderboardDTO leaderboard) { this.leaderboard = leaderboard; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorRaceChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorRaceChart.java index faf11f93c11..3848f3d2cf3 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorRaceChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorRaceChart.java @@ -3,8 +3,8 @@ package com.sap.sailing.gwt.ui.client.shared.charts; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.sap.sailing.domain.common.DetailType; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; -import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; @@ -33,11 +33,11 @@ public class MultiCompetitorRaceChart extends AbstractCompetitorRaceChart colorMap; /** - * @param raceSelectionProvider + * @param selectedRaceIdentifier * if null, this chart won't update its contents automatically upon race selection change; * otherwise, whenever the selection changes, the wind data of the race selected now is loaded from the * server and displayed in this chart. If no race is selected, the chart is cleared. The caller of this * constructor must ensure to trigger {@link RaceSelectionChangeListener#onRaceSelectionChange(List)} at * least once to ensure that this chart sets its {@link AbstractRaceChart#selectedRaceIdentifier} field. */ - public WindChart(SailingServiceAsync sailingService, RaceSelectionProvider raceSelectionProvider, Timer timer, + public WindChart(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); @@ -190,7 +188,15 @@ public class WindChart extends AbstractRaceChart implements Component selectedRaces) { - if (selectedRaces != null && !selectedRaces.isEmpty()) { - selectedRaceIdentifier = selectedRaces.iterator().next(); - } else { - selectedRaceIdentifier = null; - } - - if(selectedRaceIdentifier != null) { - clearCacheAndReload(); - if(isVisible()) - updateVisibleSeries(); - } else { - clearChart(); - } - } - /** * If in live mode, fetches what's missing since the last fix and date. If nothing has been loaded yet, * loads from the beginning up to date. If in replay mode, checks if anything has been loaded at all. If not, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/Flushable.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/Flushable.java new file mode 100644 index 00000000000..d9399bba3e2 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/Flushable.java @@ -0,0 +1,20 @@ +package com.sap.sailing.gwt.ui.client.shared.controls; + +import com.google.gwt.user.cellview.client.CellTable; +import com.google.gwt.view.client.HasData; + +/** + * This {@link Flushable} interface should be implemented by the {@link HasData display} which uses the + * {@link SelectionCheckboxColumn}. You need this interface to ensure that the display displays the correct selection + * state of the {@link SelectionCheckboxColumn}. The method {@link Flushable#flush()} will trigger a redraw of selected + * lines, e.g., by calling the method {@link CellTable#flush()}. + * + * @author Lukas Furmanek + */ +public interface Flushable { + /** + * This method will trigger a redraw of the {@link HasData display} so the {@link SelectionCheckboxColumn} is shown + * correctly again. This is especially important when the selection comes form the program and not from the user. + */ + public void flush(); +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/FlushableCellTable.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/FlushableCellTable.java new file mode 100644 index 00000000000..d5e48fb4d29 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/FlushableCellTable.java @@ -0,0 +1,40 @@ +package com.sap.sailing.gwt.ui.client.shared.controls; + +import com.google.gwt.user.cellview.client.CellTable; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.view.client.ProvidesKey; + +/** + * This {@link FlushableCellTable} provides the {@link FlushableCellTable#flush()}-method for the + * {@link SelectionCheckboxColumn}. So the {@link SelectionCheckboxColumn} can ensure that the selection state is + * displayed correct. + * + * @author D064976 + * @param + */ +public class FlushableCellTable extends CellTable implements Flushable { + public FlushableCellTable() { + super(); + } + public FlushableCellTable(final int pageSize) { + super(pageSize); + } + public FlushableCellTable(ProvidesKey keyProvider) { + super(keyProvider); + } + public FlushableCellTable(int pageSize, Resources resources) { + super(pageSize, resources); + } + public FlushableCellTable(int pageSize, ProvidesKey keyProvider) { + super(pageSize, keyProvider); + } + public FlushableCellTable(final int pageSize, Resources resources, ProvidesKey keyPrivider) { + super(pageSize, resources, keyPrivider); + } + public FlushableCellTable(final int pageSize, Resources resources, ProvidesKey keyProvider, Widget loadingIndicator) { + super(pageSize, resources, keyProvider, loadingIndicator); + } + public FlushableCellTable(final int pageSize, Resources resources, ProvidesKey keyProvider, Widget loadingIndicator, boolean enableColGroup, boolean attachLoadingPanel) { + super(pageSize,resources,keyProvider,loadingIndicator,enableColGroup, attachLoadingPanel); + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/FlushableSortedCellTableWithStylableHeaders.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/FlushableSortedCellTableWithStylableHeaders.java new file mode 100644 index 00000000000..1cb9b545917 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/FlushableSortedCellTableWithStylableHeaders.java @@ -0,0 +1,19 @@ +package com.sap.sailing.gwt.ui.client.shared.controls; + +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardTableResources; +import com.sap.sailing.gwt.ui.leaderboard.SortedCellTableWithStylableHeaders; + +/** + * This {@link FlushableSortedCellTableWithStylableHeaders} provides the + * {@link FlushableSortedCellTableWithStylableHeaders#flush()}-method for the {@link SelectionCheckboxColumn}. So the + * {@link SelectionCheckboxColumn} can ensure that the selection state is displayed correct. + * + * @author D064976 + * @param + */ +public class FlushableSortedCellTableWithStylableHeaders extends SortedCellTableWithStylableHeaders + implements Flushable { + public FlushableSortedCellTableWithStylableHeaders(int pageSize, LeaderboardTableResources resources) { + super(pageSize, resources); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/ImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/ImagesBarCell.java index 9c00995a392..c198422ffdc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/ImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/ImagesBarCell.java @@ -157,7 +157,8 @@ public abstract class ImagesBarCell extends AbstractSafeHtmlCell { if (data != null) { SafeStyles imgStyle = getImageStyle(); for (ImageSpec imageSpec : getImageSpecs()) { - SafeHtml rendered = getImageTemplate().cell(imageSpec.getActionName(), imgStyle, imageSpec.getTooltip(), + SafeHtml rendered; + rendered = getImageTemplate().cell(imageSpec.getActionName(), imgStyle, imageSpec.getTooltip(), imageSpec.getImagePrototype().getSafeHtml()); sb.append(rendered); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/SelectionCheckboxColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/SelectionCheckboxColumn.java index d328719ca5e..dc97f4a7da1 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/SelectionCheckboxColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/controls/SelectionCheckboxColumn.java @@ -1,7 +1,5 @@ package com.sap.sailing.gwt.ui.client.shared.controls; -import java.util.List; - import com.google.gwt.cell.client.Cell.Context; import com.google.gwt.dom.client.BrowserEvents; import com.google.gwt.dom.client.NativeEvent; @@ -15,11 +13,11 @@ import com.google.gwt.view.client.DefaultSelectionEventManager; import com.google.gwt.view.client.DefaultSelectionEventManager.EventTranslator; import com.google.gwt.view.client.DefaultSelectionEventManager.SelectAction; import com.google.gwt.view.client.ListDataProvider; -import com.google.gwt.view.client.MultiSelectionModel; import com.sap.sailing.domain.common.InvertibleComparator; import com.sap.sailing.domain.common.SortingOrder; -import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; import com.sap.sailing.domain.common.impl.InvertibleComparatorAdapter; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; +import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; /** * A column to be used in a {@link CellTable} that controls and reflects a table's selection model using stylable @@ -30,25 +28,31 @@ import com.sap.sailing.domain.common.impl.InvertibleComparatorAdapter; * updates work properly. *

* - * If clients don't choose to use this columns own {@link #getSelectionModel() selection model}, they have to ensure - * that selection changes with subsequent {@link #redrawRow(Object, List) redraw requests} are triggered properly upon - * selection status changes. Otherwise, the checkboxes will run out of sync with the selection status. + * Clients should use the column's own {@link #getSelectionModel() RefreshableMultiSelectionModel}. This will ensure that + * the {@link SelectionCheckboxColumn} will be refreshed correctly when the selection changes or the + * {@link ListDataProvider} has new elements. Clients should also ensure that the {@link Flushable} and the + * {@link ListDataProvider} are not null; otherwise the {@link RefreshableMultiSelectionModel selection model} + * won´t work correctly. The {@link Flushable} interface is used to ensure that the selection state is displayed + * correctly by {@link SelectionCheckboxColumn}. To ensure this, the {@link Flushable#flush()} method is called after + * every selection state change. *

- * * The column uses the {@link BetterCheckboxCell} cell to implement the display properties. Three CSS styles can be used * to parameterize this column: one for the <td> element rendering the cell, and two for the * <div> element representing a selected or deselected element. * * @author Axel Uhl (D043530) + * @author Lukas Furmanek * * @param */ -public abstract class SelectionCheckboxColumn extends AbstractSortableColumnWithMinMax { +public class SelectionCheckboxColumn extends AbstractSortableColumnWithMinMax { private final BetterCheckboxCell cell; private final String checkboxColumnCellCSSClass; private final EventTranslator selectionEventTranslator; - private final MultiSelectionModel selectionModel; - + private final RefreshableMultiSelectionModel selectionModel; + private final ListDataProvider listDataProvider; + private final Flushable display; + /** * @param selectedCheckboxCSSClass * CSS class for the <div> element representing a selected element @@ -56,27 +60,32 @@ public abstract class SelectionCheckboxColumn extends AbstractSortableColumnW * CSS class for the <div> element representing a deselected element * @param checkboxColumnCellCSSClass * CSS class for the <td> element rendering the cell + * @param entityIdentityComparator + * {@link EntityIdentityComparator} to create a {@link RefreshableMultiSelectionModel} + * @param listDataProvider + * {@link ListDataProvider} to create a {@link RefreshableMultiSelectionModel} + * @param display + * {@link Flushable} to redraw the selected elements on the display */ - protected SelectionCheckboxColumn(String selectedCheckboxCSSClass, - String deselectedCheckboxCSSClass, String checkboxColumnCellCSSClass) { - this(new BetterCheckboxCell(selectedCheckboxCSSClass, deselectedCheckboxCSSClass), checkboxColumnCellCSSClass); + public SelectionCheckboxColumn(String selectedCheckboxCSSClass, String deselectedCheckboxCSSClass, + String checkboxColumnCellCSSClass, EntityIdentityComparator entityIdentityComparator, + ListDataProvider listDataProvider, Flushable display) { + this(new BetterCheckboxCell(selectedCheckboxCSSClass, deselectedCheckboxCSSClass), checkboxColumnCellCSSClass, + entityIdentityComparator, listDataProvider, display); } - - private SelectionCheckboxColumn(BetterCheckboxCell checkboxCell, String checkboxColumnCellCSSClass) { + + private SelectionCheckboxColumn(BetterCheckboxCell checkboxCell, String checkboxColumnCellCSSClass, + EntityIdentityComparator entityIdentityComparator, ListDataProvider listDataProvider, + Flushable display) { super(checkboxCell, SortingOrder.DESCENDING); + this.display = display; + this.listDataProvider = listDataProvider; this.cell = checkboxCell; this.checkboxColumnCellCSSClass = checkboxColumnCellCSSClass; this.selectionEventTranslator = createSelectionEventTranslator(); - this.selectionModel = createSelectionModel(); + this.selectionModel = createSelectionModel(entityIdentityComparator); } - /** - * Subclasses need to tell this column what the data list is. This is necessary in order to reach all rows after the - * selection is cleared, as well as to trigger the redraw of a row by setting it again in the list data provider which - * triggers the necessary redraw operation. - */ - protected abstract ListDataProvider getListDataProvider(); - /** * @return a selection manager that should be used for the table to which this column is added; use * {@link CellTable#setSelectionModel(com.google.gwt.view.client.SelectionModel, com.google.gwt.view.client.CellPreviewEvent.Handler)} @@ -85,37 +94,37 @@ public abstract class SelectionCheckboxColumn extends AbstractSortableColumnW public CellPreviewEvent.Handler getSelectionManager() { return DefaultSelectionEventManager.createCustomManager(getSelectionEventTranslator()); } + + protected ListDataProvider getListDataProvider() { + return listDataProvider; + } - public MultiSelectionModel getSelectionModel() { + public RefreshableMultiSelectionModel getSelectionModel() { return selectionModel; } /** - * Clients should use the multi-selection model returned by this method for the {@link CellTable} to which they add this column. - * If they do so, the {@link #redrawRow(LeaderboardRowDTO, List)} method will be triggered correctly for all selection changes. - * Otherwise, clients or subclasses are responsible to issue the necessary calls to {@link #redrawRow(LeaderboardRowDTO, List)} - * after selection changes. + * Clients should use the {@link RefreshableMultiSelectionModel} returned by this method for the {@link CellTable} + * to which they add this column. If they do so, the {@link Flushable#flush()} method will be + * triggered correctly for all selection changes. Otherwise, clients or subclasses are responsible to issue the + * necessary calls to {@link Flushable#flush()} after selection changes. */ - private MultiSelectionModel createSelectionModel() { - return new MultiSelectionModel() { + private RefreshableMultiSelectionModel createSelectionModel(final EntityIdentityComparator entityIdentityComparator) { + return new RefreshableMultiSelectionModel(entityIdentityComparator, listDataProvider) { @Override public void clear() { super.clear(); - final List list = getListDataProvider().getList(); - for (T item : list) { - redrawRow(item, list); - } + display.flush(); } @Override public void setSelected(T item, boolean selected) { - final boolean redrawRequired = isSelected(item) != selected; + boolean wasSelected = isSelected(item); super.setSelected(item, selected); - if (redrawRequired) { - redrawRow(item, getListDataProvider().getList()); + if (wasSelected != selected) { + display.flush(); } } - }; } @@ -207,22 +216,4 @@ public abstract class SelectionCheckboxColumn extends AbstractSortableColumnW } }; } - - /** - * Subclasses shall use this after an update to the selection state of a row to ensure the checkboxes are - * displayed correctly and consistently again. - * - * @param row - * the row object to be re-drawn - * @param dataList - * the result of calling {@link ListDataProvider#getList()} on the table's data provider which is - * actually a list wrapper where updates to which are reflected in the table display - */ - protected void redrawRow(final T row, final List dataList) { - int rowIndex = dataList.indexOf(row); - if (rowIndex >= 0) { - dataList.set(rowIndex, row); // trigger row redraw to have check box shown in correct state - } - getListDataProvider().flush(); - } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/AdvantageLineAnimator.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/AdvantageLineAnimator.java new file mode 100644 index 00000000000..098fbadf186 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/AdvantageLineAnimator.java @@ -0,0 +1,103 @@ +package com.sap.sailing.gwt.ui.client.shared.racemap; + +import com.google.gwt.maps.client.base.LatLng; +import com.google.gwt.maps.client.mvc.MVCArray; +import com.google.gwt.maps.client.overlays.Polyline; +import com.google.gwt.user.client.Timer; + +/** + * Moves a {@link Polyline} from its current position to a target position in a given duration with a fixed + * {@link #ANIMATION_PERIOD_MILLIS animation interval}. Since the timer may be triggered at non-exact intervals, the + * offset per animation tick is calculated based on the current time for each iteration. + *

+ * + * If an animation is still going on, it is permissible for clients to invoke + * {@link #setNextPositionAndTransitionMillis(MVCArray, long)} which will have the effect that from its current position + * reached so far the {@link Polyline} will then be animated to the next target position in the specified time interval. + * + * @author Jonas Dann + * + */ +public class AdvantageLineAnimator extends Timer { + /** + * Time between two animation steps + */ + private static final int ANIMATION_PERIOD_MILLIS = 100; + + /** + * The polyline to animate by updating its {@link Polyline#getPath() path} + */ + private final Polyline advantageLine; + + /** + * The last time the coordinates in the {@link #advantageLine}'s path were updated + */ + private long lastTime; + + /** + * The remaining time for the animation to move the {@link #advantageLine} to its {@link #nextPosition target + * position}. If -1, the {@link #advantageLine} will be moved to its {@link #nextPosition next target + * position} in one step. + */ + private long durationMillis = -1; + + /** + * The "to-be" / target position to which to move the {@link #advantageLine}'s path gradually during the remaining + * {@link #durationMillis}. + */ + private MVCArray nextPosition; + + public AdvantageLineAnimator(Polyline advantageLine) { + if (advantageLine == null) { + throw new NullPointerException("Advantage line must not be null"); + } + this.advantageLine = advantageLine; + } + + /** + * @param timeForPositionTransitionMillis if -1 then no animation is requested and the {@link #advantageLine} will + * be moved to its {@link #nextPosition next target position} in one step. + * + * @param nextPosition the next target position for the {@link #advantageLine}; if null, no animation will happen at all + */ + public void setNextPositionAndTransitionMillis(MVCArray nextPosition, long timeForPositionTransitionMillis) { + if (nextPosition.getLength() != advantageLine.getPath().getLength()) { + throw new IllegalArgumentException("The next animation position for a polyline with "+advantageLine.getPath().getLength()+ + " elements must have the same number of positions but did have "+nextPosition.getLength()); + } + this.nextPosition = nextPosition; + this.lastTime = System.currentTimeMillis(); + this.durationMillis = timeForPositionTransitionMillis; + this.scheduleRepeating(ANIMATION_PERIOD_MILLIS); + } + + @Override + public void run() { + final long time = System.currentTimeMillis(); + final double moveFactor; // how much of the delta to the nextPosition shall this tick move the advantage line? + if (durationMillis > 0) { + long deltaTime = time - lastTime; + moveFactor = Math.min(1.0, (double) deltaTime / (double) durationMillis); // don't overshoot + durationMillis -= deltaTime; + } else { + moveFactor = 1; + } + if (nextPosition != null) { + final MVCArray currentPosition = this.advantageLine.getPath(); + for (int i = 0; i < nextPosition.getLength(); i++) { + final LatLng currentLatLng = currentPosition.get(i); + final LatLng nextLatLng = nextPosition.get(i); + currentPosition.setAt(i, LatLng.newInstance(currentLatLng.getLatitude() + (nextLatLng.getLatitude() - currentLatLng.getLatitude()) * moveFactor, + currentLatLng.getLongitude() + (nextLatLng.getLongitude() - currentLatLng.getLongitude()) * moveFactor)); + } + } + if (durationMillis <= 0) { + cancel(); + } + lastTime = time; + } + + public void removeAnimation() { + this.durationMillis = -1; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CombinedWindPanelStyle.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CombinedWindPanelStyle.java index b2bcaaafab6..a1d84ffb09d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CombinedWindPanelStyle.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CombinedWindPanelStyle.java @@ -6,8 +6,6 @@ import com.google.gwt.resources.client.CssResource.Shared; @Shared public interface CombinedWindPanelStyle extends CssResource { public String combinedWindPanel(); - public String combinedWindPanelTextLabel(); - public String combinedWindPanelCanvas(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java index 492a17635cb..06150e64a98 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java @@ -12,6 +12,7 @@ import java.util.Map; import java.util.Set; import com.google.gwt.maps.client.base.LatLng; +import com.google.gwt.maps.client.mvc.MVCArray; import com.google.gwt.maps.client.overlays.Polyline; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.rpc.AsyncCallback; @@ -47,22 +48,23 @@ public class FixesAndTails { private final Map> fixes; /** - * Tails of competitors currently displayed as overlays on the map. + * Tails of competitors currently displayed as overlays on the map. A tail may have an {@link MVCArray#getLength() + * empty} {@link Polyline#getPath()}. In this case, {@link #firstShownFix} and {@link #lastShownFix} will hold + * -1 for that competitor key. */ private final Map tails; - /** - * Key set is equal to that of {@link #tails} and tells what the index in in {@link #fixes} of the first fix shown + * Key set is equal to that of {@link #tails} and tells what the index in {@link #fixes} of the first fix shown * in {@link #tails} is. If a key is contained in this map, it is also contained in {@link #lastShownFix} and vice - * versa. + * versa. If a tail is present but has an empty path, this map contains -1 for that competitor. */ private final Map firstShownFix; /** - * Key set is equal to that of {@link #tails} and tells what the index in in {@link #fixes} of the last fix shown in + * Key set is equal to that of {@link #tails} and tells what the index in {@link #fixes} of the last fix shown in * {@link #tails} is. If a key is contained in this map, it is also contained in {@link #firstShownFix} and vice - * versa. + * versa. If a tail is present but has an empty path, this map contains -1 for that competitor. */ private final Map lastShownFix; @@ -129,7 +131,9 @@ public class FixesAndTails { * Creates a polyline for the competitor represented by competitorDTO, taking the fixes from * {@link #fixes fixes.get(competitorDTO)} and using the fixes starting at time point from (inclusive) * up to the last fix with time point before to. The polyline is returned. Updates are applied to - * {@link #lastShownFix}, {@link #firstShownFix} and {@link #tails}. + * {@link #lastShownFix}, {@link #firstShownFix} and {@link #tails}.

+ * + * Precondition: tails.containsKey(competitorDTO) == false */ protected Polyline createTailAndUpdateIndices(final CompetitorDTO competitorDTO, Date from, Date to, TailFactory tailFactory) { List points = new ArrayList(); @@ -173,22 +177,45 @@ public class FixesAndTails { } /** - * Adds the fixes received in result to {@link #fixes} and ensures they are still contiguous for each - * competitor. If overlapsWithKnownFixes indicates that the fixes received in result - * overlap with those already known, the fixes are merged into the list of already known fixes for the competitor. - * Otherwise, the fixes received in result replace those known so far for the respective competitor. - * The {@link #tails} affected by these fixes are updated accordingly when modifications fall inside the interval - * shown by the tail, as defined by {@link #firstShownFix} and {@link #lastShownFix}. The tails are, however, - * not trimmed according to the specification for the tail length. This has to happen elsewhere (see also - * {@link #updateTail}). + * Adds the fixes received in fixesForCompetitors to {@link #fixes} and ensures they are still + * contiguous for each competitor. If overlapsWithKnownFixes indicates that the fixes received in + * result overlap with those already known, the fixes are merged into the list of already known fixes + * for the competitor. Otherwise, the fixes received in result replace those known so far for the + * respective competitor. The {@link #tails} affected by these fixes are updated accordingly when modifications fall + * inside the interval shown by the tail, as defined by {@link #firstShownFix} and {@link #lastShownFix}. The tails + * are, however, not trimmed according to the specification for the tail length. This has to happen elsewhere (see + * also {@link #updateTail}). * * @param fixesForCompetitors * For each list the invariant must hold that an {@link GPSFixDTO#extrapolated extrapolated} fix must be * the last one in the list + * @param overlapsWithKnownFixes + * if for a competitor whose fixes are provided in fixesForCompetitors this holds + * false, any fixes previously stored for that competitor are removed, and the tail is + * deleted from the map (see {@link #removeTail(CompetitorDTO)}); the new fixes are then added to the + * {@link #fixes} map, and a new tail will have to be constructed as needed (does not happen here). If + * this map holds true, {@link #mergeFixes(CompetitorDTO, List, long)} is used to merge the + * new fixes from fixesForCompetitors into the {@link #fixes} collection, and the tail is + * left unchanged. NOTE: When a non-overlapping set of fixes is updated (false), this + * map's record for the competitor is UPDATED to true after the tail deletion and + * {@link #fixes} replacement has taken place. This helps in cases where this update is only one of two + * into which an original request was split (one quick update of the tail's head and another one for the + * longer tail itself), such that the second request that uses the same map will be considered + * having an overlap now, not leading to a replacement of the previous update originating from the same + * request. + * + * @return a {@link Runnable} for each competitor from fixesForCompetitors' key set that the caller + * must invoke before starting to create or update the key competitor's tail. The object returned may be a + * no-op, but it may also clear an existing tail if necessary. The reason why this is not executed + * immediately is that there may be a transition-based delay with which the tails are updated. In this case + * we would be producing severe flicker. By delaying the command to the point when the update is applied we + * should avoid such flicker. */ - protected void updateFixes(Map> fixesForCompetitors, + protected Map updateFixes(Map> fixesForCompetitors, Map overlapsWithKnownFixes, TailFactory tailFactory, long timeForPositionTransitionMillis) { - for (Map.Entry> e : fixesForCompetitors.entrySet()) { + final Map result = new HashMap<>(); + final Runnable noOp = new Runnable() { @Override public void run() {} }; + for (final Map.Entry> e : fixesForCompetitors.entrySet()) { if (e.getValue() != null && !e.getValue().isEmpty()) { List fixesForCompetitor = fixes.get(e.getKey()); if (fixesForCompetitor == null) { @@ -200,18 +227,18 @@ public class FixesAndTails { fixesForCompetitor.clear(); // to re-establish the invariants for tails, firstShownFix and lastShownFix, we now need to remove // all points from the competitor's polyline and clear the entries in firstShownFix and lastShownFix - if (tails.containsKey(e.getKey())) { - Polyline removedTail = tails.remove(e.getKey()); - removedTail.setMap(null); - } - firstShownFix.remove(e.getKey()); - lastShownFix.remove(e.getKey()); + result.put(e.getKey(), new Runnable() { @Override public void run() { clearTail(e.getKey()); } }); fixesForCompetitor.addAll(e.getValue()); + overlapsWithKnownFixes.put(e.getKey(), true); // In case this was only one part of a split request, the next request *does* have an overlap } else { mergeFixes(e.getKey(), e.getValue(), timeForPositionTransitionMillis); + result.put(e.getKey(), noOp); } + } else { + result.put(e.getKey(), noOp); } } + return result; } /** @@ -289,6 +316,7 @@ public class FixesAndTails { } else { intoThis.add(intoThisIndex, mergeThisFix); if (tail != null && intoThisIndex >= indexOfFirstShownFix && intoThisIndex <= indexOfLastShownFix) { + // fix inserted at a position currently visualized by tail tail.getPath().insertAt(intoThisIndex - indexOfFirstShownFix, coordinateSystem.toLatLng(mergeThisFix.position)); } if (intoThisIndex < indexOfFirstShownFix) { @@ -354,16 +382,22 @@ public class FixesAndTails { * @param delayForTailChangeInMillis * the time in milliseconds after which to actually draw the tail update, or -1 to perform * the update immediately + * @param tailPreparer + * will be {@link Runnable#run} just before the updates are applied to the tail. In + * particular, if a transition is active, the effect of running this preparer will be held back until the + * transition applies. May be null in which case simply no preparer is run. */ protected void updateTail(final Polyline tail, final CompetitorDTO competitorDTO, - final Date from, final Date to, final int delayForTailChangeInMillis) { + final Date from, final Date to, final int delayForTailChangeInMillis, final Runnable tailPreparer) { Timer delayedOrImmediateExecutor = new Timer() { @Override public void run() { + if (tailPreparer != null) { + tailPreparer.run(); + } int vertexCount = tail.getPath().getLength(); final List fixesForCompetitor = getFixes(competitorDTO); - int indexOfFirstShownFix = firstShownFix.get(competitorDTO) == null ? -1 : firstShownFix - .get(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 && fixesForCompetitor.get(indexOfFirstShownFix).timepoint.before(from)) { @@ -381,9 +415,8 @@ public class FixesAndTails { tail.getPath().insertAt(0, coordinateSystem.toLatLng(fix.position)); vertexCount++; } - // now adjust the polylines tail: remove excess vertices that are after "to" - int indexOfLastShownFix = lastShownFix.get(competitorDTO) == null ? -1 : lastShownFix - .get(competitorDTO); + // now adjust the polyline's tail: remove excess vertices that are after "to" + int indexOfLastShownFix = lastShownFix.get(competitorDTO) == null ? -1 : lastShownFix.get(competitorDTO); while (indexOfLastShownFix != -1 && vertexCount > 0 && fixesForCompetitor.get(indexOfLastShownFix).timepoint.after(to)) { if (vertexCount-1 == 0 || (indexOfLastShownFix-1 >= 0 && !fixesForCompetitor.get(indexOfLastShownFix-1).timepoint.after(to))) { @@ -400,6 +433,9 @@ public class FixesAndTails { indexOfLastShownFix++; GPSFixDTO 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 + } } firstShownFix.put(competitorDTO, indexOfFirstShownFix); lastShownFix.put(competitorDTO, indexOfLastShownFix); @@ -428,6 +464,19 @@ public class FixesAndTails { firstShownFix.remove(competitor); lastShownFix.remove(competitor); } + + /** + * Leaves the tail on the map and empties its path {@link Polyline#getPath()}. Correspondingly, the + * {@link #firstShownFix} and {@link #lastShownFix} entries for competitor are set to -1. + */ + private void clearTail(CompetitorDTO competitor) { + Polyline tail = tails.get(competitor); + if (tail != null) { + tail.getPath().clear(); + firstShownFix.put(competitor, -1); + lastShownFix.put(competitor, -1); + } + } /** * From {@link #fixes} as well as the selection of {@link #getCompetitorsToShow competitors to show}, computes the @@ -441,7 +490,7 @@ public class FixesAndTails { * @return a triple whose {@link Triple#getA() first} component contains the "from", and whose {@link Triple#getB() * second} component contains the "to" times for the competitors whose trails / positions to show; the * {@link Triple#getC() third} component tells whether the existing fixes can remain and be augmented by - * those requested or need to be replaced + * those requested (true) or need to be replaced (false) */ protected Util.Triple, Map, Map> computeFromAndTo( Date upTo, Iterable competitorsToShow, long effectiveTailLengthInMilliseconds) { @@ -476,7 +525,7 @@ public class FixesAndTails { toDate = upTo; } // only request something for the competitor if we're missing information at all - if (fromDate.before(toDate) || fromDate.equals(toDate)) { + if (!fromDate.after(toDate)) { from.put(competitor, fromDate); to.put(competitor, toDate); overlapWithKnownFixes.put(competitor, overlap); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java index ffb6f83106f..4c7e027c4c5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java @@ -23,7 +23,11 @@ public class Hoverline { this.options.setMap(polyline.getMap()); this.options.setPath(polyline.getPath()); this.options.setStrokeColor(polylineOptions.getStrokeColor()); - this.options.setZindex(polylineOptions.getZindex()); + try { + this.options.setZindex(polylineOptions.getZindex()); // if the zindex is not set, this line throws an exception in dev mode + } catch (Exception e) { + // the Z-index of polylineOptions most likely was undefined and therefore cannot be copied (GWT DevMode problem, mostly) + } this.hoverline = Polyline.newInstance(this.options); this.hoverline.setVisible(false); polyline.addMouseOverHandler(new MouseOverMapHandler() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceCompetitorSet.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceCompetitorSet.java new file mode 100644 index 00000000000..cc760d12891 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceCompetitorSet.java @@ -0,0 +1,124 @@ +package com.sap.sailing.gwt.ui.client.shared.racemap; + +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.sap.sailing.domain.common.RaceCompetitorIdsAsStringWithMD5Hash; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; +import com.sap.sse.common.filter.Filter; +import com.sap.sse.common.filter.FilterSet; + +/** + * As an extension of {@link RaceCompetitorIdsAsStringWithMD5Hash}, adds the link with a + * {@link CompetitorSelectionProvider} and allows its clients to obtain the {@link CompetitorDTO} objects for those + * competitors participating in the race under consideration. + *

+ * + * The implementation caches the {@link CompetitorDTO}s and keeps this cache up to date by + * {@link CompetitorSelectionProvider#addCompetitorSelectionChangeListener(CompetitorSelectionChangeListener)} + * listening} on the competitor selection provider for changes. When either the set of competitors for the race + * {@link #setIdsAsStringsOfCompetitorsInRace(Iterable) changes} or the set of competitors + * {@link CompetitorSelectionChangeListener#competitorsListChanged(Iterable) changes}, the {@link CompetitorDTO} + * collection is re-calculated. + * + * @author Axel Uhl (D043530) + * + */ +public class RaceCompetitorSet extends RaceCompetitorIdsAsStringWithMD5Hash { + private static final long serialVersionUID = 3357742414149799988L; + + private CompetitorSelectionProvider competitorSelection; + + /** + * A subset of the competitor selection's {@link CompetitorSelectionProvider#getAllCompetitors()} describing the + * competitors participting in the context of a specific race, or null if this set hasn't been determined + * yet, usually meaning that we have to default to all competitors of the regatta.

+ * + * When {@link #idsAsStringOfCompetitorsParticipatingInRace} is null then so is this field, and vice versa. + */ + private Iterable competitorsParticipatingInRace; + + RaceCompetitorSet() {} // for GWT serialization only + + /** + * Starts out using {@link CompetitorSelectionProvider#getAllCompetitors() all regatta competitors} as response for + * {@link #getCompetitorsParticipatingInRace()}. Only after a call to + * {@link #setIdsAsStringsOfCompetitorsInRace(Iterable)} has been received may this set become adjusted to the + * actual subset participating in the race. + */ + public RaceCompetitorSet(CompetitorSelectionProvider competitorSelection) { + super(); + this.competitorSelection = competitorSelection; + this.competitorsParticipatingInRace = competitorSelection.getAllCompetitors(); + competitorSelection.addCompetitorSelectionChangeListener(new CompetitorSelectionChangeListener() { + @Override + public void removedFromSelection(CompetitorDTO competitor) { + // we don't care about selection + } + + @Override + public void filteredCompetitorsListChanged(Iterable filteredCompetitors) { + // filtering doesn't affect who we think participates in the race + } + + @Override + public void filterChanged(FilterSet> oldFilterSet, + FilterSet> newFilterSet) { + // filtering doesn't affect who we think participates in the race + } + + @Override + public void competitorsListChanged(final Iterable competitors) { + competitorsParticipatingInRace = computeCompetitorsFromIDs(competitors); + } + + @Override + public void addedToSelection(CompetitorDTO competitor) { + // we don't care about selection + } + }); + } + + public void setIdsAsStringsOfCompetitorsInRace(Set idsAsStringsOfCompetitorsInRace) throws UnsupportedEncodingException, NoSuchAlgorithmException { + super.setIdsAsStringsOfCompetitorsInRace(idsAsStringsOfCompetitorsInRace); + competitorsParticipatingInRace = computeCompetitorsFromIDs(competitorSelection.getAllCompetitors()); + } + + public Iterable getCompetitorsParticipatingInRace() { + return competitorsParticipatingInRace; + } + + /** + * Tries to locate the competitors described by the IDs in {@link #idsAsStringOfCompetitorsParticipatingInRace} in + * competitors and returns them in a set. If not all competitors can be found, null is + * returned instead. + */ + private Set computeCompetitorsFromIDs(Iterable competitors) { + Set result; + if (getIdsOfCompetitorsParticipatingInRaceAsStrings() == null) { + result = null; + } else { + final Map competitorsByIdAsString = new HashMap<>(); + for (CompetitorDTO c : competitors) { + competitorsByIdAsString.put(c.getIdAsString(), c); + } + result = new HashSet<>(); + for (String id : getIdsOfCompetitorsParticipatingInRaceAsStrings()) { + CompetitorDTO c = competitorsByIdAsString.get(id); + if (c == null) { + result = null; + break; + } else { + result.add(c); + } + } + } + return result; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java index 8e90af78619..932bb243576 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java @@ -14,7 +14,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import com.google.gwt.canvas.dom.client.CssColor; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; @@ -83,6 +82,7 @@ import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing; import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition; +import com.sap.sailing.gwt.ui.actions.GetBoatPositionsAction; import com.sap.sailing.gwt.ui.actions.GetPolarAction; import com.sap.sailing.gwt.ui.actions.GetRaceMapDataAction; import com.sap.sailing.gwt.ui.actions.GetWindInfoAction; @@ -90,7 +90,6 @@ import com.sap.sailing.gwt.ui.client.ClientResources; import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; import com.sap.sailing.gwt.ui.client.NumberFormatterFactory; -import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener; import com.sap.sailing.gwt.ui.client.RequiresDataInitialization; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -100,6 +99,7 @@ import com.sap.sailing.gwt.ui.client.shared.filter.QuickRankProvider; import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapHelpLinesSettings.HelpLineTypes; import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes; 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; @@ -120,6 +120,7 @@ import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapAPIKey; import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapStyleHelper; import com.sap.sailing.gwt.ui.shared.racemap.RaceSimulationOverlay; import com.sap.sailing.gwt.ui.shared.racemap.WindStreamletsRaceboardOverlay; +import com.sap.sse.common.Color; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.Util.Triple; @@ -137,11 +138,14 @@ 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 RaceMap extends AbsolutePanel implements TimeListener, CompetitorSelectionChangeListener, RaceSelectionChangeListener, +public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSelectionChangeListener, RaceTimesInfoProviderListener, TailFactory, Component, RequiresDataInitialization, RequiresResize, QuickRankProvider { + private static final Color LOWLIGHTED_TAIL_COLOR = new RGBColor(200, 200, 200); public static final String GET_RACE_MAP_DATA_CATEGORY = "getRaceMapData"; public static final String GET_WIND_DATA_CATEGORY = "getWindData"; + private static final String COMPACT_HEADER_STYLE = "compactHeader"; + private MapWidget map; /** @@ -176,6 +180,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe */ private Polyline advantageLine; + private AdvantageLineAnimator advantageTimer; + /** * The windward of two Polylines representing a triangle between startline and first mark. */ @@ -286,8 +292,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe private final CompetitorSelectionProvider competitorSelection; - private List selectedRaces; - + private final RaceCompetitorSet raceCompetitorSet; + /** * Used to check if the first initial zoom to the mark markers was already done. */ @@ -420,6 +426,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe windSensorOverlays = new HashMap(); courseMarkOverlays = new HashMap(); this.competitorSelection = competitorSelection; + this.raceCompetitorSet = new RaceCompetitorSet(competitorSelection); competitorSelection.addCompetitorSelectionChangeListener(this); settings = new RaceMapSettings(); coordinateSystem = new DelegateCoordinateSystem(new IdentityCoordinateSystem()); @@ -441,6 +448,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe trueNorthIndicatorPanel = new TrueNorthIndicatorPanel(this, raceMapImageManager, combinedWindPanelStyle, stringMessages, coordinateSystem); trueNorthIndicatorPanel.setVisible(true); orientationChangeInProgress = false; + mapFirstZoomDone = false; + // TODO bug 494: reset zoom settings to user preferences } /** @@ -534,9 +543,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe RaceMap.this.add(sapLogo); } map.setControls(ControlPosition.LEFT_TOP, combinedWindPanel); - combinedWindPanel.getParent().addStyleName("CombinedWindPanelParentDiv"); map.setControls(ControlPosition.LEFT_TOP, trueNorthIndicatorPanel); - trueNorthIndicatorPanel.getParent().addStyleName("TrueNorthIndicatorPanelParentDiv"); + adjustLeftControlsIndent(); RaceMap.this.raceMapImageManager.loadMapIcons(map); map.setSize("100%", "100%"); @@ -710,6 +718,10 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe for (CompetitorInfoOverlay infoOverlay : competitorInfoOverlays.values()) { infoOverlay.removeCanvasPositionAndRotationTransition(); } + // remove the advantage line animation + if (advantageTimer != null) { + advantageTimer.removeAnimation(); + } } public void redraw() { @@ -735,17 +747,10 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe return panelForRightHeaderLabels; } - @Override - public void onRaceSelectionChange(List selectedRaces) { - mapFirstZoomDone = false; - // TODO bug 494: reset zoom settings to user preferences - this.selectedRaces = selectedRaces; - } - @Override public void raceTimesInfosReceived(Map raceTimesInfos, long clientTimeWhenRequestWasSent, Date serverTimeDuringRequest, long clientTimeWhenResponseWasReceived) { timer.adjustClientServerOffset(clientTimeWhenRequestWasSent, serverTimeDuringRequest, clientTimeWhenResponseWasReceived); - this.lastRaceTimesInfo = raceTimesInfos.get(selectedRaces.get(0)); + this.lastRaceTimesInfo = raceTimesInfos.get(raceIdentifier); } /** @@ -761,30 +766,15 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe @Override public void timeChanged(final Date newTime, final Date oldTime) { if (newTime != null && isMapInitialized) { - if (selectedRaces != null && !selectedRaces.isEmpty()) { - RegattaAndRaceIdentifier race = selectedRaces.get(selectedRaces.size() - 1); + if (raceIdentifier != null) { + RegattaAndRaceIdentifier race = raceIdentifier; final Iterable competitorsToShow = getCompetitorsToShow(); - if (race != null) { + final long transitionTimeInMillis = calculateTimeForPositionTransitionInMillis(newTime, oldTime); final com.sap.sse.common.Util.Triple, Map, Map> fromAndToAndOverlap = fixesAndTails.computeFromAndTo(newTime, competitorsToShow, settings.getEffectiveTailLengthInMilliseconds()); - int requestID = ++boatPositionRequestIDCounter; - // For those competitors for which the tails don't overlap (and therefore will be replaced by the new tail coming from the server) - // we expect some potential delay in computing the full tail. Therefore, in those cases we fire two requests: one fetching only the - // boat positions at newTime with zero tail length; and another one fetching everything else. - GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping = getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(fromAndToAndOverlap, race, newTime); - if (getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping != null) { - asyncActionsExecutor.execute(getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping, GET_RACE_MAP_DATA_CATEGORY, - getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID)); - requestID = ++boatPositionRequestIDCounter; - } - // next, do the full thing; being the later call, if request throttling kicks in, the later call - // supersedes the earlier call which may get dropped then - GetRaceMapDataAction getRaceMapDataAction = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), race, - useNullAsTimePoint() ? null : newTime, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), /* extrapolate */ true, (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null)); - asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY, - getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID)); - + // Request map data update, possibly in two calls; see method details + callGetRaceMapDataForAllOverlappingAndTipsOfNonOverlappingAndGetBoatPositionsForAllOthers(fromAndToAndOverlap, race, newTime, transitionTimeInMillis, competitorsToShow); // draw the wind into the map, get the combined wind List windSourceTypeNames = new ArrayList(); windSourceTypeNames.add(WindSourceType.EXPEDITION.name()); @@ -802,7 +792,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe List> windSourcesToShow = new ArrayList>(); if (windInfo != null) { lastCombinedWindTrackInfoDTO = windInfo; - showAdvantageLine(competitorsToShow, newTime); + showAdvantageLine(competitorsToShow, newTime, transitionTimeInMillis); for (WindSource windSource : windInfo.windTrackInfoByWindSource.keySet()) { WindTrackInfoDTO windTrackInfoDTO = windInfo.windTrackInfoByWindSource.get(windSource); switch (windSource.getType()) { @@ -836,44 +826,94 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } /** - * We assume that overlapping segments usually don't require a lot of loading time as the most typical case will be to update a longer - * tail with a few new fixes that were received since the last time tick. Non-overlapping position requests typically occur for the - * first request when no fix at all is known for the competitor yet, and when the user has radically moved the time slider to some - * other time such that given the current tail length setting the new tail segment does not overlap with the old one, requiring a full - * load of the entire tail data for that competitor.

+ * Requests updates for map data and, when received, updates the map structures accordingly. + *

* - * For the non-overlapping requests, this method creates a separate request which only loads boat positions, quick ranks, sidelines and - * mark positions for the zero-length interval at newTime, assuming that this will work fairly fast and in particular in - * O(1) time regardless of tail length, compared to fetching the entire tail for all competitors. + * The update can happen in one or two round trips. We assume that overlapping segments usually don't require a lot + * of loading time as the most typical case will be to update a longer tail with a few new fixes that were received + * since the last time tick. These supposedly fast requests are handled by a {@link GetRaceMapDataAction} which also + * requests updates for mark positions, sidelines and quick ranks. The same request also loads boat positions for + * the zero-length interval at newTime for the non-overlapping tails assuming that this will work + * fairly fast and in particular in O(1) time regardless of tail length, compared to fetching the entire tail for + * all competitors. This will at least provide quick feedback about those competitors' positions even if loading + * their entire tail may take a little longer. The {@link GetRaceMapDataAction} therefore is intended to be a rather + * quick call. + *

+ * + * Non-overlapping position requests typically occur for the first request when no fix at all is known for the + * competitor yet, or when the user has radically moved the time slider to some other time such that given the + * current tail length setting the new tail segment does not overlap with the old one, requiring a full load of the + * entire tail data for that competitor. For these non-overlapping requests, this method creates a + * {@link GetBoatPositionsAction} request loading the entire tail required, but not quick ranks, sidelines and mark + * positions. Updating the results of this call is done in {@link #updateBoatPositions(Date, long, Map, Iterable, Map, boolean)}. + *

*/ - private GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping( - Triple, Map, Map> fromAndToAndOverlap, - RegattaAndRaceIdentifier race, Date newTime) { - Map fromTimes = new HashMap<>(); - Map toTimes = new HashMap<>(); + private void callGetRaceMapDataForAllOverlappingAndTipsOfNonOverlappingAndGetBoatPositionsForAllOthers( + final Triple, Map, Map> fromAndToAndOverlap, + RegattaAndRaceIdentifier race, final Date newTime, final long transitionTimeInMillis, final Iterable competitorsToShow) { + final Map fromTimesForQuickCall = new HashMap<>(); + final Map toTimesForQuickCall = new HashMap<>(); + final Map fromTimesForNonOverlappingTailsCall = new HashMap<>(); + final Map toTimesForNonOverlappingTailsCall = new HashMap<>(); for (Map.Entry e : fromAndToAndOverlap.getC().entrySet()) { - if (!e.getValue()) { - // no overlap; add competitor to request - fromTimes.put(e.getKey(), newTime); - toTimes.put(e.getKey(), newTime); + if (e.getValue()) { + // overlap: expect a quick response; add original request interval for the competitor + fromTimesForQuickCall.put(e.getKey(), fromAndToAndOverlap.getA().get(e.getKey())); + toTimesForQuickCall.put(e.getKey(), fromAndToAndOverlap.getB().get(e.getKey())); + } else { + // no overlap; add competitor to request with a zero-length interval asking only position at newTime, not the entire tail + fromTimesForQuickCall.put(e.getKey(), newTime); + toTimesForQuickCall.put(e.getKey(), newTime); + fromTimesForNonOverlappingTailsCall.put(e.getKey(), fromAndToAndOverlap.getA().get(e.getKey())); + toTimesForNonOverlappingTailsCall.put(e.getKey(), fromAndToAndOverlap.getB().get(e.getKey())); } } - final GetRaceMapDataAction result; - if (!fromTimes.isEmpty()) { - result = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), - race, useNullAsTimePoint() ? null : newTime, fromTimes, toTimes, /* extrapolate */true, (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null)); - } else { - result = null; + final Map competitorsByIdAsString = new HashMap(); + for (CompetitorDTO competitor : competitorSelection.getAllCompetitors()) { + competitorsByIdAsString.put(competitor.getIdAsString(), competitor); + } + + // only update the tails for these competitors + // Note: the fromAndToAndOverlap.getC() map will be UPDATED by the call to updateBoatPositions happening inside + // the callback provided by getRaceMapDataCallback(...) for those + // entries that are considered not overlapping; subsequently, fromAndToOverlap.getC() will contain true for + // all its entries so that the other response received for GetBoatPositionsAction will consider this an + // overlap if it happens after this update. + asyncActionsExecutor.execute(new GetRaceMapDataAction(sailingService, competitorsByIdAsString, + race, useNullAsTimePoint() ? null : newTime, fromTimesForQuickCall, toTimesForQuickCall, /* extrapolate */true, + (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null), + raceCompetitorSet.getMd5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID()), + GET_RACE_MAP_DATA_CATEGORY, + getRaceMapDataCallback(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, ++boatPositionRequestIDCounter)); + // next, if necessary, do the full thing; the two calls have different action classes, so throttling should not drop one for the other + if (!fromTimesForNonOverlappingTailsCall.keySet().isEmpty()) { + asyncActionsExecutor.execute(new GetBoatPositionsAction(sailingService, race, fromTimesForNonOverlappingTailsCall, toTimesForNonOverlappingTailsCall, + /* extrapolate */ true), GET_RACE_MAP_DATA_CATEGORY, + new MarkedAsyncCallback<>(new AsyncCallback() { + @Override + public void onFailure(Throwable t) { + errorReporter.reportError("Error obtaining racemap data: " + t.getMessage(), true /*silentMode */); + } + + @Override + public void onSuccess(CompactBoatPositionsDTO result) { + // Note: the fromAndToAndOverlap.getC() map will be UPDATED by the call to updateBoatPositions for those + // entries that are considered not overlapping; subsequently, fromAndToOverlap.getC() will contain true for + // all its entries so that the other response received for GetRaceMapDataAction will consider this an + // overlap if it happens after this update. + updateBoatPositions(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, + result.getBoatPositionsForCompetitors(competitorsByIdAsString), /* updateTailsOnly */ true); + } + })); } - return result; } private AsyncCallback getRaceMapDataCallback( - final Date oldTime, final Date newTime, + final long transitionTimeInMillis, final Map hasTailOverlapForCompetitor, final Iterable competitorsToShow, final int requestID) { - return new AsyncCallback() { + return new MarkedAsyncCallback<>(new AsyncCallback() { @Override public void onFailure(Throwable caught) { errorReporter.reportError("Error obtaining racemap data: " + caught.getMessage(), true /*silentMode */); @@ -882,22 +922,27 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe @Override public void onSuccess(RaceMapDataDTO raceMapDataDTO) { if (map != null && raceMapDataDTO != null) { - quickRanks = raceMapDataDTO.quickRanks; - competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber = - raceMapDataDTO.competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber; - if (showViewSimulation && settings.isShowSimulationOverlay()) { - lastLegNumber = raceMapDataDTO.coursePositions.currentLegNumber; - simulationOverlay.updateLeg(Math.max(lastLegNumber,1), /* clearCanvas */ false, raceMapDataDTO.simulationResultVersion); - } // process response only if not received out of order if (startedProcessingRequestID < requestID) { startedProcessingRequestID = requestID; + if (raceMapDataDTO.raceCompetitorIdsAsStrings != null) { + try { + raceCompetitorSet.setIdsAsStringsOfCompetitorsInRace(raceMapDataDTO.raceCompetitorIdsAsStrings); + } catch (Exception e) { + GWT.log("Error trying to update competitor set for race "+raceIdentifier.getRaceName()+ + " in regatta "+raceIdentifier.getRegattaName(), e); + } + } + quickRanks = raceMapDataDTO.quickRanks; + competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber = + raceMapDataDTO.competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber; + if (showViewSimulation && settings.isShowSimulationOverlay()) { + lastLegNumber = raceMapDataDTO.coursePositions.currentLegNumber; + simulationOverlay.updateLeg(Math.max(lastLegNumber, 1), /* clearCanvas */ false, raceMapDataDTO.simulationResultVersion); + } // Do boat specific actions Map> boatData = raceMapDataDTO.boatPositions; - long timeForPositionTransitionMillis = calculateTimeForPositionTransition(newTime, oldTime); - fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, timeForPositionTransitionMillis); - showBoatsOnMap(newTime, timeForPositionTransitionMillis, getCompetitorsToShow()); - showCompetitorInfoOnMap(newTime, timeForPositionTransitionMillis, competitorSelection.getSelectedFilteredCompetitors()); + updateBoatPositions(newTime, transitionTimeInMillis, hasTailOverlapForCompetitor, competitorsToShow, boatData, /* updateTailsOnly */ false); if (douglasMarkers != null) { removeAllMarkDouglasPeuckerpoints(); } @@ -905,17 +950,14 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe removeAllManeuverMarkers(); } - // Do mark specific actions - showCourseMarksOnMap(raceMapDataDTO.coursePositions); if (requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown) { updateCoordinateSystemFromSettings(); } - showCourseSidelinesOnMap(raceMapDataDTO.courseSidelines); + // Do mark specific actions + showCourseMarksOnMap(raceMapDataDTO.coursePositions); + showCourseSidelinesOnMap(raceMapDataDTO.courseSidelines); showStartAndFinishAndCourseMiddleLines(raceMapDataDTO.coursePositions); showStartLineToFirstMarkTriangle(raceMapDataDTO.coursePositions); - // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to - // adjust to new boat positions - showAdvantageLine(competitorsToShow, newTime); // Rezoom the map LatLngBounds zoomToBounds = null; @@ -943,7 +985,44 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe lastTimeChangeBeforeInitialization = newTime; } } - }; + }); + } + + /** + * @param hasTailOverlapForCompetitor + * if for a competitor whose fixes are provided in fixesForCompetitors this holds + * false, any fixes previously stored for that competitor are removed, and the tail is + * deleted from the map (see {@link #removeTail(CompetitorDTO)}); the new fixes are then added to the + * {@link #fixes} map, and a new tail will have to be constructed as needed (does not happen here). If + * this map holds true, {@link #mergeFixes(CompetitorDTO, List, long)} is used to merge the + * new fixes from fixesForCompetitors into the {@link #fixes} collection, and the tail is + * left unchanged. NOTE: When a non-overlapping set of fixes is updated (false), this + * map's record for the competitor is UPDATED to true after the tail deletion and + * {@link #fixes} replacement has taken place. This helps in cases where this update is only one of two + * into which an original request was split (one quick update of the tail's head and another one for the + * longer tail itself), such that the second request that uses the same map will be considered + * having an overlap now, not leading to a replacement of the previous update originating from the same + * request. See also {@link FixesAndTails#updateFixes(Map, Map, TailFactory, long)}. + * @param updateTailsOnly + * if true, only the tails are updated according to boatData and + * hasTailOverlapForCompetitor, but the advantage line is not updated, and neither are the + * competitor info bubbles moved. This assumes that the tips of these tails are loaded in a separate call + * which does update those structures. In particular, tails that do not appear in + * boatData are not removed from the map in case updateTailsOnly is + * true. + */ + private void updateBoatPositions(final Date newTime, final long transitionTimeInMillis, + final Map hasTailOverlapForCompetitor, + final Iterable competitorsToShow, Map> boatData, boolean updateTailsOnly) { + final Map tailPreparersPerCompetitor = + fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); + showBoatsOnMap(newTime, transitionTimeInMillis, competitorsToShow, updateTailsOnly, tailPreparersPerCompetitor); + if (!updateTailsOnly) { + showCompetitorInfoOnMap(newTime, transitionTimeInMillis, competitorSelection.getSelectedFilteredCompetitors()); + // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to + // adjust to new boat positions + showAdvantageLine(competitorsToShow, newTime, transitionTimeInMillis); + } } private void showCourseSidelinesOnMap(List sidelinesDTOs) { @@ -1134,7 +1213,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } } - private long calculateTimeForPositionTransition(final Date newTime, final Date oldTime) { + private long calculateTimeForPositionTransitionInMillis(final Date newTime, final Date oldTime) { final long timeForPositionTransitionMillis; boolean hasTimeJumped = oldTime != null && Math.abs(oldTime.getTime() - newTime.getTime()) > 3*timer.getRefreshInterval(); if (timer.getPlayState() == PlayStates.Playing && !hasTimeJumped) { @@ -1147,37 +1226,58 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe return timeForPositionTransitionMillis; } - private void showBoatsOnMap(final Date newTime, final long timeForPositionTransitionMillis, final Iterable competitorsToShow) { + /** + * @param updateTailsOnly + * if false, tails of competitors not in competitorsToShow are removed from the + * map + * @param tailPreparersPerCompetitor + * executed before creating or updating the respective competitor's tail + */ + private void showBoatsOnMap(final Date newTime, final long timeForPositionTransitionMillis, + final Iterable competitorsToShow, boolean updateTailsOnly, + Map tailPreparersPerCompetitor) { if (map != null) { Date tailsFromTime = new Date(newTime.getTime() - settings.getEffectiveTailLengthInMilliseconds()); Date tailsToTime = newTime; - Set competitorDTOsOfUnusedTails = new HashSet(fixesAndTails.getCompetitorsWithTails()); - Set competitorDTOsOfUnusedBoatCanvases = new HashSet(boatOverlays.keySet()); + Set competitorDTOsOfUnusedTails = new HashSet(); + Set competitorDTOsOfUnusedBoatCanvases = new HashSet(); + if (!updateTailsOnly) { + competitorDTOsOfUnusedTails.addAll(fixesAndTails.getCompetitorsWithTails()); + competitorDTOsOfUnusedBoatCanvases.addAll(boatOverlays.keySet()); + } for (CompetitorDTO competitorDTO : competitorsToShow) { + Runnable tailPreparer = tailPreparersPerCompetitor.get(competitorDTO); if (fixesAndTails.hasFixesFor(competitorDTO)) { Polyline tail = fixesAndTails.getTail(competitorDTO); if (tail == null) { + if (tailPreparer != null) { // could be we didn't receive boat position data for this competitor; then no tailPreparer will have been created + tailPreparer.run(); // presumably a no-op, but you never know... + } tail = fixesAndTails.createTailAndUpdateIndices(competitorDTO, tailsFromTime, tailsToTime, this); } else { fixesAndTails.updateTail(tail, competitorDTO, tailsFromTime, tailsToTime, - (int) (timeForPositionTransitionMillis==-1?-1:timeForPositionTransitionMillis/2)); - competitorDTOsOfUnusedTails.remove(competitorDTO); + (int) (timeForPositionTransitionMillis==-1?-1:timeForPositionTransitionMillis/2), tailPreparer); + if (!updateTailsOnly) { + competitorDTOsOfUnusedTails.remove(competitorDTO); + } PolylineOptions newOptions = createTailStyle(competitorDTO, displayHighlighted(competitorDTO)); tail.setOptions(newOptions); } boolean usedExistingBoatCanvas = updateBoatCanvasForCompetitor(competitorDTO, newTime, timeForPositionTransitionMillis); - if (usedExistingBoatCanvas) { + if (usedExistingBoatCanvas && !updateTailsOnly) { competitorDTOsOfUnusedBoatCanvases.remove(competitorDTO); } } } - for (CompetitorDTO unusedBoatCanvasCompetitorDTO : competitorDTOsOfUnusedBoatCanvases) { - BoatOverlay boatCanvas = boatOverlays.get(unusedBoatCanvasCompetitorDTO); - boatCanvas.removeFromMap(); - boatOverlays.remove(unusedBoatCanvasCompetitorDTO); - } - for (CompetitorDTO unusedTailCompetitorDTO : competitorDTOsOfUnusedTails) { - fixesAndTails.removeTail(unusedTailCompetitorDTO); + if (!updateTailsOnly) { + for (CompetitorDTO unusedBoatCanvasCompetitorDTO : competitorDTOsOfUnusedBoatCanvases) { + BoatOverlay boatCanvas = boatOverlays.get(unusedBoatCanvasCompetitorDTO); + boatCanvas.removeFromMap(); + boatOverlays.remove(unusedBoatCanvasCompetitorDTO); + } + for (CompetitorDTO unusedTailCompetitorDTO : competitorDTOsOfUnusedTails) { + fixesAndTails.removeTail(unusedTailCompetitorDTO); + } } } } @@ -1221,7 +1321,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe return null; } - private void showAdvantageLine(Iterable competitorsToShow, Date date) { + private void showAdvantageLine(Iterable competitorsToShow, Date date, long timeForPositionTransitionMillis) { if (map != null && lastRaceTimesInfo != null && quickRanks != null && lastCombinedWindTrackInfoDTO != null) { boolean drawAdvantageLine = false; if (settings.getHelpLinesSettings().isVisible(HelpLineTypes.ADVANTAGELINE)) { @@ -1287,6 +1387,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } break; } + MVCArray nextPath = MVCArray.newInstance(); LatLng advantageLinePos1 = calculatePositionAlongRhumbline(posAheadOfFirstBoat, coordinateSystem.mapDegreeBearing(rotatedBearingDeg1), advantageLineLengthInKm / 2.0); LatLng advantageLinePos2 = calculatePositionAlongRhumbline(posAheadOfFirstBoat, @@ -1300,6 +1401,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe options.setStrokeOpacity(0.5); advantageLine = Polyline.newInstance(options); + advantageTimer = new AdvantageLineAnimator(advantageLine); MVCArray pointsAsArray = MVCArray.newInstance(); pointsAsArray.insertAt(0, advantageLinePos1); pointsAsArray.insertAt(1, advantageLinePos2); @@ -1317,12 +1419,13 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } }); } else { - advantageLine.getPath().removeAt(1); - advantageLine.getPath().removeAt(0); - advantageLine.getPath().insertAt(0, advantageLinePos1); - advantageLine.getPath().insertAt(1, advantageLinePos2); - advantageLineMouseOverHandler.setTrueWindBearing(bearingOfCombinedWindInDeg); - advantageLineMouseOverHandler.setDate(new Date(windFix.measureTimepoint)); + nextPath.push(advantageLinePos1); + nextPath.push(advantageLinePos2); + advantageTimer.setNextPositionAndTransitionMillis(nextPath, timeForPositionTransitionMillis); + if (advantageLineMouseOverHandler != null) { + advantageLineMouseOverHandler.setTrueWindBearing(bearingOfCombinedWindInDeg); + advantageLineMouseOverHandler.setDate(new Date(windFix.measureTimepoint)); + } } drawAdvantageLine = true; } @@ -1331,6 +1434,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe if (advantageLine != null) { advantageLine.setMap(null); advantageLine = null; + advantageTimer = null; } } } @@ -1941,8 +2045,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe vPanel.add(createInfoWindowLabelAndValue(stringMessages.degreesBoatToTheWind(), (int) Math.abs(lastFix.degreesBoatToTheWind) + " " + stringMessages.degreesShort())); } - if (!selectedRaces.isEmpty()) { - RegattaAndRaceIdentifier race = selectedRaces.get(selectedRaces.size() - 1); + if (raceIdentifier != null) { + RegattaAndRaceIdentifier race = raceIdentifier; if (race != null) { Map from = new HashMap(); from.put(competitorDTO, fixesAndTails.getFixes(competitorDTO).get(fixesAndTails.getFirstShownFix(competitorDTO)).timepoint); @@ -1992,13 +2096,30 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe return vPanel; } + /** + * @return the {@link CompetitorSelectionProvider#getSelectedCompetitors()} if + * {@link RaceMapSettings#isShowOnlySelectedCompetitors() only selected competitors are to be shown}, the + * {@link CompetitorSelectionProvider#getFilteredCompetitors() filtered competitors} otherwise. In both cases, + * if we have {@link RaceCompetitorSet information about the competitors participating} in this map's race, + * the result set is reduced to those, no matter if other regatta participants would otherwise have been in + * the result set + */ private Iterable getCompetitorsToShow() { - Iterable result; + final Set result = new HashSet<>(); Iterable selection = competitorSelection.getSelectedCompetitors(); + final Set raceCompetitorIdsAsString = raceCompetitorSet.getIdsOfCompetitorsParticipatingInRaceAsStrings(); if (!settings.isShowOnlySelectedCompetitors() || Util.isEmpty(selection)) { - result = competitorSelection.getFilteredCompetitors(); + for (final CompetitorDTO filteredCompetitor : competitorSelection.getFilteredCompetitors()) { + if (raceCompetitorIdsAsString == null || raceCompetitorIdsAsString.contains(filteredCompetitor.getIdAsString())) { + result.add(filteredCompetitor); + } + } } else { - result = selection; + for (final CompetitorDTO selectedCompetitor : selection) { + if (raceCompetitorIdsAsString == null || raceCompetitorIdsAsString.contains(selectedCompetitor.getIdAsString())) { + result.add(selectedCompetitor); + } + } } return result; } @@ -2456,6 +2577,25 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe map.triggerResize(); zoomMapToNewBounds(settings.getZoomSettings().getNewBounds(RaceMap.this)); } + // Adjust RaceMap headers to avoid overlapping based on the RaceMap width + boolean isCompactHeader = this.getOffsetWidth() <= 600; + getLeftHeaderPanel().setStyleName(COMPACT_HEADER_STYLE, isCompactHeader); + getRightHeaderPanel().setStyleName(COMPACT_HEADER_STYLE, isCompactHeader); + + // Adjust combined wind and true north indicator panel indent, based on the RaceMap height + if (combinedWindPanel.getParent() != null && trueNorthIndicatorPanel.getParent() != null) { + this.adjustLeftControlsIndent(); + } + } + + private void adjustLeftControlsIndent() { + combinedWindPanel.getParent().setStyleName("CombinedWindPanelParentDiv"); + trueNorthIndicatorPanel.getParent().setStyleName("TrueNorthIndicatorPanelParentDiv"); + String leftControlsIndentStyle = getLeftControlsIndentStyle(); + if (leftControlsIndentStyle != null) { + combinedWindPanel.getParent().addStyleName(leftControlsIndentStyle); + trueNorthIndicatorPanel.getParent().addStyleName(leftControlsIndentStyle); + } } @Override @@ -2481,10 +2621,10 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe options.setGeodesic(true); options.setStrokeOpacity(1.0); boolean noCompetitorSelected = Util.isEmpty(competitorSelection.getSelectedCompetitors()); - if (isHighlighted || noCompetitorSelected) { + if (isHighlighted || noCompetitorSelected || getSettings().isShowOnlySelectedCompetitors()) { options.setStrokeColor(competitorSelection.getColor(competitor, raceIdentifier).getAsHtml()); } else { - options.setStrokeColor(CssColor.make(200, 200, 200).toString()); + options.setStrokeColor(LOWLIGHTED_TAIL_COLOR.getAsHtml()); } if (isHighlighted) { options.setStrokeWeight(2); @@ -2606,4 +2746,11 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } return mapOptions; } + + /** + * @return CSS style name to adjust the indent of left controls (combined wind and true north indicator). + */ + protected String getLeftControlsIndentStyle() { + return null; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettings.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettings.java index ef42bc32951..e47cdb01de3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettings.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettings.java @@ -20,9 +20,9 @@ public class RaceMapSettings extends AbstractSettings { private RaceMapHelpLinesSettings helpLinesSettings; - private boolean transparentHoverlines = true; + private boolean transparentHoverlines = false; // as discussed with Stefan on 2015-12-08 - private int hoverlineStrokeWeight = 10; + private int hoverlineStrokeWeight = 15; // as discussed with Stefan on 2015-12-08 private long tailLengthInMilliseconds = 100000l; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/DateAndTimeFormatterUtil.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/DateAndTimeFormatterUtil.java index 3938fc8090b..44b43feb9e6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/DateAndTimeFormatterUtil.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/DateAndTimeFormatterUtil.java @@ -60,4 +60,12 @@ public class DateAndTimeFormatterUtil { return result; } + + public static String getClientTimeZoneAsGMTString() { + Date now = new Date(); + @SuppressWarnings("deprecation") + int localeTimezoneOffset = now.getTimezoneOffset(); + TimeZone localeTimeZone = TimeZone.createTimeZone(localeTimezoneOffset); + return localeTimeZone.getGMTString(now); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/AbstractResultsPresenter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/AbstractResultsPresenter.java index 8effafd7f9f..4cf316b6e8b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/AbstractResultsPresenter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/AbstractResultsPresenter.java @@ -5,6 +5,7 @@ import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.DeckLayoutPanel; import com.google.gwt.user.client.ui.DockLayoutPanel; @@ -124,7 +125,7 @@ public abstract class AbstractResultsPresenter im public void showError(String error) { if (state != ResultsPresenterState.ERROR) { mainPanel.setWidgetHidden(controlsPanel, true); - errorLabel.setHTML(error); + errorLabel.setHTML(SafeHtmlUtils.fromString(error).asString()); state = ResultsPresenterState.ERROR; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/PlainResultsPresenter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/PlainResultsPresenter.java index 310e27258f8..49a0a67b67d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/PlainResultsPresenter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/PlainResultsPresenter.java @@ -5,6 +5,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.Widget; @@ -29,18 +30,23 @@ public class PlainResultsPresenter extends AbstractResultsPresenterWithDataProvi @Override protected void internalShowNumberResult(Map resultValues) { QueryResultDTO result = getCurrentResult(); - - StringBuilder resultsBuilder = new StringBuilder("" + result.getResultSignifier() + ""); - resultsBuilder.append(""); + SafeHtmlBuilder resultsBuilder = new SafeHtmlBuilder(); + resultsBuilder.appendHtmlConstant(""); + resultsBuilder.appendEscaped(result.getResultSignifier()); + resultsBuilder.appendHtmlConstant(""); + resultsBuilder.appendHtmlConstant("
"); for (GroupKey key : getSortedKeys(result)) { - resultsBuilder.append(""); - resultsBuilder.append(""); - resultsBuilder.append(""); - resultsBuilder.append(""); + resultsBuilder.appendHtmlConstant(""); + resultsBuilder.appendHtmlConstant(""); + resultsBuilder.appendHtmlConstant(""); + resultsBuilder.appendHtmlConstant(""); } - resultsBuilder.append("
" + key.toString() + ":" + resultValues.get(key).doubleValue() + "
"); + resultsBuilder.appendEscaped(key.toString()); + resultsBuilder.appendHtmlConstant(":"); + resultsBuilder.append(resultValues.get(key).doubleValue()); + resultsBuilder.appendHtmlConstant("
"); - - resultsLabel.setHTML(resultsBuilder.toString()); + resultsBuilder.appendHtmlConstant(""); + resultsLabel.setHTML(resultsBuilder.toSafeHtml().asString()); } private Iterable getSortedKeys(QueryResultDTO result) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompetitorFilter.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompetitorFilter.css index 3d626453747..5f27df5b24a 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompetitorFilter.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompetitorFilter.css @@ -1,5 +1,6 @@ .competitorFilterContainer { - width: 400px; + max-width: 400px; + width: 100%; height: 2.14em; background-color: #FFFFFF; margin-left: 0px; @@ -22,7 +23,7 @@ .searchInput { font-size: .933333333333333em; height: 2.142857142857143em; - width: 280px; + width: 70%; border: none; background-color: #eaeaea; padding: 0; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ExpandableSortableColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ExpandableSortableColumn.java index 15fad2b3fc8..22a487be9bf 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ExpandableSortableColumn.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ExpandableSortableColumn.java @@ -149,15 +149,15 @@ public abstract class ExpandableSortableColumn extends LeaderboardSortableCol */ public void toggleExpansion() { if (isExpansionEnabled()) { - final boolean oldBusyState = getLeaderboardPanel().getBusyIndicator().isBusy(); - getLeaderboardPanel().getBusyIndicator().setBusy(true); + final boolean oldBusyState = getLeaderboardPanel().isBusy(); + getLeaderboardPanel().setBusyState(true); setTogglingInProcess(true); final CellTable table = getLeaderboardPanel().getLeaderboardTable(); if (isExpanded()) { for (AbstractSortableColumnWithMinMax column : getAllVisibleChildren()) { getLeaderboardPanel().removeColumn(column); // removes only the children currently displayed } - getLeaderboardPanel().getBusyIndicator().setBusy(false); + getLeaderboardPanel().setBusyState(false); setTogglingInProcess(false); // important: toggle expanded state after asking for all visible children setExpanded(!isExpanded()); @@ -177,7 +177,7 @@ public abstract class ExpandableSortableColumn extends LeaderboardSortableCol } getLeaderboardPanel().getLeaderboardTable().redraw(); } - getLeaderboardPanel().getBusyIndicator().setBusy(oldBusyState); + getLeaderboardPanel().setBusyState(oldBusyState); setTogglingInProcess(false); } }); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java index 1f54ca0315f..79f24c91d28 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java @@ -258,7 +258,7 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { result = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, /* overallDetails */ overallDetails, null, /* autoExpandFirstRace */false, refreshIntervalMillis, numberOfLastRacesToShow, - raceColumnSelectionStrategy); + raceColumnSelectionStrategy, /* showCompetitorSailIdColumns */ true, /*showCompetitorFullNameColumn*/ true); } return result; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java index 11c7854898e..f96f590ed24 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java @@ -86,6 +86,7 @@ import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.controls.AbstractSortableColumnWithMinMax; +import com.sap.sailing.gwt.ui.client.shared.controls.FlushableSortedCellTableWithStylableHeaders; import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sailing.gwt.ui.client.shared.filter.CompetitorRaceRankFilter; import com.sap.sailing.gwt.ui.client.shared.filter.FilterWithUI; @@ -101,7 +102,10 @@ import com.sap.sse.common.filter.FilterSet; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; +import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator; +import com.sap.sse.gwt.client.controls.busyindicator.BusyStateChangeListener; +import com.sap.sse.gwt.client.controls.busyindicator.BusyStateProvider; import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator; import com.sap.sse.gwt.client.player.PlayStateListener; import com.sap.sse.gwt.client.player.TimeListener; @@ -122,7 +126,8 @@ import com.sap.sse.gwt.client.useragent.UserAgentDetails; * */ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayStateListener, DisplayedLeaderboardRowsProvider, - Component, IsEmbeddableComponent, CompetitorSelectionChangeListener, LeaderboardFetcher { + Component, IsEmbeddableComponent, CompetitorSelectionChangeListener, LeaderboardFetcher, + BusyStateProvider { public static final String LOAD_LEADERBOARD_DATA_CATEGORY = "loadLeaderboardData"; protected static final NumberFormat scoreFormat = NumberFormat.getFormat("0.##"); @@ -161,7 +166,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private final StringMessages stringMessages; - private final SortedCellTable leaderboardTable; + private final FlushableSortedCellTableWithStylableHeaders leaderboardTable; private final MultiSelectionModel leaderboardSelectionModel; @@ -296,6 +301,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private ImageResource playIcon; private final BusyIndicator busyIndicator; + private final Set busyStateChangeListeners; /** * Tells whether the leaderboard settings were explicitly changed by an external call to @@ -344,7 +350,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private Element elementToBlur; private boolean showSelectionCheckbox; - private List leaderboardUpdateListener; + private final List leaderboardUpdateListener; private boolean initialCompetitorFilterHasBeenApplied = false; private final boolean showCompetitorFilterStatus; @@ -477,8 +483,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS break; } - final boolean oldBusyState = getBusyIndicator().isBusy(); - getBusyIndicator().setBusy(true); + final boolean oldBusyState = isBusy(); + setBusyState(true); Runnable doWhenNecessaryDetailHasBeenLoaded = new Runnable() { @Override public void run() { @@ -501,7 +507,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS getLeaderboardTable().sortColumn(raceColumnByRaceName, /* ascending */true); } } - getBusyIndicator().setBusy(oldBusyState); + setBusyState(oldBusyState); } }; if (oldShallAddOverallDetails == shallAddOverallDetails() || oldShallAddOverallDetails || getLeaderboard().hasOverallDetails()) { @@ -669,6 +675,13 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS final String twoLetterIsoCountryCode = competitor.getTwoLetterIsoCountryCode(); final String flagImageURL = competitor.getFlagImageURL(); + boolean showBoatColor = !isShowCompetitorFullName() && LeaderboardPanel.this.isEmbedded && preSelectedRace != null; + if (showBoatColor) { + String competitorColor = LeaderboardPanel.this.competitorSelectionProvider.getColor( + competitorFetcher.getCompetitor(object), LeaderboardPanel.this.preSelectedRace).getAsHtml(); + sb.appendHtmlConstant("

"); + } + if (flagImageURL != null && !flagImageURL.isEmpty()) { sb.appendHtmlConstant(""); sb.appendHtmlConstant(" "); @@ -685,6 +698,9 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } } sb.appendEscaped(competitor.getSailID()); + if (showBoatColor) { + sb.appendHtmlConstant("
"); + } } @Override @@ -1593,7 +1609,17 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS private class LeaderboardSelectionCheckboxColumn extends com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn implements CompetitorSelectionChangeListener { protected LeaderboardSelectionCheckboxColumn(final CompetitorSelectionProvider competitorSelectionProvider) { - super(tableResources.cellTableStyle().cellTableCheckboxSelected(), tableResources.cellTableStyle().cellTableCheckboxDeselected(), tableResources.cellTableStyle().cellTableCheckboxColumnCell()); + super(tableResources.cellTableStyle().cellTableCheckboxSelected(), tableResources.cellTableStyle().cellTableCheckboxDeselected(), + tableResources.cellTableStyle().cellTableCheckboxColumnCell(), new EntityIdentityComparator() { + @Override + public boolean representSameEntity(LeaderboardRowDTO dto1, LeaderboardRowDTO dto2) { + return dto1.competitor.getIdAsString().equals(dto2.competitor.getIdAsString()); + } + @Override + public int hashCode(LeaderboardRowDTO t) { + return t.competitor.getIdAsString().hashCode(); + } + }, getData(), leaderboardTable); competitorSelectionProvider.addCompetitorSelectionChangeListener(this); } @@ -1611,17 +1637,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS */ @Override public void addedToSelection(CompetitorDTO competitor) { - final LeaderboardRowDTO row = getRow(competitor); - if (row != null) { - redrawRow(row); - } - } - - private void redrawRow(final LeaderboardRowDTO row) { - final List leaderboardDataList = getData().getList(); - synchronized (leaderboardDataList) { - redrawRow(row, leaderboardDataList); - } + getSelectionModel().setSelected(getRow(competitor.getIdAsString()), true); } /** @@ -1629,19 +1645,11 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS */ @Override public void removedFromSelection(CompetitorDTO competitor) { - final LeaderboardRowDTO row = getRow(competitor); - if (row != null) { - redrawRow(row); - } + getSelectionModel().setSelected(getRow(competitor.getIdAsString()), false); } @Override public void updateMinMax() {} - - @Override - protected ListDataProvider getListDataProvider() { - return getData(); - } } private class TotalRankColumn extends LeaderboardSortableColumnWithMinMax { @@ -1770,7 +1778,6 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS break; } totalRankColumn = new TotalRankColumn(); - selectionCheckboxColumn = new LeaderboardSelectionCheckboxColumn(competitorSelectionProvider); RACE_COLUMN_HEADER_STYLE = tableResources.cellTableStyle().cellTableRaceColumnHeader(); LEG_COLUMN_HEADER_STYLE = tableResources.cellTableStyle().cellTableLegColumnHeader(); LEG_DETAIL_COLUMN_HEADER_STYLE = tableResources.cellTableStyle().cellTableLegDetailColumnHeader(); @@ -1778,7 +1785,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS LEG_COLUMN_STYLE = tableResources.cellTableStyle().cellTableLegColumn(); LEG_DETAIL_COLUMN_STYLE = tableResources.cellTableStyle().cellTableLegDetailColumn(); TOTAL_COLUMN_STYLE = tableResources.cellTableStyle().cellTableTotalColumn(); - leaderboardTable = new SortedCellTableWithStylableHeaders( + leaderboardTable = new FlushableSortedCellTableWithStylableHeaders( /* pageSize */10000, tableResources); leaderboardTable.addCellPreviewHandler(new CellPreviewEvent.Handler() { @Override @@ -1792,6 +1799,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } }); leaderboardTable.ensureDebugId("LeaderboardCellTable"); + selectionCheckboxColumn = new LeaderboardSelectionCheckboxColumn(competitorSelectionProvider); getLeaderboardTable().setWidth("100%"); leaderboardSelectionModel = new MultiSelectionModel(); // remember handler registration so we can temporarily remove it and re-add it to suspend selection events while we're actively changing it @@ -1824,7 +1832,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS contentPanel.setStyleName(STYLE_LEADERBOARD_CONTENT); busyIndicator = new SimpleBusyIndicator(false, 0.8f); busyIndicator.ensureDebugId("BusyIndicator"); - + busyStateChangeListeners = new HashSet<>(); // the information panel if (!isEmbedded) { @@ -1920,7 +1928,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS filterClearButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { - getBusyIndicator().setBusy(true); + setBusyState(true); competitorFilterPanel.clearAllActiveFilters(); timeChanged(new Date(), null); } @@ -2237,19 +2245,19 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS @Override public void onSuccess(LeaderboardDTO result) { updateLeaderboard(result); - getBusyIndicator().setBusy(false); + setBusyState(false); } @Override public void onFailure(Throwable caught) { - getBusyIndicator().setBusy(false); + setBusyState(false); getErrorReporter() .reportError("Error trying to obtain leaderboard contents: " + caught.getMessage(), true /* silentMode */); } }); } else { - getBusyIndicator().setBusy(false); + setBusyState(false); } } @@ -2399,32 +2407,13 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS scoreCorrectionLastUpdateTimeLabel.setText(""); } - List> liveRaces = leaderboard.getLiveRaces(timer.getLiveTimePointInMillis()); - boolean hasLiveRace = !liveRaces.isEmpty(); + boolean hasLiveRace = !leaderboard.getLiveRaces(timer.getLiveTimePointInMillis()).isEmpty(); liveRaceLabel.setText(hasLiveRace ? getLiveRacesText() : ""); - if (hasLiveRace) { - String liveRaceText = ""; - if(liveRaces.size() == 1) { - com.sap.sse.common.Util.Pair liveRace = liveRaces.get(0); - liveRaceText = stringMessages.raceIsLive("'" + liveRace.getA().getRaceColumnName() + "'"); - } else { - String raceNames = ""; - for (com.sap.sse.common.Util.Pair liveRace : liveRaces) { - raceNames += "'" + liveRace.getA().getRaceColumnName() + "', "; - } - // remove last ", " - raceNames = raceNames.substring(0, raceNames.length() - 2); - liveRaceText = stringMessages.racesAreLive(raceNames); - } - liveRaceLabel.setText(liveRaceText); - } else { - liveRaceLabel.setText(""); - } scoreCorrectionLastUpdateTimeLabel.setVisible(!hasLiveRace); liveRaceLabel.setVisible(hasLiveRace); } informLeaderboardUpdateListenersAboutLeaderboardUpdated(leaderboard); - getBusyIndicator().setBusy(false); + setBusyState(false); } } @@ -3120,10 +3109,10 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS return stringMessages.leaderboard(); } - private LeaderboardRowDTO getRow(CompetitorDTO competitor) { + private LeaderboardRowDTO getRow(String competitorIdAsString) { synchronized (getData().getList()) { for (LeaderboardRowDTO row : getData().getList()) { - if (row.competitor.equals(competitor)) { + if (row.competitor.getIdAsString().equals(competitorIdAsString)) { return row; } } @@ -3133,7 +3122,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS @Override public void addedToSelection(CompetitorDTO competitor) { - LeaderboardRowDTO row = getRow(competitor); + LeaderboardRowDTO row = getRow(competitor.getIdAsString()); if (row != null) { leaderboardSelectionModel.setSelected(row, true); } @@ -3141,21 +3130,11 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS @Override public void removedFromSelection(CompetitorDTO competitor) { - LeaderboardRowDTO row = getRow(competitor); + LeaderboardRowDTO row = getRow(competitor.getIdAsString()); if (row != null) { leaderboardSelectionModel.setSelected(row, false); } } - - @Override - public BusyIndicator getBusyIndicator() { - return busyIndicator; - } - - @Override - public boolean hasBusyIndicator() { - return true; - } private Iterable getSelectedRows() { return leaderboardSelectionModel.getSelectedSet(); @@ -3197,7 +3176,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS if (timer != null) { timer.removeTimeListener(this); } - if(leaderboardUpdateListener != null) { + if (leaderboardUpdateListener != null) { leaderboardUpdateListener.clear(); } } @@ -3256,18 +3235,20 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS public String getLiveRacesText() { String result = ""; List> liveRaces = leaderboard.getLiveRaces(timer.getLiveTimePointInMillis()); + boolean isMeta = leaderboard.type.isMetaLeaderboard(); if (!liveRaces.isEmpty()) { - if(liveRaces.size() == 1) { + if (liveRaces.size() == 1) { com.sap.sse.common.Util.Pair liveRace = liveRaces.get(0); - result = stringMessages.raceIsLive("'" + liveRace.getA().getRaceColumnName() + "'"); + String text = "'" + liveRace.getA().getRaceColumnName() + "'"; + result = isMeta ? stringMessages.regattaIsLive(text) : stringMessages.raceIsLive(text); } else { - String raceNames = ""; + String names = ""; for (com.sap.sse.common.Util.Pair liveRace : liveRaces) { - raceNames += "'" + liveRace.getA().getRaceColumnName() + "', "; + names += "'" + liveRace.getA().getRaceColumnName() + "', "; } // remove last ", " - raceNames = raceNames.substring(0, raceNames.length() - 2); - result = stringMessages.racesAreLive(raceNames); + names = names.substring(0, names.length() - 2); + result = isMeta ? stringMessages.regattasAreLive(names) : stringMessages.racesAreLive(names); } } return result; @@ -3277,4 +3258,29 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS public String getDependentCssClassName() { return "leaderboard"; } + + @Override + public void addBusyStateChangeListener(BusyStateChangeListener listener) { + busyStateChangeListeners.add(listener); + } + + @Override + public void removeBusyStateChangeListener(BusyStateChangeListener listener) { + busyStateChangeListeners.remove(listener); + } + + @Override + public boolean isBusy() { + return busyIndicator.isBusy(); + } + + @Override + public void setBusyState(boolean isBusy) { + if (busyIndicator.isBusy() != isBusy) { + busyIndicator.setBusy(isBusy); + for (BusyStateChangeListener listener : busyStateChangeListeners) { + listener.onBusyStateChange(isBusy); + } + } + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java index e2d4275010b..8cdeefda0ea 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java @@ -78,11 +78,11 @@ public class LeaderboardSettingsFactory { /* refresh interval */ null, /* name of race to sort */ nameOfRaceToSort, /* ascending */ true, /* updateUponPlayStateChange */ true, raceColumnSelection.getType(), /*showAddedScores*/ false, /*showOverallRacesCompleted*/ false, - /*showCompetitorSailIdColumns*/showCompetitorSailIdColumn, /*showCompetitorFullNameColumn*/showCompetitorNameColumn); + showCompetitorSailIdColumn, showCompetitorNameColumn); break; case Replay: settings = createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, nameOfRaceToSort, /* autoExpandFirstRace */ - nameOfRaceColumnToShow != null, showRegattaRank); + nameOfRaceColumnToShow != null, showRegattaRank, showCompetitorSailIdColumn, showCompetitorNameColumn); break; } return settings; @@ -127,28 +127,32 @@ public class LeaderboardSettingsFactory { * such a meta leaderboard exists; if multiple such meta leaderboards exist, they are all shown */ public LeaderboardSettings createNewDefaultSettings(List namesOfRaceColumnsToShow, - List namesOfRacesToShow, String nameOfRaceToSort, boolean autoExpandPreSelectedRace, Boolean showRegattaRank) { + List namesOfRacesToShow, String nameOfRaceToSort, boolean autoExpandPreSelectedRace, Boolean showRegattaRank, + boolean showCompetitorSailIdColumn, boolean showCompetitorFullNameColumn) { final List overallDetails = createOverallDetailsForShowRegattaRank(showRegattaRank); return createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, overallDetails, - nameOfRaceToSort, autoExpandPreSelectedRace, /* refreshIntervalMillis */ null); + nameOfRaceToSort, autoExpandPreSelectedRace, /* refreshIntervalMillis */ null, + showCompetitorSailIdColumn, showCompetitorFullNameColumn); } /** - * Like {@link #createNewDefaultSettings(List, List, String, boolean, boolean)}, only that an additional refresh interval for auto-refresh + * Like {@link #createNewDefaultSettings(List, List, String, boolean, boolean, boolean, boolean)}, only that an additional refresh interval for auto-refresh * may be specified; if null, no auto-refresh shall be performed */ - public LeaderboardSettings createNewDefaultSettings(List namesOfRaceColumnsToShow, + private LeaderboardSettings createNewDefaultSettings(List namesOfRaceColumnsToShow, List namesOfRacesToShow, List overallDetailsToShow, String nameOfRaceToSort, - boolean autoExpandPreSelectedRace, Long refreshIntervalMillis) { + boolean autoExpandPreSelectedRace, Long refreshIntervalMillis, boolean showCompetitorSailIdColumns, boolean showCompetitorFullNameColumn) { return createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, overallDetailsToShow, nameOfRaceToSort, autoExpandPreSelectedRace, refreshIntervalMillis, - /* numberOfLastRacesToShow */null, /* raceColumnSelectionStrategy */ RaceColumnSelectionStrategies.EXPLICIT); + /* numberOfLastRacesToShow */null, /* raceColumnSelectionStrategy */ RaceColumnSelectionStrategies.EXPLICIT, + showCompetitorSailIdColumns, showCompetitorFullNameColumn); } public LeaderboardSettings createNewDefaultSettings(List namesOfRaceColumnsToShow, List namesOfRacesToShow, List overallDetailsToShow, String nameOfRaceToSort, boolean autoExpandPreSelectedRace, Long refreshIntervalMillis, Integer numberOfLastRacesToShow, - RaceColumnSelectionStrategies raceColumnSelectionStrategy) { + RaceColumnSelectionStrategies raceColumnSelectionStrategy, boolean showCompetitorSailIdColumns, + boolean showCompetitorFullNameColumn) { if (namesOfRaceColumnsToShow != null && namesOfRacesToShow != null) { throw new IllegalArgumentException("Can specify race columns either by column or by race name, not both"); } @@ -168,7 +172,7 @@ public class LeaderboardSettingsFactory { autoExpandPreSelectedRace, refreshIntervalMillis, /* sort by column */ nameOfRaceToSort, /* ascending */ true, /* updateUponPlayStateChange */ true, raceColumnSelectionStrategy, /*showAddedScores*/ false, /*showOverallRacesCompleted*/ false, - /*showCompetitorSailIdColumns*/true, /*showCompetitorFullNameColumn*/true); + showCompetitorSailIdColumns, showCompetitorFullNameColumn); } public LeaderboardSettings mergeLeaderboardSettings(LeaderboardSettings settingsWithRaceSelection, LeaderboardSettings settingsWithDetails) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java index b3cc3e30c4f..dec01ccef93 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java @@ -106,7 +106,7 @@ public class LeaderboardUrlConfigurationDialog extends SettingsDialog implements TimeListener { +public class MultiLeaderboardPanel extends AbstractLazyComponent implements TimeListener, SelectedLeaderboardChangeProvider { private LeaderboardPanel selectedLeaderboardPanel; private FlowPanel selectedLeaderboardFlowPanel; @@ -60,7 +62,8 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent leaderboardUpdateListener; + private final Set leaderboardUpdateListeners; + private final Set selectedLeaderboardChangeListeners; public MultiLeaderboardPanel(SailingServiceAsync sailingService, String metaLeaderboardName, AsyncActionsExecutor asyncActionsExecutor, Timer timer, boolean isEmbedded, String preselectedLeaderboardName, RaceIdentifier preselectedRace, @@ -81,13 +84,14 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent>(); leaderboardNamesAndSettings = new HashMap(); - leaderboardUpdateListener = new ArrayList(); + leaderboardUpdateListeners = new HashSet(); + selectedLeaderboardChangeListeners = new HashSet(); } private LeaderboardSettings getOrCreateLeaderboardSettings(String leaderboardName, LeaderboardSettings currentLeaderboardSettings) { LeaderboardSettings newLeaderboardSettings = leaderboardNamesAndSettings.get(leaderboardName); if(newLeaderboardSettings == null) { - newLeaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, false, /* showRegattaRank */ true); + newLeaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true); } if(currentLeaderboardSettings != null) { newLeaderboardSettings = LeaderboardSettingsFactory.getInstance().mergeLeaderboardSettings(newLeaderboardSettings, currentLeaderboardSettings); @@ -152,7 +156,7 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent> newLeaderboardNamesAndDisplayNames) { @@ -178,35 +182,34 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent leaderboardNameAndDisplayName : leaderboardNamesAndDisplayNames) { FlowPanel tabFlowPanel = new FlowPanel(); leaderboardsTabPanel.add(tabFlowPanel, leaderboardNameAndDisplayName.getB(), false); - if(selectedLeaderboardName != null && selectedLeaderboardName.equals(leaderboardNameAndDisplayName.getA())) { + if (selectedLeaderboardName != null + && selectedLeaderboardName.equals(leaderboardNameAndDisplayName.getA())) { leaderboardsTabPanel.selectTab(index); } index++; } - // show the last leaderboard when no leaderboard is selected yet - if(selectedLeaderboardName == null && leaderboardCount > 0) { - leaderboardsTabPanel.selectTab(leaderboardCount-1); + // show the last leaderboard when no leaderboard is selected yet + if (selectedLeaderboardName == null && leaderboardCount > 0) { + leaderboardsTabPanel.selectTab(leaderboardCount - 1); } - leaderboardsTabPanel.setVisible(leaderboardCount > 0); - if(!isEmbedded) { + if (!isEmbedded) { leaderboardsLabel.setVisible(leaderboardCount > 0); } } } private void updateSelectedLeaderboard(String newSelectedLeaderboardName, int newTabIndex) { - if(newSelectedLeaderboardName != null) { - if(selectedLeaderboardPanel != null && selectedLeaderboardFlowPanel != null) { + if (newSelectedLeaderboardName != null) { + if (selectedLeaderboardPanel != null && selectedLeaderboardFlowPanel != null) { selectedLeaderboardPanel.removeAllListeners(); selectedLeaderboardFlowPanel.remove(selectedLeaderboardPanel); selectedLeaderboardPanel = null; @@ -215,18 +218,18 @@ public class MultiLeaderboardPanel extends AbstractLazyComponentnull LeaderboardPanel + */ + void onSelectedLeaderboardChanged(LeaderboardPanel selectedLeaderboard); + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SelectedLeaderboardChangeProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SelectedLeaderboardChangeProvider.java new file mode 100644 index 00000000000..e19f52bd4bb --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SelectedLeaderboardChangeProvider.java @@ -0,0 +1,11 @@ +package com.sap.sailing.gwt.ui.leaderboard; + + +public interface SelectedLeaderboardChangeProvider { + + void addSelectedLeaderboardChangeListener(SelectedLeaderboardChangeListener listener); + + void removeSelectedLeaderboardChangeListener(SelectedLeaderboardChangeListener listener); + + void setSelectedLeaderboard(LeaderboardPanel selectedLeaderboard); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java index 78e4426adf6..cb204479253 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java @@ -319,13 +319,13 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { final RowUpdateWhiteboard whiteboard = new RowUpdateWhiteboard( EditableLeaderboardPanel.this.getData()); getWhiteboardOwner().whiteboardProduced(whiteboard); - getBusyIndicator().setBusy(true); + setBusyState(true); getSailingService().updateLeaderboardMaxPointsReason(getLeaderboardName(), row.competitor.getIdAsString(), raceColumnName, value == null || value.trim().length() == 0 ? null : MaxPointsReason.valueOf(value.trim()), getLeaderboardDisplayDate(), new AsyncCallback>() { @Override public void onFailure(Throwable t) { - getBusyIndicator().setBusy(false); + setBusyState(false); getErrorReporter().reportError( "Error trying to update max points reason for competitor " + row.competitor.getName() + " in leaderboard " + getLeaderboardName() @@ -334,7 +334,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { @Override public void onSuccess(Util.Triple newNetAndTotalPointsAndIsCorrected) { - getBusyIndicator().setBusy(false); + setBusyState(false); row.fieldsByRaceColumnName.get(raceColumnName).reasonForMaxPoints = value == null || value.length() == 0 ? null : MaxPointsReason.valueOf(value.trim()); row.fieldsByRaceColumnName.get(raceColumnName).netPoints = newNetAndTotalPointsAndIsCorrected.getA(); @@ -444,14 +444,14 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { final RowUpdateWhiteboard whiteboard = new RowUpdateWhiteboard( EditableLeaderboardPanel.this.getData()); getWhiteboardOwner().whiteboardProduced(whiteboard); - getBusyIndicator().setBusy(true); + setBusyState(true); getSailingService().updateLeaderboardScoreCorrection(getLeaderboardName(), row.competitor.getIdAsString(), raceColumnName, value == null || value.trim().length() == 0 ? null : value.trim().equals("n/a") ? null : Double.valueOf(value.trim()), getLeaderboardDisplayDate(), new AsyncCallback>() { @Override public void onFailure(Throwable t) { - getBusyIndicator().setBusy(false); + setBusyState(false); getErrorReporter().reportError("Error trying to update score correction for competitor "+ row.competitor.getName()+" in leaderboard "+getLeaderboardName()+ " for race "+raceColumnName+": "+t.getMessage()+ @@ -460,7 +460,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { @Override public void onSuccess(Util.Triple newNetAndTotalPointsAndIsCorrected) { - getBusyIndicator().setBusy(false); + setBusyState(false); final LeaderboardEntryDTO leaderboardEntryDTO = row.fieldsByRaceColumnName.get(raceColumnName); leaderboardEntryDTO.netPoints = value == null || value.length() == 0 ? newNetAndTotalPointsAndIsCorrected .getA() : Double.valueOf(value.trim()); @@ -523,13 +523,13 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { row.fieldsByRaceColumnName.get(raceColumnName).netPoints, new DialogCallback>() { @Override public void ok(final Util.Pair editedObject) { - getBusyIndicator().setBusy(true); + setBusyState(true); getSailingService().updateLeaderboardScoreCorrection(getLeaderboardName(), row.competitor.getIdAsString(), raceColumnName, editedObject.getB(), getLeaderboardDisplayDate(), new AsyncCallback>() { @Override public void onFailure(Throwable t) { - getBusyIndicator().setBusy(false); + setBusyState(false); getErrorReporter().reportError("Error trying to update score correction for competitor "+ row.competitor.getName()+" in leaderboard "+getLeaderboardName()+ " for race "+raceColumnName+": "+t.getMessage()+ @@ -543,7 +543,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { new AsyncCallback>() { @Override public void onFailure(Throwable t) { - getBusyIndicator().setBusy(false); + setBusyState(false); getErrorReporter().reportError("Error trying to update score correction for competitor "+ row.competitor.getName()+" in leaderboard "+getLeaderboardName()+ " for race "+raceColumnName+": "+t.getMessage()+ @@ -552,7 +552,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { @Override public void onSuccess(Util.Triple newNetAndTotalPointsAndIsCorrected) { - getBusyIndicator().setBusy(false); + setBusyState(false); final LeaderboardEntryDTO leaderboardEntryDTO = row.fieldsByRaceColumnName.get(raceColumnName); leaderboardEntryDTO.reasonForMaxPoints = editedObject.getA(); leaderboardEntryDTO.netPoints = newNetAndTotalPointsAndIsCorrected.getA(); @@ -584,7 +584,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { String leaderboardName, String leaderboardGroupName, final ErrorReporter errorReporter, final StringMessages stringMessages, UserAgentDetails userAgent) { super(sailingService, asyncActionsExecutor, LeaderboardSettingsFactory.getInstance().createNewDefaultSettings( - /* racesToShow */ null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true), + /* racesToShow */ null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true), new CompetitorSelectionModel(/* hasMultiSelection */true), leaderboardName, errorReporter, stringMessages, userAgent, /* showRaceDetails */ true); suppressedCompetitorsShown = new ListDataProvider(new ArrayList()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/MatchAndApplyScoreCorrectionsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/MatchAndApplyScoreCorrectionsDialog.java index 61bf83687bb..066d0212239 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/MatchAndApplyScoreCorrectionsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/MatchAndApplyScoreCorrectionsDialog.java @@ -409,11 +409,11 @@ public class MatchAndApplyScoreCorrectionsDialog extends DataEntryDialog() { @Override public void onFailure(Throwable caught) { - leaderboardPanel.getBusyIndicator().setBusy(false); + leaderboardPanel.setBusyState(false); errorReporter.reportError(stringMessages.errorUpdatingScoresForLeaderboard(result.getLeaderboardName(), caught.getMessage())); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/ResultSelectionAndApplyDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/ResultSelectionAndApplyDialog.java index ffe969fe32e..660be8c30ad 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/ResultSelectionAndApplyDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/ResultSelectionAndApplyDialog.java @@ -214,19 +214,19 @@ public class ResultSelectionAndApplyDialog extends DataEntryDialog() { @Override public void onFailure(Throwable caught) { - leaderboardPanel.getBusyIndicator().setBusy(false); + leaderboardPanel.setBusyState(false); errorReporter.reportError(stringMessages.errorObtainingScoreCorrections(scoreCorrectionProviderName, eventName, boatClassName, timePointWhenResultPublished.toString(), caught.getMessage())); } @Override public void onSuccess(RegattaScoreCorrectionDTO result) { - leaderboardPanel.getBusyIndicator().setBusy(false); + leaderboardPanel.setBusyState(false); new MatchAndApplyScoreCorrectionsDialog(leaderboardPanel, stringMessages, sailingService, errorReporter, result).show(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/EmbeddedMapAndWindChartEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/EmbeddedMapAndWindChartEntryPoint.java index 926e3ca3efe..127cb23ee23 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/EmbeddedMapAndWindChartEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/EmbeddedMapAndWindChartEntryPoint.java @@ -2,9 +2,7 @@ package com.sap.sailing.gwt.ui.raceboard; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; @@ -29,7 +27,6 @@ import com.sap.sailing.gwt.ui.client.CompetitorColorProviderImpl; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; -import com.sap.sailing.gwt.ui.client.RaceSelectionModel; import com.sap.sailing.gwt.ui.client.TimePanel; import com.sap.sailing.gwt.ui.client.TimePanelSettings; import com.sap.sailing.gwt.ui.client.shared.charts.WindChart; @@ -141,9 +138,6 @@ public class EmbeddedMapAndWindChartEntryPoint extends AbstractSailingEntryPoint title.append(fleetName); } Window.setTitle(title.toString()); - final RaceSelectionModel raceSelectionModel = new RaceSelectionModel(); - final List raceList = Collections.singletonList(selectedRaceIdentifier); - raceSelectionModel.setSelection(raceList); final long refreshInterval = Duration.ONE_SECOND.times(3).asMillis(); final Timer timer = new Timer(play ? PlayModes.Live : PlayModes.Replay); AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor(); @@ -151,7 +145,8 @@ public class EmbeddedMapAndWindChartEntryPoint extends AbstractSailingEntryPoint // Use a TimePanel to manage wind chart zoom, although the TimePanel itself is not being displayed; // let the time panel always return to "live" mode. final TimePanel timePanel = new TimePanel( - timer, timeRangeWithZoomProvider, getStringMessages(), /* canReplayWhileLive */ false) { + timer, timeRangeWithZoomProvider, getStringMessages(), /* canReplayWhileLive */ false, + /* isScreenLargeEnoughToOfferChartSupport set to true iff wind chart will be displayed */ showWindChart) { protected boolean isLiveModeToBeMadePossible() { return true; } @@ -187,14 +182,12 @@ public class EmbeddedMapAndWindChartEntryPoint extends AbstractSailingEntryPoint } mapSettings.setWindUp(windUp); raceMap.updateSettings(mapSettings); - raceMap.onRaceSelectionChange(raceList); final WindChart windChart; if (showWindChart) { - windChart = new WindChart(sailingService, raceSelectionModel, timer, + windChart = new WindChart(sailingService, selectedRaceIdentifier, timer, timeRangeWithZoomProvider, new WindChartSettings(), getStringMessages(), asyncActionsExecutor, /* errorReporter */ EmbeddedMapAndWindChartEntryPoint.this, /* compactChart */ true); - windChart.onRaceSelectionChange(raceList); } else { windChart = null; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java index 219d66c3acb..d348e6b80bd 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java @@ -2,10 +2,10 @@ package com.sap.sailing.gwt.ui.raceboard; import java.util.Collections; -import java.util.List; import java.util.UUID; import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; @@ -17,12 +17,10 @@ import com.google.gwt.user.client.ui.DockLayoutPanel; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootLayoutPanel; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint; import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; import com.sap.sailing.gwt.ui.client.MediaService; import com.sap.sailing.gwt.ui.client.MediaServiceAsync; -import com.sap.sailing.gwt.ui.client.RaceSelectionModel; import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; import com.sap.sailing.gwt.ui.shared.RaceWithCompetitorsDTO; @@ -97,7 +95,12 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint { sailingService.getRaceboardData(regattaName, raceName, leaderboardName, leaderboardGroupName, eventId, new AsyncCallback() { @Override public void onSuccess(RaceboardDataDTO result) { - checkUrlParameters(result, canReplayWhileLiveIsPossible, showMapControls); + // Determine if the screen is large enough to display charts such as the competitor chart or the wind chart. + // This decision is made once based on the initial screen height. Resizing the window afterwards will have + // no impact on the chart support, i.e. they are available/unavailable based on the initial decision. + boolean isScreenLargeEnoughToOfferChartSupport = Document.get().getClientHeight() >= 600; + checkUrlParameters(result, canReplayWhileLiveIsPossible, showMapControls, + isScreenLargeEnoughToOfferChartSupport); } @Override @@ -116,7 +119,8 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint { vp.add(new Label(message)); } - private void checkUrlParameters(RaceboardDataDTO raceboardData, boolean canReplayWhileLiveIsPossible, boolean showMapControls) { + private void checkUrlParameters(RaceboardDataDTO raceboardData, boolean canReplayWhileLiveIsPossible, boolean showMapControls, + boolean isScreenLargeEnoughToOfferChartSupport) { if (!raceboardData.isValidLeaderboard()) { createErrorPage(getStringMessages().noSuchLeaderboard()); return; @@ -140,29 +144,28 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint { } selectedRace = raceboardData.getRace(); Window.setTitle(selectedRace.getName()); - RaceSelectionModel raceSelectionModel = new RaceSelectionModel(); - List singletonList = Collections.singletonList(selectedRace.getRaceIdentifier()); - raceSelectionModel.setSelection(singletonList); Timer timer = new Timer(PlayModes.Replay, 1000l); AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor(); - RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, asyncActionsExecutor, this, singletonList, 5000l /* requestInterval*/); + RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, asyncActionsExecutor, this, + Collections.singletonList(selectedRace.getRaceIdentifier()), 5000l /* requestInterval*/); RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, getUserService(), asyncActionsExecutor, - raceboardData.getCompetitorAndTheirBoats(), timer, raceSelectionModel, leaderboardName, leaderboardGroupName, eventId, - raceboardViewConfig, RaceBoardEntryPoint.this, getStringMessages(), userAgent, raceTimesInfoProvider, showMapControls); + raceboardData.getCompetitorAndTheirBoats(), timer, selectedRace.getRaceIdentifier(), leaderboardName, leaderboardGroupName, eventId, + raceboardViewConfig, RaceBoardEntryPoint.this, getStringMessages(), userAgent, raceTimesInfoProvider, showMapControls, + isScreenLargeEnoughToOfferChartSupport); createRaceBoardInOneScreenMode(raceBoardPanel, raceboardViewConfig); } private FlowPanel createTimePanel(RaceBoardPanel raceBoardPanel) { - FlowPanel timeLineInnerBgPanel = new FlowPanel(); + FlowPanel timeLineInnerBgPanel = new ResizableFlowPanel(); timeLineInnerBgPanel.addStyleName("timeLineInnerBgPanel"); timeLineInnerBgPanel.add(raceBoardPanel.getTimePanel()); - FlowPanel timeLineInnerPanel = new FlowPanel(); + FlowPanel timeLineInnerPanel = new ResizableFlowPanel(); timeLineInnerPanel.add(timeLineInnerBgPanel); timeLineInnerPanel.addStyleName("timeLineInnerPanel"); - FlowPanel timelinePanel = new FlowPanel(); + FlowPanel timelinePanel = new ResizableFlowPanel(); timelinePanel.add(timeLineInnerPanel); timelinePanel.addStyleName("timeLinePanel"); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java index 414c16d8f1e..32f83a70e7a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java @@ -1,12 +1,13 @@ package com.sap.sailing.gwt.ui.raceboard; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.core.shared.GWT; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Style.Unit; @@ -17,8 +18,10 @@ import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.MenuBar; +import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.UIObject; +import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; @@ -36,8 +39,6 @@ import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory; import com.sap.sailing.gwt.ui.client.GlobalNavigationPanel; import com.sap.sailing.gwt.ui.client.LeaderboardUpdateListener; import com.sap.sailing.gwt.ui.client.MediaServiceAsync; -import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; -import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; import com.sap.sailing.gwt.ui.client.RaceTimePanel; import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -60,7 +61,6 @@ import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory; import com.sap.sse.common.filter.FilterSet; import com.sap.sse.common.settings.AbstractSettings; import com.sap.sse.gwt.client.ErrorReporter; -import com.sap.sse.gwt.client.URLEncoder; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.player.TimeRangeWithZoomModel; import com.sap.sse.gwt.client.player.Timer; @@ -79,7 +79,7 @@ import com.sap.sse.security.ui.client.UserService; * @author Frank Mittag, Axel Uhl (d043530) * */ -public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeListener, LeaderboardUpdateListener, PopupPositionProvider { +public class RaceBoardPanel extends SimplePanel implements LeaderboardUpdateListener, PopupPositionProvider, RequiresResize { private final SailingServiceAsync sailingService; private final MediaServiceAsync mediaService; private final UUID eventId; @@ -91,7 +91,6 @@ public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeLi private final List componentViewers; private RaceTimePanel timePanel; private final Timer timer; - private final RaceSelectionProvider raceSelectionProvider; private final UserAgentDetails userAgent; private final CompetitorSelectionProvider competitorSelectionProvider; private final TimeRangeWithZoomModel timeRangeWithZoomModel; @@ -123,18 +122,21 @@ public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeLi * @param eventId * an optional event that can be used for "back"-navigation in case the race board shows a race in the * context of an event; may be null. + * @param isScreenLargeEnoughToOfferChartSupport + * if the screen is large enough to display charts such as the competitor chart or the wind chart, a + * padding is provided for the RaceTimePanel that aligns its right border with that of the charts, and + * the charts are created. This decision is made once on startup in the {@link RaceBoardEntryPoint} class. */ public RaceBoardPanel(SailingServiceAsync sailingService, MediaServiceAsync mediaService, UserService userService, AsyncActionsExecutor asyncActionsExecutor, Map competitorsAndTheirBoats, - Timer timer, RaceSelectionProvider theRaceSelectionProvider, String leaderboardName, + Timer timer, RegattaAndRaceIdentifier selectedRaceIdentifier, String leaderboardName, String leaderboardGroupName, UUID eventId, RaceBoardViewConfiguration raceboardViewConfiguration, ErrorReporter errorReporter, final StringMessages stringMessages, - UserAgentDetails userAgent, RaceTimesInfoProvider raceTimesInfoProvider, boolean showMapControls) { + UserAgentDetails userAgent, RaceTimesInfoProvider raceTimesInfoProvider, boolean showMapControls, boolean isScreenLargeEnoughToOfferChartSupport) { this.sailingService = sailingService; this.mediaService = mediaService; this.stringMessages = stringMessages; this.raceboardViewConfiguration = raceboardViewConfiguration; - this.raceSelectionProvider = theRaceSelectionProvider; this.raceTimesInfoProvider = raceTimesInfoProvider; this.errorReporter = errorReporter; this.userAgent = userAgent; @@ -142,11 +144,10 @@ public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeLi this.eventId = eventId; this.currentRaceHasBeenSelectedOnce = false; this.leaderboardName = leaderboardName; - raceSelectionProvider.addRaceSelectionChangeListener(this); - selectedRaceIdentifier = raceSelectionProvider.getSelectedRaces().iterator().next(); + this.selectedRaceIdentifier = selectedRaceIdentifier; this.setRaceBoardName(selectedRaceIdentifier.getRaceName()); this.asyncActionsExecutor = asyncActionsExecutor; - FlowPanel mainPanel = new FlowPanel(); + FlowPanel mainPanel = new ResizableFlowPanel(); mainPanel.setSize("100%", "100%"); setWidget(mainPanel); raceInformationHeader = new FlowPanel(); @@ -161,7 +162,33 @@ public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeLi raceMapResources.combinedWindPanelStyle().ensureInjected(); raceMap = new RaceMap(sailingService, asyncActionsExecutor, errorReporter, timer, competitorSelectionProvider, stringMessages, showMapControls, getConfiguration().isShowViewStreamlets(), getConfiguration().isShowViewStreamletColors(), getConfiguration().isShowViewSimulation(), - selectedRaceIdentifier, raceMapResources.combinedWindPanelStyle(), /* showHeaderPanel */ true); + selectedRaceIdentifier, raceMapResources.combinedWindPanelStyle(), /* showHeaderPanel */ true) { + private static final String INDENT_SMALL_CONTROL_STYLE = "indentsmall"; + private static final String INDENT_BIG_CONTROL_STYLE = "indentbig"; + @Override + public void onResize() { + super.onResize(); + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + // Show/hide the leaderboard panels toggle button text based on the race map height + leaderboardAndMapViewer.setLeftComponentToggleButtonTextVisibilityAndDraggerPosition(raceMap.getOffsetHeight() > 400); + } + }); + } + + @Override + protected String getLeftControlsIndentStyle() { + // Calculate style name for left control indent based on race map height an leaderboard panel visibility + if (raceMap.getOffsetHeight() <= 300) { + return INDENT_BIG_CONTROL_STYLE; + } + if (leaderboardPanel.isVisible() && raceMap.getOffsetHeight() <= 500) { + return INDENT_SMALL_CONTROL_STYLE; + } + return super.getLeftControlsIndentStyle(); + } + }; CompetitorFilterPanel competitorSearchTextBox = new CompetitorFilterPanel(competitorSelectionProvider, stringMessages, raceMap, new LeaderboardFetcher() { @Override @@ -172,11 +199,14 @@ public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeLi raceMap.getLeftHeaderPanel().add(raceInformationHeader); raceMap.getRightHeaderPanel().add(regattaAndRaceTimeInformationHeader); - leaderboardPanel = createLeaderboardPanel(leaderboardName, leaderboardGroupName, competitorSearchTextBox); + // Determine if the screen is large enough to initially display the leaderboard panel on the left side of the + // map based on the initial screen width. Afterwards, the leaderboard panel visibility can be toggled as usual. + boolean isScreenLargeEnoughToInitiallyDisplayLeaderboard = Document.get().getClientWidth() >= 1024; + leaderboardPanel = createLeaderboardPanel(leaderboardName, leaderboardGroupName, competitorSearchTextBox, isScreenLargeEnoughToInitiallyDisplayLeaderboard); leaderboardPanel.setTitle(stringMessages.leaderboard()); leaderboardPanel.getElement().getStyle().setMarginLeft(6, Unit.PX); leaderboardPanel.getElement().getStyle().setMarginTop(10, Unit.PX); - createOneScreenView(leaderboardName, leaderboardGroupName, eventId, mainPanel, showMapControls, raceMap, userService); // initializes the raceMap field + createOneScreenView(leaderboardName, leaderboardGroupName, eventId, mainPanel, showMapControls, isScreenLargeEnoughToOfferChartSupport, isScreenLargeEnoughToInitiallyDisplayLeaderboard, raceMap, userService); // initializes the raceMap field leaderboardPanel.addLeaderboardUpdateListener(this); // in case the URL configuration contains the name of a competitors filter set we try to activate it // FIXME the competitorsFilterSets has now moved to CompetitorSearchTextBox (which should probably be renamed); pass on the parameters to the LeaderboardPanel and see what it does with it @@ -190,40 +220,43 @@ public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeLi } } timePanel = new RaceTimePanel(timer, timeRangeWithZoomModel, stringMessages, raceTimesInfoProvider, - raceboardViewConfiguration.isCanReplayDuringLiveRaces()); + raceboardViewConfiguration.isCanReplayDuringLiveRaces(), isScreenLargeEnoughToOfferChartSupport, selectedRaceIdentifier); timeRangeWithZoomModel.addTimeZoomChangeListener(timePanel); raceTimesInfoProvider.addRaceTimesInfoProviderListener(timePanel); - raceSelectionProvider.addRaceSelectionChangeListener(timePanel); - timePanel.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); } /** - * @param event an optional event; may be null or else can be used to show some context information in - * the {@link GlobalNavigationPanel}. + * @param event + * an optional event; may be null or else can be used to show some context information in + * the {@link GlobalNavigationPanel}. + * @param isScreenLargeEnoughToOfferChartSupport + * if the screen is large enough to display charts such as the competitor chart or the wind chart, a + * padding is provided for the RaceTimePanel that aligns its right border with that of the charts, and + * the charts are created. + * @param isScreenLargeEnoughToInitiallyDisplayLeaderboard TODO */ private void createOneScreenView(String leaderboardName, String leaderboardGroupName, UUID event, FlowPanel mainPanel, - boolean showMapControls, RaceMap raceMap, UserService userService) { + boolean showMapControls, boolean isScreenLargeEnoughToOfferChartSupport, boolean isScreenLargeEnoughToInitiallyDisplayLeaderboard, RaceMap raceMap, UserService userService) { // create the default leaderboard and select the right race raceTimesInfoProvider.addRaceTimesInfoProviderListener(raceMap); - raceMap.onRaceSelectionChange(Collections.singletonList(selectedRaceIdentifier)); List> components = new ArrayList>(); - competitorChart = new MultiCompetitorRaceChart(sailingService, asyncActionsExecutor, competitorSelectionProvider, raceSelectionProvider, - timer, timeRangeWithZoomModel, stringMessages, errorReporter, true, true, leaderboardGroupName, leaderboardName); - competitorChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); - competitorChart.getEntryWidget().setTitle(stringMessages.competitorCharts()); - competitorChart.setVisible(false); - components.add(competitorChart); - windChart = new WindChart(sailingService, raceSelectionProvider, timer, timeRangeWithZoomModel, new WindChartSettings(), - stringMessages, asyncActionsExecutor, errorReporter, /* compactChart */ true); - windChart.setVisible(false); - windChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); - windChart.getEntryWidget().setTitle(stringMessages.windChart()); - components.add(windChart); - editMarkPassingPanel = new EditMarkPassingsPanel(sailingService, selectedRaceIdentifier, - stringMessages, competitorSelectionProvider, errorReporter, timer); - editMarkPassingPanel.setLeaderboard(leaderboardPanel.getLeaderboard()); - editMarkPassingPanel.getEntryWidget().setTitle(stringMessages.editMarkPassings()); - components.add(editMarkPassingPanel); + if (isScreenLargeEnoughToOfferChartSupport) { + competitorChart = new MultiCompetitorRaceChart(sailingService, asyncActionsExecutor, competitorSelectionProvider, selectedRaceIdentifier, + timer, timeRangeWithZoomModel, stringMessages, errorReporter, true, true, leaderboardGroupName, leaderboardName); + competitorChart.getEntryWidget().setTitle(stringMessages.competitorCharts()); + competitorChart.setVisible(false); + components.add(competitorChart); + windChart = new WindChart(sailingService, selectedRaceIdentifier, timer, timeRangeWithZoomModel, new WindChartSettings(), + stringMessages, asyncActionsExecutor, errorReporter, /* compactChart */ true); + windChart.setVisible(false); + windChart.getEntryWidget().setTitle(stringMessages.windChart()); + components.add(windChart); + editMarkPassingPanel = new EditMarkPassingsPanel(sailingService, selectedRaceIdentifier, + stringMessages, competitorSelectionProvider, errorReporter, timer); + editMarkPassingPanel.setLeaderboard(leaderboardPanel.getLeaderboard()); + editMarkPassingPanel.getEntryWidget().setTitle(stringMessages.editMarkPassings()); + components.add(editMarkPassingPanel); + } boolean autoSelectMedia = getConfiguration().isAutoSelectMedia(); MediaPlayerManagerComponent mediaPlayerManagerComponent = new MediaPlayerManagerComponent( selectedRaceIdentifier, raceTimesInfoProvider, timer, mediaService, userService, stringMessages, @@ -233,13 +266,12 @@ public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeLi for (ComponentViewer componentViewer : componentViewers) { mainPanel.add(componentViewer.getViewerWidget()); } - boolean showLeaderboard = getConfiguration().isShowLeaderboard(); - if (Document.get().getClientWidth() <= 1024) { - showLeaderboard = false; - } + boolean showLeaderboard = getConfiguration().isShowLeaderboard() && isScreenLargeEnoughToInitiallyDisplayLeaderboard; setLeaderboardVisible(showLeaderboard); - setWindChartVisible(getConfiguration().isShowWindChart()); - setCompetitorChartVisible(getConfiguration().isShowCompetitorsChart()); + if (isScreenLargeEnoughToOfferChartSupport) { + setWindChartVisible(getConfiguration().isShowWindChart()); + setCompetitorChartVisible(getConfiguration().isShowCompetitorsChart()); + } // make sure to load leaderboard data for filtering to work if (!showLeaderboard) { leaderboardPanel.setVisible(true); @@ -258,13 +290,16 @@ public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeLi } } - private LeaderboardPanel createLeaderboardPanel(String leaderboardName, String leaderboardGroupName, CompetitorFilterPanel competitorSearchTextBox) { + private LeaderboardPanel createLeaderboardPanel(String leaderboardName, String leaderboardGroupName, + CompetitorFilterPanel competitorSearchTextBox, boolean isScreenLargeEnoughToInitiallyDisplayLeaderboard) { LeaderboardSettings leaderBoardSettings = LeaderboardSettingsFactory.getInstance() .createNewSettingsForPlayMode(timer.getPlayMode(), /* nameOfRaceToSort */ selectedRaceIdentifier.getRaceName(), /* nameOfRaceColumnToShow */ null, /* nameOfRaceToShow */ selectedRaceIdentifier.getRaceName(), new ExplicitRaceColumnSelectionWithPreselectedRace(selectedRaceIdentifier), /* showRegattaRank */ false, - /*showCompetitorSailIdColumn*/true, /*showCompetitorFullNameColumn*/true); + /*showCompetitorSailIdColumn*/true, + /* don't showCompetitorFullNameColumn in case screen is so small that we don't + * even display the leaderboard initially */ isScreenLargeEnoughToInitiallyDisplayLeaderboard); return new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderBoardSettings, selectedRaceIdentifier != null, selectedRaceIdentifier, competitorSelectionProvider, timer, leaderboardGroupName, leaderboardName, errorReporter, stringMessages, userAgent, /* showRaceDetails */ true, competitorSearchTextBox, @@ -334,10 +369,6 @@ public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeLi return errorReporter; } - @Override - public void onRaceSelectionChange(List selectedRaces) { - } - public RaceBoardViewConfiguration getConfiguration() { return raceboardViewConfiguration; } @@ -345,7 +376,9 @@ public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeLi @Override public void updatedLeaderboard(LeaderboardDTO leaderboard) { leaderboardAndMapViewer.setLeftComponentWidth(leaderboardPanel.getContentPanel().getOffsetWidth()); - editMarkPassingPanel.setLeaderboard(leaderboard); + if (editMarkPassingPanel != null) { + editMarkPassingPanel.setLeaderboard(leaderboard); + } } @Override @@ -360,7 +393,7 @@ public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeLi if (fleetForRaceName.equals(LeaderboardNameConstants.DEFAULT_FLEET_NAME)) { fleetForRaceName = ""; } else { - fleetForRaceName = " - "+fleetForRaceName; + fleetForRaceName = (seriesName.isEmpty() ? "" : " - ") + fleetForRaceName; } final Label raceNameLabel = new Label(stringMessages.race() + " " + raceColumn.getRaceColumnName()); raceNameLabel.setStyleName("RaceName-Label"); @@ -370,15 +403,9 @@ public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeLi raceInformationHeader.add(raceNameLabel); raceInformationHeader.add(raceAdditionalInformationLabel); final Anchor regattaNameAnchor = new Anchor(raceIdentifier.getRegattaName()); + regattaNameAnchor.setTitle(raceIdentifier.getRegattaName()); if (eventId != null) { - // we don't use the EntryPointLinkFactory here, because of lacking support for Places - String debugParam = Window.Location.getParameter("gwt.codesvr"); - String link = "/gwt/Home.html"; - if (debugParam != null && !debugParam.isEmpty()) { - link += "?gwt.codesvr=" + debugParam; - } - link += "#EventPlace:eventId="+eventId.toString(); - link += "&navigationTab=Regatta&leaderboardName=" + URLEncoder.encode(leaderboardName); + String link = EntryPointLinkFactory.createLeaderboardPlaceLink(eventId.toString(), leaderboardName); regattaNameAnchor.setHref(link); } else { String leaderboardGroupNameParam = Window.Location.getParameter("leaderboardGroupName"); @@ -394,19 +421,6 @@ public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeLi } } regattaNameAnchor.setStyleName("RegattaName-Anchor"); - - // TODO: Strange behavior... check -// Window.addResizeHandler(new ResizeHandler() { -// @Override -// public void onResize(ResizeEvent event) { -// int headerPanelWidth = raceMap.getRightHeaderPanel().getOffsetWidth() - 150; // 150px is the width of the sapLogo -// int raceNameAndFleetLabelWidth = raceInformationHeader.getOffsetWidth(); -// int regattaAnchorWidth = regattaNameAnchor.getOffsetWidth(); -// boolean overlap = raceNameAndFleetLabelWidth + regattaAnchorWidth > headerPanelWidth; -// raceInformationHeader.setVisible(!overlap); -// } -// }); - Label raceTimeLabel = computeRaceInformation(raceColumn, fleet); raceTimeLabel.setStyleName("RaceTime-Label"); regattaAndRaceTimeInformationHeader.clear(); @@ -433,5 +447,13 @@ public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeLi raceInformationLabel.setText(formatter.format(raceColumn.getStartDate(fleet))); return raceInformationLabel; } + + @Override + public void onResize() { + Widget child = getWidget(); + if (child != null && child instanceof RequiresResize) { + ((RequiresResize) child).onResize(); + } + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/ResizableFlowPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/ResizableFlowPanel.java new file mode 100644 index 00000000000..d83d0a64407 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/ResizableFlowPanel.java @@ -0,0 +1,23 @@ +package com.sap.sailing.gwt.ui.raceboard; + +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.RequiresResize; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.user.client.ui.WidgetCollection; + +/** + * Utility class extending {@link FlowPanel} and implementing {@link RequiresResize} interface. All + * {@link #getChildren() children} also implementing {@link RequiresResize} are informed by calling their + * {@link RequiresResize#onResize()} method. + */ +class ResizableFlowPanel extends FlowPanel implements RequiresResize { + @Override + public void onResize() { + WidgetCollection children = getChildren(); + for (Widget widget : children) { + if (widget instanceof RequiresResize) { + ((RequiresResize) widget).onResize(); + } + } + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java index d6ba3b34ed7..6ff63bfde4b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java @@ -1,305 +1,334 @@ -package com.sap.sailing.gwt.ui.raceboard; - -import java.util.ArrayList; -import java.util.List; - -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.user.client.ui.AbsolutePanel; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.DockLayoutPanel.Direction; -import com.google.gwt.user.client.ui.LayoutPanel; -import com.google.gwt.user.client.ui.Panel; -import com.google.gwt.user.client.ui.RequiresResize; -import com.google.gwt.user.client.ui.ScrollPanel; -import com.google.gwt.user.client.ui.Widget; -import com.google.gwt.user.client.ui.WidgetCollection; -import com.sap.sailing.domain.common.security.Roles; -import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.client.media.MediaManagementControl; -import com.sap.sailing.gwt.ui.client.media.MediaPlayerManager; -import com.sap.sailing.gwt.ui.client.media.MediaPlayerManager.PlayerChangeListener; -import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent; -import com.sap.sailing.gwt.ui.client.media.MediaSingleSelectionControl; -import com.sap.sailing.gwt.ui.client.shared.charts.EditMarkPassingsPanel; -import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; -import com.sap.sse.common.Util.Pair; -import com.sap.sse.common.settings.AbstractSettings; -import com.sap.sse.gwt.client.shared.components.Component; -import com.sap.sse.gwt.client.shared.components.ComponentViewer; -import com.sap.sse.gwt.client.shared.components.SettingsDialog; -import com.sap.sse.security.shared.DefaultRoles; -import com.sap.sse.security.ui.client.UserService; -import com.sap.sse.security.ui.client.UserStatusEventHandler; -import com.sap.sse.security.ui.shared.UserDTO; - -/** - * Component Viewer that uses a {@link TouchSplitLayoutPanel} to display its components. - * - * TODO: Refactor to make sure it is really only performing operations that are related to view. Currently it is digging - * too deep into components and setting titles or even creating video buttons. - */ -public class SideBySideComponentViewer implements ComponentViewer, UserStatusEventHandler { - - private static final int DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT = 200; - private final int MIN_LEADERBOARD_WIDTH = 432; // works well for 505 and ESS - - /** - * Absolute Panel that informs its children about a resize - */ - class ResizableAbsolutePanel extends AbsolutePanel implements RequiresResize { - public void onResize() { - WidgetCollection children = getChildren(); - for (Widget widget : children) { - if (widget instanceof RequiresResize) { - ((RequiresResize) widget).onResize(); - } - } - } - } - - private final LeaderboardPanel leftComponent; - private final Component rightComponent; - private final List> components; - private final ScrollPanel leftScrollPanel; - private final StringMessages stringMessages; - private final Button mediaSelectionButton; - private final Button mediaManagementButton; - private final EditMarkPassingsPanel markPassingsPanel; - - private LayoutPanel mainPanel; - - private TouchSplitLayoutPanel splitLayoutPanel; - private int savedSplitPosition = -1; - private boolean layoutForLeftComponentForcedOnce = false; - - public SideBySideComponentViewer(final LeaderboardPanel leftComponentP, final Component rightComponentP, - final MediaPlayerManagerComponent mediaPlayerManagerComponent, List> components, - final StringMessages stringMessages, UserService userService, EditMarkPassingsPanel markPassingsPanel) { - this.stringMessages = stringMessages; - this.leftComponent = leftComponentP; - this.rightComponent = rightComponentP; - this.components = components; - this.mediaSelectionButton = createMediaSelectionButton(mediaPlayerManagerComponent); - this.mediaManagementButton = createMediaManagementButton(mediaPlayerManagerComponent); - this.markPassingsPanel = markPassingsPanel; - - userService.addUserStatusEventHandler(this); - - mediaPlayerManagerComponent.setPlayerChangeListener(new PlayerChangeListener() { - - public void notifyStateChange(){ - String caption; - String tooltip; - switch (mediaPlayerManagerComponent.getAssignedMediaTracks().size()) { - case 0: - caption = stringMessages.mediaNoVideosCaption(); - tooltip = caption; - mediaSelectionButton.setVisible(false); - break; - case 1: - mediaSelectionButton.setVisible(true); - if(mediaPlayerManagerComponent.isPlaying()){ - caption = stringMessages.mediaHideVideoCaption(); - tooltip = stringMessages.mediaHideVideoTooltip(); - } - else{ - caption = stringMessages.mediaShowVideoCaption(); - tooltip = stringMessages.mediaShowVideoTooltip(mediaPlayerManagerComponent.getAssignedMediaTracks().iterator().next().title); - } - break; - default: - mediaSelectionButton.setVisible(true); - caption = stringMessages.mediaSelectVideoCaption(mediaPlayerManagerComponent.getAssignedMediaTracks().size()); - tooltip = stringMessages.mediaSelectVideoTooltip(); - break; - } - if (mediaPlayerManagerComponent.isPlaying()) { - mediaSelectionButton.addStyleDependentName("mediaplaying"); - } else { - mediaSelectionButton.removeStyleDependentName("mediaplaying"); - } - mediaSelectionButton.setText(caption); - mediaSelectionButton.setTitle(tooltip); - mediaManagementButton.setVisible(mediaPlayerManagerComponent.allowsEditing()); - } - - }); - this.leftScrollPanel = new ScrollPanel(); - this.leftScrollPanel.add(leftComponentP.getEntryWidget()); - this.leftScrollPanel.setTitle(leftComponentP.getEntryWidget().getTitle()); - this.mainPanel = new LayoutPanel(); - this.mainPanel.setSize("100%", "100%"); - this.mainPanel.getElement().getStyle().setMarginTop(-12, Unit.PX); - this.mainPanel.setStyleName("SideBySideComponentViewer-MainPanel"); - this.splitLayoutPanel = new TouchSplitLayoutPanel(/* horizontal splitter width */3, /* vertical splitter height */ - 25); - this.mainPanel.add(splitLayoutPanel); - - // initialize components - they need to be added before other widgets to get the right width - initializeComponents(); - - // initialize the leaderboard component - savedSplitPosition = MIN_LEADERBOARD_WIDTH; - splitLayoutPanel.insert(leftScrollPanel, leftComponent, Direction.WEST, savedSplitPosition); - - // create a panel that will contain the horizontal toggle buttons - ResizableAbsolutePanel panelForMapAndHorizontalToggleButtons = new ResizableAbsolutePanel(); - panelForMapAndHorizontalToggleButtons.add(rightComponent.getEntryWidget()); - splitLayoutPanel.insert(panelForMapAndHorizontalToggleButtons, rightComponent, Direction.CENTER, 0); - - // add additional toggle buttons panel that currently only contains the video button - List> additionalVerticalButtons = new ArrayList>(); - additionalVerticalButtons.add(new Pair(mediaSelectionButton, - mediaPlayerManagerComponent.getDependentCssClassName())); - if (/* TODO check for correct role; was: user != null */ true) { - additionalVerticalButtons.add(new Pair(mediaManagementButton, - "managemedia")); - } - onUserStatusChange(userService.getCurrentUser()); - - // ensure that toggle buttons are positioned right - splitLayoutPanel.lastComponentHasBeenAdded(this, panelForMapAndHorizontalToggleButtons, - additionalVerticalButtons); - } - - /** - * Create the video control button that shows or hides the video popup - * - * @param userAgent - */ - private Button createMediaSelectionButton(final MediaPlayerManager mediaPlayerManager) { - final Button result = new Button(); - final MediaSingleSelectionControl multiSelectionControl = new MediaSingleSelectionControl(mediaPlayerManager, - result, stringMessages); - result.addClickHandler(new ClickHandler() { - - @Override - public void onClick(ClickEvent event) { - if (mediaPlayerManager.getAssignedMediaTracks().size() == 1) { - if (mediaPlayerManager.isPlaying()) { - mediaPlayerManager.stopAll(); - } else { - mediaPlayerManager.playDefault(); - - } - } else { - multiSelectionControl.show(); - } - } - }); - - // hide button initially as we defer showing the button to the asynchroneous - // task that gets launched by the media service to get video tracks - result.setVisible(false); - return result; - } - - /** - * Create the video control button that shows or hides the video popup - * - * @param userAgent - */ - private Button createMediaManagementButton(final MediaPlayerManager mediaPlayerManager) { - final Button result = new Button(stringMessages.mediaManageMediaCaption()); - result.setTitle(stringMessages.mediaManageMediaTooltip()); - // onClick - final MediaManagementControl multiSelectionControl = new MediaManagementControl(mediaPlayerManager, - result, stringMessages); - result.addClickHandler(new ClickHandler() { - - @Override - public void onClick(ClickEvent event) { - multiSelectionControl.show(); - } - }); - - // hide button initially as we defer showing the button to the asynchroneous - // task that gets launched by the media service to get video tracks - result.setVisible(false); - return result; - } - - private void initializeComponents() { - for (final Component component : components) { - splitLayoutPanel.insert(component.getEntryWidget(), component, Direction.SOUTH, 200); - } - } - - public void showSettingsDialog(Component component) { - if (component.hasSettings()) { - new SettingsDialog(component, stringMessages).show(); - } - } - - /** - * Called whenever the layout of {@link TouchSplitLayoutPanel} or other components change. Controls the visibility - * based on the {@link Component}s visibility. Each {@link Component} is in charge to not display any data or update - * itself when it is not visible. - */ - public void forceLayout() { - if (!leftComponent.isVisible() && rightComponent.isVisible()) { - // the leaderboard is not visible, but the map is - if (isWidgetInSplitPanel(leftScrollPanel)) { - if (leftScrollPanel.getOffsetWidth() > 0) { - savedSplitPosition = leftScrollPanel.getOffsetWidth(); - } - splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */true, - savedSplitPosition); - } - } else if (leftComponent.isVisible() && rightComponent.isVisible()) { - // the leaderboard and the map are visible - splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */false, savedSplitPosition); - } else if (!leftComponent.isVisible() && !rightComponent.isVisible()) { - } - - for (Component component : components) { - final boolean isComponentVisible = component.isVisible(); - splitLayoutPanel.setWidgetVisibility(component.getEntryWidget(), component, !isComponentVisible, - DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT); - } - splitLayoutPanel.forceLayout(); - } - - private boolean isWidgetInSplitPanel(Widget widget) { - int widgetIndex = splitLayoutPanel.getWidgetIndex(widget); - return widgetIndex >= 0; - } - - public Panel getViewerWidget() { - return mainPanel; - } - - public Component getRootComponent() { - return null; - } - - public String getViewerName() { - return ""; - } - - public void setLeftComponentWidth(int width) { - // TODO: The information provided by width is wrong - // need to find a way to get the correct information - if (!layoutForLeftComponentForcedOnce) { - savedSplitPosition = MIN_LEADERBOARD_WIDTH; - forceLayout(); - } - layoutForLeftComponentForcedOnce = true; - } - - @Override - public void onUserStatusChange(UserDTO user) { - Button toggleButton = splitLayoutPanel.getAssociatedSplitter(markPassingsPanel).getToggleButton(); - if (user != null - && (user.hasRole(DefaultRoles.ADMIN.getRolename()) || user.hasRole(Roles.eventmanager.getRolename()))) { - toggleButton.setVisible(true); - forceLayout(); - } else { - markPassingsPanel.setVisible(false); - toggleButton.setVisible(false); - forceLayout(); - } - } -} +package com.sap.sailing.gwt.ui.raceboard; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gwt.dom.client.Style; +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.user.client.Window; +import com.google.gwt.user.client.ui.AbsolutePanel; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.DockLayoutPanel.Direction; +import com.google.gwt.user.client.ui.LayoutPanel; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.RequiresResize; +import com.google.gwt.user.client.ui.ScrollPanel; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.user.client.ui.WidgetCollection; +import com.sap.sailing.domain.common.security.Roles; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.media.MediaManagementControl; +import com.sap.sailing.gwt.ui.client.media.MediaPlayerManager; +import com.sap.sailing.gwt.ui.client.media.MediaPlayerManager.PlayerChangeListener; +import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent; +import com.sap.sailing.gwt.ui.client.media.MediaSingleSelectionControl; +import com.sap.sailing.gwt.ui.client.shared.charts.EditMarkPassingsPanel; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; +import com.sap.sailing.gwt.ui.raceboard.TouchSplitLayoutPanel.Splitter; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.settings.AbstractSettings; +import com.sap.sse.gwt.client.shared.components.Component; +import com.sap.sse.gwt.client.shared.components.ComponentViewer; +import com.sap.sse.gwt.client.shared.components.SettingsDialog; +import com.sap.sse.security.shared.DefaultRoles; +import com.sap.sse.security.ui.client.UserService; +import com.sap.sse.security.ui.client.UserStatusEventHandler; +import com.sap.sse.security.ui.shared.UserDTO; + +/** + * Component Viewer that uses a {@link TouchSplitLayoutPanel} to display its components. + * + * TODO: Refactor to make sure it is really only performing operations that are related to view. Currently it is digging + * too deep into components and setting titles or even creating video buttons. + */ +public class SideBySideComponentViewer implements ComponentViewer, UserStatusEventHandler { + + private static final int DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT = 200; + private final int MIN_LEADERBOARD_WIDTH = Math.min(432, Window.getClientWidth() - 40); // fallback value "432" works well for 505 and ESS + + /** + * Absolute Panel that informs its children about a resize + */ + class ResizableAbsolutePanel extends AbsolutePanel implements RequiresResize { + public void onResize() { + WidgetCollection children = getChildren(); + for (Widget widget : children) { + if (widget instanceof RequiresResize) { + ((RequiresResize) widget).onResize(); + } + } + } + } + + private final LeaderboardPanel leftComponent; + private final Component rightComponent; + private final List> components; + private final ScrollPanel leftScrollPanel; + private final StringMessages stringMessages; + private final Button mediaSelectionButton; + private final Button mediaManagementButton; + private final EditMarkPassingsPanel markPassingsPanel; + + private LayoutPanel mainPanel; + + private TouchSplitLayoutPanel splitLayoutPanel; + private int savedSplitPosition = -1; + private boolean layoutForLeftComponentForcedOnce = false; + + public SideBySideComponentViewer(final LeaderboardPanel leftComponentP, final Component rightComponentP, + final MediaPlayerManagerComponent mediaPlayerManagerComponent, List> components, + final StringMessages stringMessages, UserService userService, EditMarkPassingsPanel markPassingsPanel) { + this.stringMessages = stringMessages; + this.leftComponent = leftComponentP; + this.rightComponent = rightComponentP; + this.components = components; + this.mediaSelectionButton = createMediaSelectionButton(mediaPlayerManagerComponent); + this.mediaManagementButton = createMediaManagementButton(mediaPlayerManagerComponent); + this.markPassingsPanel = markPassingsPanel; + userService.addUserStatusEventHandler(this); + mediaPlayerManagerComponent.setPlayerChangeListener(new PlayerChangeListener() { + public void notifyStateChange() { + String caption; + String tooltip; + switch (mediaPlayerManagerComponent.getAssignedMediaTracks().size()) { + case 0: + caption = stringMessages.mediaNoVideosCaption(); + tooltip = caption; + mediaSelectionButton.setVisible(false); + break; + case 1: + mediaSelectionButton.setVisible(true); + if(mediaPlayerManagerComponent.isPlaying()){ + caption = stringMessages.mediaHideVideoCaption(); + tooltip = stringMessages.mediaHideVideoTooltip(); + } + else{ + caption = stringMessages.mediaShowVideoCaption(); + tooltip = stringMessages.mediaShowVideoTooltip(mediaPlayerManagerComponent.getAssignedMediaTracks().iterator().next().title); + } + break; + default: + mediaSelectionButton.setVisible(true); + caption = stringMessages.mediaSelectVideoCaption(mediaPlayerManagerComponent.getAssignedMediaTracks().size()); + tooltip = stringMessages.mediaSelectVideoTooltip(); + break; + } + if (mediaPlayerManagerComponent.isPlaying()) { + mediaSelectionButton.addStyleDependentName("mediaplaying"); + } else { + mediaSelectionButton.removeStyleDependentName("mediaplaying"); + } + mediaSelectionButton.setText(caption); + mediaSelectionButton.setTitle(tooltip); + mediaManagementButton.setVisible(mediaPlayerManagerComponent.allowsEditing()); + } + }); + this.leftScrollPanel = new ScrollPanel(); + this.leftScrollPanel.add(leftComponentP.getEntryWidget()); + this.leftScrollPanel.setTitle(leftComponentP.getEntryWidget().getTitle()); + this.mainPanel = new LayoutPanel() { + @Override + public void onResize() { + int leftWidth = leftScrollPanel.getOffsetWidth(); + // The left scroll panel is potentially resized to ensure it is not too wide when the screen gets narrower, + // e.g. when resizing browser window or changing mobile device orientation. An offset of 40px is used, so + // the panels size slider and its toggle button is always accessable if it is open. + savedSplitPosition = Math.min(leftWidth > 0 ? leftWidth : savedSplitPosition, Window.getClientWidth() - 40); + splitLayoutPanel.setWidgetSize(leftScrollPanel, savedSplitPosition); + super.onResize(); + } + }; + this.mainPanel.setSize("100%", "100%"); + this.mainPanel.getElement().getStyle().setMarginTop(-12, Unit.PX); + this.mainPanel.setStyleName("SideBySideComponentViewer-MainPanel"); + this.splitLayoutPanel = new TouchSplitLayoutPanel(/* horizontal splitter width */3, /* vertical splitter height */ 25); + this.mainPanel.add(splitLayoutPanel); + + // initialize components - they need to be added before other widgets to get the right width + initializeComponents(); + + // initialize the leaderboard component + savedSplitPosition = MIN_LEADERBOARD_WIDTH; + splitLayoutPanel.insert(leftScrollPanel, leftComponent, Direction.WEST, savedSplitPosition); + + // create a panel that will contain the horizontal toggle buttons + ResizableAbsolutePanel panelForMapAndHorizontalToggleButtons = new ResizableAbsolutePanel(); + panelForMapAndHorizontalToggleButtons.add(rightComponent.getEntryWidget()); + splitLayoutPanel.insert(panelForMapAndHorizontalToggleButtons, rightComponent, Direction.CENTER, 0); + + // add additional toggle buttons panel that currently only contains the video button + List> additionalVerticalButtons = new ArrayList>(); + additionalVerticalButtons.add(new Pair(mediaSelectionButton, + mediaPlayerManagerComponent.getDependentCssClassName())); + if (/* TODO check for correct role; was: user != null */ true) { + additionalVerticalButtons.add(new Pair(mediaManagementButton, + "managemedia")); + } + onUserStatusChange(userService.getCurrentUser()); + // ensure that toggle buttons are positioned right + splitLayoutPanel.lastComponentHasBeenAdded(this, panelForMapAndHorizontalToggleButtons, + additionalVerticalButtons); + } + + /** + * Create the video control button that shows or hides the video popup + * + * @param userAgent + */ + private Button createMediaSelectionButton(final MediaPlayerManager mediaPlayerManager) { + final Button result = new Button(); + final MediaSingleSelectionControl multiSelectionControl = new MediaSingleSelectionControl(mediaPlayerManager, + result, stringMessages); + result.addClickHandler(new ClickHandler() { + + @Override + public void onClick(ClickEvent event) { + if (mediaPlayerManager.getAssignedMediaTracks().size() == 1) { + if (mediaPlayerManager.isPlaying()) { + mediaPlayerManager.stopAll(); + } else { + mediaPlayerManager.playDefault(); + + } + } else { + multiSelectionControl.show(); + } + } + }); + + // hide button initially as we defer showing the button to the asynchroneous + // task that gets launched by the media service to get video tracks + result.setVisible(false); + return result; + } + + /** + * Create the video control button that shows or hides the video popup + * + * @param userAgent + */ + private Button createMediaManagementButton(final MediaPlayerManager mediaPlayerManager) { + final Button result = new Button(stringMessages.mediaManageMediaCaption()); + result.setTitle(stringMessages.mediaManageMediaTooltip()); + // onClick + final MediaManagementControl multiSelectionControl = new MediaManagementControl(mediaPlayerManager, + result, stringMessages); + result.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + multiSelectionControl.show(); + } + }); + // hide button initially as we defer showing the button to the asynchronous + // task that gets launched by the media service to get video tracks + result.setVisible(false); + return result; + } + + private void initializeComponents() { + for (final Component component : components) { + splitLayoutPanel.insert(component.getEntryWidget(), component, Direction.SOUTH, 200); + } + } + + public void showSettingsDialog(Component component) { + if (component.hasSettings()) { + new SettingsDialog(component, stringMessages).show(); + } + } + + /** + * Called whenever the layout of {@link TouchSplitLayoutPanel} or other components change. Controls the visibility + * based on the {@link Component}s visibility. Each {@link Component} is in charge to not display any data or update + * itself when it is not visible. + */ + public void forceLayout() { + if (!leftComponent.isVisible() && rightComponent.isVisible()) { + // the leaderboard is not visible, but the map is + if (isWidgetInSplitPanel(leftScrollPanel)) { + if (leftScrollPanel.getOffsetWidth() > 0) { + savedSplitPosition = Math.min(savedSplitPosition, leftScrollPanel.getOffsetWidth()); + } + splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */true, + savedSplitPosition); + } + } else if (leftComponent.isVisible() && rightComponent.isVisible()) { + // the leaderboard and the map are visible + splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */false, savedSplitPosition); + } else if (!leftComponent.isVisible() && !rightComponent.isVisible()) { + } + + for (Component component : components) { + final boolean isComponentVisible = component.isVisible(); + splitLayoutPanel.setWidgetVisibility(component.getEntryWidget(), component, !isComponentVisible, + DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT); + } + splitLayoutPanel.forceLayout(); + } + + private boolean isWidgetInSplitPanel(Widget widget) { + int widgetIndex = splitLayoutPanel.getWidgetIndex(widget); + return widgetIndex >= 0; + } + + public Panel getViewerWidget() { + return mainPanel; + } + + public Component getRootComponent() { + return null; + } + + public String getViewerName() { + return ""; + } + + public void setLeftComponentWidth(int width) { + // TODO: The information provided by width is wrong + // need to find a way to get the correct information + if (!layoutForLeftComponentForcedOnce) { + savedSplitPosition = MIN_LEADERBOARD_WIDTH; + forceLayout(); + } + layoutForLeftComponentForcedOnce = true; + } + + @Override + public void onUserStatusChange(UserDTO user) { + final Splitter associatedSplitter = splitLayoutPanel.getAssociatedSplitter(markPassingsPanel); + if (associatedSplitter != null) { // if the panel is not present, the splitter may not be found + final Button toggleButton = associatedSplitter.getToggleButton(); + if (user != null + && (user.hasRole(DefaultRoles.ADMIN.getRolename()) || + user.hasRole(Roles.eventmanager.getRolename()))) { + toggleButton.setVisible(true); + forceLayout(); + } else { + markPassingsPanel.setVisible(false); + toggleButton.setVisible(false); + forceLayout(); + } + } + } + + /** + * Shows/hides the text on left components toggle button by modifying CSS font-size property and adjust + * the dragger position by modifying CSS margin-top property. + * + * @param visible + * true to show the button text, false to hide it + */ + void setLeftComponentToggleButtonTextVisibilityAndDraggerPosition(final boolean visible) { + Splitter leftScrollPanelSplitter = splitLayoutPanel.getAssociatedSplitter(leftScrollPanel); + if (leftScrollPanelSplitter != null) { + Style toggleButtonStyle = leftScrollPanelSplitter.getToggleButton().getElement().getStyle(); + if (visible) toggleButtonStyle.clearFontSize(); + else toggleButtonStyle.setFontSize(0, Unit.PX); + Style drapperStyle = leftScrollPanelSplitter.getDragger().getElement().getStyle(); + if (visible) drapperStyle.clearMarginTop(); + else drapperStyle.setMarginTop(-25, Unit.PX); + } + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/TouchSplitLayoutPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/TouchSplitLayoutPanel.java index 3f6b991c931..fed18802b88 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/TouchSplitLayoutPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/TouchSplitLayoutPanel.java @@ -613,7 +613,7 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel { if (lastVerticalSplitter != null) { Panel panel = createToggleButtonPanel(allVerticalSplitters, "gwt-SplitLayoutPanel-NorthSouthToggleButton-Panel", "gwt-SplitLayoutPanel-NorthSouthToggleButton", - cm, additionalVerticalButtonsAndStyles); + cm, additionalVerticalButtonsAndStyles); // TODO additional buttons will only be displayed if at least one vertical splitter is... on purpose? lastVerticalSplitter.addToogleButtons(panel); lastVerticalSplitter.setVisible(true); lastVerticalSplitter.setDraggerVisible(false); @@ -621,7 +621,7 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel { ensureVerticalToggleButtonPosition(); if (lastHorizontalSplitter != null) { Panel horizontalButtonsPanel = createToggleButtonPanel(allHorizontalSplitters, - "gwt-SplitLayoutPanel-EastToggleButton-Panel", "gwt-SplitLayoutPanel-EastToggleButton", cm, null); + "gwt-SplitLayoutPanel-EastToggleButton-Panel", "gwt-SplitLayoutPanel-EastToggleButton", cm, /* additional buttons and styles */ null); panelForHorizontalButtons.add(horizontalButtonsPanel); lastHorizontalSplitter.setVisible(false); lastHorizontalSplitter.setDraggerVisible(false); @@ -842,6 +842,24 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel { } } } + + /** + * Sets the size of the given {@link Widget} if it is {@link Widget#isVisible() visible}. Otherwise, the given size + * will be persisted as the widgets {@link LayoutData#oldSize old size}. + * + * @param widget the {@link Widget} to resize + * @param size the new size + */ + public void setWidgetSize(Widget widget, int size) { + final Splitter splitter = getAssociatedSplitter(widget); + if (splitter != null) { + if (widget.isVisible()) { + splitter.setAssociatedWidgetSize(size, /* defer */false); + } else { + ((LayoutData) widget.getLayoutData()).oldSize = size; + } + } + } private void assertIsChild2(Widget widget) { assert (widget == null) || (widget.getParent() == this) : "The specified widget is not a child of this panel"; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 03cda85673b..2809b240812 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -17,6 +17,7 @@ import java.net.URLEncoder; import java.net.UnknownHostException; import java.text.ParseException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; @@ -229,6 +230,7 @@ import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache; import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater; +import com.sap.sailing.domain.leaderboard.meta.MetaLeaderboardColumn; import com.sap.sailing.domain.persistence.DomainObjectFactory; import com.sap.sailing.domain.persistence.MongoObjectFactory; import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory; @@ -285,6 +287,7 @@ import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants; import com.sap.sailing.gwt.ui.adminconsole.RaceLogSetTrackingTimesDTO; import com.sap.sailing.gwt.ui.client.SailingService; import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO; +import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO; import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO; import com.sap.sailing.gwt.ui.shared.CompetitorRaceDataDTO; import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO; @@ -848,7 +851,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S final RaceColumnDTO raceColumnDTO = columnFactory.createRaceColumnDTO(raceColumn.getName(), raceColumn.isMedalRace(), raceColumn.getExplicitFactor(), raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, - raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null); + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, + raceColumn instanceof MetaLeaderboardColumn); raceColumnDTOs.add(raceColumnDTO); } return raceColumnDTOs; @@ -857,8 +861,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S private RaceInfoDTO createRaceInfoDTO(String seriesName, RaceColumn raceColumn, Fleet fleet) { RaceInfoDTO raceInfoDTO = new RaceInfoDTO(); RaceLog raceLog = raceColumn.getRaceLog(fleet); + final TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); + raceInfoDTO.isTracked = trackedRace != null ? true : false; if (raceLog != null) { - raceInfoDTO.isTracked = raceColumn.getTrackedRace(fleet) != null ? true : false; ReadonlyRaceState state = ReadonlyRaceStateImpl.create(getService(), raceLog); TimePoint startTime = state.getStartTime(); if (startTime != null) { @@ -874,13 +879,14 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } else { raceInfoDTO.finishedTime = null; if (raceInfoDTO.isTracked) { - TimePoint endOfRace = raceColumn.getTrackedRace(fleet).getEndOfRace(); + TimePoint endOfRace = trackedRace.getEndOfRace(); raceInfoDTO.finishedTime = endOfRace != null ? endOfRace.asDate() : null; } } + final TimePoint now = MillisecondsTimePoint.now(); if (startTime != null) { - FlagPoleState activeFlagState = state.getRacingProcedure().getActiveFlags(startTime, MillisecondsTimePoint.now()); + FlagPoleState activeFlagState = state.getRacingProcedure().getActiveFlags(startTime, now); List activeFlags = activeFlagState.getCurrentState(); FlagPoleState previousFlagState = activeFlagState.getPreviousState(state.getRacingProcedure(), startTime); List previousFlags = previousFlagState.getCurrentState(); @@ -912,7 +918,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S CourseBase lastCourse = state.getCourseDesign(); if (lastCourse != null) { - raceInfoDTO.lastCourseDesign = convertToRaceCourseDTO(lastCourse, /* trackedRace */ null, /* timePoint */ null); + raceInfoDTO.lastCourseDesign = convertToRaceCourseDTO(lastCourse, new TrackedRaceMarkPositionFinder(trackedRace), now); raceInfoDTO.lastCourseName = lastCourse.getName(); } @@ -930,7 +936,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S Wind wind = state.getWindFix(); if (wind != null) { - raceInfoDTO.lastWind = createWindDTOFromAlreadyAveraged(wind, MillisecondsTimePoint.now()); + raceInfoDTO.lastWind = createWindDTOFromAlreadyAveraged(wind, now); } fillStartProcedureSpecifics(raceInfoDTO, state); @@ -940,7 +946,6 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S raceInfoDTO.fleetName = fleet.getName(); raceInfoDTO.fleetOrdering = fleet.getOrdering(); raceInfoDTO.raceIdentifier = raceColumn.getRaceIdentifier(fleet); - raceInfoDTO.isTracked = raceColumn.getTrackedRace(fleet) != null ? true : false; return raceInfoDTO; } @@ -1631,8 +1636,21 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public CompactRaceMapDataDTO getRaceMapData(RegattaAndRaceIdentifier raceIdentifier, Date date, Map fromPerCompetitorIdAsString, Map toPerCompetitorIdAsString, - boolean extrapolate, LegIdentifier simulationLegIdentifier) throws NoWindException { - final Map> boatPositions = getBoatPositions(raceIdentifier, + boolean extrapolate, LegIdentifier simulationLegIdentifier, + byte[] md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID) throws NoWindException { + final HashSet raceCompetitorIdsAsStrings; + final TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); + // if md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID is null, Arrays.equals will return false, and the + // competitor set will be calculated and returned to the client + if (trackedRace == null || Arrays.equals(md5OfIdsAsStringOfCompetitorParticipatingInRaceInAlphanumericOrderOfTheirID, trackedRace.getRace().getCompetitorMD5())) { + raceCompetitorIdsAsStrings = null; // tracked race not found or still same MD5 hash, suggesting unchanged competitor set + } else { + raceCompetitorIdsAsStrings = new HashSet<>(); + for (final Competitor c : trackedRace.getRace().getCompetitors()) { + raceCompetitorIdsAsStrings.add(c.getId().toString()); + } + } + final Map> boatPositions = getBoatPositionsInternal(raceIdentifier, fromPerCompetitorIdAsString, toPerCompetitorIdAsString, extrapolate); final CoursePositionsDTO coursePositions = getCoursePositions(raceIdentifier, date); final List courseSidelines = getCourseSidelines(raceIdentifier, date); @@ -1642,7 +1660,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S SimulationService simulationService = getService().getSimulationService(); simulationResultVersion = simulationService.getSimulationResultsVersion(simulationLegIdentifier); } - return new CompactRaceMapDataDTO(boatPositions, coursePositions, courseSidelines, quickRanks, simulationResultVersion); + return new CompactRaceMapDataDTO(boatPositions, coursePositions, courseSidelines, quickRanks, simulationResultVersion, raceCompetitorIdsAsStrings); } private Map getCompetitorBoatsForRace(RaceDefinition race, List competitorDTOs) { @@ -1662,6 +1680,14 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return competitorBoats; } + + @Override + public CompactBoatPositionsDTO getBoatPositions(RegattaAndRaceIdentifier raceIdentifier, + Map fromPerCompetitorIdAsString, Map toPerCompetitorIdAsString, + boolean extrapolate) throws NoWindException { + return new CompactBoatPositionsDTO(getBoatPositionsInternal(raceIdentifier, fromPerCompetitorIdAsString, toPerCompetitorIdAsString, extrapolate)); + } + /** * {@link LegType}s are cached within the method with a resolution of one minute. The cache key is a pair of * {@link TrackedLegOfCompetitor} and {@link TimePoint}. @@ -1680,7 +1706,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S * @return a map where for each competitor participating in the race the list of GPS fixes in increasing * chronological order is provided. The last one is the last position at or before date. */ - private Map> getBoatPositions(RegattaAndRaceIdentifier raceIdentifier, + private Map> getBoatPositionsInternal(RegattaAndRaceIdentifier raceIdentifier, Map fromPerCompetitorIdAsString, Map toPerCompetitorIdAsString, boolean extrapolate) throws NoWindException { @@ -1716,6 +1742,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } finally { track.unlockAfterRead(); } + final Set extrapolatedFixes; if (fixes.isEmpty()) { // then there was no (smoothened) fix between fromTimePoint and toTimePointExcluding; estimate... TimePoint middle = new MillisecondsTimePoint((toTimePointExcluding.asMillis()+fromTimePoint.asMillis())/2); @@ -1727,7 +1754,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S logger.finest(""+competitor.getName()+": " + estimatedFix+" (estimated)"); } fixes.add(estimatedFix); + extrapolatedFixes = Collections.singleton(estimatedFix); + } else { + extrapolatedFixes = Collections.emptySet(); } + } else { + extrapolatedFixes = Collections.emptySet(); } Iterator fixIter = fixes.iterator(); if (fixIter.hasNext()) { @@ -1757,8 +1789,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S legType = null; } WindDTO windDTO = wind == null ? null : createWindDTOFromAlreadyAveraged(wind, toTimePointExcluding); - GPSFixDTO fixDTO = createGPSFixDTO(fix, estimatedSpeed, windDTO, tack, legType, /* extrapolate */ - false); + GPSFixDTO fixDTO = createGPSFixDTO(fix, estimatedSpeed, windDTO, tack, legType, /* extrapolate */ extrapolatedFixes.contains(fix)); fixesForCompetitor.add(fixDTO); if (fixIter.hasNext()) { fix = fixIter.next(); @@ -2403,7 +2434,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S RaceColumnDTO raceColumnDTO = leaderboardDTO.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(), raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, - fleetDTO, raceColumn.isMedalRace(), raceIdentifier, raceDTO); + fleetDTO, raceColumn.isMedalRace(), raceIdentifier, raceDTO, raceColumn instanceof MetaLeaderboardColumn); RaceLog raceLog = raceColumn.getRaceLog(fleet); RaceLogTrackingState raceLogTrackingState = raceLog == null ? RaceLogTrackingState.NOT_A_RACELOG_TRACKED_RACE : new RaceLogTrackingStateAnalyzer(raceLog).analyze(); @@ -3302,8 +3333,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S if (replicatingFromMaster == null) { master = null; } else { - master = new ReplicationMasterDTO(replicatingFromMaster.getHostname(), replicatingFromMaster.getMessagingPort(), - replicatingFromMaster.getServletPort()); + master = new ReplicationMasterDTO(replicatingFromMaster.getHostname(), replicatingFromMaster.getServletPort(), + replicatingFromMaster.getMessagingHostname(), replicatingFromMaster.getMessagingPort(), replicatingFromMaster.getExchangeName()); } return new ReplicationStateDTO(master, replicaDTOs, service.getServerIdentifier().toString()); } @@ -3437,8 +3468,13 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public Pair resolveImageDimensions(String imageUrlAsString) throws Exception { - URL imageURL = new URL(imageUrlAsString); - Pair imageDimensions = MediaUtils.getImageDimensions(imageURL); + final Pair imageDimensions; + if (imageUrlAsString != null && !imageUrlAsString.isEmpty()) { + URL imageURL = new URL(imageUrlAsString); + imageDimensions = MediaUtils.getImageDimensions(imageURL); + } else { + imageDimensions = null; + } return imageDimensions; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactBoatPositionsDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactBoatPositionsDTO.java new file mode 100644 index 00000000000..6aaab8b0af5 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactBoatPositionsDTO.java @@ -0,0 +1,31 @@ +package com.sap.sailing.gwt.ui.shared; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.google.gwt.user.client.rpc.IsSerializable; +import com.sap.sailing.domain.common.dto.CompetitorDTO; + +public class CompactBoatPositionsDTO implements IsSerializable { + private Map> boatPositions; + + CompactBoatPositionsDTO() {} // for GWT serialization only + + public CompactBoatPositionsDTO(Map> boatPositions) { + super(); + this.boatPositions = new HashMap<>(); + for (final Entry> e : boatPositions.entrySet()) { + this.boatPositions.put(e.getKey().getIdAsString(), e.getValue()); + } + } + + public Map> getBoatPositionsForCompetitors(Map competitorsByIdsAsStrings) { + final Map> result = new HashMap<>(); + for (final Entry> e : boatPositions.entrySet()) { + result.put(competitorsByIdsAsStrings.get(e.getKey()), e.getValue()); + } + return result; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactRaceMapDataDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactRaceMapDataDTO.java index d62034481db..bcde6f2e11f 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactRaceMapDataDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/CompactRaceMapDataDTO.java @@ -2,12 +2,14 @@ package com.sap.sailing.gwt.ui.shared; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import com.google.gwt.user.client.rpc.IsSerializable; +import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sse.common.Util; @@ -23,7 +25,7 @@ import com.sap.sse.common.Util; * */ public class CompactRaceMapDataDTO implements IsSerializable { - private Map> boatPositionsByCompetitorIdAsString; + private CompactBoatPositionsDTO boatPositionsByCompetitorIdAsString; private CoursePositionsDTO coursePositions; private List courseSidelines; @@ -31,14 +33,18 @@ public class CompactRaceMapDataDTO implements IsSerializable { private LinkedHashMap competitorsInOrderOfWindwardDistanceTraveledFarthestFirstIdAsStringAndOneBasedLegNumber; private long simulationResultVersion; + /** + * null if the client's request contained an equal MD5 as the race's competitors {@link RaceDefinition#getCompetitorMD5() produce}; + * otherwise, the set of competitor IDs, {@link Object#toString() converted to strings}, in no particular order. + */ + private HashSet raceCompetitorIdsAsStrings; + CompactRaceMapDataDTO() {} public CompactRaceMapDataDTO(Map> boatPositions, CoursePositionsDTO coursePositions, - List courseSidelines, QuickRanksDTO quickRanks, long simulationResultVersion) { - this.boatPositionsByCompetitorIdAsString = new HashMap>(); - for (Map.Entry> e : boatPositions.entrySet()) { - this.boatPositionsByCompetitorIdAsString.put(e.getKey().getIdAsString(), e.getValue()); - } + List courseSidelines, QuickRanksDTO quickRanks, long simulationResultVersion, HashSet raceCompetitorIdsAsStrings) { + this.boatPositionsByCompetitorIdAsString = new CompactBoatPositionsDTO(boatPositions); + this.raceCompetitorIdsAsStrings = raceCompetitorIdsAsStrings; this.quickRanks = new ArrayList(quickRanks == null ? 0 : Util.size(quickRanks.getQuickRanks())); this.competitorsInOrderOfWindwardDistanceTraveledFarthestFirstIdAsStringAndOneBasedLegNumber = new LinkedHashMap<>(); Map competitorIdAsStringToOneBasedLegNumber = new HashMap<>(); @@ -59,11 +65,7 @@ public class CompactRaceMapDataDTO implements IsSerializable { this.simulationResultVersion = simulationResultVersion; } - public RaceMapDataDTO getRaceMapDataDTO(Iterable competitors) { - Map competitorsByIdAsString = new HashMap(); - for (CompetitorDTO competitor : competitors) { - competitorsByIdAsString.put(competitor.getIdAsString(), competitor); - } + public RaceMapDataDTO getRaceMapDataDTO(Map competitorsByIdAsString) { RaceMapDataDTO result = new RaceMapDataDTO(); result.quickRanks = new LinkedHashMap(this.quickRanks.size()); for (CompactQuickRankDTO compactQuickRank : this.quickRanks) { @@ -74,14 +76,7 @@ public class CompactRaceMapDataDTO implements IsSerializable { } result.courseSidelines = courseSidelines; result.coursePositions = coursePositions; - result.boatPositions = new HashMap>(); - for (Map.Entry> e : boatPositionsByCompetitorIdAsString.entrySet()) { - final CompetitorDTO competitor = competitorsByIdAsString.get(e.getKey()); - if (competitor != null) { - // maybe null in case the competitor was added, e.g., by unsuppressing, while this call was underway - result.boatPositions.put(competitor, e.getValue()); - } - } + result.boatPositions = boatPositionsByCompetitorIdAsString.getBoatPositionsForCompetitors(competitorsByIdAsString); result.competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber = new LinkedHashMap<>(); for (Entry i : competitorsInOrderOfWindwardDistanceTraveledFarthestFirstIdAsStringAndOneBasedLegNumber.entrySet()) { CompetitorDTO c = competitorsByIdAsString.get(i.getKey()); @@ -90,6 +85,7 @@ public class CompactRaceMapDataDTO implements IsSerializable { } } result.simulationResultVersion = simulationResultVersion; + result.raceCompetitorIdsAsStrings = this.raceCompetitorIdsAsStrings; return result; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceMapDataDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceMapDataDTO.java index c0f5edb0614..571fc5b0123 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceMapDataDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RaceMapDataDTO.java @@ -1,5 +1,6 @@ package com.sap.sailing.gwt.ui.shared; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -14,4 +15,5 @@ public class RaceMapDataDTO implements IsSerializable { public LinkedHashMap quickRanks; public LinkedHashMap competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber; public long simulationResultVersion; + public HashSet raceCompetitorIdsAsStrings; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ReplicationMasterDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ReplicationMasterDTO.java index 76973fdec47..f9a4591133d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ReplicationMasterDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ReplicationMasterDTO.java @@ -6,12 +6,16 @@ public class ReplicationMasterDTO implements IsSerializable { private String hostname; private int messagingPort; private int servletPort; + private String messagingHostname; + private String exchangeName; ReplicationMasterDTO() {} - public ReplicationMasterDTO(String hostname, int messagingPort, int servletPort) { + public ReplicationMasterDTO(String hostname, int servletPort, String messagingHostname, int messagingPort, String exchangeName) { super(); this.hostname = hostname; - this.messagingPort = messagingPort; this.servletPort = servletPort; + this.messagingHostname = messagingHostname; + this.messagingPort = messagingPort; + this.exchangeName = exchangeName; } public String getHostname() { return hostname; @@ -22,4 +26,10 @@ public class ReplicationMasterDTO implements IsSerializable { public int getServletPort() { return servletPort; } + public String getMessagingHostname() { + return messagingHostname; + } + public String getExchangeName() { + return exchangeName; + } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMainPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMainPanel.java index 815def789c1..a6723a5f6a5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMainPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMainPanel.java @@ -309,7 +309,9 @@ public class SimulatorMainPanel extends SimplePanel { timer.setTime(windParams.getStartTime().getTime()); int secondsTimeStep = (int) windParams.getTimeStep().asSeconds(); timer.setPlaySpeedFactor(secondsTimeStep); - timePanel = new TimePanel(timer, timeRangeProvider, stringMessages, false); + timePanel = new TimePanel(timer, timeRangeProvider, stringMessages, false, + /* isScreenLargeEnoughToOfferChartSupport: no wind or competitor chart is shown; use full horizontal + * extension of time panel */ false); busyIndicator = new SimpleBusyIndicator(false, 0.8f); simulatorMap = new SimulatorMap(simulatorSvc, stringMessages, errorReporter, xRes, yRes, border, streamletPars, timer, timePanel, windParams, busyIndicator, mode, this, showMapControls, diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html index 2c07a7f3241..6c0d43b7814 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html @@ -7,13 +7,30 @@
December 2015
    -
  • New line hover behavior in race map. When the mouse is moved onto a line, for example the start line, a transparent, - wider line is activated behind it that acts as a buffer for the thin line, where the text displayed by the hover is still shown. - This makes it easier to hover over the lines. These wider lines can be made semi transparent instead of transparent - by unticking the corresponding checkbox in the settings of the race map. Furthermore the stroke weight of the wider lines can be - configured in the same settings dialog. +
  • The advantage line is now animated and moves smoothly with the boats.
  • +
  • New line hover behavior in race map. When the mouse is moved onto a line, for example the start line, a transparent, + wider line is activated behind it that acts as a buffer for the thin line, where the text displayed by the hover is still shown. + This makes it easier to hover over the lines. These wider lines can be made semi transparent instead of transparent + by unticking the corresponding checkbox in the settings of the race map. Furthermore the stroke weight of the wider lines can be + configured in the same settings dialog. +
  • +
  • The race viewer (RaceBoard.html) now intelligently chooses a layout that suits the screen size. + When only little horizontal space is available, only the sail number and nationality but not the + competitor's long name is displayed by default. When vertical space is scarce, competitor and wind + charts are disabled and more horizontal space can be given to the time slider at the bottom. The + leaderboard section can then be scaled to smaller widths and will reduce the column widths better + than in the past. +
  • +
  • With the changes described above, the RaceBoard is now also accessible from the races page on mobile devices. +
  • +
  • For overall / series leaderboards such as a national sailing league or the Extreme Sailing Series, the + live status of the overall leaderboard is now indicated. As long as a live race is ongoing, the respective + column in the overall leaderboard is highlighted, and a comment is displayed that indicates which regatta + is currently still live. +
+
November 2015
    diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/NoUI.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/NoUI.gwt.xml index ea5872a6f56..eb7cd8bbdb9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/NoUI.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/NoUI.gwt.xml @@ -26,7 +26,9 @@ - + + + @@ -51,6 +53,8 @@ + + diff --git a/java/com.sap.sailing.selenium.test/ci-test-environment.xml b/java/com.sap.sailing.selenium.test/ci-test-environment.xml old mode 100755 new mode 100644 diff --git a/java/com.sap.sailing.selenium.test/local-test-environment.xml b/java/com.sap.sailing.selenium.test/local-test-environment.xml index 12359527203..bc2f96f3c75 100755 --- a/java/com.sap.sailing.selenium.test/local-test-environment.xml +++ b/java/com.sap.sailing.selenium.test/local-test-environment.xml @@ -39,7 +39,7 @@ webdriver.firefox.bin - c:/Program Files (x86)/Mozilla Firefox/firefox.exe + c:\Program Files (x86)\Mozilla Firefox 24\firefox.exe diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/AdminConsolePage.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/AdminConsolePage.java index 4b56dafba9c..507c762dd5b 100755 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/AdminConsolePage.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/AdminConsolePage.java @@ -15,6 +15,7 @@ import com.sap.sailing.selenium.core.BySeleniumId; import com.sap.sailing.selenium.core.ElementSearchConditions; import com.sap.sailing.selenium.core.FindBy; import com.sap.sailing.selenium.pages.HostPage; +import com.sap.sailing.selenium.pages.adminconsole.connectors.SmartphoneTrackingEventManagementPanelPO; import com.sap.sailing.selenium.pages.adminconsole.leaderboard.LeaderboardConfigurationPanelPO; import com.sap.sailing.selenium.pages.adminconsole.leaderboard.LeaderboardGroupConfigurationPanelPO; import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaStructureManagementPanelPO; @@ -66,6 +67,8 @@ public class AdminConsolePage extends HostPage { private static final String COMPETITOR_PANEL_TAB_LABEL = "Competitors"; //$NON-NLS-1$ private static final String COMPETITOR_PANEL_TAB_IDENTIFIER = "CompetitorPanel"; //$NON-NLS-1$ + private static final String SMARTPHONETRACKINGPANEL_PANEL_TAB_LABEL = "Smartphone Tracking"; //$NON-NLS-1$ + private static final String SMARTPHONETRACKINGPANEL_PANEL_TAB_IDENTIFIER = "SmartphoneTrackingPanel"; //$NON-NLS-1$ /** *

    Goes to the administration console and returns the representing page object.

    * @@ -130,6 +133,12 @@ public class AdminConsolePage extends HostPage { COMPETITOR_PANEL_TAB_IDENTIFIER, false)); } + public SmartphoneTrackingEventManagementPanelPO goToSmartphoneTrackingPanel() { + goToTab(TRACTRAC_EVENTS_TAB_PARENT_LABEL, TRACTRAC_EVENTS_TAB_PARENT_IDENTIFIER, true); + return new SmartphoneTrackingEventManagementPanelPO(this.driver, goToTab(SMARTPHONETRACKINGPANEL_PANEL_TAB_LABEL, + SMARTPHONETRACKINGPANEL_PANEL_TAB_IDENTIFIER, false)); + } + /** *

    Verifies that the current page is the administration console by checking the title of the page.

    */ diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/connectors/SmartphoneTrackingEventManagementPanelPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/connectors/SmartphoneTrackingEventManagementPanelPO.java new file mode 100644 index 00000000000..f49f719c273 --- /dev/null +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/connectors/SmartphoneTrackingEventManagementPanelPO.java @@ -0,0 +1,40 @@ +package com.sap.sailing.selenium.pages.adminconsole.connectors; + +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; + +import com.sap.sailing.selenium.core.BySeleniumId; +import com.sap.sailing.selenium.core.FindBy; +import com.sap.sailing.selenium.pages.PageArea; +import com.sap.sailing.selenium.pages.adminconsole.tracking.RaceColumnTableWrapperPO; +import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesListPO; +import com.sap.sailing.selenium.pages.gwt.CellTablePO; +import com.sap.sailing.selenium.pages.gwt.DataEntryPO; +import com.sap.sailing.selenium.pages.gwt.GenericCellTablePO; + +public class SmartphoneTrackingEventManagementPanelPO extends PageArea { + @FindBy(how = BySeleniumId.class, using = "AvailableLeaderboardsTable") + private WebElement leaderboardTable; + + @FindBy(how = BySeleniumId.class, using = "RaceColumnTable") + private WebElement raceColumnTableWrapper; + + @FindBy(how = BySeleniumId.class, using = "TrackedRacesListComposite") + private WebElement trackedRacesListComposite; + + public SmartphoneTrackingEventManagementPanelPO(WebDriver driver, WebElement element) { + super(driver, element); + } + + public CellTablePO getLeaderboardTable() { + return new GenericCellTablePO<>(this.driver, this.leaderboardTable, DataEntryPO.class); + } + + public RaceColumnTableWrapperPO getRaceColumnTableWrapper() { + return new RaceColumnTableWrapperPO(this.driver, this.raceColumnTableWrapper); + } + + public TrackedRacesListPO getTrackedRaceListComposite() { + return new TrackedRacesListPO(this.driver, this.trackedRacesListComposite); + } +} diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/RaceColumnTableWrapperPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/RaceColumnTableWrapperPO.java new file mode 100644 index 00000000000..03b72d75a03 --- /dev/null +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/RaceColumnTableWrapperPO.java @@ -0,0 +1,24 @@ +package com.sap.sailing.selenium.pages.adminconsole.tracking; + +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; + +import com.sap.sailing.selenium.core.BySeleniumId; +import com.sap.sailing.selenium.core.FindBy; +import com.sap.sailing.selenium.pages.PageArea; +import com.sap.sailing.selenium.pages.gwt.CellTablePO; +import com.sap.sailing.selenium.pages.gwt.DataEntryPO; +import com.sap.sailing.selenium.pages.gwt.GenericCellTablePO; + +public class RaceColumnTableWrapperPO extends PageArea { + @FindBy(how = BySeleniumId.class, using = "WrappedTable") + private WebElement raceColumnTable; + + public RaceColumnTableWrapperPO(WebDriver driver, WebElement element) { + super(driver,element); + } + + public CellTablePO getRaceColumnTable() { + return new GenericCellTablePO<>(this.driver, raceColumnTable, DataEntryPO.class); + } +} diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesCompetitorCreateDialogPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesCompetitorEditDialogPO.java similarity index 85% rename from java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesCompetitorCreateDialogPO.java rename to java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesCompetitorEditDialogPO.java index b7b4f317153..c0b6235d228 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesCompetitorCreateDialogPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesCompetitorEditDialogPO.java @@ -7,7 +7,7 @@ import com.sap.sailing.selenium.core.BySeleniumId; import com.sap.sailing.selenium.core.FindBy; import com.sap.sailing.selenium.pages.common.DataEntryDialogPO; -public class TrackedRacesCompetitorCreateDialogPO extends DataEntryDialogPO { +public class TrackedRacesCompetitorEditDialogPO extends DataEntryDialogPO { @FindBy(how = BySeleniumId.class, using = "NameTextBox") private WebElement nameTextBox; @@ -20,7 +20,7 @@ public class TrackedRacesCompetitorCreateDialogPO extends DataEntryDialogPO { @FindBy(how = BySeleniumId.class, using = "OkButton") private WebElement okButton; - public TrackedRacesCompetitorCreateDialogPO(WebDriver driver, WebElement element) { + public TrackedRacesCompetitorEditDialogPO(WebDriver driver, WebElement element) { super(driver, element); } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesCompetitorTablePO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesCompetitorTablePO.java index cb3732f9cf7..8a1fa640fd6 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesCompetitorTablePO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesCompetitorTablePO.java @@ -5,45 +5,58 @@ import org.openqa.selenium.WebElement; import com.sap.sailing.selenium.pages.gwt.CellTablePO; import com.sap.sailing.selenium.pages.gwt.DataEntryPO; - +import com.sap.sailing.selenium.core.BySeleniumId; +import com.sap.sailing.selenium.core.FindBy; +import com.sap.sailing.selenium.pages.adminconsole.ActionsHelper; import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesCompetitorTablePO.CompetitorEntry; public class TrackedRacesCompetitorTablePO extends CellTablePO { public static class CompetitorEntry extends DataEntryPO { + @FindBy(how = BySeleniumId.class, using = "ACTION_EDIT") + private WebElement editButton; + private static final String NAME_COLUMN = "Name"; private static final String SAILID_COLUMN = "Sail number"; private static final String BOATCLASSNAME_COLUMN = "Boat Class"; private static final String ID_COLUMN = "ID"; - + protected CompetitorEntry(TrackedRacesCompetitorTablePO table, WebElement element) { super(table, element); } - + @Override public String getIdentifier() { return getId(); } - + public String getId() { return getColumnContent(ID_COLUMN); } - + public String getName() { return getColumnContent(NAME_COLUMN); } - + public String getSailId() { return getColumnContent(SAILID_COLUMN); } - + public String getBoatClassName() { return getColumnContent(BOATCLASSNAME_COLUMN); } + + public TrackedRacesCompetitorEditDialogPO clickEditButton() { + WebElement action = ActionsHelper.findEditAction(getWebElement()); + action.click(); + WebElement dialog = findElementBySeleniumId(this.driver, "CompetitorEditDialog"); + return new TrackedRacesCompetitorEditDialogPO(this.driver, dialog); + } } + public TrackedRacesCompetitorTablePO(WebDriver driver, WebElement element) { - super(driver,element); + super(driver, element); } - + @Override protected CompetitorEntry createDataEntry(WebElement element) { return new CompetitorEntry(this, element); diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesCompetitorsPanelPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesCompetitorsPanelPO.java index 096e5ab9fe9..e3c215f1d83 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesCompetitorsPanelPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesCompetitorsPanelPO.java @@ -8,21 +8,32 @@ import com.sap.sailing.selenium.core.FindBy; import com.sap.sailing.selenium.pages.PageArea; public class TrackedRacesCompetitorsPanelPO extends PageArea { + @FindBy(how = BySeleniumId.class, using = "RefreshButton") + private WebElement refreshButton; + @FindBy(how = BySeleniumId.class, using = "AddCompetitorButton") private WebElement addButton; @FindBy(how = BySeleniumId.class, using = "CompetitorsTable") private WebElement competitorsTable; + + public final WebDriver driver; public TrackedRacesCompetitorsPanelPO(WebDriver driver, WebElement element) { super(driver, element); + this.driver = driver; } - public TrackedRacesCompetitorCreateDialogPO pushAddButton() { + public TrackedRacesCompetitorEditDialogPO pushAddButton() { this.addButton.click(); waitForAjaxRequests(); WebElement dialog = findElementBySeleniumId(this.driver, "CompetitorEditDialog"); - return new TrackedRacesCompetitorCreateDialogPO(this.driver, dialog); + return new TrackedRacesCompetitorEditDialogPO(this.driver, dialog); + } + + public void pushRefreshButton() { + this.refreshButton.click(); + waitForAjaxRequests(); } public TrackedRacesCompetitorTablePO getCompetitorTable() { diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesListPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesListPO.java index 810a9c075b7..2f1252d119f 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesListPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tracking/TrackedRacesListPO.java @@ -257,7 +257,7 @@ public class TrackedRacesListPO extends PageArea { } } - private CellTablePO getTrackedRacesTable() { + public CellTablePO getTrackedRacesTable() { // if(!this.trackedRacesTable.isDisplayed()) { // return null; // } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tractrac/TracTracEventManagementPanelPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tractrac/TracTracEventManagementPanelPO.java index 38b01c917c7..893148b6aa3 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tractrac/TracTracEventManagementPanelPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/adminconsole/tractrac/TracTracEventManagementPanelPO.java @@ -1,7 +1,6 @@ package com.sap.sailing.selenium.pages.adminconsole.tractrac; import java.util.ArrayList; -import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Objects; @@ -229,7 +228,23 @@ public class TracTracEventManagementPanelPO extends PageArea { } public void startTrackingForRace(TrackableRaceDescriptor race) { - startTrackingForRaces(Arrays.asList(race)); + //startTrackingForRaces(Arrays.asList(race)); + CellTablePO table = getTrackableRacesTable(); + DataEntryPO entryToSelect = null; + for (DataEntryPO entry : table.getEntries()) { + TrackableRaceDescriptor entryDiscribtor = new TrackableRaceDescriptor(entry.getColumnContent("Event"), + entry.getColumnContent("Race"), entry.getColumnContent("Boat Class")); + if (race.equals(entryDiscribtor)) { + entryToSelect = entry; + break; + } + } + table.selectEntry(entryToSelect); + this.startTrackingButton.click(); + ExpectedCondition condition = ExpectedConditions.alertIsPresent(); + if (condition.apply(this.driver) == null) { + waitForAjaxRequests(); + } } public void startTrackingForRaces(List races) { diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CellTablePO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CellTablePO.java index cbbd9773c7e..c0d6683aa40 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CellTablePO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CellTablePO.java @@ -54,6 +54,8 @@ public abstract class CellTablePO extends PageArea { private static final String LOADING_ANIMATION_XPATH = "./td/div/div/div/img"; protected static final String SELECTED_ROW_CSS_CLASS = "GIU0CSQDCQ"; //$NON-NLS-1$ + // Use the following line for testing with Super-Dev-Mode (SDM): +// protected static final String SELECTED_ROW_CSS_CLASS = "com-sap-sailing-gwt-ui-adminconsole-AdminConsoleTableResources-AdminConsoleTableStyle-cellTableSelectedRow"; //$NON-NLS-1$ private static final String LOADING_ANIMATION_IMAGE = "data:image/gif;base64," + //$NON-NLS-1$ "R0lGODlhKwALAPEAAP///0tKSqampktKSiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGx" + //$NON-NLS-1$ diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestCompetitorCreation.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestCompetitorCreation.java index 0c2a8f3f4b5..fa4f0ad68b8 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestCompetitorCreation.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestCompetitorCreation.java @@ -8,7 +8,7 @@ import org.junit.Before; import org.junit.Test; import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage; -import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesCompetitorCreateDialogPO; +import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesCompetitorEditDialogPO; import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesCompetitorTablePO.CompetitorEntry; import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesCompetitorsPanelPO; import com.sap.sailing.selenium.test.AbstractSeleniumTest; @@ -24,7 +24,7 @@ public class TestCompetitorCreation extends AbstractSeleniumTest { @Test public void testOpenCreateCompetitorDialog() { final TrackedRacesCompetitorsPanelPO competitorsPanel = goToCompetitorsPanel(); - final TrackedRacesCompetitorCreateDialogPO dialog = competitorsPanel.pushAddButton(); // fails with an exception if the dialog is not found + final TrackedRacesCompetitorEditDialogPO dialog = competitorsPanel.pushAddButton(); // fails with an exception if the dialog is not found assertNotNull(dialog); } @@ -37,7 +37,7 @@ public class TestCompetitorCreation extends AbstractSeleniumTest { @Test public void testCompetitorCreation() { final TrackedRacesCompetitorsPanelPO competitorsPanel = goToCompetitorsPanel(); - final TrackedRacesCompetitorCreateDialogPO dialog = competitorsPanel.pushAddButton(); + final TrackedRacesCompetitorEditDialogPO dialog = competitorsPanel.pushAddButton(); final String name = ""+System.currentTimeMillis(); dialog.setNameTextBox(name); String sailId = ""+System.currentTimeMillis(); diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestRefreshableSelectionModel.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestRefreshableSelectionModel.java new file mode 100644 index 00000000000..da05c9f9a7c --- /dev/null +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestRefreshableSelectionModel.java @@ -0,0 +1,253 @@ +package com.sap.sailing.selenium.test.adminconsole; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.support.ui.ExpectedCondition; +import org.openqa.selenium.support.ui.WebDriverWait; + +import com.sap.sailing.selenium.core.WebDriverWindow; +import com.sap.sailing.selenium.core.WindowManager; +import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage; +import com.sap.sailing.selenium.pages.adminconsole.connectors.SmartphoneTrackingEventManagementPanelPO; +import com.sap.sailing.selenium.pages.adminconsole.leaderboard.LeaderboardConfigurationPanelPO; +import com.sap.sailing.selenium.pages.adminconsole.leaderboard.LeaderboardDetailsPanelPO.RaceDescriptor; +import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaDetailsCompositePO; +import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaStructureManagementPanelPO; +import com.sap.sailing.selenium.pages.adminconsole.regatta.SeriesEditDialogPO; +import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaListCompositePO.RegattaDescriptor; +import com.sap.sailing.selenium.pages.adminconsole.tracking.RaceColumnTableWrapperPO; +import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesCompetitorEditDialogPO; +import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesCompetitorsPanelPO; +import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesCompetitorTablePO.CompetitorEntry; +import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesListPO.TrackedRaceDescriptor; +import com.sap.sailing.selenium.pages.adminconsole.tractrac.TracTracEventManagementPanelPO.TrackableRaceDescriptor; +import com.sap.sailing.selenium.pages.gwt.CellTablePO; +import com.sap.sailing.selenium.pages.gwt.DataEntryPO; +import com.sap.sailing.selenium.test.AbstractSeleniumTest; + +public class TestRefreshableSelectionModel extends AbstractSeleniumTest { + private static WindowManager manager; + private static WebDriverWindow windowForEdit; + private static WebDriverWindow windowForSelection; + + // TestMaintenanceOfSelectionAfterDataChanges + private CompetitorEntry competitorEntry; + private CompetitorEntry competitorEntryToSelect; + + // TestRefreshOfDependingUIElements + private static final String REGATTA = "IDM 2013"; //$NON-NLS-1$ + + private static final String LEADERBOARD = "IDM 2013 (5O5)"; //$NON-NLS-1$ + + private static final String EVENT = "IDM 5O5 2013"; //$NON-NLS-1$ + private static final String BOAT_CLASS = "5O5"; //$NON-NLS-1$ + private static final String RACE = "Race %d"; //$NON-NLS-1$ + + private RegattaDescriptor regatta; + + private List trackableRaces; + private List trackedRaces; + private List leaderboardRaces; + + @Override + @Before + public void setUp() { + clearState(getContextRoot()); + manager = this.environment.getWindowManager(); + windowForEdit = manager.getCurrentWindow(); + windowForSelection = manager.openNewWindow(); + super.setUp(); + } + + private TrackedRacesCompetitorsPanelPO goToCompetitorsPanel() { + final AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); + final TrackedRacesCompetitorsPanelPO competitorsPanel = adminConsole.goToTrackedRacesCompetitors(); + return competitorsPanel; + } + + @Test + public void testMaintenanceOfSelectionAfterDataChanges() { + windowForEdit.switchToWindow(); + final TrackedRacesCompetitorsPanelPO competitorsPanel = goToCompetitorsPanel(); + + for (int i = 0; i < 2; i++) { + TrackedRacesCompetitorEditDialogPO dialog = competitorsPanel.pushAddButton(); + dialog.setNameTextBox("" + System.currentTimeMillis()); + dialog.setSailIdTextBox("" + System.currentTimeMillis()); + dialog.setBoatClassNameSuggestBox("RS-X"); + dialog.pressOk(); + } + + TrackedRacesCompetitorEditDialogPO dialog = competitorsPanel.pushAddButton(); + final String name = "" + System.currentTimeMillis(); + dialog.setNameTextBox(name); + final String sailId = "" + System.currentTimeMillis(); + dialog.setSailIdTextBox(sailId); + final String boatClassName = "Laser Int."; + dialog.setBoatClassNameSuggestBox(boatClassName); + dialog.pressOk(); + + boolean found = false; + for (final CompetitorEntry it : competitorsPanel.getCompetitorTable().getEntries()) { + String itName = it.getName(); + if (itName.equals(name)) { + found = true; + competitorEntry = it; + break; + } + } + assertTrue(found); + + windowForSelection.switchToWindow(); + TrackedRacesCompetitorsPanelPO competitorPanelForSelection = goToCompetitorsPanel(); + found = false; + for (final CompetitorEntry it : competitorPanelForSelection.getCompetitorTable().getEntries()) { + String itName = it.getName(); + if (itName.equals(name)) { + found = true; + competitorEntryToSelect = it; + break; + } + } + assertTrue(found); + competitorPanelForSelection.getCompetitorTable().selectEntry(competitorEntryToSelect); + + assertEquals(1, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().size()); + assertEquals(name, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().get(0).getName()); + assertEquals(sailId, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().get(0).getSailId()); + assertEquals(boatClassName, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().get(0).getBoatClassName()); + // change competitor + windowForEdit.switchToWindow(); + dialog = competitorEntry.clickEditButton(); + final String changedName = "" + System.currentTimeMillis(); + dialog.setNameTextBox(changedName); + final String changedSailId = "" + System.currentTimeMillis(); + dialog.setSailIdTextBox(changedSailId); + dialog.pressOk(); + + // assert selection + windowForSelection.switchToWindow(); + + competitorPanelForSelection.pushRefreshButton(); + WebDriverWait waitTimer = new WebDriverWait(competitorPanelForSelection.driver, 10); + ExpectedCondition condition = new ExpectedCondition() { + @Override + public Boolean apply(WebDriver arg0) { + return competitorPanelForSelection.getCompetitorTable().getSelectedEntries().size() == 1; + } + }; + waitTimer.until(condition); + + assertEquals(1, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().size()); + for (final CompetitorEntry it : competitorPanelForSelection.getCompetitorTable().getEntries()) { + String itName = it.getName(); + if (itName.equals(changedName)) { + found = true; + competitorEntryToSelect = it; + break; + } + } + assertTrue(found); + assertEquals(changedName, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().get(0).getName()); + assertEquals(changedSailId, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().get(0).getSailId()); + assertEquals(boatClassName, competitorPanelForSelection.getCompetitorTable().getSelectedEntries().get(0).getBoatClassName()); + } + + private void setUpTestRefreshOfDependingUIElements() { + this.regatta = new RegattaDescriptor(REGATTA, BOAT_CLASS); + this.trackableRaces = new ArrayList<>(); + this.trackedRaces = new ArrayList<>(); + this.leaderboardRaces = new ArrayList<>(); + for (int i = 1; i < 8; i++) { + String raceName = String.format(RACE, i); + TrackableRaceDescriptor trackableRace = new TrackableRaceDescriptor(EVENT, raceName, BOAT_CLASS); + TrackedRaceDescriptor trackedRace = new TrackedRaceDescriptor(this.regatta.toString(), BOAT_CLASS, + raceName); + RaceDescriptor leaderboardRace = new RaceDescriptor(String.format("R%s", i), "Default", false, false, 0); + + this.trackableRaces.add(trackableRace); + this.trackedRaces.add(trackedRace); + this.leaderboardRaces.add(leaderboardRace); + } + configureLeaderboard(); + } + + private void configureLeaderboard() { + // Open the admin console for some configuration steps + AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); + + // Create a regatta with 1 series and 5 races as well as a leaderborad + RegattaStructureManagementPanelPO regattaStructure = adminConsole.goToRegattaStructure(); + regattaStructure.createRegatta(this.regatta); + + RegattaDetailsCompositePO regattaDetails = regattaStructure.getRegattaDetails(this.regatta); + SeriesEditDialogPO seriesDialog = regattaDetails.editSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME); + seriesDialog.addRaces(1, 5); + seriesDialog.pressOk(); + + LeaderboardConfigurationPanelPO leaderboardConfiguration = adminConsole.goToLeaderboardConfiguration(); + leaderboardConfiguration.createRegattaLeaderboard(this.regatta); + } + @Test + public void testRefreshOfDependingUIElements() { + windowForSelection.switchToWindow(); + setUpTestRefreshOfDependingUIElements(); + AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); + SmartphoneTrackingEventManagementPanelPO smartphoneTrackingPanel = adminConsole.goToSmartphoneTrackingPanel(); + CellTablePO leaderboards = smartphoneTrackingPanel.getLeaderboardTable(); + // select leaderboard + DataEntryPO entryToSelect = null; + for(DataEntryPO entry : leaderboards.getEntries()) { + if(entry.getColumnContent("Name").equals(LEADERBOARD)) { + entryToSelect = entry; + break; + } + } + assertNotNull(entryToSelect); + leaderboards.selectEntry(entryToSelect); + + RaceColumnTableWrapperPO raceColumnTableWrapper = smartphoneTrackingPanel.getRaceColumnTableWrapper(); + CellTablePO raceColumnTable = raceColumnTableWrapper.getRaceColumnTable(); + final int anzRaceColumns = raceColumnTable.getEntries().size(); + assertEquals(5, anzRaceColumns); + + // Open a second window & setup second window + windowForEdit.switchToWindow(); + + AdminConsolePage adminConsoleForEdit = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); + RegattaStructureManagementPanelPO regattaStructure = adminConsoleForEdit.goToRegattaStructure(); + + RegattaDetailsCompositePO regattaDetails = regattaStructure.getRegattaDetails(this.regatta); + SeriesEditDialogPO seriesDialog = regattaDetails.editSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME); + seriesDialog.addRaces(6, 7); + seriesDialog.pressOk(); + + windowForSelection.switchToWindow(); + adminConsole.goToTracTracEvents(); + smartphoneTrackingPanel = adminConsole.goToSmartphoneTrackingPanel(); + leaderboards = smartphoneTrackingPanel.getLeaderboardTable(); + // select leaderboard + entryToSelect = null; + for(DataEntryPO entry : leaderboards.getEntries()) { + if(entry.getColumnContent("Name").equals(LEADERBOARD)) { + entryToSelect = entry; + break; + } + } + assertNotNull(entryToSelect); + leaderboards.selectEntry(entryToSelect); + + raceColumnTableWrapper = smartphoneTrackingPanel.getRaceColumnTableWrapper(); + raceColumnTable = raceColumnTableWrapper.getRaceColumnTable(); + final int newAnzRaceColumns = raceColumnTable.getEntries().size(); + assertEquals(7, newAnzRaceColumns); + } +} diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestSmartphoneTrackingEventManagementPanel.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestSmartphoneTrackingEventManagementPanel.java new file mode 100644 index 00000000000..316460cbcd5 --- /dev/null +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/adminconsole/TestSmartphoneTrackingEventManagementPanel.java @@ -0,0 +1,142 @@ +package com.sap.sailing.selenium.test.adminconsole; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage; +import com.sap.sailing.selenium.pages.adminconsole.connectors.SmartphoneTrackingEventManagementPanelPO; +import com.sap.sailing.selenium.pages.adminconsole.leaderboard.LeaderboardConfigurationPanelPO; +import com.sap.sailing.selenium.pages.adminconsole.leaderboard.LeaderboardDetailsPanelPO; +import com.sap.sailing.selenium.pages.adminconsole.leaderboard.LeaderboardDetailsPanelPO.RaceDescriptor; +import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaDetailsCompositePO; +import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaStructureManagementPanelPO; +import com.sap.sailing.selenium.pages.adminconsole.regatta.SeriesEditDialogPO; +import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaListCompositePO.RegattaDescriptor; +import com.sap.sailing.selenium.pages.adminconsole.tracking.RaceColumnTableWrapperPO; +import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesListPO; +import com.sap.sailing.selenium.pages.adminconsole.tracking.TrackedRacesListPO.TrackedRaceDescriptor; +import com.sap.sailing.selenium.pages.adminconsole.tractrac.TracTracEventManagementPanelPO; +import com.sap.sailing.selenium.pages.adminconsole.tractrac.TracTracEventManagementPanelPO.TrackableRaceDescriptor; +import com.sap.sailing.selenium.pages.gwt.CellTablePO; +import com.sap.sailing.selenium.pages.gwt.DataEntryPO; +import com.sap.sailing.selenium.test.AbstractSeleniumTest; + + +public class TestSmartphoneTrackingEventManagementPanel extends AbstractSeleniumTest { + private static final String IDM_5O5_2013_JSON_URL = + "http://traclive.dk/events/event_20130917_IDMO/jsonservice.php"; //$NON-NLS-1$ + + private static final String REGATTA = "IDM 2013"; //$NON-NLS-1$ + + private static final String LEADERBOARD = "IDM 2013 (5O5)"; //$NON-NLS-1$ + + private static final String EVENT = "IDM 5O5 2013"; //$NON-NLS-1$ + private static final String BOAT_CLASS = "5O5"; //$NON-NLS-1$ + private static final String RACE = "Race %d"; //$NON-NLS-1$ + + private RegattaDescriptor regatta; + + private List trackableRaces; + private List trackedRaces; + private List leaderboardRaces; + + @Override + @Before + public void setUp() { + this.regatta = new RegattaDescriptor(REGATTA, BOAT_CLASS); + this.trackableRaces = new ArrayList<>(); + this.trackedRaces = new ArrayList<>(); + this.leaderboardRaces = new ArrayList<>(); + for (int i = 1; i < 6; i++) { + String raceName = String.format(RACE, i); + TrackableRaceDescriptor trackableRace = new TrackableRaceDescriptor(EVENT, raceName, BOAT_CLASS); + TrackedRaceDescriptor trackedRace = new TrackedRaceDescriptor(this.regatta.toString(), BOAT_CLASS, raceName); + RaceDescriptor leaderboardRace = new RaceDescriptor(String.format("R%s", i), "Default", false, false, 0); + + this.trackableRaces.add(trackableRace); + this.trackedRaces.add(trackedRace); + this.leaderboardRaces.add(leaderboardRace); + } + clearState(getContextRoot()); + super.setUp(); + configureLeaderboard(); + } + + @Test + public void testAutomaticSelectionOfLinkedRaceInRaceTable() { + AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); + LeaderboardConfigurationPanelPO leaderboardConfigurationPanelPO = adminConsole.goToLeaderboardConfiguration(); + LeaderboardDetailsPanelPO leaderboardDetails = leaderboardConfigurationPanelPO.getLeaderboardDetails(this.regatta.toString()); + + for (int i = 0; i < 5; i++) { + leaderboardDetails.linkRace(this.leaderboardRaces.get(i), this.trackedRaces.get(i)); + } + + SmartphoneTrackingEventManagementPanelPO smartphoneTrackingPanel = adminConsole.goToSmartphoneTrackingPanel(); + CellTablePO leaderboards = smartphoneTrackingPanel.getLeaderboardTable(); + // select leaderboard + DataEntryPO entryToSelect = null; + for(DataEntryPO entry : leaderboards.getEntries()) { + if(entry.getColumnContent("Name").equals(LEADERBOARD)) { + entryToSelect = entry; + break; + } + } + assertNotNull(entryToSelect); + leaderboards.selectEntry(entryToSelect); + for (int i = 0; i < 5; i++) { + RaceColumnTableWrapperPO raceColumnTableWrapper = smartphoneTrackingPanel.getRaceColumnTableWrapper(); + CellTablePO raceColumnTable = raceColumnTableWrapper.getRaceColumnTable(); + // select RaceColumn + DataEntryPO raceToSelect = null; + for (DataEntryPO entry : raceColumnTable.getEntries()) { + if (entry.getColumnContent("Race").equals(leaderboardRaces.get(i).getName())) { + raceToSelect = entry; + break; + } + } + assertNotNull(raceToSelect); + raceColumnTable.selectEntry(raceToSelect); + + TrackedRacesListPO trackedRaces = smartphoneTrackingPanel.getTrackedRaceListComposite(); + List seleceted = trackedRaces.getTrackedRacesTable().getSelectedEntries(); + assertEquals(1, seleceted.size()); + assertEquals(this.trackedRaces.get(i).race, seleceted.get(0).getColumnContent("Race")); + } + } + + private void configureLeaderboard() { + // Open the admin console for some configuration steps + AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot()); + + // Create a regatta with 1 series and 5 races as well as a leaderborad + RegattaStructureManagementPanelPO regattaStructure = adminConsole.goToRegattaStructure(); + regattaStructure.createRegatta(this.regatta); + + RegattaDetailsCompositePO regattaDetails = regattaStructure.getRegattaDetails(this.regatta); + SeriesEditDialogPO seriesDialog = regattaDetails.editSeries(RegattaStructureManagementPanelPO.DEFAULT_SERIES_NAME); + seriesDialog.addRaces(1, 5); + seriesDialog.pressOk(); + + LeaderboardConfigurationPanelPO leaderboardConfiguration = adminConsole.goToLeaderboardConfiguration(); + leaderboardConfiguration.createRegattaLeaderboard(this.regatta); + + // Start the tracking for the races and wait until they are ready to use + TracTracEventManagementPanelPO tracTracEvents = adminConsole.goToTracTracEvents(); + tracTracEvents.listTrackableRaces(IDM_5O5_2013_JSON_URL); + tracTracEvents.setReggataForTracking(this.regatta); + tracTracEvents.setTrackSettings(false, false, false); + // TODO: There exists a bug in Selenium with key modifiers (Issue 3734 and 6817), so we can't use multi + // selection (Firefox on Windows) + //tracTracEvents.startTrackingForRaces(this.trackableRaces); + for(TrackableRaceDescriptor race : this.trackableRaces) { + tracTracEvents.startTrackingForRace(race); + } + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway.ess40/.classpath b/java/com.sap.sailing.server.gateway.ess40/.classpath deleted file mode 100644 index eca7bdba8f0..00000000000 --- a/java/com.sap.sailing.server.gateway.ess40/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/java/com.sap.sailing.server.gateway.ess40/.gitignore b/java/com.sap.sailing.server.gateway.ess40/.gitignore deleted file mode 100644 index 44519b5db33..00000000000 --- a/java/com.sap.sailing.server.gateway.ess40/.gitignore +++ /dev/null @@ -1 +0,0 @@ -bin/ diff --git a/java/com.sap.sailing.server.gateway.ess40/.project b/java/com.sap.sailing.server.gateway.ess40/.project index 856b75c9da9..18883cf12dd 100644 --- a/java/com.sap.sailing.server.gateway.ess40/.project +++ b/java/com.sap.sailing.server.gateway.ess40/.project @@ -1,28 +1,28 @@ - - - com.sap.sailing.server.gateway.ess40 - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - + + + com.sap.sailing.server.gateway.ess40 + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/java/com.sap.sailing.server.gateway.ess40/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sailing.server.gateway.ess40/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index a698e59674f..00000000000 --- a/java/com.sap.sailing.server.gateway.ess40/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,12 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.8 -org.eclipse.jdt.core.compiler.debug.lineNumber=generate -org.eclipse.jdt.core.compiler.debug.localVariable=generate -org.eclipse.jdt.core.compiler.debug.sourceFile=generate -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.source=1.8 diff --git a/java/com.sap.sailing.server.gateway.ess40/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sailing.server.gateway.ess40/.settings/org.eclipse.pde.core.prefs deleted file mode 100644 index b7e72d019c2..00000000000 --- a/java/com.sap.sailing.server.gateway.ess40/.settings/org.eclipse.pde.core.prefs +++ /dev/null @@ -1,3 +0,0 @@ -eclipse.preferences.version=1 -pluginProject.extensions=false -resolve.requirebundle=false diff --git a/java/com.sap.sailing.server.gateway.ess40/META-INF/MANIFEST.MF b/java/com.sap.sailing.server.gateway.ess40/META-INF/MANIFEST.MF deleted file mode 100644 index f91c2c01d96..00000000000 --- a/java/com.sap.sailing.server.gateway.ess40/META-INF/MANIFEST.MF +++ /dev/null @@ -1,24 +0,0 @@ -Manifest-Version: 1.0 -Bundle-ManifestVersion: 2 -Bundle-Name: Gateway for ESS40 -Bundle-SymbolicName: com.sap.sailing.server.gateway.ess40 -Bundle-Version: 1.0.0.qualifier -Bundle-Vendor: SAP -Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Import-Package: org.osgi.framework;version="1.3.0", - org.osgi.util.tracker;version="1.3.1", - com.sap.sailing.server -Bundle-ActivationPolicy: lazy -Require-Bundle: com.sap.sailing.domain, - com.sap.sailing.domain.tractracadapter, - com.sap.sailing.domain.persistence, - com.sap.sse.mongodb, - org.mongodb.mongo-java-driver;bundle-version="2.13.0", - javax.servlet;bundle-version="2.6.0", - com.sap.sailing.domain.common, - com.sap.sailing.server.gateway, - com.sap.sse.common, - com.sap.sse.replication, - com.sap.sse -Bundle-ClassPath: . -Web-ContextPath: /gateway-ess40 diff --git a/java/com.sap.sailing.server.gateway.ess40/WEB-INF/web.xml b/java/com.sap.sailing.server.gateway.ess40/WEB-INF/web.xml deleted file mode 100644 index 12863624767..00000000000 --- a/java/com.sap.sailing.server.gateway.ess40/WEB-INF/web.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - default - org.eclipse.jetty.servlet.DefaultServlet - - - dirAllowed - false - - - - - ESS40ResultsAsCSVServlet - com.sap.sailing.server.gateway.ess40.ESS40ResultsAsCSVServlet - - - ESS40ResultsAsCSVServlet - /csv/* - - diff --git a/java/com.sap.sailing.server.gateway.ess40/build.properties b/java/com.sap.sailing.server.gateway.ess40/build.properties deleted file mode 100644 index c70d0a9f4f4..00000000000 --- a/java/com.sap.sailing.server.gateway.ess40/build.properties +++ /dev/null @@ -1,7 +0,0 @@ -source.. = src/ -output.. = bin/ -bin.includes = META-INF/,\ - .,\ - WEB-INF/,\ - css/,\ - js/ diff --git a/java/com.sap.sailing.server.gateway.ess40/css/asc.gif b/java/com.sap.sailing.server.gateway.ess40/css/asc.gif deleted file mode 100644 index e4a4c5b3209..00000000000 Binary files a/java/com.sap.sailing.server.gateway.ess40/css/asc.gif and /dev/null differ diff --git a/java/com.sap.sailing.server.gateway.ess40/css/bg.gif b/java/com.sap.sailing.server.gateway.ess40/css/bg.gif deleted file mode 100644 index b0f66561832..00000000000 Binary files a/java/com.sap.sailing.server.gateway.ess40/css/bg.gif and /dev/null differ diff --git a/java/com.sap.sailing.server.gateway.ess40/css/desc.gif b/java/com.sap.sailing.server.gateway.ess40/css/desc.gif deleted file mode 100644 index 1308bbe6a9f..00000000000 Binary files a/java/com.sap.sailing.server.gateway.ess40/css/desc.gif and /dev/null differ diff --git a/java/com.sap.sailing.server.gateway.ess40/css/leaderboard.css b/java/com.sap.sailing.server.gateway.ess40/css/leaderboard.css deleted file mode 100644 index 1874c009ec4..00000000000 --- a/java/com.sap.sailing.server.gateway.ess40/css/leaderboard.css +++ /dev/null @@ -1,42 +0,0 @@ -body { - background: #4d4d4d; - font-family: Verdana, Arial, sans-serif; -} - -h1,h2,h3,h4 { - color: #fff; - font-size: 18px; -} - -table { - border-collapse: separate; - border: none; - border-spacing: 0; -} - -.leaderboard-points-TotalPoints-NotDiscarded { - font-weight: bold; -} - -.leaderboard-points-MaxPoints-NotDiscarded { - font-weight: bold; -} - -.leaderboard-points-TotalPoints-Discarded { - filter: Alpha(opacity=50); - opacity: 0.5; - moz-opacity: 0.5; - text-decoration: line-through; -} - -.leaderboard-points-MaxPoints-Discarded { - filter: Alpha(opacity=50); - opacity: 0.5; - moz-opacity: 0.5; - text-decoration: line-through; -} - -table.tablesorter tbody td.last-row { - height: 29px; - padding-bottom: 0; -} \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway.ess40/css/row-bg.png b/java/com.sap.sailing.server.gateway.ess40/css/row-bg.png deleted file mode 100644 index 79d7f92203a..00000000000 Binary files a/java/com.sap.sailing.server.gateway.ess40/css/row-bg.png and /dev/null differ diff --git a/java/com.sap.sailing.server.gateway.ess40/css/tablesorter.css b/java/com.sap.sailing.server.gateway.ess40/css/tablesorter.css deleted file mode 100644 index 0ebf819c0a6..00000000000 --- a/java/com.sap.sailing.server.gateway.ess40/css/tablesorter.css +++ /dev/null @@ -1,58 +0,0 @@ -/* table sorter table */ -table.tablesorter { - font-family:arial; - background-color: #CDCDCD; - margin:10px 0pt 15px; - font-size: 8pt; - text-align: left; -} - -table.tablesorter thead tr th, table.tablesorter tfoot tr th { - background-color: #4d4d4d; - border-bottom: 1px solid #4d4d4d; - font-size: 12px; - padding: 4px; - color: #fff; - border-right: 1px solid #9c9c9c; -} - -table.tablesorter thead tr .header { - background-image: url(bg.gif); - background-repeat: no-repeat; - background-position: center right; - cursor: pointer; - padding-left: 10px; - padding-right: 20px; -} - -table.tablesorter tbody td { - color: #fff; - padding: 0 4px 4px 15px; - background: url(row-bg.png); - vertical-align: middle; - border-right: 1px solid #9c9c9c; - font-size: 12px; - font-variant: small-caps; - height: 36px; - font-weight: bold; -} - -table.tablesorter tbody tr:last-child td { - height: 29px; -} - -table.tablesorter tbody tr.odd td { - background-color:#F0F0F6; -} - -table.tablesorter thead tr .headerSortUp { - background-image: url(asc.gif); -} - -table.tablesorter thead tr .headerSortDown { - background-image: url(desc.gif); -} - -table.tablesorter thead tr .headerSortDown, table.tablesorter thead tr .headerSortUp { - background-color: #949494; -} diff --git a/java/com.sap.sailing.server.gateway.ess40/js/ess_act_leaderboard.html b/java/com.sap.sailing.server.gateway.ess40/js/ess_act_leaderboard.html deleted file mode 100644 index 12171eccbc7..00000000000 --- a/java/com.sap.sailing.server.gateway.ess40/js/ess_act_leaderboard.html +++ /dev/null @@ -1,102 +0,0 @@ - - - - Extreme Sailing Series leaderboard - - - - - - - - - -

    -

    -
    - - - \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway.ess40/js/ess_series_leaderboard.html b/java/com.sap.sailing.server.gateway.ess40/js/ess_series_leaderboard.html deleted file mode 100644 index c99c0d6fdad..00000000000 --- a/java/com.sap.sailing.server.gateway.ess40/js/ess_series_leaderboard.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - Extreme Sailing Series leaderboard - - - - - - - - - -

    -

    -
    - - - \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway.ess40/js/jquery-latest.js b/java/com.sap.sailing.server.gateway.ess40/js/jquery-latest.js deleted file mode 100644 index ac7e7009dc9..00000000000 --- a/java/com.sap.sailing.server.gateway.ess40/js/jquery-latest.js +++ /dev/null @@ -1,154 +0,0 @@ -/*! - * jQuery JavaScript Library v1.4.2 - * http://jquery.com/ - * - * Copyright 2010, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2010, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Sat Feb 13 22:33:48 2010 -0500 - */ -(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, -Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& -(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, -a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== -"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, -function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
    a"; -var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected, -parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent= -false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n= -s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true, -applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando]; -else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this, -a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b=== -w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i, -cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected= -c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); -a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g, -function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split("."); -k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a), -C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B=0){a.type= -e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&& -f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive; -if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data", -e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a, -"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a, -d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, -e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift(); -t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D|| -g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, -CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, -g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, -text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, -setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= -h[3];l=0;for(m=h.length;l=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== -"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, -h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& -q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; -if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="

    ";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); -(function(){var g=s.createElement("div");g.innerHTML="
    ";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: -function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= -{},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== -"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", -d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? -a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== -1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"},F={option:[1,""],legend:[1,"
    ","
    "],thead:[1,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],col:[2,"","
    "],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
    ","
    "];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= -c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, -wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, -prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, -this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); -return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, -""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); -return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", -""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= -c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? -c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= -function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= -Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, -"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= -a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= -a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=//gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!== -"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("
    ").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this}, -serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), -function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href, -global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&& -e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)? -"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache=== -false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B= -false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since", -c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E|| -d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x); -g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status=== -1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b=== -"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional; -if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration=== -"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]|| -c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start; -this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now= -this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem, -e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b
    "; -a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b); -c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a, -d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top- -f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset": -"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in -e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window); diff --git a/java/com.sap.sailing.server.gateway.ess40/js/jquery.tablesorter.js b/java/com.sap.sailing.server.gateway.ess40/js/jquery.tablesorter.js deleted file mode 100644 index 9b587312261..00000000000 --- a/java/com.sap.sailing.server.gateway.ess40/js/jquery.tablesorter.js +++ /dev/null @@ -1,1031 +0,0 @@ -/* - * - * TableSorter 2.0 - Client-side table sorting with ease! - * Version 2.0.5b - * @requires jQuery v1.2.3 - * - * Copyright (c) 2007 Christian Bach - * Examples and docs at: http://tablesorter.com - * Dual licensed under the MIT and GPL licenses: - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - * - */ -/** - * - * @description Create a sortable table with multi-column sorting capabilitys - * - * @example $('table').tablesorter(); - * @desc Create a simple tablesorter interface. - * - * @example $('table').tablesorter({ sortList:[[0,0],[1,0]] }); - * @desc Create a tablesorter interface and sort on the first and secound column column headers. - * - * @example $('table').tablesorter({ headers: { 0: { sorter: false}, 1: {sorter: false} } }); - * - * @desc Create a tablesorter interface and disableing the first and second column headers. - * - * - * @example $('table').tablesorter({ headers: { 0: {sorter:"integer"}, 1: {sorter:"currency"} } }); - * - * @desc Create a tablesorter interface and set a column parser for the first - * and second column. - * - * - * @param Object - * settings An object literal containing key/value pairs to provide - * optional settings. - * - * - * @option String cssHeader (optional) A string of the class name to be appended - * to sortable tr elements in the thead of the table. Default value: - * "header" - * - * @option String cssAsc (optional) A string of the class name to be appended to - * sortable tr elements in the thead on a ascending sort. Default value: - * "headerSortUp" - * - * @option String cssDesc (optional) A string of the class name to be appended - * to sortable tr elements in the thead on a descending sort. Default - * value: "headerSortDown" - * - * @option String sortInitialOrder (optional) A string of the inital sorting - * order can be asc or desc. Default value: "asc" - * - * @option String sortMultisortKey (optional) A string of the multi-column sort - * key. Default value: "shiftKey" - * - * @option String textExtraction (optional) A string of the text-extraction - * method to use. For complex html structures inside td cell set this - * option to "complex", on large tables the complex option can be slow. - * Default value: "simple" - * - * @option Object headers (optional) An array containing the forces sorting - * rules. This option let's you specify a default sorting rule. Default - * value: null - * - * @option Array sortList (optional) An array containing the forces sorting - * rules. This option let's you specify a default sorting rule. Default - * value: null - * - * @option Array sortForce (optional) An array containing forced sorting rules. - * This option let's you specify a default sorting rule, which is - * prepended to user-selected rules. Default value: null - * - * @option Boolean sortLocaleCompare (optional) Boolean flag indicating whatever - * to use String.localeCampare method or not. Default set to true. - * - * - * @option Array sortAppend (optional) An array containing forced sorting rules. - * This option let's you specify a default sorting rule, which is - * appended to user-selected rules. Default value: null - * - * @option Boolean widthFixed (optional) Boolean flag indicating if tablesorter - * should apply fixed widths to the table columns. This is usefull when - * using the pager companion plugin. This options requires the dimension - * jquery plugin. Default value: false - * - * @option Boolean cancelSelection (optional) Boolean flag indicating if - * tablesorter should cancel selection of the table headers text. - * Default value: true - * - * @option Boolean debug (optional) Boolean flag indicating if tablesorter - * should display debuging information usefull for development. - * - * @type jQuery - * - * @name tablesorter - * - * @cat Plugins/Tablesorter - * - * @author Christian Bach/christian.bach@polyester.se - */ - -(function ($) { - $.extend({ - tablesorter: new - function () { - - var parsers = [], - widgets = []; - - this.defaults = { - cssHeader: "header", - cssAsc: "headerSortUp", - cssDesc: "headerSortDown", - cssChildRow: "expand-child", - sortInitialOrder: "asc", - sortMultiSortKey: "shiftKey", - sortForce: null, - sortAppend: null, - sortLocaleCompare: true, - textExtraction: "simple", - parsers: {}, widgets: [], - widgetZebra: { - css: ["even", "odd"] - }, headers: {}, widthFixed: false, - cancelSelection: true, - sortList: [], - headerList: [], - dateFormat: "us", - decimal: '/\.|\,/g', - onRenderHeader: null, - selectorHeaders: 'thead th', - debug: false - }; - - /* debuging utils */ - - function benchmark(s, d) { - log(s + "," + (new Date().getTime() - d.getTime()) + "ms"); - } - - this.benchmark = benchmark; - - function log(s) { - if (typeof console != "undefined" && typeof console.debug != "undefined") { - console.log(s); - } else { - alert(s); - } - } - - /* parsers utils */ - - function buildParserCache(table, $headers) { - - if (table.config.debug) { - var parsersDebug = ""; - } - - if (table.tBodies.length == 0) return; // In the case of empty tables - var rows = table.tBodies[0].rows; - - if (rows[0]) { - - var list = [], - cells = rows[0].cells, - l = cells.length; - - for (var i = 0; i < l; i++) { - - var p = false; - - if ($.metadata && ($($headers[i]).metadata() && $($headers[i]).metadata().sorter)) { - - p = getParserById($($headers[i]).metadata().sorter); - - } else if ((table.config.headers[i] && table.config.headers[i].sorter)) { - - p = getParserById(table.config.headers[i].sorter); - } - if (!p) { - - p = detectParserForColumn(table, rows, -1, i); - } - - if (table.config.debug) { - parsersDebug += "column:" + i + " parser:" + p.id + "\n"; - } - - list.push(p); - } - } - - if (table.config.debug) { - log(parsersDebug); - } - - return list; - }; - - function detectParserForColumn(table, rows, rowIndex, cellIndex) { - var l = parsers.length, - node = false, - nodeValue = false, - keepLooking = true; - while (nodeValue == '' && keepLooking) { - rowIndex++; - if (rows[rowIndex]) { - node = getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex); - nodeValue = trimAndGetNodeText(table.config, node); - if (table.config.debug) { - log('Checking if value was empty on row:' + rowIndex); - } - } else { - keepLooking = false; - } - } - for (var i = 1; i < l; i++) { - if (parsers[i].is(nodeValue, table, node)) { - return parsers[i]; - } - } - // 0 is always the generic parser (text) - return parsers[0]; - } - - function getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex) { - return rows[rowIndex].cells[cellIndex]; - } - - function trimAndGetNodeText(config, node) { - return $.trim(getElementText(config, node)); - } - - function getParserById(name) { - var l = parsers.length; - for (var i = 0; i < l; i++) { - if (parsers[i].id.toLowerCase() == name.toLowerCase()) { - return parsers[i]; - } - } - return false; - } - - /* utils */ - - function buildCache(table) { - - if (table.config.debug) { - var cacheTime = new Date(); - } - - var totalRows = (table.tBodies[0] && table.tBodies[0].rows.length) || 0, - totalCells = (table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length) || 0, - parsers = table.config.parsers, - cache = { - row: [], - normalized: [] - }; - - for (var i = 0; i < totalRows; ++i) { - - /** Add the table data to main data array */ - var c = $(table.tBodies[0].rows[i]), - cols = []; - - // if this is a child row, add it to the last row's children and - // continue to the next row - if (c.hasClass(table.config.cssChildRow)) { - cache.row[cache.row.length - 1] = cache.row[cache.row.length - 1].add(c); - // go to the next for loop - continue; - } - - cache.row.push(c); - - for (var j = 0; j < totalCells; ++j) { - cols.push(parsers[j].format(getElementText(table.config, c[0].cells[j]), table, c[0].cells[j])); - } - - cols.push(cache.normalized.length); // add position for rowCache - cache.normalized.push(cols); - cols = null; - }; - - if (table.config.debug) { - benchmark("Building cache for " + totalRows + " rows:", cacheTime); - } - - return cache; - }; - - function getElementText(config, node) { - - var text = ""; - - if (!node) return ""; - - if (!config.supportsTextContent) config.supportsTextContent = node.textContent || false; - - if (config.textExtraction == "simple") { - if (config.supportsTextContent) { - text = node.textContent; - } else { - if (node.childNodes[0] && node.childNodes[0].hasChildNodes()) { - text = node.childNodes[0].innerHTML; - } else { - text = node.innerHTML; - } - } - } else { - if (typeof(config.textExtraction) == "function") { - text = config.textExtraction(node); - } else { - text = $(node).text(); - } - } - return text; - } - - function appendToTable(table, cache) { - - if (table.config.debug) { - var appendTime = new Date() - } - - var c = cache, - r = c.row, - n = c.normalized, - totalRows = n.length, - checkCell = (n[0].length - 1), - tableBody = $(table.tBodies[0]), - rows = []; - - - for (var i = 0; i < totalRows; i++) { - var pos = n[i][checkCell]; - - rows.push(r[pos]); - - if (!table.config.appender) { - - //var o = ; - var l = r[pos].length; - for (var j = 0; j < l; j++) { - tableBody[0].appendChild(r[pos][j]); - } - - // - } - } - - - - if (table.config.appender) { - - table.config.appender(table, rows); - } - - rows = null; - - if (table.config.debug) { - benchmark("Rebuilt table:", appendTime); - } - - // apply table widgets - applyWidget(table); - - // trigger sortend - setTimeout(function () { - $(table).trigger("sortEnd"); - }, 0); - - }; - - function buildHeaders(table) { - - if (table.config.debug) { - var time = new Date(); - } - - var meta = ($.metadata) ? true : false; - - var header_index = computeTableHeaderCellIndexes(table); - - $tableHeaders = $(table.config.selectorHeaders, table).each(function (index) { - - this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex]; - // this.column = index; - this.order = formatSortingOrder(table.config.sortInitialOrder); - - - this.count = this.order; - - if (checkHeaderMetadata(this) || checkHeaderOptions(table, index)) this.sortDisabled = true; - if (checkHeaderOptionsSortingLocked(table, index)) this.order = this.lockedOrder = checkHeaderOptionsSortingLocked(table, index); - - if (!this.sortDisabled) { - var $th = $(this).addClass(table.config.cssHeader); - if (table.config.onRenderHeader) table.config.onRenderHeader.apply($th); - } - - // add cell to headerList - table.config.headerList[index] = this; - }); - - if (table.config.debug) { - benchmark("Built headers:", time); - log($tableHeaders); - } - - return $tableHeaders; - - }; - - // from: - // http://www.javascripttoolbox.com/lib/table/examples.php - // http://www.javascripttoolbox.com/temp/table_cellindex.html - - - function computeTableHeaderCellIndexes(t) { - var matrix = []; - var lookup = {}; - var thead = t.getElementsByTagName('THEAD')[0]; - var trs = thead.getElementsByTagName('TR'); - - for (var i = 0; i < trs.length; i++) { - var cells = trs[i].cells; - for (var j = 0; j < cells.length; j++) { - var c = cells[j]; - - var rowIndex = c.parentNode.rowIndex; - var cellId = rowIndex + "-" + c.cellIndex; - var rowSpan = c.rowSpan || 1; - var colSpan = c.colSpan || 1 - var firstAvailCol; - if (typeof(matrix[rowIndex]) == "undefined") { - matrix[rowIndex] = []; - } - // Find first available column in the first row - for (var k = 0; k < matrix[rowIndex].length + 1; k++) { - if (typeof(matrix[rowIndex][k]) == "undefined") { - firstAvailCol = k; - break; - } - } - lookup[cellId] = firstAvailCol; - for (var k = rowIndex; k < rowIndex + rowSpan; k++) { - if (typeof(matrix[k]) == "undefined") { - matrix[k] = []; - } - var matrixrow = matrix[k]; - for (var l = firstAvailCol; l < firstAvailCol + colSpan; l++) { - matrixrow[l] = "x"; - } - } - } - } - return lookup; - } - - function checkCellColSpan(table, rows, row) { - var arr = [], - r = table.tHead.rows, - c = r[row].cells; - - for (var i = 0; i < c.length; i++) { - var cell = c[i]; - - if (cell.colSpan > 1) { - arr = arr.concat(checkCellColSpan(table, headerArr, row++)); - } else { - if (table.tHead.length == 1 || (cell.rowSpan > 1 || !r[row + 1])) { - arr.push(cell); - } - // headerArr[row] = (i+row); - } - } - return arr; - }; - - function checkHeaderMetadata(cell) { - if (($.metadata) && ($(cell).metadata().sorter === false)) { - return true; - }; - return false; - } - - function checkHeaderOptions(table, i) { - if ((table.config.headers[i]) && (table.config.headers[i].sorter === false)) { - return true; - }; - return false; - } - - function checkHeaderOptionsSortingLocked(table, i) { - if ((table.config.headers[i]) && (table.config.headers[i].lockedOrder)) return table.config.headers[i].lockedOrder; - return false; - } - - function applyWidget(table) { - var c = table.config.widgets; - var l = c.length; - for (var i = 0; i < l; i++) { - - getWidgetById(c[i]).format(table); - } - - } - - function getWidgetById(name) { - var l = widgets.length; - for (var i = 0; i < l; i++) { - if (widgets[i].id.toLowerCase() == name.toLowerCase()) { - return widgets[i]; - } - } - }; - - function formatSortingOrder(v) { - if (typeof(v) != "Number") { - return (v.toLowerCase() == "desc") ? 1 : 0; - } else { - return (v == 1) ? 1 : 0; - } - } - - function isValueInArray(v, a) { - var l = a.length; - for (var i = 0; i < l; i++) { - if (a[i][0] == v) { - return true; - } - } - return false; - } - - function setHeadersCss(table, $headers, list, css) { - // remove all header information - $headers.removeClass(css[0]).removeClass(css[1]); - - var h = []; - $headers.each(function (offset) { - if (!this.sortDisabled) { - h[this.column] = $(this); - } - }); - - var l = list.length; - for (var i = 0; i < l; i++) { - h[list[i][0]].addClass(css[list[i][1]]); - } - } - - function fixColumnWidth(table, $headers) { - var c = table.config; - if (c.widthFixed) { - var colgroup = $(''); - $("tr:first td", table.tBodies[0]).each(function () { - colgroup.append($('').css('width', $(this).width())); - }); - $(table).prepend(colgroup); - }; - } - - function updateHeaderSortCount(table, sortList) { - var c = table.config, - l = sortList.length; - for (var i = 0; i < l; i++) { - var s = sortList[i], - o = c.headerList[s[0]]; - o.count = s[1]; - o.count++; - } - } - - /* sorting methods */ - - function multisort(table, sortList, cache) { - - if (table.config.debug) { - var sortTime = new Date(); - } - - var dynamicExp = "var sortWrapper = function(a,b) {", - l = sortList.length; - - // TODO: inline functions. - for (var i = 0; i < l; i++) { - - var c = sortList[i][0]; - var order = sortList[i][1]; - // var s = (getCachedSortType(table.config.parsers,c) == "text") ? - // ((order == 0) ? "sortText" : "sortTextDesc") : ((order == 0) ? - // "sortNumeric" : "sortNumericDesc"); - // var s = (table.config.parsers[c].type == "text") ? ((order == 0) - // ? makeSortText(c) : makeSortTextDesc(c)) : ((order == 0) ? - // makeSortNumeric(c) : makeSortNumericDesc(c)); - var s = (table.config.parsers[c].type == "text") ? ((order == 0) ? makeSortFunction("text", "asc", c) : makeSortFunction("text", "desc", c)) : ((order == 0) ? makeSortFunction("numeric", "asc", c) : makeSortFunction("numeric", "desc", c)); - var e = "e" + i; - - dynamicExp += "var " + e + " = " + s; // + "(a[" + c + "],b[" + c - // + "]); "; - dynamicExp += "if(" + e + ") { return " + e + "; } "; - dynamicExp += "else { "; - - } - - // if value is the same keep orignal order - var orgOrderCol = cache.normalized[0].length - 1; - dynamicExp += "return a[" + orgOrderCol + "]-b[" + orgOrderCol + "];"; - - for (var i = 0; i < l; i++) { - dynamicExp += "}; "; - } - - dynamicExp += "return 0; "; - dynamicExp += "}; "; - - if (table.config.debug) { - benchmark("Evaling expression:" + dynamicExp, new Date()); - } - - eval(dynamicExp); - - cache.normalized.sort(sortWrapper); - - if (table.config.debug) { - benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time:", sortTime); - } - - return cache; - }; - - function makeSortFunction(type, direction, index) { - var a = "a[" + index + "]", - b = "b[" + index + "]"; - if (type == 'text' && direction == 'asc') { - return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + a + " < " + b + ") ? -1 : 1 )));"; - } else if (type == 'text' && direction == 'desc') { - return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + b + " < " + a + ") ? -1 : 1 )));"; - } else if (type == 'numeric' && direction == 'asc') { - return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + a + " - " + b + "));"; - } else if (type == 'numeric' && direction == 'desc') { - return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + b + " - " + a + "));"; - } - }; - - function makeSortText(i) { - return "((a[" + i + "] < b[" + i + "]) ? -1 : ((a[" + i + "] > b[" + i + "]) ? 1 : 0));"; - }; - - function makeSortTextDesc(i) { - return "((b[" + i + "] < a[" + i + "]) ? -1 : ((b[" + i + "] > a[" + i + "]) ? 1 : 0));"; - }; - - function makeSortNumeric(i) { - return "a[" + i + "]-b[" + i + "];"; - }; - - function makeSortNumericDesc(i) { - return "b[" + i + "]-a[" + i + "];"; - }; - - function sortText(a, b) { - if (table.config.sortLocaleCompare) return a.localeCompare(b); - return ((a < b) ? -1 : ((a > b) ? 1 : 0)); - }; - - function sortTextDesc(a, b) { - if (table.config.sortLocaleCompare) return b.localeCompare(a); - return ((b < a) ? -1 : ((b > a) ? 1 : 0)); - }; - - function sortNumeric(a, b) { - return a - b; - }; - - function sortNumericDesc(a, b) { - return b - a; - }; - - function getCachedSortType(parsers, i) { - return parsers[i].type; - }; /* public methods */ - this.construct = function (settings) { - return this.each(function () { - // if no thead or tbody quit. - if (!this.tHead || !this.tBodies) return; - // declare - var $this, $document, $headers, cache, config, shiftDown = 0, - sortOrder; - // new blank config object - this.config = {}; - // merge and extend. - config = $.extend(this.config, $.tablesorter.defaults, settings); - // store common expression for speed - $this = $(this); - // save the settings where they read - $.data(this, "tablesorter", config); - // build headers - $headers = buildHeaders(this); - // try to auto detect column type, and store in tables config - this.config.parsers = buildParserCache(this, $headers); - // build the cache for the tbody cells - cache = buildCache(this); - // get the css class names, could be done else where. - var sortCSS = [config.cssDesc, config.cssAsc]; - // fixate columns if the users supplies the fixedWidth option - fixColumnWidth(this); - // apply event handling to headers - // this is to big, perhaps break it out? - $headers.click( - - function (e) { - var totalRows = ($this[0].tBodies[0] && $this[0].tBodies[0].rows.length) || 0; - if (!this.sortDisabled && totalRows > 0) { - // Only call sortStart if sorting is - // enabled. - $this.trigger("sortStart"); - // store exp, for speed - var $cell = $(this); - // get current column index - var i = this.column; - // get current column sort order - this.order = this.count++ % 2; - // always sort on the locked order. - if(this.lockedOrder) this.order = this.lockedOrder; - - // user only whants to sort on one - // column - if (!e[config.sortMultiSortKey]) { - // flush the sort list - config.sortList = []; - if (config.sortForce != null) { - var a = config.sortForce; - for (var j = 0; j < a.length; j++) { - if (a[j][0] != i) { - config.sortList.push(a[j]); - } - } - } - // add column to sort list - config.sortList.push([i, this.order]); - // multi column sorting - } else { - // the user has clicked on an all - // ready sortet column. - if (isValueInArray(i, config.sortList)) { - // revers the sorting direction - // for all tables. - for (var j = 0; j < config.sortList.length; j++) { - var s = config.sortList[j], - o = config.headerList[s[0]]; - if (s[0] == i) { - o.count = s[1]; - o.count++; - s[1] = o.count % 2; - } - } - } else { - // add column to sort list array - config.sortList.push([i, this.order]); - } - }; - setTimeout(function () { - // set css for headers - setHeadersCss($this[0], $headers, config.sortList, sortCSS); - appendToTable( - $this[0], multisort( - $this[0], config.sortList, cache) - ); - }, 1); - // stop normal event by returning false - return false; - } - // cancel selection - }).mousedown(function () { - if (config.cancelSelection) { - this.onselectstart = function () { - return false - }; - return false; - } - }); - // apply easy methods that trigger binded events - $this.bind("update", function () { - var me = this; - setTimeout(function () { - // rebuild parsers. - me.config.parsers = buildParserCache( - me, $headers); - // rebuild the cache map - cache = buildCache(me); - }, 1); - }).bind("updateCell", function (e, cell) { - var config = this.config; - // get position from the dom. - var pos = [(cell.parentNode.rowIndex - 1), cell.cellIndex]; - // update cache - cache.normalized[pos[0]][pos[1]] = config.parsers[pos[1]].format( - getElementText(config, cell), cell); - }).bind("sorton", function (e, list) { - $(this).trigger("sortStart"); - config.sortList = list; - // update and store the sortlist - var sortList = config.sortList; - // update header count index - updateHeaderSortCount(this, sortList); - // set css for headers - setHeadersCss(this, $headers, sortList, sortCSS); - // sort the table and append it to the dom - appendToTable(this, multisort(this, sortList, cache)); - }).bind("appendCache", function () { - appendToTable(this, cache); - }).bind("applyWidgetId", function (e, id) { - getWidgetById(id).format(this); - }).bind("applyWidgets", function () { - // apply widgets - applyWidget(this); - }); - if ($.metadata && ($(this).metadata() && $(this).metadata().sortlist)) { - config.sortList = $(this).metadata().sortlist; - } - // if user has supplied a sort list to constructor. - if (config.sortList.length > 0) { - $this.trigger("sorton", [config.sortList]); - } - // apply widgets - applyWidget(this); - }); - }; - this.addParser = function (parser) { - var l = parsers.length, - a = true; - for (var i = 0; i < l; i++) { - if (parsers[i].id.toLowerCase() == parser.id.toLowerCase()) { - a = false; - } - } - if (a) { - parsers.push(parser); - }; - }; - this.addWidget = function (widget) { - widgets.push(widget); - }; - this.formatFloat = function (s) { - var i = parseFloat(s); - return (isNaN(i)) ? 0 : i; - }; - this.formatInt = function (s) { - var i = parseInt(s); - return (isNaN(i)) ? 0 : i; - }; - this.isDigit = function (s, config) { - // replace all an wanted chars and match. - return /^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g, ''))); - }; - this.clearTableBody = function (table) { - if ($.browser.msie) { - function empty() { - while (this.firstChild) - this.removeChild(this.firstChild); - } - empty.apply(table.tBodies[0]); - } else { - table.tBodies[0].innerHTML = ""; - } - }; - } - }); - - // extend plugin scope - $.fn.extend({ - tablesorter: $.tablesorter.construct - }); - - // make shortcut - var ts = $.tablesorter; - - // add default parsers - ts.addParser({ - id: "text", - is: function (s) { - return true; - }, format: function (s) { - return $.trim(s.toLocaleLowerCase()); - }, type: "text" - }); - - ts.addParser({ - id: "digit", - is: function (s, table) { - var c = table.config; - return $.tablesorter.isDigit(s, c); - }, format: function (s) { - return $.tablesorter.formatFloat(s); - }, type: "numeric" - }); - - ts.addParser({ - id: "currency", - is: function (s) { - return /^[£$€?.]/.test(s); - }, format: function (s) { - return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g), "")); - }, type: "numeric" - }); - - ts.addParser({ - id: "ipAddress", - is: function (s) { - return /^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s); - }, format: function (s) { - var a = s.split("."), - r = "", - l = a.length; - for (var i = 0; i < l; i++) { - var item = a[i]; - if (item.length == 2) { - r += "0" + item; - } else { - r += item; - } - } - return $.tablesorter.formatFloat(r); - }, type: "numeric" - }); - - ts.addParser({ - id: "url", - is: function (s) { - return /^(https?|ftp|file):\/\/$/.test(s); - }, format: function (s) { - return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//), '')); - }, type: "text" - }); - - ts.addParser({ - id: "isoDate", - is: function (s) { - return /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s); - }, format: function (s) { - return $.tablesorter.formatFloat((s != "") ? new Date(s.replace( - new RegExp(/-/g), "/")).getTime() : "0"); - }, type: "numeric" - }); - - ts.addParser({ - id: "percent", - is: function (s) { - return /\%$/.test($.trim(s)); - }, format: function (s) { - return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g), "")); - }, type: "numeric" - }); - - ts.addParser({ - id: "usLongDate", - is: function (s) { - return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/)); - }, format: function (s) { - return $.tablesorter.formatFloat(new Date(s).getTime()); - }, type: "numeric" - }); - - ts.addParser({ - id: "shortDate", - is: function (s) { - return /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s); - }, format: function (s, table) { - var c = table.config; - s = s.replace(/\-/g, "/"); - if (c.dateFormat == "us") { - // reformat the string in ISO format - s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$1/$2"); - } else if (c.dateFormat == "uk") { - // reformat the string in ISO format - s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$2/$1"); - } else if (c.dateFormat == "dd/mm/yy" || c.dateFormat == "dd-mm-yy") { - s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/, "$1/$2/$3"); - } - return $.tablesorter.formatFloat(new Date(s).getTime()); - }, type: "numeric" - }); - ts.addParser({ - id: "time", - is: function (s) { - return /^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s); - }, format: function (s) { - return $.tablesorter.formatFloat(new Date("2000/01/01 " + s).getTime()); - }, type: "numeric" - }); - ts.addParser({ - id: "metadata", - is: function (s) { - return false; - }, format: function (s, table, cell) { - var c = table.config, - p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName; - return $(cell).metadata()[p]; - }, type: "numeric" - }); - // add default widgets - ts.addWidget({ - id: "zebra", - format: function (table) { - if (table.config.debug) { - var time = new Date(); - } - var $tr, row = -1, - odd; - // loop through the visible rows - $("tr:visible", table.tBodies[0]).each(function (i) { - $tr = $(this); - // style children rows the same way the parent - // row was styled - if (!$tr.hasClass(table.config.cssChildRow)) row++; - odd = (row % 2 == 0); - $tr.removeClass( - table.config.widgetZebra.css[odd ? 0 : 1]).addClass( - table.config.widgetZebra.css[odd ? 1 : 0]) - }); - if (table.config.debug) { - $.tablesorter.benchmark("Applying Zebra widget", time); - } - } - }); -})(jQuery); \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway.ess40/js/jquery.tablesorter.min.js b/java/com.sap.sailing.server.gateway.ess40/js/jquery.tablesorter.min.js deleted file mode 100644 index f81aafb6e4d..00000000000 --- a/java/com.sap.sailing.server.gateway.ess40/js/jquery.tablesorter.min.js +++ /dev/null @@ -1,4 +0,0 @@ - -(function($){$.extend({tablesorter:new -function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",cssChildRow:"expand-child",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,sortLocaleCompare:true,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'/\.|\,/g',onRenderHeader:null,selectorHeaders:'thead th',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}if(table.tBodies.length==0)return;var rows=table.tBodies[0].rows;if(rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function checkHeaderOptionsSortingLocked(table,i){if((table.config.headers[i])&&(table.config.headers[i].lockedOrder))return table.config.headers[i].lockedOrder;return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i b["+i+"]) ? 1 : 0));";};function makeSortTextDesc(i){return"((b["+i+"] < a["+i+"]) ? -1 : ((b["+i+"] > a["+i+"]) ? 1 : 0));";};function makeSortNumeric(i){return"a["+i+"]-b["+i+"];";};function makeSortNumericDesc(i){return"b["+i+"]-a["+i+"];";};function sortText(a,b){if(table.config.sortLocaleCompare)return a.localeCompare(b);return((ab)?1:0));};function sortTextDesc(a,b){if(table.config.sortLocaleCompare)return b.localeCompare(a);return((ba)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$.data(this,"tablesorter",config);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){$this.trigger("sortStart");var $cell=$(this);var i=this.column;this.order=this.count++%2;if(this.lockedOrder)this.order=this.lockedOrder;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i - - 4.0.0 - - root - com.sap.sailing - 1.0.0-SNAPSHOT - - com.sap.sailing.server.gateway.ess40 - eclipse-plugin - diff --git a/java/com.sap.sailing.server.gateway.ess40/src/com/sap/sailing/server/gateway/ess40/ESS40ResultsAsCSVServlet.java b/java/com.sap.sailing.server.gateway.ess40/src/com/sap/sailing/server/gateway/ess40/ESS40ResultsAsCSVServlet.java deleted file mode 100644 index 64cab6d02c9..00000000000 --- a/java/com.sap.sailing.server.gateway.ess40/src/com/sap/sailing/server/gateway/ess40/ESS40ResultsAsCSVServlet.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.sap.sailing.server.gateway.ess40; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.RaceColumn; -import com.sap.sailing.domain.common.MaxPointsReason; -import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.server.RacingEventService; -import com.sap.sailing.server.gateway.AbstractCSVHttpServlet; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -/** - * Exports leaderboards of the ESS40 Series to a specific .csv format which is used by a native iOS app to show them. - * Sample csv files can be found at http://www.extremesailingseries.com/app/results/csv_uploads/ - * - * @author Frank - * - */ -public class ESS40ResultsAsCSVServlet extends AbstractCSVHttpServlet { - private static final long serialVersionUID = -3975664653148197608L; - private static final String PARAM_NAME_LEADERBOARDNAME = "leaderboardName"; - private static final String PARAM_NAME_RESULTSTATE = "resultState"; - private enum ResultStates { Live, Preliminary, Final }; - private final ResultStates DEFAULT_RESULT_STATE = ResultStates.Final; - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - RacingEventService racingEventService = getService(); - String pathInfo = request.getPathInfo(); - String leaderboardNameParam = request.getParameter(PARAM_NAME_LEADERBOARDNAME); - - if (leaderboardNameParam == null) { - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - "You need to specify a leaderboard name using the " + PARAM_NAME_LEADERBOARDNAME + " parameter"); - return; - } - - Leaderboard leaderboard = racingEventService.getLeaderboardByName(leaderboardNameParam); - - if (pathInfo.equals("/leaderboard") && leaderboard != null) { - try { - String fileName = leaderboardNameParam + ".csv"; - List> csv = new ArrayList>(); - - setCSVResponseHeader(response, fileName); - - ResultStates resultState = resolveRequestedResultState(request.getParameter(PARAM_NAME_RESULTSTATE)); - TimePoint requestTimePoint = MillisecondsTimePoint.now(); - TimePoint resultTimePoint = calculateTimePointForResultState(leaderboard, resultState); - - // competitors are ordered according to total rank - List suppressedCompetitors = new ArrayList(); - for (Competitor c : leaderboard.getSuppressedCompetitors()) { - suppressedCompetitors.add(c); - } - List competitorsFromBestToWorst = leaderboard.getCompetitorsFromBestToWorst(resultTimePoint != null ? resultTimePoint : requestTimePoint); - Map> rankedCompetitorsPerColumn = new HashMap>(); - Iterable raceColumns = leaderboard.getRaceColumns(); - - if(resultTimePoint != null) { - for (Competitor competitor : competitorsFromBestToWorst) { - if (!suppressedCompetitors.contains(competitor)) { - // int totalRank = competitorsFromBestToWorst.indexOf(competitor) + 1; - Double totalPoints = leaderboard.getTotalPoints(competitor, resultTimePoint); - if (leaderboard.hasCarriedPoints(competitor)) { - Double carriedPoints = leaderboard.getCarriedPoints(competitor); - if (carriedPoints != null) { - totalPoints = totalPoints != null ? totalPoints += carriedPoints : carriedPoints; - } - } - - List csvLine = new ArrayList(); - String competitorDisplayName = leaderboard.getDisplayName(competitor); - csvLine.add(competitorDisplayName != null ? competitorDisplayName : competitor.getName()); - csv.add(csvLine); - - // TODO: we should only export complete races where all competitors have valid totalPoints for a race - for (RaceColumn raceColumn : raceColumns) { - List rankedCompetitorsForColumn = rankedCompetitorsPerColumn.get(raceColumn); - if (rankedCompetitorsForColumn == null) { - rankedCompetitorsForColumn = leaderboard.getCompetitorsFromBestToWorst(raceColumn, - resultTimePoint); - rankedCompetitorsPerColumn.put(raceColumn, rankedCompetitorsForColumn); - } - // Double netRacePoints = leaderboard.getNetPoints(competitor, raceColumn, timePoint); - Double totalRacePoints = leaderboard.getTotalPoints(competitor, raceColumn, resultTimePoint); - MaxPointsReason maxPointsReason = leaderboard.getMaxPointsReason(competitor, raceColumn, resultTimePoint); - int rank = rankedCompetitorsForColumn.indexOf(competitor)+1; - if(totalRacePoints != null) { - if (maxPointsReason == null || maxPointsReason == MaxPointsReason.NONE) { - csvLine.add(rank); - csvLine.add(totalRacePoints); - - } else { - csvLine.add(maxPointsReason.name()); - csvLine.add(totalRacePoints); - } - } else { - /* Make sure to also include null columns */ - csvLine.add(0); - csvLine.add(0); - } - } - } - } - } else { - for (Competitor competitor : competitorsFromBestToWorst) { - if (!suppressedCompetitors.contains(competitor)) { - List csvLine = new ArrayList(); - String competitorDisplayName = leaderboard.getDisplayName(competitor); - csvLine.add(competitorDisplayName != null ? competitorDisplayName : competitor.getName()); - csv.add(csvLine); - } - } - } - writeCsv(csv, ',', true, response.getOutputStream()); - } catch (Exception e) { - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error during leaderboard export"); - } - } else { - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error during leaderboard export"); - } - } - - @Override - protected void writeCsv (List> csv, char separator, boolean quoteStrings, OutputStream output) throws IOException { - DecimalFormat df = new DecimalFormat("0.##"); - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, "UTF-8")); - for (List row : csv) { - StringBuilder line = new StringBuilder(); - int column = 1; - for (Iterator iter = row.iterator(); iter.hasNext();) { - T fieldObject = iter.next(); - String field = String.valueOf(fieldObject).replace("\"", "\"\""); - if(fieldObject instanceof String && column == 1) { - field = '"' + field + '"'; - } else if(fieldObject instanceof Double) { - field = df.format(fieldObject); - } - line.append(field); - if (iter.hasNext()) { - line.append(separator); - } - column++; - } - writer.write(line.toString()); - writer.newLine(); - } - writer.flush(); - } - - private ResultStates resolveRequestedResultState(String resultStateParam) { - ResultStates result = DEFAULT_RESULT_STATE; - if(resultStateParam != null) { - for(ResultStates state: ResultStates.values()) { - if(state.name().equalsIgnoreCase(resultStateParam)) { - result = state; - break; - } - } - } - return result; - } - - private TimePoint calculateTimePointForResultState(Leaderboard leaderboard, ResultStates resultState) { - TimePoint result = null; - switch (resultState) { - case Live: - result = MillisecondsTimePoint.now(); - break; - case Preliminary: - case Final: - if(leaderboard.getScoreCorrection() != null) { - result = leaderboard.getScoreCorrection().getTimePointOfLastCorrectionsValidity(); - } - break; - } - - return result; - } -} diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/impl/WindJsonGetServletRefactoringTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/impl/WindJsonGetServletRefactoringTest.java deleted file mode 100755 index cf143018ba9..00000000000 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/impl/WindJsonGetServletRefactoringTest.java +++ /dev/null @@ -1,161 +0,0 @@ -package com.sap.sailing.server.gateway.impl; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.argThat; -import static org.mockito.Matchers.isA; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.stub; -import static org.mockito.Mockito.when; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import org.hamcrest.Matcher; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentMatcher; - -import com.sap.sailing.domain.base.RaceDefinition; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.Wind; -import com.sap.sailing.domain.common.WindSource; -import com.sap.sailing.domain.common.WindSourceType; -import com.sap.sailing.domain.common.impl.DegreeBearingImpl; -import com.sap.sailing.domain.common.impl.DegreePosition; -import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; -import com.sap.sailing.domain.common.impl.WindImpl; -import com.sap.sailing.domain.common.impl.WindSourceImpl; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.domain.tracking.WindTrack; -import com.sap.sailing.domain.tracking.impl.CombinedWindTrackImpl; -import com.sap.sailing.domain.tracking.impl.WindTrackImpl; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -/** - * After refactoring the {@link WindJsonGetServlet} to first copy the relevant fixes to hold the read lock only - * for a short time, this test uses the old implementation to compute the expected result and compares the - * output received from the servlet. - * - * @author Axel Uhl (d043530) - * - */ -public class WindJsonGetServletRefactoringTest { - private WindJsonGetServlet servlet; - - @Before - public void setUp() { - servlet = new WindJsonGetServlet(); - } - - private JSONObject getExpectedResult(String windSourceToRead, TrackedRace trackedRace, TimePoint from, TimePoint to) { - JSONObject jsonWindTracks = new JSONObject(); - List windSources = servlet.getAvailableWindSources(trackedRace); - JSONArray jsonWindSourcesDisplayed = new JSONArray(); - for (WindSource windSource : windSources) { - JSONObject windSourceInformation = new JSONObject(); - windSourceInformation.put("typeName", windSource.getType().name()); - windSourceInformation.put("id", windSource.getId() != null ? windSource.getId().toString() : ""); - jsonWindSourcesDisplayed.add(windSourceInformation); - } - jsonWindTracks.put("availableWindSources", jsonWindSourcesDisplayed); - for (WindSource windSource : windSources) { - if("ALL".equals(windSourceToRead) || windSource.getType().name().equalsIgnoreCase(windSourceToRead)) { - JSONArray jsonWindArray = new JSONArray(); - WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource); - windTrack.lockForRead(); - try { - Iterator windIter = windTrack.getFixesIterator(from, /* inclusive */true); - while (windIter.hasNext()) { - Wind wind = windIter.next(); - if (wind.getTimePoint().compareTo(to) > 0) { - break; - } - JSONObject jsonWind = new JSONObject(); - jsonWind.put("truebearingdeg", wind.getBearing().getDegrees()); - jsonWind.put("knotspeed", wind.getKnots()); - jsonWind.put("meterspersecondspeed", wind.getMetersPerSecond()); - if (wind.getTimePoint() != null) { - jsonWind.put("timepoint", wind.getTimePoint().asMillis()); - jsonWind.put("dampenedtruebearingdeg", - windTrack.getAveragedWind(wind.getPosition(), wind.getTimePoint()) - .getBearing().getDegrees()); - jsonWind.put("dampenedknotspeed", - windTrack.getAveragedWind(wind.getPosition(), wind.getTimePoint()).getKnots()); - jsonWind.put("dampenedmeterspersecondspeed", - windTrack.getAveragedWind(wind.getPosition(), wind.getTimePoint()) - .getMetersPerSecond()); - } - if (wind.getPosition() != null) { - jsonWind.put("latdeg", wind.getPosition().getLatDeg()); - jsonWind.put("lngdeg", wind.getPosition().getLngDeg()); - } - jsonWindArray.add(jsonWind); - } - } finally { - windTrack.unlockAfterRead(); - } - jsonWindTracks.put(windSource.toString()+(windSource.getId()!=null ? "-"+windSource.getId():""), jsonWindArray); - } - } - return jsonWindTracks; - } - - @Test - public void compareOldAndNewImplementation() { - TrackedRace trackedRace = mock(TrackedRace.class); - final WindSourceImpl webWindSource = new WindSourceImpl(WindSourceType.WEB); - when(trackedRace.getWindSources()).thenReturn(new HashSet<>(Arrays.asList(new WindSource[] { webWindSource }))); - RaceDefinition race = mock(RaceDefinition.class); - when(race.getName()).thenReturn("Race Name"); - when(trackedRace.getRace()).thenReturn(race); - Set noWindSourcesToExclude = Collections.emptySet(); - when(trackedRace.getWindSourcesToExclude()).thenReturn(noWindSourcesToExclude); - WindTrackImpl webWindTrack = new WindTrackImpl(/* millisecondsOverWhichToAverage */ 30000, /* useSpeed */ true, /* nameForReadWriteLock */ "compareOldAndNewImplementation"); - when(trackedRace.getOrCreateWindTrack(webWindSource)).thenReturn(webWindTrack); - WindSourceImpl combinedWindSource = new WindSourceImpl(WindSourceType.COMBINED); - CombinedWindTrackImpl combinedWindTrack = new CombinedWindTrackImpl(trackedRace, /* baseConfidence */ 0.5); - when(trackedRace.getOrCreateWindTrack(combinedWindSource)).thenReturn(combinedWindTrack); - final TimePoint now = MillisecondsTimePoint.now(); - final TimePoint earlier = now.minus(10000); - final TimePoint later = now.plus(10000); - final Matcher isTooEarlyOrTooLate = new ArgumentMatcher() { - @Override - public boolean matches(Object item) { - return item instanceof TimePoint && - ((TimePoint) item).before(earlier); - } - }; - final Matcher isNeitherTooEarlyNorTooLate = new ArgumentMatcher() { - @Override - public boolean matches(Object argument) { - return !isTooEarlyOrTooLate.matches(argument); - } - }; - stub(trackedRace.getWind(isA(Position.class), argThat(isTooEarlyOrTooLate))).toReturn(null); - Wind someCombinedWindFix = new WindImpl(new DegreePosition(50, 4), now, - new KnotSpeedWithBearingImpl(15, new DegreeBearingImpl(234))); - stub(trackedRace.getWind(isA(Position.class), argThat(isNeitherTooEarlyNorTooLate))).toReturn(someCombinedWindFix); - webWindTrack.add(new WindImpl(new DegreePosition(49, 3), now, - new KnotSpeedWithBearingImpl(12, new DegreeBearingImpl(123)))); - System.out.println(getExpectedResult(WindJsonGetServlet.ALL, trackedRace, earlier, later)); - assertWindSourcesEquals(getExpectedResult(WindJsonGetServlet.ALL, trackedRace, earlier, later), - servlet.getResult(WindJsonGetServlet.ALL, "", trackedRace, earlier, later)); - } - - private void assertWindSourcesEquals(JSONObject expectedResult, JSONObject result) { - Set expectedWindSourceNames = ((JSONArray) expectedResult.get("availableWindSources")).stream().map(o->o.toString()).collect(Collectors.toSet()); - Set actualWindSourceNames = ((JSONArray) result.get("availableWindSources")).stream().map(o->o.toString()).collect(Collectors.toSet()); - assertEquals(expectedWindSourceNames, actualWindSourceNames); - for (String windSourceName : expectedWindSourceNames) { - assertEquals(expectedResult.get(windSourceName), result.get(windSourceName)); - } - } -} diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/RegattasJsonExportTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/RegattasJsonExportTest.java deleted file mode 100644 index e2988cf6570..00000000000 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/RegattasJsonExportTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.sap.sailing.server.gateway.test; - -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.List; -import java.util.UUID; - -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.json.simple.JSONValue; -import org.junit.Before; -import org.junit.Test; - -import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.base.Fleet; -import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sailing.domain.ranking.OneDesignRankingMetric; -import com.sap.sailing.server.gateway.impl.RegattasJsonGetServlet; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public class RegattasJsonExportTest extends AbstractJsonExportTest { - private String boatClassName = "49er"; - private String regattaName = "TestRegatta"; - - @Before - public void setUp() { - super.setUp(); - List series = new ArrayList(); - List fleets = new ArrayList(); - List raceColumnNames = new ArrayList(); - fleets.add(new FleetImpl("Fleet1")); - fleets.add(new FleetImpl("Fleet2")); - Series testSeries = new SeriesImpl("TestSeries", /* isMedal */false, fleets, - raceColumnNames, /* trackedRegattaRegistry */null); - series.add(testSeries); - final Calendar cal = new GregorianCalendar(); - cal.set(2014, 5, 6, 10, 00); - final TimePoint startDate = new MillisecondsTimePoint(cal.getTime()); - cal.set(2014, 5, 8, 16, 00); - final TimePoint endDate = new MillisecondsTimePoint(cal.getTime()); - racingEventService.createRegatta(RegattaImpl.getDefaultName(regattaName, boatClassName), boatClassName, startDate, endDate, - UUID.randomUUID(), series, /*persistent*/ true, - DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); - testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null); - testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null); - } - - @Test - public void testExportRegattasAsJson() throws Exception { - String jsonString = callJsonHttpServlet(new RegattasJsonGetServlet(), "GET", null); - - Object obj= JSONValue.parse(jsonString); - JSONArray array= (JSONArray) obj; - - assertTrue(array.size() == 1); - - JSONObject firstElement = (JSONObject) array.get(0); - String jsonName = (String) firstElement.get("name"); - String jsonBoatClass = (String) firstElement.get("boatclass"); - - assertTrue(RegattaImpl.getDefaultName(regattaName, boatClassName).equals(jsonName)); - assertTrue(boatClassName.equals(jsonBoatClass)); - } - -} diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/AbstractJaxRsApiTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/AbstractJaxRsApiTest.java index 003eb5d0197..96928919c5c 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/AbstractJaxRsApiTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/AbstractJaxRsApiTest.java @@ -57,6 +57,14 @@ public abstract class AbstractJaxRsApiTest { return new String(entity, "UTF-8"); } + protected TimePoint parseTimepointFromJsonNumber(Long timePointAsJsonNumber) throws ParseException { + TimePoint result = null; + if(timePointAsJsonNumber != null) { + result = new MillisecondsTimePoint(timePointAsJsonNumber); + } + return result; + } + protected TimePoint parseTimepointFromJsonString(String timePointAsJsonString) throws ParseException { TimePoint result = null; if(timePointAsJsonString != null && !timePointAsJsonString.isEmpty()) { @@ -71,7 +79,7 @@ public abstract class AbstractJaxRsApiTest { BoatClass boatClass = new BoatClassImpl("505", /* typicallyStartsUpwind */ true); for (int i = 1; i <= numberOfCompetitorsToCreate; i++) { String competitorName = "C" + i; - Competitor competitor = new CompetitorImpl(123, competitorName, Color.RED, null, null, new TeamImpl("STG", Collections.singleton( + Competitor competitor = new CompetitorImpl(new Integer(i), competitorName, Color.RED, null, null, new TeamImpl("STG", Collections.singleton( new PersonImpl(competitorName, new NationalityImpl("GER"), /* dateOfBirth */ null, "This is famous "+competitorName)), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/TeamImageTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/CompetitorsResourceTeamImageTest.java similarity index 98% rename from java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/TeamImageTest.java rename to java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/CompetitorsResourceTeamImageTest.java index a683b781e77..7c7892a8860 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/TeamImageTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/CompetitorsResourceTeamImageTest.java @@ -33,7 +33,7 @@ import com.sap.sse.filestorage.InvalidPropertiesException; import com.sap.sse.filestorage.OperationFailedException; import com.sap.sse.filestorage.testsupport.AmazonS3TestSupport; -public class TeamImageTest extends AbstractJaxRsApiTest { +public class CompetitorsResourceTeamImageTest extends AbstractJaxRsApiTest { private static final String name = "Heiko KRÖGER"; private static final String id = "af855a56-9726-4a9c-a77e-da955bd289be"; private static final String boatClassName = "49er"; diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/LeaderboardGroupJsonExportTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardGroupResourceTest.java similarity index 85% rename from java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/LeaderboardGroupJsonExportTest.java rename to java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardGroupResourceTest.java index 3a795f6f72c..c2f63c2009c 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/LeaderboardGroupJsonExportTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardGroupResourceTest.java @@ -1,4 +1,4 @@ -package com.sap.sailing.server.gateway.test; +package com.sap.sailing.server.gateway.test.jaxrs; import static org.junit.Assert.assertTrue; @@ -6,6 +6,8 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; +import javax.ws.rs.core.Response; + import org.json.simple.JSONArray; import org.json.simple.JSONValue; import org.junit.Before; @@ -25,11 +27,11 @@ import com.sap.sailing.domain.leaderboard.LeaderboardGroup; import com.sap.sailing.domain.ranking.OneDesignRankingMetric; import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.server.gateway.impl.LeaderboardGroupsJsonGetServlet; +import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardGroupsResource; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; -public class LeaderboardGroupJsonExportTest extends AbstractJsonExportTest { +public class LeaderboardGroupResourceTest extends AbstractJaxRsApiTest { private Regatta regatta; private LeaderboardGroup leaderboardGroup; private String boatClassName = "49er"; @@ -74,10 +76,15 @@ public class LeaderboardGroupJsonExportTest extends AbstractJsonExportTest { } @Test - public void testExportLeaderboardGroupsAsJson() throws Exception { - String jsonString = callJsonHttpServlet(new LeaderboardGroupsJsonGetServlet(), "GET", null); - Object obj = JSONValue.parse(jsonString); - JSONArray array = (JSONArray) obj; + public void testExportLeaderboardGroupsAsJson() throws Exception { + LeaderboardGroupsResource resource = new LeaderboardGroupsResource(); + LeaderboardGroupsResource spyResource = spyResource(resource); + Response leaderboardGroupsResponse = spyResource.getLeaderboardGroups(); + + String jsonString = (String) leaderboardGroupsResponse.getEntity(); + Object obj= JSONValue.parse(jsonString); + JSONArray array= (JSONArray) obj; + assertTrue(array.size() == 1); String jsonFirstLeaderboardGroup = (String) array.get(0); assertTrue(leaderboardGroup.getName().equals(jsonFirstLeaderboardGroup)); diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResourcePingMarkTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourcePingMarkTest.java similarity index 97% rename from java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResourcePingMarkTest.java rename to java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourcePingMarkTest.java index 805ec188b63..e378af1f371 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResourcePingMarkTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourcePingMarkTest.java @@ -1,4 +1,4 @@ -package com.sap.sailing.server.gateway.jaxrs.api; +package com.sap.sailing.server.gateway.test.jaxrs; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -29,7 +29,7 @@ import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.DeviceMapping; import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory; import com.sap.sailing.domain.ranking.OneDesignRankingMetric; -import com.sap.sailing.server.gateway.test.jaxrs.AbstractJaxRsApiTest; +import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardsResource; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.common.impl.MillisecondsTimePoint; diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/LeaderboardJsonExportTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceTest.java similarity index 52% rename from java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/LeaderboardJsonExportTest.java rename to java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceTest.java index e69d0b84d1d..509dbc22b1f 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/LeaderboardJsonExportTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceTest.java @@ -1,17 +1,16 @@ -package com.sap.sailing.server.gateway.test; +package com.sap.sailing.server.gateway.test.jaxrs; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.UUID; +import javax.ws.rs.core.Response; + import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.JSONValue; @@ -32,11 +31,11 @@ import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; import com.sap.sailing.domain.ranking.OneDesignRankingMetric; import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.server.gateway.impl.LeaderboardJsonGetServlet; +import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardsResource; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; -public class LeaderboardJsonExportTest extends AbstractJsonExportTest { +public class LeaderboardsResourceTest extends AbstractJaxRsApiTest { private Regatta regatta; private RegattaLeaderboard regattaLeaderboard; private String boatClassName = "49er"; @@ -69,8 +68,8 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest { TimePoint now = MillisecondsTimePoint.now(); RaceColumn r1Column = series.get(0).getRaceColumnByName("R1"); - TrackedRace r1Fleet1 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet1Competitors); - TrackedRace r1Fleet2 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet2Competitors); + TrackedRace r1Fleet1 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet1Competitors, regatta); + TrackedRace r1Fleet2 = new MockedTrackedRaceWithStartTimeAndRanks(now, fleet2Competitors, regatta); r1Column.setTrackedRace(r1Column.getFleetByName("Fleet1"), r1Fleet1); r1Column.setTrackedRace(r1Column.getFleetByName("Fleet2"), r1Fleet2); @@ -82,12 +81,10 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest { public void testExportEmptyLeaderboardAsJson() throws Exception { dropAndCreateRegatta(); - Map requestParameters = new HashMap<>(); - requestParameters.put("leaderboardName", regatta.getName()); - requestParameters.put("resultState", LeaderboardJsonGetServlet.ResultStates.Final.name()); - - String jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters); - + LeaderboardsResource resource = spyResource(new LeaderboardsResource()); + Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + String jsonString = (String) leaderboardReponse.getEntity(); Object obj= JSONValue.parse(jsonString); JSONObject jsonObject = (JSONObject) obj; @@ -95,10 +92,10 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest { String jsonResultState = (String) jsonObject.get("resultState"); assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); - assertTrue(LeaderboardJsonGetServlet.ResultStates.Final.name().equals(jsonResultState)); + assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); // resultTimepoint should be null if there are no 'final' results yet - TimePoint resultTimePoint = parseTimepointFromJsonString((String) jsonObject.get("resultTimepoint")); + TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); assertNull(resultTimePoint); JSONArray jsonCompetitors = (JSONArray) jsonObject.get("competitors"); @@ -109,12 +106,10 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest { public void testExportLeaderboardWithFinalResultStateAsJson() throws Exception { dropAndCreateRegatta(); - Map requestParameters = new HashMap<>(); - requestParameters.put("leaderboardName", regatta.getName()); - requestParameters.put("resultState", LeaderboardJsonGetServlet.ResultStates.Final.name()); - - String jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters); - + LeaderboardsResource resource = spyResource(new LeaderboardsResource()); + Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + String jsonString = (String) leaderboardReponse.getEntity(); Object obj= JSONValue.parse(jsonString); JSONObject jsonObject = (JSONObject) obj; @@ -122,18 +117,21 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest { String jsonResultState = (String) jsonObject.get("resultState"); assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); - assertTrue(LeaderboardJsonGetServlet.ResultStates.Final.name().equals(jsonResultState)); + assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); - TimePoint resultTimePoint = parseTimepointFromJsonString((String) jsonObject.get("resultTimepoint")); + TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); assertNull(resultTimePoint); - regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(MillisecondsTimePoint.now()); - jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters); + regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(MillisecondsTimePoint.now()); - obj= JSONValue.parse(jsonString); + LeaderboardsResource resource2 = spyResource(new LeaderboardsResource()); + Response leaderboardReponse2 = resource2.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + String jsonString2 = (String) leaderboardReponse2.getEntity(); + obj= JSONValue.parse(jsonString2); jsonObject = (JSONObject) obj; - resultTimePoint = parseTimepointFromJsonString((String) jsonObject.get("resultTimepoint")); + resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); assertNotNull(resultTimePoint); } @@ -141,12 +139,10 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest { public void testExportLeaderboardWithLiveResultStateAsJson() throws Exception { dropAndCreateRegatta(); - Map requestParameters = new HashMap<>(); - requestParameters.put("leaderboardName", regatta.getName()); - requestParameters.put("resultState", LeaderboardJsonGetServlet.ResultStates.Live.name()); - - String jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters); - + LeaderboardsResource resource = spyResource(new LeaderboardsResource()); + Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Live, null); + + String jsonString = (String) leaderboardReponse.getEntity(); Object obj= JSONValue.parse(jsonString); JSONObject jsonObject = (JSONObject) obj; @@ -154,85 +150,29 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest { String jsonResultState = (String) jsonObject.get("resultState"); assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); - assertTrue(LeaderboardJsonGetServlet.ResultStates.Live.name().equals(jsonResultState)); + assertTrue(LeaderboardsResource.ResultStates.Live.name().equals(jsonResultState)); - TimePoint resultTimePoint = parseTimepointFromJsonString((String) jsonObject.get("resultTimepoint")); + TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); assertNotNull(resultTimePoint); JSONArray jsonCompetitors = (JSONArray) jsonObject.get("competitors"); assertTrue(jsonCompetitors.size() == 4); } - - @Test - public void testBypassSmartFutureCacheInExportLeaderboardAsJson() throws Exception { - dropAndCreateRegatta(); - - Map requestParameters = new HashMap<>(); - requestParameters.put("leaderboardName", regatta.getName()); - requestParameters.put("resultState", LeaderboardJsonGetServlet.ResultStates.Live.name()); - requestParameters.put("useCache", "false"); - - regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(MillisecondsTimePoint.now()); - - Thread.sleep(100); - - // call the servlet for the first time - String jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters); - - Object obj= JSONValue.parse(jsonString); - JSONObject jsonObject = (JSONObject) obj; - - String jsonLeaderboardName = (String) jsonObject.get("name"); - String jsonResultState = (String) jsonObject.get("resultState"); - - assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); - assertTrue(LeaderboardJsonGetServlet.ResultStates.Live.name().equals(jsonResultState)); - - TimePoint resultTimePoint = parseTimepointFromJsonString((String) jsonObject.get("resultTimepoint")); - TimePoint requestTimepoint = parseTimepointFromJsonString((String) jsonObject.get("requestTimepoint")); - assertNotNull(resultTimePoint); - - regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(MillisecondsTimePoint.now()); - - Thread.sleep(100); - - // second call - it's expected NOT to get the same result as we bypass the cache - String jsonString2 = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters); - - Object obj2= JSONValue.parse(jsonString2); - JSONObject jsonObject2 = (JSONObject) obj2; - - String jsonLeaderboardName2 = (String) jsonObject2.get("name"); - String jsonResultState2 = (String) jsonObject2.get("resultState"); - - assertTrue(jsonLeaderboardName.equals(jsonLeaderboardName2)); - assertTrue(jsonResultState.equals(jsonResultState2)); - - TimePoint resultTimePoint2 = parseTimepointFromJsonString((String) jsonObject2.get("resultTimepoint")); - TimePoint requestTimepoint2 = parseTimepointFromJsonString((String) jsonObject2.get("requestTimepoint")); - assertNotNull(resultTimePoint2); - System.out.println("resultTimePoint: " + resultTimePoint.toString()); - System.out.println("resultTimePoint2: " + resultTimePoint2.toString()); - assertFalse(resultTimePoint.equals(resultTimePoint2)); - assertFalse(requestTimepoint.equals(requestTimepoint2)); - } @Test public void testSmartFutureCacheInExportLeaderboardAsJson() throws Exception { dropAndCreateRegatta(); - Map requestParameters = new HashMap<>(); - requestParameters.put("leaderboardName", regatta.getName()); - requestParameters.put("resultState", LeaderboardJsonGetServlet.ResultStates.Final.name()); - TimePoint now = MillisecondsTimePoint.now(); regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(now); Thread.sleep(100); // call the servlet for the first time - String jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters); - + LeaderboardsResource resource = spyResource(new LeaderboardsResource()); + Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + String jsonString = (String) leaderboardReponse.getEntity(); Object obj= JSONValue.parse(jsonString); JSONObject jsonObject = (JSONObject) obj; @@ -240,17 +180,18 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest { String jsonResultState = (String) jsonObject.get("resultState"); assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); - assertTrue(LeaderboardJsonGetServlet.ResultStates.Final.name().equals(jsonResultState)); + assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); - TimePoint resultTimePoint = parseTimepointFromJsonString((String) jsonObject.get("resultTimepoint")); - TimePoint requestTimepoint = parseTimepointFromJsonString((String) jsonObject.get("requestTimepoint")); + TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); assertNotNull(resultTimePoint); // second call - it's expected to get the same result from the cache Thread.sleep(100); - String jsonString2 = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters); - + LeaderboardsResource resource2 = spyResource(new LeaderboardsResource()); + Response leaderboardReponse2 = resource2.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + String jsonString2 = (String) leaderboardReponse2.getEntity(); Object obj2= JSONValue.parse(jsonString2); JSONObject jsonObject2 = (JSONObject) obj2; @@ -260,13 +201,9 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest { assertTrue(jsonLeaderboardName.equals(jsonLeaderboardName2)); assertTrue(jsonResultState.equals(jsonResultState2)); - TimePoint resultTimePoint2 = parseTimepointFromJsonString((String) jsonObject2.get("resultTimepoint")); - TimePoint requestTimepoint2 = parseTimepointFromJsonString((String) jsonObject2.get("requestTimepoint")); + TimePoint resultTimePoint2 = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); assertNotNull(resultTimePoint2); assertTrue(resultTimePoint.equals(resultTimePoint2)); - System.out.println("requestTimepoint: " + requestTimepoint.toString()); - System.out.println("requestTimepoint2: " + requestTimepoint2.toString()); - assertFalse(requestTimepoint.equals(requestTimepoint2)); } @Test @@ -274,12 +211,10 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest { dropAndCreateRegatta(); Integer maxCompetitorsCount = 2; - Map requestParameters = new HashMap<>(); - requestParameters.put("leaderboardName", regatta.getName()); - requestParameters.put("resultState", LeaderboardJsonGetServlet.ResultStates.Final.name()); - - String jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters); - + LeaderboardsResource resource = spyResource(new LeaderboardsResource()); + Response leaderboardReponse = resource.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + String jsonString = (String) leaderboardReponse.getEntity(); Object obj= JSONValue.parse(jsonString); JSONObject jsonObject = (JSONObject) obj; @@ -287,16 +222,18 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest { String jsonResultState = (String) jsonObject.get("resultState"); assertTrue(regattaLeaderboard.getName().equals(jsonLeaderboardName)); - assertTrue(LeaderboardJsonGetServlet.ResultStates.Final.name().equals(jsonResultState)); + assertTrue(LeaderboardsResource.ResultStates.Final.name().equals(jsonResultState)); - TimePoint resultTimePoint = parseTimepointFromJsonString((String) jsonObject.get("resultTimepoint")); + TimePoint resultTimePoint = parseTimepointFromJsonNumber((Long) jsonObject.get("resultTimepoint")); assertNull(resultTimePoint); regattaLeaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(MillisecondsTimePoint.now()); // first call for 'all' competitors - requestParameters.put("maxCompetitorsCount", null); - jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters); + LeaderboardsResource resource2 = spyResource(new LeaderboardsResource()); + Response leaderboardReponse2 = resource2.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + jsonString = (String) leaderboardReponse2.getEntity(); obj= JSONValue.parse(jsonString); jsonObject = (JSONObject) obj; JSONArray jsonCompetitors = (JSONArray) jsonObject.get("competitors"); @@ -305,8 +242,10 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest { Thread.sleep(100); // second call with maxCompetitorsCount set - requestParameters.put("maxCompetitorsCount", maxCompetitorsCount.toString()); - jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters); + LeaderboardsResource resource3 = spyResource(new LeaderboardsResource()); + Response leaderboardReponse3 = resource3.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, maxCompetitorsCount); + + jsonString = (String) leaderboardReponse3.getEntity(); obj= JSONValue.parse(jsonString); jsonObject = (JSONObject) obj; jsonCompetitors = (JSONArray) jsonObject.get("competitors"); @@ -315,8 +254,10 @@ public class LeaderboardJsonExportTest extends AbstractJsonExportTest { Thread.sleep(100); // third call for 'all' competitors - requestParameters.put("maxCompetitorsCount", null); - jsonString = callJsonHttpServlet(new LeaderboardJsonGetServlet(), "GET", requestParameters); + LeaderboardsResource resource4 = spyResource(new LeaderboardsResource()); + Response leaderboardReponse4 = resource4.getLeaderboard(regatta.getName(), LeaderboardsResource.ResultStates.Final, null); + + jsonString = (String) leaderboardReponse4.getEntity(); obj= JSONValue.parse(jsonString); jsonObject = (JSONObject) obj; jsonCompetitors = (JSONArray) jsonObject.get("competitors"); diff --git a/java/com.sap.sailing.server.gateway/WEB-INF/web.xml b/java/com.sap.sailing.server.gateway/WEB-INF/web.xml index e365210fa0f..fc3ef5cc826 100644 --- a/java/com.sap.sailing.server.gateway/WEB-INF/web.xml +++ b/java/com.sap.sailing.server.gateway/WEB-INF/web.xml @@ -39,16 +39,7 @@ INCLUDE ERROR - - - AdminApp - com.sap.sailing.server.gateway.impl.AdminApp - - - AdminApp - /admin - - + RCConfiguration com.sap.sailing.server.gateway.impl.rc.ConfigurationJsonGetServlet @@ -113,16 +104,6 @@ - - - RegattaAndRaceDataJsonGetServlet - com.sap.sailing.server.gateway.impl.RegattaAndRaceDataJsonGetServlet - - - RegattaAndRaceDataJsonGetServlet - /moderator - - ExpeditionThroughHttpPostServlet com.sap.sailing.server.gateway.impl.ExpeditionThroughHttpPostServlet @@ -132,86 +113,6 @@ /expedition - - EventsJsonGetServlet - com.sap.sailing.server.gateway.impl.EventsJsonGetServlet - - - EventsJsonGetServlet - /events - - - - EventRaceStatesServlet - com.sap.sailing.server.gateway.impl.EventRaceStatesJsonGetServlet - - - EventRaceStatesServlet - /eventRaceStates - - - - RegattasJsonGetServlet - com.sap.sailing.server.gateway.impl.RegattasJsonGetServlet - - - RegattasJsonGetServlet - /regattas - - - RegattaJsonGetServlet - com.sap.sailing.server.gateway.impl.RegattaJsonGetServlet - - - RegattaJsonGetServlet - /regatta - - - - LeaderboardsJsonGetServlet - com.sap.sailing.server.gateway.impl.LeaderboardsJsonGetServlet - - - LeaderboardsJsonGetServlet - /leaderboards - - - - LeaderboardJsonGetServlet - com.sap.sailing.server.gateway.impl.LeaderboardJsonGetServlet - - - LeaderboardJsonGetServlet - /leaderboard - - - - LeaderboardGroupJsonGetServlet - com.sap.sailing.server.gateway.impl.LeaderboardGroupJsonGetServlet - - - LeaderboardGroupJsonGetServlet - /leaderboardgroup - - - - LeaderboardGroupsJsonGetServlet - com.sap.sailing.server.gateway.impl.LeaderboardGroupsJsonGetServlet - - - LeaderboardGroupsJsonGetServlet - /leaderboardgroups - - - - WindJsonGetServlet - com.sap.sailing.server.gateway.impl.WindJsonGetServlet - - - WindJsonGetServlet - /wind - - WindImportServlet com.sap.sailing.server.gateway.expeditionimport.WindImportServlet diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/AdminApp.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/AdminApp.java deleted file mode 100755 index c81ba30b1cd..00000000000 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/AdminApp.java +++ /dev/null @@ -1,453 +0,0 @@ -package com.sap.sailing.server.gateway.impl; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.SocketException; -import java.net.URISyntaxException; -import java.net.URL; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; - -import com.sap.sailing.domain.base.RaceDefinition; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.NoWindException; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.SpeedWithBearing; -import com.sap.sailing.domain.common.Wind; -import com.sap.sailing.domain.common.WindSource; -import com.sap.sailing.domain.common.WindSourceType; -import com.sap.sailing.domain.common.impl.DegreeBearingImpl; -import com.sap.sailing.domain.common.impl.DegreePosition; -import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; -import com.sap.sailing.domain.common.impl.WindImpl; -import com.sap.sailing.domain.common.impl.WindSourceImpl; -import com.sap.sailing.domain.tracking.DynamicTrackedRace; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.domain.tracking.WindTrack; -import com.sap.sailing.domain.tractracadapter.RaceRecord; -import com.sap.sailing.server.gateway.SailingServerHttpServlet; -import com.sap.sse.InvalidDateException; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public class AdminApp extends SailingServerHttpServlet { - private static final long serialVersionUID = -6849138354941569249L; - - private static final String ACTION_NAME_ADD_WIND_TO_MARKS = "addwindtomarksforonehour"; - - private static final String ACTION_NAME_LIST_RACES_IN_REGATTA = "listracesinregatta"; - - private static final String ACTION_NAME_STOP_REGATTA = "stopregatta"; - - private static final String ACTION_NAME_STOP_RACE = "stoprace"; - - private static final String ACTION_NAME_SET_WIND = "setwind"; - - private static final String ACTION_NAME_REMOVE_WIND = "removewind"; - - private static final String ACTION_NAME_SHOW_WIND = "showwind"; - - private static final String ACTION_NAME_SET_AVERAGING = "setaveraging"; - - private static final String PARAM_NAME_WIND_AVERAGING_INTERVAL_MILLIS = "windaveragingintervalmillis"; - - private static final String PARAM_NAME_SPEED_AVERAGING_INTERVAL_MILLIS = "speedaveragingintervalmillis"; - - private static final String PARAM_NAME_EVENT_JSON_URL = "eventJSONURL"; - - private static final String PARAM_NAME_FROM_TIME = "fromtime"; - - private static final String PARAM_NAME_FROM_TIME_MILLIS = "fromtimeasmillis"; - - private static final String PARAM_NAME_TO_TIME = "totime"; - - private static final String PARAM_NAME_TO_TIME_MILLIS = "totimeasmillis"; - - private static final String PARAM_NAME_WINDSOURCE_NAME = "sourcename"; - - private static final String PARAM_NAME_BEARING = "truebearingdegrees"; - - private static final String PARAM_NAME_SPEED = "knotspeed"; - - private static final String PARAM_NAME_LATDEG = "latdeg"; - - private static final String PARAM_NAME_LNGDEG = "lngdeg"; - - private static final String ACTION_NAME_LIST_WINDTRACKERS = "listwindtrackers"; - - private static final String ACTION_NAME_SUBSCRIBE_RACE_FOR_EXPEDITION_WIND = "receiveexpeditionwind"; - - private static final String ACTION_NAME_UNSUBSCRIBE_RACE_FOR_EXPEDITION_WIND = "stopreceivingexpeditionwind"; - - private static final String PARAM_NAME_PORT = "port"; - - private static final String PARAM_NAME_CORRECT_EXPEDITION_WIND_BEARING_BY_DECLINATION = "correctexpeditionwindbearingbydeclination"; - - public AdminApp() { - } - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - try { - String action = req.getParameter(PARAM_ACTION); - if (action != null) { - if (ACTION_NAME_STOP_REGATTA.equals(action)) { - stopRegatta(req, resp); - } else if (ACTION_NAME_LIST_RACES_IN_REGATTA.equals(action)) { - listRacesInEvent(req, resp); - } else if (ACTION_NAME_STOP_RACE.equals(action)) { - stopRace(req, resp); - } else if (ACTION_NAME_SUBSCRIBE_RACE_FOR_EXPEDITION_WIND.equals(action)) { - startReceivingExpeditionWindForRace(req, resp); - } else if (ACTION_NAME_UNSUBSCRIBE_RACE_FOR_EXPEDITION_WIND.equals(action)) { - stopReceivingExpeditionWindForRace(req, resp); - } else if (ACTION_NAME_LIST_WINDTRACKERS.equals(action)) { - listWindTrackers(req, resp); - } else if (ACTION_NAME_SET_WIND.equals(action)) { - setWind(req, resp); - } else if (ACTION_NAME_REMOVE_WIND.equals(action)) { - removeWind(req, resp); - } else if (ACTION_NAME_SHOW_WIND.equals(action)) { - showWind(req, resp); - } else if (ACTION_NAME_ADD_WIND_TO_MARKS.equals(action)) { - addWindToMarks(req, resp); - } else if (ACTION_NAME_SET_AVERAGING.equals(action)) { - setAveraging(req, resp); - } else { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unknown action \""+action+"\""); - } - } else { - resp.getWriter().println("Hello admin!"); - } - } catch (Exception e) { - resp.getWriter().println("Error processing request:"); - e.printStackTrace(resp.getWriter()); - } - } - - private void setAveraging(HttpServletRequest req, HttpServletResponse resp) throws IOException { - Regatta regatta = getRegatta(req); - if (regatta == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Event not found"); - } else { - RaceDefinition race = getRaceDefinition(req); - if (race == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found"); - } else { - DynamicTrackedRace trackedRace = getService().getOrCreateTrackedRegatta(regatta).getTrackedRace(race); - String windAveragingIntervalInMIllis = req.getParameter(PARAM_NAME_WIND_AVERAGING_INTERVAL_MILLIS); - if (windAveragingIntervalInMIllis != null) { - trackedRace.setMillisecondsOverWhichToAverageWind(Long.valueOf(windAveragingIntervalInMIllis)); - } - String speedAveragingIntervalInMIllis = req.getParameter(PARAM_NAME_SPEED_AVERAGING_INTERVAL_MILLIS); - if (speedAveragingIntervalInMIllis != null) { - trackedRace.setMillisecondsOverWhichToAverageSpeed(Long.valueOf(speedAveragingIntervalInMIllis)); - } - } - } - } - - private void listRacesInEvent(HttpServletRequest req, HttpServletResponse resp) throws IOException, ParseException, - org.json.simple.parser.ParseException, URISyntaxException { - URL jsonURL = new URL(req.getParameter(PARAM_NAME_EVENT_JSON_URL)); - List raceRecords = getTracTracAdapterFactory() - .getOrCreateTracTracAdapter(getService().getBaseDomainFactory()).getTracTracRaceRecords(jsonURL, true) - .getB(); - JSONArray result = new JSONArray(); - for (RaceRecord raceRecord : raceRecords) { - JSONObject jsonRaceRecord = new JSONObject(); - jsonRaceRecord.put("name", raceRecord.getName()); - jsonRaceRecord.put("ID", raceRecord.getID()); - jsonRaceRecord.put("paramURL", raceRecord.getParamURL()); - jsonRaceRecord.put("replayURL", raceRecord.getReplayURL()); - result.add(jsonRaceRecord); - } - result.writeJSONString(resp.getWriter()); - } - - private void addWindToMarks(HttpServletRequest req, HttpServletResponse resp) throws IOException, - InvalidDateException, NoWindException { - Regatta regatta = getRegatta(req); - if (regatta == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Event not found"); - } else { - RaceDefinition race = getRaceDefinition(req); - if (race == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found"); - } else { - TrackedRace trackedRace = getService().getOrCreateTrackedRegatta(regatta).getTrackedRace(race); - TimePoint time = readTimePointParam(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS, MillisecondsTimePoint.now()); - TimePoint oneHourLater = new MillisecondsTimePoint(time.asMillis()+3600*1000); - String[] latitudes = req.getParameterValues(PARAM_NAME_LATDEG); - String[] longitudes = req.getParameterValues(PARAM_NAME_LNGDEG); - JSONArray result = new JSONArray(); - if (latitudes != null && longitudes != null) { - for (int i = 0; i < Math.max(latitudes.length, longitudes.length); i++) { - double latDeg = Double.valueOf(latitudes[i]); - double lngDeg = Double.valueOf(longitudes[i]); - DegreePosition pos = new DegreePosition(latDeg, lngDeg); - Wind wind = trackedRace.getWind(pos, time); - if (wind == null) { - throw new NoWindException("No wind set for race "+race.getName()+ - " in regatta "+regatta.getName()+" while computing wind lines on marks"); - } - Distance d = wind.travel(time, oneHourLater); - Position to = pos.translateGreatCircle(wind.getBearing(), d); - JSONObject record = new JSONObject(); - record.put("markLatDeg", latDeg); - record.put("markLngDeg", lngDeg); - record.put("windTrueBearingDeg", wind.getBearing().getDegrees()); - record.put("windKnotSpeed", wind.getKnots()); - record.put("toLatDeg", to.getLatDeg()); - record.put("toLngDeg", to.getLngDeg()); - result.add(record); - } - } - result.writeJSONString(resp.getWriter()); - } - } - } - - private void showWind(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvalidDateException { - Regatta regatta = getRegatta(req); - if (regatta == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Event not found"); - } else { - RaceDefinition race = getRaceDefinition(req); - if (race == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found"); - } else { - TrackedRace trackedRace = getService().getOrCreateTrackedRegatta(regatta).getTrackedRace(race); - TimePoint from = readTimePointParam(req, PARAM_NAME_FROM_TIME, PARAM_NAME_FROM_TIME_MILLIS, - trackedRace.getStartOfRace()==null?new MillisecondsTimePoint(0): - /* 24h before race start */ new MillisecondsTimePoint(trackedRace.getStartOfRace().asMillis()-24*3600*1000)); - TimePoint to = readTimePointParam(req, PARAM_NAME_TO_TIME, PARAM_NAME_TO_TIME_MILLIS, MillisecondsTimePoint.now()); - JSONObject jsonWindTracks = new JSONObject(); - List windSources = new ArrayList(); - for (WindSource windSource : trackedRace.getWindSources()) { - windSources.add(windSource); - } - for (WindSource windSourceToExclude : trackedRace.getWindSourcesToExclude()) { - windSources.remove(windSourceToExclude); - } - windSources.add(new WindSourceImpl(WindSourceType.COMBINED)); - for (WindSource windSource : windSources) { - JSONArray jsonWindArray = new JSONArray(); - WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource); - windTrack.lockForRead(); - try { - Iterator windIter = windTrack.getFixesIterator(from, /* inclusive */true); - while (windIter.hasNext()) { - Wind wind = windIter.next(); - if (wind.getTimePoint().compareTo(to) > 0) { - break; - } - JSONObject jsonWind = new JSONObject(); - jsonWind.put("truebearingdeg", wind.getBearing().getDegrees()); - jsonWind.put("knotspeed", wind.getKnots()); - jsonWind.put("meterspersecondspeed", wind.getMetersPerSecond()); - if (wind.getTimePoint() != null) { - jsonWind.put("timepoint", wind.getTimePoint().asMillis()); - jsonWind.put("dampenedtruebearingdeg", - windTrack.getAveragedWind(wind.getPosition(), wind.getTimePoint()) - .getBearing().getDegrees()); - jsonWind.put("dampenedknotspeed", - windTrack.getAveragedWind(wind.getPosition(), wind.getTimePoint()).getKnots()); - jsonWind.put("dampenedmeterspersecondspeed", - windTrack.getAveragedWind(wind.getPosition(), wind.getTimePoint()) - .getMetersPerSecond()); - } - if (wind.getPosition() != null) { - jsonWind.put("latdeg", wind.getPosition().getLatDeg()); - jsonWind.put("lngdeg", wind.getPosition().getLngDeg()); - } - jsonWindArray.add(jsonWind); - } - } finally { - windTrack.unlockAfterRead(); - } - jsonWindTracks.put(windSource.toString(), jsonWindArray); - } - jsonWindTracks.writeJSONString(resp.getWriter()); - } - } - } - - private void setWind(HttpServletRequest req, HttpServletResponse resp) throws IOException { - Regatta e = getRegatta(req); - Iterable regattas; - if (e == null) { - regattas = getService().getAllRegattas(); - } else { - regattas = Collections.singleton(e); - } - for (Regatta regatta : regattas) { - RaceDefinition r = getRaceDefinition(regatta, req); - Iterable races; - if (r == null) { - races = regatta.getAllRaces(); - } else { - races = Collections.singleton(r); - } - for (RaceDefinition race : races) { - String bearingAsString = req.getParameter(PARAM_NAME_BEARING); - if (bearingAsString != null) { - Bearing bearing = new DegreeBearingImpl(Double.valueOf(bearingAsString)); - String speedAsString = req.getParameter(PARAM_NAME_SPEED); - SpeedWithBearing speed; - if (speedAsString == null) { - // only bearing provided; no speed; assume speed as 1kn - speed = new KnotSpeedWithBearingImpl(1, bearing); - } else { - speed = new KnotSpeedWithBearingImpl(Double.valueOf(speedAsString), bearing); - } - Position p = null; - String lat = req.getParameter(PARAM_NAME_LATDEG); - if (lat != null) { - String lng = req.getParameter(PARAM_NAME_LNGDEG); - if (lng != null) { - p = new DegreePosition(Double.valueOf(lat), Double.valueOf(lng)); - } - } - try { - TimePoint timePoint = readTimePointParam(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS, MillisecondsTimePoint.now()); - Wind wind = new WindImpl(p, timePoint, speed); - final DynamicTrackedRace trackedRace = getService().getOrCreateTrackedRegatta(regatta).getExistingTrackedRace(race); - if (trackedRace != null) { - trackedRace.recordWind(wind, new WindSourceImpl(WindSourceType.WEB)); - } - } catch (InvalidDateException ex) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Couldn't parse time specification " + ex.getMessage()); - } - } else { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "wind bearing parameter "+PARAM_NAME_BEARING+" missing"); - } - } - } - } - - private void removeWind(HttpServletRequest req, HttpServletResponse resp) throws IOException { - Regatta regatta = getRegatta(req); - if (regatta == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Event not found"); - } else { - RaceDefinition race = getRaceDefinition(req); - if (race == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found"); - } else { - String sourceName = req.getParameter(PARAM_NAME_WINDSOURCE_NAME); - if (sourceName == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Wind source name not provided"); - } else { - WindSourceType windSourceType = WindSourceType.valueOf(sourceName); - if (windSourceType == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Wind source type name " + sourceName + " unknown"); - } else { - try { - final DynamicTrackedRace trackedRace = getService().getOrCreateTrackedRegatta(regatta) - .getTrackedRace(race); - WindTrack windTrack = trackedRace.getOrCreateWindTrack(trackedRace.getWindSources(windSourceType).iterator().next()); - TimePoint timePoint = readTimePointParam(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS, - MillisecondsTimePoint.now()); - Wind wind = windTrack.getLastFixAtOrBefore(timePoint); - if (wind != null && wind.getTimePoint().equals(timePoint)) { - windTrack.remove(wind); - resp.getWriter().println("Successfully removed entry "+wind); - } else { - resp.getWriter().println( - "No wind recorded for regatta " + regatta.getName() + " and race " + race.getName() - + " at " + timePoint.asDate()+". No error, just no effect :-)"); - } - } catch (InvalidDateException e) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Couldn't parse time specification " + e.getMessage()); - } - } - } - } - } - } - - private void listWindTrackers(HttpServletRequest req, HttpServletResponse resp) throws IOException { - JSONArray windTrackers = new JSONArray(); - for (Util.Triple regattaAndRaceAndPort : getService().getWindTrackedRaces()) { - JSONObject windTracker = new JSONObject(); - windTracker.put("regattaname", regattaAndRaceAndPort.getA().getName()); - windTracker.put("racename", regattaAndRaceAndPort.getB().getName()); - windTracker.put("windtrackerinfo", regattaAndRaceAndPort.getC()); - windTrackers.add(windTracker); - } - windTrackers.writeJSONString(resp.getWriter()); - } - - private void stopReceivingExpeditionWindForRace(HttpServletRequest req, HttpServletResponse resp) throws SocketException, IOException { - Regatta regatta = getRegatta(req); - if (regatta == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Event not found"); - } else { - RaceDefinition race = getRaceDefinition(req); - if (race == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found"); - } else { - getService().stopTrackingWind(regatta, race); - } - } - } - - private void startReceivingExpeditionWindForRace(HttpServletRequest req, HttpServletResponse resp) throws Exception { - Regatta regatta = getRegatta(req); - if (regatta == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Event not found"); - } else { - RaceDefinition race = getRaceDefinition(req); - if (race == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found"); - } else { - String portParam = req.getParameter(PARAM_NAME_PORT); - if (portParam == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "No port parameter provided"); - } else { - String correctByDeclination = req - .getParameter(PARAM_NAME_CORRECT_EXPEDITION_WIND_BEARING_BY_DECLINATION); - getService().startTrackingWind(regatta, race, Boolean.valueOf(correctByDeclination)); - } - } - } - } - - private void stopRegatta(HttpServletRequest req, HttpServletResponse resp) throws MalformedURLException, IOException, - InterruptedException { - Regatta regatta = getRegatta(req); - if (regatta != null) { - getService().stopTracking(regatta); - } else { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Event not found"); - } - } - - private void stopRace(HttpServletRequest req, HttpServletResponse resp) throws MalformedURLException, IOException, InterruptedException { - Regatta regatta = getRegatta(req); - if (regatta != null) { - RaceDefinition race = getRaceDefinition(req); - if (race == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found"); - } else { - getService().stopTracking(regatta, race); - } - } else { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Regatta not found"); - } - } -} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java deleted file mode 100644 index 43ee3eb47f1..00000000000 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventRaceStatesJsonGetServlet.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.sap.sailing.server.gateway.impl; - -import java.io.IOException; -import java.util.UUID; -import java.util.logging.Logger; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.json.simple.JSONObject; - -import com.sap.sailing.domain.base.Event; -import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.server.gateway.AbstractJsonHttpServlet; -import com.sap.sailing.server.gateway.jaxrs.api.EventRaceStatesSerializer; -import com.sap.sse.common.Util.Pair; - -public class EventRaceStatesJsonGetServlet extends AbstractJsonHttpServlet { - private static final long serialVersionUID = -4820965681871902242L; - - private final static Logger logger = Logger.getLogger(EventRaceStatesJsonGetServlet.class.getName()); - - private static final String PARAM_NAME_EVENTID = "eventId"; - private static final String PARAM_NAME_FILTER_BY_LEADERBOARD = "filterByLeaderboard"; - private static final String PARAM_NAME_FILTER_BY_COURSEAREA = "filterByCourseArea"; - private static final String PARAM_NAME_FILTER_BY_DAYOFFSET = "filterByDayOffset"; - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - String eventIdParam = request.getParameter(PARAM_NAME_EVENTID); - String filterByCourseAreaParam = request.getParameter(PARAM_NAME_FILTER_BY_COURSEAREA); - String filterByLeaderboardParam = request.getParameter(PARAM_NAME_FILTER_BY_LEADERBOARD); - String filterByDayOffsetParam = request.getParameter(PARAM_NAME_FILTER_BY_DAYOFFSET); - - if (eventIdParam == null) { - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "You need to specify a event id using the "+ - PARAM_NAME_EVENTID + " parameter"); - } else { - Event event = getService().getEvent(convertIdentifierStringToUuid(eventIdParam)); - if (event == null) { - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Event "+eventIdParam+" not found"); - } else { - EventRaceStatesSerializer eventRaceStatesSerializer = new EventRaceStatesSerializer(filterByCourseAreaParam, - filterByLeaderboardParam, filterByDayOffsetParam, getService()); - JSONObject result = eventRaceStatesSerializer.serialize(new Pair>(event, getService().getLeaderboards().values())); - setJsonResponseHeader(response); - result.writeJSONString(response.getWriter()); - } - } - } - - /** - * Tries to convert the given identifier to a UUID. When the identifier is not a UUID, null is returned. - * @param identifierToConvert the identifier as a String - * @return a UUID when the given identifier is a UUID, null otherwise - */ - private UUID convertIdentifierStringToUuid(String identifierToConvert) { - UUID result = null; - if (identifierToConvert != null) { - try { - result = UUID.fromString(identifierToConvert); - } catch (IllegalArgumentException iae) { - logger.warning("The identifier " + identifierToConvert + " could not be converted to a UUID"); - //in this case null is returned - } - } - return result; - } -} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventsJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventsJsonGetServlet.java deleted file mode 100644 index 5fe78835175..00000000000 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/EventsJsonGetServlet.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.sap.sailing.server.gateway.impl; - -import java.io.IOException; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.json.simple.JSONArray; - -import com.sap.sailing.domain.base.EventBase; -import com.sap.sailing.server.gateway.AbstractJsonHttpServlet; -import com.sap.sailing.server.gateway.serialization.JsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.CourseAreaJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.EventBaseJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer; - -public class EventsJsonGetServlet extends AbstractJsonHttpServlet { - private static final long serialVersionUID = -7313766879733880267L; - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - JsonSerializer eventSerializer = new EventBaseJsonSerializer(new VenueJsonSerializer(new CourseAreaJsonSerializer()), new LeaderboardGroupBaseJsonSerializer()); - JSONArray result = new JSONArray(); - for (EventBase event : getService().getAllEvents()) { - result.add(eventSerializer.serialize(event)); - } - setJsonResponseHeader(response); - result.writeJSONString(response.getWriter()); - } -} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java index 15d05cc0f1d..72cd10d3c1f 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java @@ -49,21 +49,22 @@ public class FileUploadServlet extends AbstractFileUploadServlet { */ final JSONArray resultList = new JSONArray(); for (FileItem fileItem : fileItems) { - JSONObject result = new JSONObject(); - String fileExtension = ""; - String fileType = fileItem.getContentType(); + final JSONObject result = new JSONObject(); + final String fileExtension; + final String fileType = fileItem.getContentType(); if (fileType.equals("image/jpeg")) { fileExtension = ".jpg"; } else if (fileType.equals("image/png")) { fileExtension = ".png"; + } else { + fileExtension = ""; } - URI fileUri; try { if (fileItem.getSize() > 1024 * 1024 * MAX_SIZE_IN_MB) { throw new WebApplicationException(Response.status(Status.BAD_REQUEST) .entity("Image is larger than " + MAX_SIZE_IN_MB + "MB").build()); } - fileUri = getService().getFileStorageManagementService().getActiveFileStorageService() + final URI fileUri = getService().getFileStorageManagementService().getActiveFileStorageService() .storeFile(fileItem.getInputStream(), fileExtension, fileItem.getSize()); result.put(JSON_FILE_NAME, fileItem.getName()); result.put(JSON_FILE_URI, fileUri.toString()); @@ -75,6 +76,12 @@ public class FileUploadServlet extends AbstractFileUploadServlet { } resultList.add(result); } - resp.getOutputStream().write(resultList.toJSONString().getBytes("UTF-8")); + // surprise, surprise: see https://www.sencha.com/forum/showthread.php?132949-Fileupload-Invalid-JSON-string + // When sending a JSON response for a file upload, don't use application/json as the content type. It would lead + // to wrapping the content by a
     tag. Use text/html instead which should deliver the content to the app running
    +        // in the browser unchanged.
    +        resp.setContentType("text/html");
    +        resp.setCharacterEncoding("UTF-8");
    +        resp.getWriter().append(resultList.toJSONString());
         }
     }
    diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/LeaderboardGroupJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/LeaderboardGroupJsonGetServlet.java
    deleted file mode 100644
    index 3b2860218dc..00000000000
    --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/LeaderboardGroupJsonGetServlet.java
    +++ /dev/null
    @@ -1,124 +0,0 @@
    -package com.sap.sailing.server.gateway.impl;
    -
    -import java.io.IOException;
    -
    -import javax.servlet.ServletException;
    -import javax.servlet.http.HttpServletRequest;
    -import javax.servlet.http.HttpServletResponse;
    -
    -import org.json.simple.JSONArray;
    -import org.json.simple.JSONObject;
    -
    -import com.sap.sailing.domain.base.Fleet;
    -import com.sap.sailing.domain.base.RaceColumn;
    -import com.sap.sailing.domain.base.Regatta;
    -import com.sap.sailing.domain.common.LeaderboardNameConstants;
    -import com.sap.sailing.domain.leaderboard.Leaderboard;
    -import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
    -import com.sap.sailing.domain.leaderboard.MetaLeaderboard;
    -import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
    -import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
    -import com.sap.sailing.domain.tracking.TrackedRace;
    -import com.sap.sailing.server.gateway.AbstractJsonHttpServlet;
    -import com.sap.sse.common.TimePoint;
    -import com.sap.sse.common.impl.MillisecondsTimePoint;
    -
    -public class LeaderboardGroupJsonGetServlet extends AbstractJsonHttpServlet {
    -    private static final long serialVersionUID = 1351616646322956825L;
    -    private static final String PARAM_NAME_LEADERBOARDGROUPNAME = "leaderboardGroupName";
    -
    -    @Override
    -    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    -        String leaderboardGroupName = req.getParameter(PARAM_NAME_LEADERBOARDGROUPNAME);
    -        if (leaderboardGroupName == null) {
    -            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Need to specify the name of a leaderboard group using the "+
    -                    PARAM_NAME_LEADERBOARDGROUPNAME+" parameter");
    -        } else {
    -            LeaderboardGroup leaderboardGroup = getService().getLeaderboardGroupByName(leaderboardGroupName);
    -            if (leaderboardGroup == null) {
    -                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "LeaderboardGroup "+leaderboardGroupName+" not found");
    -            } else {
    -                TimePoint timePoint = MillisecondsTimePoint.now();
    -                JSONObject jsonLeaderboardGroup = new JSONObject();
    -                jsonLeaderboardGroup.put("name", leaderboardGroup.getName());
    -                jsonLeaderboardGroup.put("description", leaderboardGroup.getDescription());
    -                jsonLeaderboardGroup.put("displayName", leaderboardGroup.getDisplayName());
    -                jsonLeaderboardGroup.put("timepoint", timePoint.toString());
    -                JSONArray jsonLeaderboardEntries = new JSONArray();
    -                jsonLeaderboardGroup.put("leaderboards", jsonLeaderboardEntries);
    -                for (Leaderboard leaderboard: leaderboardGroup.getLeaderboards()) {
    -                    boolean isMetaLeaderboard = leaderboard instanceof MetaLeaderboard ? true : false;
    -                    boolean isRegattaLeaderboard = leaderboard instanceof RegattaLeaderboard ? true : false;
    -
    -                    JSONObject jsonLeaderboard = new JSONObject();
    -                    jsonLeaderboard.put("name", leaderboard.getName());
    -                    jsonLeaderboard.put("displayName", leaderboard.getDisplayName());
    -                    jsonLeaderboard.put("isMetaLeaderboard", isMetaLeaderboard);
    -                    jsonLeaderboard.put("isRegattaLeaderboard", isRegattaLeaderboard);
    -                    jsonLeaderboardEntries.add(jsonLeaderboard);
    -
    -                    SettableScoreCorrection scoreCorrection = leaderboard.getScoreCorrection();
    -                    if (scoreCorrection != null) {
    -                        jsonLeaderboard.put("scoringComment", scoreCorrection.getComment());
    -                        TimePoint lastUpdateTimepoint = scoreCorrection.getTimePointOfLastCorrectionsValidity();
    -                        jsonLeaderboard.put("lastScoringUpdate", lastUpdateTimepoint != null ? lastUpdateTimepoint.asDate().toString(): null);
    -                    } else {
    -                        jsonLeaderboard.put("scoringComment", null);
    -                        jsonLeaderboard.put("lastScoringUpdate", null);
    -                    }
    -
    -                    if (isRegattaLeaderboard) {
    -                        RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) leaderboard;
    -                        Regatta regatta = regattaLeaderboard.getRegatta();
    -                        jsonLeaderboard.put("scoringScheme", leaderboard.getScoringScheme().getType());
    -                        jsonLeaderboard.put("regattaName", regatta.getName());
    -                    } else {
    -                        jsonLeaderboard.put("scoringScheme", leaderboard.getScoringScheme().getType());
    -                        jsonLeaderboard.put("regattaName", null);
    -                        JSONArray jsonSeriesEntries = new JSONArray();
    -                        jsonLeaderboard.put("series", jsonSeriesEntries);
    -
    -                        // write a 'default' series to be conform with our common regatta structure 
    -                        JSONObject jsonSeries = new JSONObject();
    -                        jsonSeriesEntries.add(jsonSeries);
    -                        jsonSeries.put("name", LeaderboardNameConstants.DEFAULT_SERIES_NAME);
    -                        jsonSeries.put("isMedalSeries", null);
    -                        JSONArray jsonFleetsEntries = new JSONArray();
    -                        jsonSeries.put("fleets", jsonFleetsEntries);
    -                        Fleet fleet = leaderboard.getFleet(LeaderboardNameConstants.DEFAULT_FLEET_NAME);
    -                        if (fleet != null) {
    -                            JSONObject jsonFleet = new JSONObject();
    -                            jsonFleet.put("name", fleet.getName());
    -                            jsonFleet.put("color", fleet.getColor() != null ? fleet.getColor().getAsHtml() : null);
    -                            jsonFleet.put("ordering", fleet.getOrdering());
    -                            jsonFleetsEntries.add(jsonFleet);
    -                            JSONArray jsonRacesEntries = new JSONArray();
    -                            jsonFleet.put("races", jsonRacesEntries);
    -                            for (RaceColumn raceColumn: leaderboard.getRaceColumns()) {
    -                                JSONObject jsonRaceColumn = new JSONObject();
    -                                jsonRaceColumn.put("name", raceColumn.getName());
    -                                jsonRaceColumn.put("isMedalRace" , raceColumn.isMedalRace());
    -                                TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
    -                                if (trackedRace != null) {
    -                                    jsonRaceColumn.put("isTracked", true);
    -                                    jsonRaceColumn.put("regattaName", trackedRace.getTrackedRegatta().getRegatta().getName());
    -                                    jsonRaceColumn.put("trackedRaceName", trackedRace.getRace().getName());
    -                                    jsonRaceColumn.put("hasGpsData", trackedRace.hasGPSData());
    -                                    jsonRaceColumn.put("hasWindData", trackedRace.hasWindData());
    -                                } else {
    -                                    jsonRaceColumn.put("isTracked", false);
    -                                    jsonRaceColumn.put("trackedRaceName", null);
    -                                    jsonRaceColumn.put("hasGpsData", false);
    -                                    jsonRaceColumn.put("hasWindData", false);
    -                                }
    -                                jsonRacesEntries.add(jsonRaceColumn);
    -                            }
    -                        }
    -                    }
    -                }
    -                setJsonResponseHeader(resp);
    -                jsonLeaderboardGroup.writeJSONString(resp.getWriter());
    -            }
    -        }
    -    }
    -}
    diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/LeaderboardGroupsJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/LeaderboardGroupsJsonGetServlet.java
    deleted file mode 100644
    index 4b80bcc4681..00000000000
    --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/LeaderboardGroupsJsonGetServlet.java
    +++ /dev/null
    @@ -1,29 +0,0 @@
    -package com.sap.sailing.server.gateway.impl;
    -
    -import java.io.IOException;
    -import java.util.Map;
    -
    -import javax.servlet.ServletException;
    -import javax.servlet.http.HttpServletRequest;
    -import javax.servlet.http.HttpServletResponse;
    -
    -import org.json.simple.JSONArray;
    -
    -import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
    -import com.sap.sailing.server.gateway.AbstractJsonHttpServlet;
    -
    -public class LeaderboardGroupsJsonGetServlet extends AbstractJsonHttpServlet {
    -    private static final long serialVersionUID = -2193421590275280102L;
    -
    -    @Override
    -    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    -        JSONArray jsonLeaderboardGroups = new JSONArray();
    -        Map leaderboardGroups = getService().getLeaderboardGroups();
    -        for (String leaderboardGroupName : leaderboardGroups.keySet()) {
    -            jsonLeaderboardGroups.add(leaderboardGroupName);
    -        }
    -        setJsonResponseHeader(resp);
    -        jsonLeaderboardGroups.writeJSONString(resp.getWriter());
    -    }
    -
    -}
    diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/LeaderboardJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/LeaderboardJsonGetServlet.java
    deleted file mode 100755
    index 7d670c6e933..00000000000
    --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/LeaderboardJsonGetServlet.java
    +++ /dev/null
    @@ -1,506 +0,0 @@
    -package com.sap.sailing.server.gateway.impl;
    -
    -import java.io.IOException;
    -import java.io.StringWriter;
    -import java.text.SimpleDateFormat;
    -import java.util.HashMap;
    -import java.util.Iterator;
    -import java.util.LinkedHashMap;
    -import java.util.LinkedHashSet;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.Map.Entry;
    -import java.util.Set;
    -import java.util.logging.Logger;
    -
    -import javax.servlet.ServletException;
    -import javax.servlet.http.HttpServletRequest;
    -import javax.servlet.http.HttpServletResponse;
    -
    -import org.json.simple.JSONArray;
    -import org.json.simple.JSONObject;
    -
    -import com.sap.sailing.domain.base.Competitor;
    -import com.sap.sailing.domain.base.Fleet;
    -import com.sap.sailing.domain.base.Nationality;
    -import com.sap.sailing.domain.base.RaceColumn;
    -import com.sap.sailing.domain.base.Waypoint;
    -import com.sap.sailing.domain.common.MaxPointsReason;
    -import com.sap.sailing.domain.common.NoWindException;
    -import com.sap.sailing.domain.leaderboard.Leaderboard;
    -import com.sap.sailing.domain.leaderboard.LeaderboardCacheManager;
    -import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
    -import com.sap.sailing.domain.leaderboard.caching.LeaderboardCache;
    -import com.sap.sailing.domain.tracking.MarkPassing;
    -import com.sap.sailing.domain.tracking.TrackedRace;
    -import com.sap.sailing.server.gateway.AbstractJsonHttpServlet;
    -import com.sap.sailing.server.gateway.HttpRequestUtils;
    -import com.sap.sailing.server.gateway.ParseHttpParameterException;
    -import com.sap.sse.common.TimePoint;
    -import com.sap.sse.common.Util;
    -import com.sap.sse.common.impl.MillisecondsTimePoint;
    -import com.sap.sse.util.SmartFutureCache;
    -import com.sap.sse.util.SmartFutureCache.CacheUpdater;
    -import com.sap.sse.util.SmartFutureCache.UpdateInterval;
    -
    -/**
    - * Exports a leaderboard to the JSON format. Uses a {@link SmartFutureCache}.
    - * @author Frank, Axel Uhl
    - * 
    - */
    -public class LeaderboardJsonGetServlet extends AbstractJsonHttpServlet implements LeaderboardCache {
    -    private static final long serialVersionUID = -2460691283231361152L;
    -    private static final Logger logger = Logger.getLogger(LeaderboardJsonGetServlet.class.getName());
    -    private static final String PARAM_NAME_LEADERBOARDNAME = "leaderboardName";
    -    private static final String PARAM_NAME_RESULTSTATE = "resultState";
    -    private static final String PARAM_NAME_USE_CACHE = "useCache";
    -    private static final String PARAM_NAME_MAX_COMPETITORS_COUNT = "maxCompetitorsCount";
    -    
    -    // Competitor constants used to build leaderboard json
    -    private static final String COMPETITOR_ROOT_KEY = "competitors";
    -    private static final String COMPETITOR_NAME = "name";
    -    private static final String COMPETITOR_DISPLAY_NAME = "displayName";
    -    private static final String COMPETITOR_ID = "id";
    -    private static final String COMPETITOR_SAIL_ID = "sailID";
    -    private static final String COMPETITOR_NATIONALITY = "nationality";
    -    private static final String COMPETITOR_COUNTRY_CODE = "countryCode";
    -    private static final String COMPETITOR_RANK = "rank";
    -    private static final String COMPETITOR_CARRIED_POINTS = "carriedPoints";
    -    private static final String COMPETITOR_TOTAL_POINTS = "totalPoints";
    -    private static final String COMPETITOR_RACE_SCORES = "raceScores";
    -    private static final String COMPETITOR_FLEET = "fleet";
    -    private static final String COMPETITOR_NET_POINTS = "netPoints";
    -    private static final String COMPETITOR_UNCORRECTED_NET_POINTS = "uncorrectedNetPoints";
    -    private static final String COMPETITOR_MAX_POINTS_REASON = "maxPointsReason";
    -    private static final String COMPETITOR_RACE_RANK = "raceRank";
    -    private static final String COMPETITOR_IS_DISCARDED = "isDiscarded";
    -    private static final String COMPETITOR_IS_CORRECTED = "isCorrected";
    -
    -    public static enum ResultStates { Live, Preliminary, Final };
    -    
    -    // for backward compatibility the default result state is live
    -    private final ResultStates DEFAULT_RESULT_STATE = ResultStates.Live;
    -    private static SimpleDateFormat TIMEPOINT_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
    -    
    -    private static final int MAX_TOTAL_NUMBER_OF_CACHE_ENTRIES = 1000;
    -    
    -    private int totalNumberOfCacheEntries;
    -    
    -    private int cacheHits;
    -    private int cacheMisses;
    -    
    -    /**
    -     * The cache values are linked hash maps keeping the rendered JSON objects as String per time point/result state pair. The cache uses
    -     * a linked hash map because this way old values can be evicted easily, keeping the cache below a maximum size.
    -     * Also, typing the {@link SmartFutureCache} with {@link LinkedHashMap} instead of only {@link Map} ensures that also when
    -     * computing cache updates, new values are appended "at the end" and therefore tend to survive longer than any value appended
    -     * earlier.
    -     */
    -    private final SmartFutureCache, StringBuffer>, LeaderboardJsonCacheUpdateInterval> cache;
    -    
    -    /**
    -     * Used to observe the leaderboards cached so far and triggering {@link #cache} updates.
    -     */
    -    private final LeaderboardCacheManager cacheManager;
    -    
    -    private static class LeaderboardJsonCacheUpdateInterval implements UpdateInterval {
    -        private final LinkedHashSet> timePointsAndResultStates;
    -        
    -        public LeaderboardJsonCacheUpdateInterval(Iterable> timePointsAndResultStates) {
    -            assert timePointsAndResultStates != null;
    -            this.timePointsAndResultStates = new LinkedHashSet<>();
    -            Util.addAll(timePointsAndResultStates, this.timePointsAndResultStates);
    -        }
    -
    -        public LinkedHashSet> getTimePointsAndResultStates() {
    -            return timePointsAndResultStates;
    -        }
    -
    -        /**
    -         * Appends the otherUpdateInterval to this interval and returns the new interval. Using a
    -         * {@link LinkedHashSet} internally, the ordering of time point and result state pairs is preserved, and
    -         * in conjunction with the cache eviction strategy, the newest additions are the most likely to survive in
    -         * the cache.
    -         */
    -        @Override
    -        public LeaderboardJsonCacheUpdateInterval join(LeaderboardJsonCacheUpdateInterval otherUpdateInterval) {
    -            LinkedHashSet> newTimePointsAndResultStates = new LinkedHashSet<>();
    -            newTimePointsAndResultStates.addAll(getTimePointsAndResultStates());
    -            newTimePointsAndResultStates.addAll(otherUpdateInterval.getTimePointsAndResultStates());
    -            return new LeaderboardJsonCacheUpdateInterval(newTimePointsAndResultStates);
    -        }
    -        
    -    }
    -    
    -    private class LeaderboardJsonCacheUpdater
    -            implements
    -            CacheUpdater, StringBuffer>, LeaderboardJsonCacheUpdateInterval> {
    -        @Override
    -        public LinkedHashMap, StringBuffer> computeCacheUpdate(Leaderboard key,
    -                LeaderboardJsonCacheUpdateInterval updateInterval) throws Exception {
    -            final LinkedHashMap, StringBuffer> result;
    -            if (updateInterval == null) {
    -                result = null;
    -            } else {
    -                // no removeEldestEntry override required here; map is merged into another LinkedHashMap created in provideNewCacheValue
    -                result = new LinkedHashMap<>();
    -                for (com.sap.sse.common.Util.Triple timePointAndResultState : updateInterval.getTimePointsAndResultStates()) {
    -                    StringWriter sw = new StringWriter();
    -                    computeLeaderboardJson(key, timePointAndResultState).writeJSONString(sw);
    -                    result.put(timePointAndResultState, sw.getBuffer());
    -                }
    -            }
    -            return result;
    -        }
    -
    -        @Override
    -        public LinkedHashMap, StringBuffer> provideNewCacheValue(Leaderboard leaderboard,
    -                LinkedHashMap, StringBuffer> oldCacheValue,
    -                LinkedHashMap, StringBuffer> computedCacheUpdate,
    -                LeaderboardJsonCacheUpdateInterval updateInterval) {
    -            final LinkedHashMap, StringBuffer> result;
    -            if (computedCacheUpdate == null) {
    -                result = null;
    -                if (oldCacheValue != null) {
    -                    totalNumberOfCacheEntries -= oldCacheValue.size();
    -                }
    -            } else {
    -                // Note: don't use access-based ordering as it turns the get(...) call into a "write" access
    -                result = new LinkedHashMap, StringBuffer>(16, 0.75f) {
    -                    private static final long serialVersionUID = -6197983565575024084L;
    -                    @Override
    -                    protected boolean removeEldestEntry(Entry, StringBuffer> eldest) {
    -                        final boolean result;
    -                        if (totalNumberOfCacheEntries > MAX_TOTAL_NUMBER_OF_CACHE_ENTRIES) {
    -                            totalNumberOfCacheEntries--;
    -                            result = true;
    -                        } else {
    -                            result = false;
    -                        }
    -                        return result;
    -                    }
    -                };
    -                if (oldCacheValue != null) {
    -                    result.putAll(oldCacheValue);
    -                    totalNumberOfCacheEntries -= oldCacheValue.size();
    -                }
    -                // ensure we get update triggers; note that oldCacheValue!=null doesn't mean that there are currently listeners active;
    -                // the listeners could have been removed, and the SmartFutureCache will keep old values around as there is no explicit
    -                // cache eviction for a SmartFutureCache.
    -                cacheManager.add(leaderboard);
    -                result.putAll(computedCacheUpdate);
    -                totalNumberOfCacheEntries += result.size();
    -            }
    -            return result;
    -        }
    -    }
    -    
    -    public LeaderboardJsonGetServlet() {
    -        cache = new SmartFutureCache<>(new LeaderboardJsonCacheUpdater(), "LeaderboardJsonExporServlet SmartFutureCache for "+this);
    -        cacheManager = new LeaderboardCacheManager(this);
    -    }
    -
    -    @Override
    -    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    -        TimePoint requestTimePoint = MillisecondsTimePoint.now();
    -        String leaderboardName = req.getParameter(PARAM_NAME_LEADERBOARDNAME);
    -        String useCacheParam = req.getParameter(PARAM_NAME_USE_CACHE);
    -        Integer maxCompetitorsCount = null;
    -        try {
    -            maxCompetitorsCount = HttpRequestUtils.getIntParameter(req, PARAM_NAME_MAX_COMPETITORS_COUNT);
    -        } catch (ParseHttpParameterException e1) {
    -        }
    -        boolean useCache = true; // default
    -        if (useCacheParam != null && "false".equalsIgnoreCase(useCacheParam)) {
    -            useCache = false;
    -        }
    -        if (leaderboardName == null) {
    -            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Need to specify a leaderboard name using the "+
    -                    PARAM_NAME_LEADERBOARDNAME+" parameter");
    -        } else {
    -            Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
    -            if (leaderboard == null) {
    -                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Leaderboard "+leaderboardName+" not found");
    -            } else {
    -                try {
    -                    ResultStates resultState = resolveRequestedResultState(req.getParameter(PARAM_NAME_RESULTSTATE));
    -                    TimePoint resultTimePoint = calculateTimePointForResultState(leaderboard, resultState);
    -                    StringBuffer jsonLeaderboardAsString;
    -                    if (resultTimePoint != null) {
    -                        com.sap.sse.common.Util.Triple resultStateAndTimePointAndMaxCompetitorsCount =
    -                                new com.sap.sse.common.Util.Triple<>(resultTimePoint, resultState, maxCompetitorsCount);
    -                        if (useCache) {
    -                            jsonLeaderboardAsString = getLeaderboardJsonFromCacheOrCompute(leaderboard, resultStateAndTimePointAndMaxCompetitorsCount);
    -                        } else {
    -                            StringWriter sw = new StringWriter();
    -                            computeLeaderboardJson(leaderboard, resultStateAndTimePointAndMaxCompetitorsCount).writeJSONString(sw);
    -                            jsonLeaderboardAsString = sw.getBuffer();
    -                        }
    -                    } else {
    -                        StringWriter sw = new StringWriter();
    -                        createEmptyLeaderboardJson(leaderboard, resultState, requestTimePoint, maxCompetitorsCount).writeJSONString(sw);
    -                        jsonLeaderboardAsString = sw.getBuffer();
    -                    }
    -                    setJsonResponseHeader(resp);
    -                    synchronized (jsonLeaderboardAsString) {
    -                        int indexOfFirstOpeningBrace = jsonLeaderboardAsString.indexOf("{");
    -                        final String requestTimePointAsJson = "\"requestTimepoint\": \"" + formatTimePoint(requestTimePoint) + "\", ";
    -                        if (indexOfFirstOpeningBrace >= 0) {
    -                            jsonLeaderboardAsString.insert(indexOfFirstOpeningBrace+1, requestTimePointAsJson);
    -                        }
    -                        resp.getWriter().write(jsonLeaderboardAsString.toString());
    -                        if (indexOfFirstOpeningBrace >= 0) {
    -                            jsonLeaderboardAsString.delete(indexOfFirstOpeningBrace+1, indexOfFirstOpeningBrace+1+requestTimePointAsJson.length());
    -                        }
    -                    }
    -                } catch (NoWindException e) {
    -                    resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
    -                }
    -            }
    -        }
    -    }
    -
    -    private StringBuffer getLeaderboardJsonFromCacheOrCompute(Leaderboard leaderboard,
    -            com.sap.sse.common.Util.Triple timePointAndResultStateAndMaxCompetitorsCount) throws NoWindException {
    -        final StringBuffer result;
    -        Map, StringBuffer> cacheEntry = cache.get(leaderboard, /* waitForLatest */ false);
    -        if (cacheEntry == null || !cacheEntry.containsKey(timePointAndResultStateAndMaxCompetitorsCount)) {
    -            cacheMisses++;
    -            LinkedHashSet> timePointsAndResultStatesAndMaxCompetitorsCount = new LinkedHashSet<>();
    -            timePointsAndResultStatesAndMaxCompetitorsCount.add(timePointAndResultStateAndMaxCompetitorsCount);
    -            cache.triggerUpdate(leaderboard, new LeaderboardJsonCacheUpdateInterval(timePointsAndResultStatesAndMaxCompetitorsCount));
    -            // now wait for this entry to be computed
    -            cacheEntry = cache.get(leaderboard, /* waitForLatest */ true);
    -            StringBuffer jsonObjectAsString = cacheEntry.get(timePointAndResultStateAndMaxCompetitorsCount);
    -            result = jsonObjectAsString;
    -        } else {
    -            cacheHits++;
    -            result = cacheEntry.get(timePointAndResultStateAndMaxCompetitorsCount);
    -        }
    -        logger.finest(this+" cache hits/misses: "+cacheHits+"/"+cacheMisses);
    -        return result;
    -    }
    -
    -    private JSONObject computeLeaderboardJson(Leaderboard leaderboard,
    -            com.sap.sse.common.Util.Triple timePointAndResultStateAndMaxCompetitorsCount) throws NoWindException {
    -        TimePoint resultTimePoint = timePointAndResultStateAndMaxCompetitorsCount.getA();
    -        ResultStates resultState = timePointAndResultStateAndMaxCompetitorsCount.getB();
    -        Integer maxCompetitorsCount = timePointAndResultStateAndMaxCompetitorsCount.getC();
    -        List competitorsOrderedByTotalRank = leaderboard.getCompetitorsFromBestToWorst(resultTimePoint);
    -        Map> rankedCompetitorsPerColumn = new HashMap>();
    -        JSONObject jsonLeaderboard = new JSONObject();
    -        
    -        writeCommonLeaderboardData(jsonLeaderboard, leaderboard, resultState, resultTimePoint, maxCompetitorsCount);
    -
    -        JSONArray jsonCompetitorEntries = new JSONArray();
    -        jsonLeaderboard.put(COMPETITOR_ROOT_KEY, jsonCompetitorEntries);
    -        int counter = 1;
    -        for (Competitor competitor : competitorsOrderedByTotalRank) {
    -            if(maxCompetitorsCount != null && counter > maxCompetitorsCount) {
    -                break;
    -            }
    -            JSONObject jsonCompetitor = new JSONObject();
    -            jsonCompetitor.put(COMPETITOR_NAME, competitor.getName());
    -            final String displayName = leaderboard.getDisplayName(competitor);
    -            jsonCompetitor.put(COMPETITOR_DISPLAY_NAME, displayName==null?competitor.getName():displayName);
    -            jsonCompetitor.put(COMPETITOR_ID, competitor.getId().toString());
    -            jsonCompetitor.put(COMPETITOR_SAIL_ID, competitor.getBoat().getSailID());
    -            Nationality nationality = competitor.getTeam().getNationality();
    -            jsonCompetitor.put(COMPETITOR_NATIONALITY, nationality != null ? nationality.getThreeLetterIOCAcronym(): null);
    -            jsonCompetitor.put(COMPETITOR_COUNTRY_CODE, nationality != null ? (nationality.getCountryCode() != null ? nationality.getCountryCode().getTwoLetterISOCode(): null) : null);
    -            
    -            jsonCompetitor.put(COMPETITOR_RANK, competitorsOrderedByTotalRank.indexOf(competitor) + 1);
    -            jsonCompetitor.put(COMPETITOR_CARRIED_POINTS, leaderboard.getCarriedPoints(competitor));
    -            jsonCompetitor.put(COMPETITOR_TOTAL_POINTS, leaderboard.getTotalPoints(competitor, resultTimePoint));
    -            jsonCompetitorEntries.add(jsonCompetitor);
    -            JSONObject jsonRaceColumns = new JSONObject();
    -            jsonCompetitor.put(COMPETITOR_RACE_SCORES, jsonRaceColumns);
    -            final Set discardedRaceColumns = leaderboard.getResultDiscardingRule().getDiscardedRaceColumns(competitor, leaderboard,
    -                    leaderboard.getRaceColumns(), resultTimePoint);
    -            for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
    -                List rankedCompetitorsForColumn = rankedCompetitorsPerColumn.get(raceColumn);
    -                if (rankedCompetitorsForColumn == null) {
    -                    rankedCompetitorsForColumn = leaderboard.getCompetitorsFromBestToWorst(raceColumn, resultTimePoint);
    -                    rankedCompetitorsPerColumn.put(raceColumn, rankedCompetitorsForColumn);
    -                }
    -                JSONObject jsonEntry = new JSONObject();
    -                jsonRaceColumns.put(raceColumn.getName(), jsonEntry);
    -                final Fleet fleetOfCompetitor = raceColumn.getFleetOfCompetitor(competitor);
    -                jsonEntry.put(COMPETITOR_FLEET, fleetOfCompetitor==null?"":fleetOfCompetitor.getName());
    -                jsonEntry.put(COMPETITOR_NET_POINTS, leaderboard.getNetPoints(competitor, raceColumn, resultTimePoint));
    -                jsonEntry.put(COMPETITOR_UNCORRECTED_NET_POINTS, leaderboard.getTrackedRank(competitor, raceColumn, resultTimePoint));
    -                jsonEntry.put(COMPETITOR_TOTAL_POINTS, leaderboard.getTotalPoints(competitor, raceColumn, resultTimePoint, discardedRaceColumns));
    -                MaxPointsReason maxPointsReason = leaderboard.getMaxPointsReason(competitor, raceColumn, resultTimePoint);
    -                jsonEntry.put(COMPETITOR_MAX_POINTS_REASON, maxPointsReason != null ? maxPointsReason.toString(): null);
    -                jsonEntry.put(COMPETITOR_RANK, rankedCompetitorsForColumn.indexOf(competitor)+1);
    -                final TrackedRace trackedRace = raceColumn.getTrackedRace(competitor);
    -                if (trackedRace != null) {
    -                    jsonEntry.put(COMPETITOR_RACE_RANK, trackedRace.getRank(competitor, resultTimePoint));
    -                }
    -                jsonEntry.put(COMPETITOR_IS_DISCARDED, leaderboard.isDiscarded(competitor, raceColumn, resultTimePoint));
    -                jsonEntry.put(COMPETITOR_IS_CORRECTED, leaderboard.getScoreCorrection().isScoreCorrected(competitor, raceColumn, resultTimePoint));
    -            }
    -            counter++;
    -        }
    -        return jsonLeaderboard;
    -    }
    -
    -    private String formatTimePoint(TimePoint timepoint) {
    -        return timepoint == null ? null : TIMEPOINT_FORMATTER.format(timepoint.asDate());
    -    }
    -    
    -    private JSONObject createEmptyLeaderboardJson(Leaderboard leaderboard,
    -            ResultStates resultState, TimePoint requestTimePoint, Integer maxCompetitorsCount) throws NoWindException {
    -        JSONObject jsonLeaderboard = new JSONObject();
    -        
    -        writeCommonLeaderboardData(jsonLeaderboard, leaderboard, resultState, null, maxCompetitorsCount);
    -
    -        JSONArray jsonCompetitorEntries = new JSONArray();
    -        jsonLeaderboard.put(COMPETITOR_ROOT_KEY, jsonCompetitorEntries);
    -        for (Competitor competitor : leaderboard.getCompetitors()) {
    -            JSONObject jsonCompetitor = new JSONObject();
    -            jsonCompetitor.put(COMPETITOR_NAME, competitor.getName());
    -            final String displayName = leaderboard.getDisplayName(competitor);
    -            jsonCompetitor.put(COMPETITOR_DISPLAY_NAME, displayName==null?competitor.getName():displayName);
    -            jsonCompetitor.put(COMPETITOR_ID, competitor.getId().toString());
    -            jsonCompetitor.put(COMPETITOR_SAIL_ID, competitor.getBoat().getSailID());
    -            Nationality nationality = competitor.getTeam().getNationality();
    -            jsonCompetitor.put(COMPETITOR_NATIONALITY, nationality != null ? nationality.getThreeLetterIOCAcronym(): null);
    -            jsonCompetitor.put(COMPETITOR_COUNTRY_CODE, nationality != null ? (nationality.getCountryCode() != null ? nationality.getCountryCode().getTwoLetterISOCode(): null) : null);
    -            
    -            jsonCompetitor.put(COMPETITOR_RANK, 0);
    -            jsonCompetitor.put(COMPETITOR_CARRIED_POINTS, null);
    -            jsonCompetitor.put(COMPETITOR_TOTAL_POINTS, null);
    -            jsonCompetitorEntries.add(jsonCompetitor);
    -            JSONObject jsonRaceColumns = new JSONObject();
    -            jsonCompetitor.put(COMPETITOR_RACE_SCORES, jsonRaceColumns);
    -            for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
    -                JSONObject jsonEntry = new JSONObject();
    -                jsonRaceColumns.put(raceColumn.getName(), jsonEntry);
    -                final Fleet fleetOfCompetitor = raceColumn.getFleetOfCompetitor(competitor);
    -                jsonEntry.put(COMPETITOR_FLEET, fleetOfCompetitor==null?"":fleetOfCompetitor.getName());
    -                jsonEntry.put(COMPETITOR_NET_POINTS, null);
    -                jsonEntry.put(COMPETITOR_TOTAL_POINTS, null);
    -                jsonEntry.put(COMPETITOR_MAX_POINTS_REASON, "");
    -                jsonEntry.put(COMPETITOR_RANK, 0);
    -                jsonEntry.put(COMPETITOR_IS_DISCARDED, false);
    -                jsonEntry.put(COMPETITOR_IS_CORRECTED, false);
    -            }
    -        }
    -        return jsonLeaderboard;
    -    }
    -
    -    private void writeCommonLeaderboardData(JSONObject jsonLeaderboard, Leaderboard leaderboard, ResultStates resultState, 
    -            TimePoint resultTimePoint, Integer maxCompetitorsCount) {
    -        jsonLeaderboard.put("name", leaderboard.getName());
    -        
    -        // for backward compatibility 
    -        jsonLeaderboard.put("timepoint", formatTimePoint(resultTimePoint));
    -
    -        jsonLeaderboard.put("resultTimepoint", formatTimePoint(resultTimePoint));
    -        jsonLeaderboard.put("resultState", resultState.name());
    -        jsonLeaderboard.put("maxCompetitorsCount", maxCompetitorsCount);
    -        
    -        SettableScoreCorrection scoreCorrection = leaderboard.getScoreCorrection();
    -        if (scoreCorrection != null) {
    -            jsonLeaderboard.put("scoringComment", scoreCorrection.getComment());
    -            TimePoint lastUpdateTimepoint = scoreCorrection.getTimePointOfLastCorrectionsValidity();
    -            jsonLeaderboard.put("lastScoringUpdate", formatTimePoint(lastUpdateTimepoint));
    -        } else {
    -            jsonLeaderboard.put("scoringComment", null);
    -            jsonLeaderboard.put("lastScoringUpdate", null);
    -        }
    -        
    -        JSONArray jsonColumnNames = new JSONArray();
    -        jsonLeaderboard.put("columnNames", jsonColumnNames);
    -        for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
    -            jsonColumnNames.add(raceColumn.getName());
    -        }
    -    }
    -    
    -    private ResultStates resolveRequestedResultState(String resultStateParam) {
    -        ResultStates result = DEFAULT_RESULT_STATE;
    -        if(resultStateParam != null) {
    -            for(ResultStates state: ResultStates.values()) {
    -                if(state.name().equalsIgnoreCase(resultStateParam)) {
    -                    result = state;
    -                    break;
    -                }
    -            }
    -        }
    -        return result;
    -    }
    -
    -    private TimePoint calculateTimePointForResultState(Leaderboard leaderboard, ResultStates resultState) {
    -        TimePoint result = null;
    -        switch (resultState) {
    -        case Live:
    -            result = leaderboard.getTimePointOfLatestModification();
    -            if (result == null) {
    -                result = MillisecondsTimePoint.now();
    -            }
    -            break;
    -        case Preliminary:
    -        case Final:
    -            if (leaderboard.getScoreCorrection() != null && leaderboard.getScoreCorrection().getTimePointOfLastCorrectionsValidity() != null) {
    -                result = leaderboard.getScoreCorrection().getTimePointOfLastCorrectionsValidity();
    -                // As we don't have implemented bug 1246 (Define a clear result state for races and leaderboards) so far
    -                // we need to make sure that the timpoint for the final state is not determined in the middle of a running race,
    -                // because this would deliver not only final results but also some "mixed-in" live results.
    -                // Therefore, if there is a race that hasn't finished yet and whose first start mark passing is before
    -                // the current result, move result to before the start mark passing.
    -                for (TrackedRace trackedRace : leaderboard.getTrackedRaces()) {
    -                    TimePoint endOfRace = trackedRace.getEndOfRace();
    -                    if (endOfRace == null) {
    -                        Waypoint firstWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint();
    -                        if (firstWaypoint != null) {
    -                            Iterable markPassingsForFirstWaypoint = trackedRace.getMarkPassingsInOrder(firstWaypoint);
    -                            if (markPassingsForFirstWaypoint != null) {
    -                                trackedRace.lockForRead(markPassingsForFirstWaypoint);
    -                                try {
    -                                    Iterator i = markPassingsForFirstWaypoint.iterator();
    -                                    if (i.hasNext()) {
    -                                        TimePoint earliestMarkPassingTimePoint = i.next().getTimePoint();
    -                                        if (result == null || earliestMarkPassingTimePoint.before(result)) {
    -                                            result = earliestMarkPassingTimePoint.minus(1);
    -                                        }
    -                                    }
    -                                } finally {
    -                                    trackedRace.unlockAfterRead(markPassingsForFirstWaypoint);
    -                                }
    -                            }
    -                        }
    -                    }
    -                }
    -            }
    -            break;
    -        }
    -        return result;
    -    }
    -
    -    @Override
    -    public void add(Leaderboard leaderboard) {
    -        // nothing to do; the triggerUpdate call on the SmartFutureCache in doGet ensures that a record for the leaderboard
    -        // will be created in the SmartFutureCache
    -    }
    -
    -    /**
    -     * Invalidation triggers the recalculation for all time point / result state pairs for which the cache currently
    -     * holds values for leaderboard. It preserves the order in which they were inserted, so values requested
    -     * later (not necessarily having a later time point!) will be re-calculated and re-added later and therefore have a higher
    -     * chance of surviving cache eviction. Still, the cache won't grow beyond its limits because it uses a {@link LinkedHashMap}
    -     * with total size (aggregated size of all these maps)-based eviction.

    - * - * At the same time, existing cache entries won't simply be removed by the invalidation but remain in place for clients - * asking with waitForLatest==false and will only be replaced once the re-calculation has completed. It will be - * the cache size set by {@link #MAX_TOTAL_NUMBER_OF_CACHE_ENTRIES} that roughly decides how long a recalculation can last. - * The higher this number, the more entries a map for a single leaderboard may have which all will be re-calculated when - * this method is called. - */ - @Override - public void invalidate(Leaderboard leaderboard) { - final LinkedHashMap, StringBuffer> currentCachedValueForLeaderboard = - cache.get(leaderboard, /* waitForLatest */ false); - cache.triggerUpdate(leaderboard, new LeaderboardJsonCacheUpdateInterval(currentCachedValueForLeaderboard.keySet())); - } -} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/LeaderboardsJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/LeaderboardsJsonGetServlet.java deleted file mode 100755 index 9e1af2fe56b..00000000000 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/LeaderboardsJsonGetServlet.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.sap.sailing.server.gateway.impl; - -import java.io.IOException; -import java.util.Map; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.json.simple.JSONArray; - -import com.sap.sailing.domain.leaderboard.Leaderboard; -import com.sap.sailing.server.gateway.AbstractJsonHttpServlet; - -public class LeaderboardsJsonGetServlet extends AbstractJsonHttpServlet { - private static final long serialVersionUID = -2193421590275280102L; - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - JSONArray jsonLeaderboards = new JSONArray(); - Map leaderboards = getService().getLeaderboards(); - for (String leaderboardName : leaderboards.keySet()) { - jsonLeaderboards.add(leaderboardName); - } - setJsonResponseHeader(resp); - jsonLeaderboards.writeJSONString(resp.getWriter()); - } - -} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattaAndRaceDataJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattaAndRaceDataJsonGetServlet.java deleted file mode 100755 index ae77e9aa002..00000000000 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattaAndRaceDataJsonGetServlet.java +++ /dev/null @@ -1,407 +0,0 @@ -package com.sap.sailing.server.gateway.impl; - -import java.io.IOException; -import java.util.Iterator; -import java.util.Map; -import java.util.logging.Logger; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; - -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.Leg; -import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.base.Nationality; -import com.sap.sailing.domain.base.Person; -import com.sap.sailing.domain.base.RaceDefinition; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.NoWindException; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.Speed; -import com.sap.sailing.domain.common.Tack; -import com.sap.sailing.domain.common.Wind; -import com.sap.sailing.domain.common.tracking.GPSFix; -import com.sap.sailing.domain.common.tracking.GPSFixMoving; -import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; -import com.sap.sailing.domain.tracking.GPSFixTrack; -import com.sap.sailing.domain.tracking.TrackedLeg; -import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.domain.tracking.WindPositionMode; -import com.sap.sailing.server.gateway.AbstractJsonHttpServlet; -import com.sap.sse.InvalidDateException; -import com.sap.sse.common.CountryCode; -import com.sap.sse.common.Duration; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public class RegattaAndRaceDataJsonGetServlet extends AbstractJsonHttpServlet { - private static final Logger logger = Logger.getLogger(RegattaAndRaceDataJsonGetServlet.class.getName()); - - private static final long serialVersionUID = 1333207389294903999L; - - private static final String ACTION_NAME_LIST_EVENTS = "listevents"; - - private static final String ACTION_NAME_SHOW_RACE = "showrace"; - - private static final String ACTION_NAME_SHOW_WAYPOINTS = "showwaypoints"; - - private static final String ACTION_NAME_SHOW_BOAT_POSITIONS = "showboatpositions"; - - private static final String PARAM_NAME_SINCE_UPDATE = "sinceupdate"; - - private static final String PARAM_NAME_SINCE = "since"; - - private static final String PARAM_NAME_SINCE_MILLIS = "sinceasmillis"; - - private static final String PARAM_NAME_TO = "to"; - - private static final String PARAM_NAME_TO_MILLIS = "toasmillis"; - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - try { - String action = req.getParameter(PARAM_ACTION); - if (action != null) { - if (ACTION_NAME_LIST_EVENTS.equals(action)) { - listEvents(resp); - } else if (ACTION_NAME_SHOW_RACE.equals(action)) { - showRace(req, resp); - } else if (ACTION_NAME_SHOW_WAYPOINTS.equals(action)) { - showWaypoints(req, resp); - } else if (ACTION_NAME_SHOW_BOAT_POSITIONS.equals(action)) { - showBoatPositions(req, resp); - } else { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unknown action \""+action+"\""); - } - } else { - resp.getWriter().println("No action specified!"); - } - } catch (Exception e) { - resp.getWriter().println("Error processing request:"); - e.printStackTrace(resp.getWriter()); - } - } - - private void showBoatPositions(HttpServletRequest req, HttpServletResponse resp) throws IOException { - TrackedRace trackedRace = getTrackedRace(req); - if (trackedRace == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found"); - } else { - try { - TimePoint sinceTimePoint = readTimePointParam(req, PARAM_NAME_SINCE, PARAM_NAME_SINCE_MILLIS, null); - TimePoint toTimePoint = readTimePointParam(req, PARAM_NAME_TO, PARAM_NAME_TO_MILLIS, null); - JSONObject jsonRace = new JSONObject(); - jsonRace.put("name", trackedRace.getRace().getName()); - JSONArray jsonCompetitors = new JSONArray(); - for (Competitor competitor : trackedRace.getRace().getCompetitors()) { - JSONObject jsonCompetitor = new JSONObject(); - jsonCompetitor.put("name", competitor.getName()); - GPSFixTrack track = trackedRace.getTrack(competitor); - JSONArray jsonFixes = new JSONArray(); - track.lockForRead(); - try { - Iterator fixIter; - if (sinceTimePoint == null) { - fixIter = track.getFixes().iterator(); - } else { - fixIter = track.getFixesIterator(sinceTimePoint, /* inclusive */true); - } - while (fixIter.hasNext()) { - GPSFixMoving fix = fixIter.next(); - if (toTimePoint != null && fix.getTimePoint() != null - && toTimePoint.compareTo(fix.getTimePoint()) < 0) { - break; - } - JSONObject jsonFix = new JSONObject(); - jsonFix.put("timepoint", fix.getTimePoint().asMillis()); - jsonFix.put("latdeg", fix.getPosition().getLatDeg()); - jsonFix.put("lngdeg", fix.getPosition().getLngDeg()); - jsonFix.put("truebearingdeg", fix.getSpeed().getBearing().getDegrees()); - jsonFix.put("knotspeed", fix.getSpeed().getKnots()); - String tackName; - try { - final Tack tack = trackedRace.getTack(competitor, fix.getTimePoint()); - if (tack != null) { - tackName = tack.name(); - jsonFix.put("tack", tackName); - } - } catch (NoWindException e) { - // don't output tack - } - jsonFixes.add(jsonFix); - } - } finally { - track.unlockAfterRead(); - } - jsonCompetitor.put("track", jsonFixes); - jsonCompetitors.add(jsonCompetitor); - } - jsonRace.put("competitors", jsonCompetitors); - setJsonResponseHeader(resp); - jsonRace.writeJSONString(resp.getWriter()); - } catch (InvalidDateException e) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Couldn't parse time specification " + e.getMessage()); - } - } - } - - private void showWaypoints(HttpServletRequest req, HttpServletResponse resp) throws IOException { - TrackedRace trackedRace = getTrackedRace(req); - if (trackedRace == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found"); - } else { - try { - TimePoint timePoint = readTimePointParam(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS, - trackedRace.getStartOfRace() != null ? trackedRace.getStartOfRace() - : trackedRace.getStartOfTracking() != null ? trackedRace.getStartOfTracking() - : trackedRace.getTimePointOfNewestEvent() == null ? MillisecondsTimePoint.now() - : trackedRace.getTimePointOfNewestEvent()); - JSONArray jsonWaypoints = new JSONArray(); - for (Waypoint waypoint : trackedRace.getRace().getCourse().getWaypoints()) { - JSONObject jsonWaypoint = new JSONObject(); - jsonWaypoint.put("name", waypoint.getName()); - JSONArray jsonMarks = new JSONArray(); - for (Mark mark : waypoint.getMarks()) { - JSONObject jsonMark = new JSONObject(); - jsonMark.put("name", mark.getName()); - GPSFixTrack markTrack = trackedRace.getOrCreateTrack(mark); - GPSFix lastFixAtOrBefore = markTrack.getLastFixAtOrBefore(timePoint); - if (lastFixAtOrBefore != null) { - Position markPosition = lastFixAtOrBefore.getPosition(); - if (markPosition != null) { - jsonMark.put("lat", markPosition.getLatDeg()); - jsonMark.put("lng", markPosition.getLngDeg()); - } - } - jsonMarks.add(jsonMark); - } - jsonWaypoint.put("marks", jsonMarks); - jsonWaypoints.add(jsonWaypoint); - } - setJsonResponseHeader(resp); - jsonWaypoints.writeJSONString(resp.getWriter()); - } catch (InvalidDateException e) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Couldn't parse time specification " + e.getMessage()); - } - } - } - - private void showRace(HttpServletRequest req, HttpServletResponse resp) throws IOException, InterruptedException { - long start = System.currentTimeMillis(); - TrackedRace trackedRace = getTrackedRace(req); - if (trackedRace == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found"); - } else { - try { - TimePoint timePoint = readTimePointParam(req, PARAM_NAME_TIME, PARAM_NAME_TIME_MILLIS, - trackedRace.getTimePointOfNewestEvent()==null?MillisecondsTimePoint.now():trackedRace.getTimePointOfNewestEvent()); - String sinceUpdateString = req.getParameter(PARAM_NAME_SINCE_UPDATE); - if (sinceUpdateString != null) { - int sinceUpdate = Integer.valueOf(sinceUpdateString); - // block until there is new data: - System.out.println("Blocking at "+trackedRace.getUpdateCount()+" waiting for "+sinceUpdate); - trackedRace.waitForNextUpdate(sinceUpdate); - } - final RankingInfo rankingInfo = trackedRace.getRankingMetric().getRankingInfo(timePoint); - JSONObject jsonRace = new JSONObject(); - jsonRace.put("name", trackedRace.getRace().getName()); - jsonRace.put("startoftracking", trackedRace.getStartOfTracking() == null ? 0l : trackedRace - .getStartOfTracking().asMillis()); - jsonRace.put("endoftracking", trackedRace.getEndOfTracking() == null ? 0l : trackedRace - .getEndOfTracking().asMillis()); - jsonRace.put("start", trackedRace.getStartOfRace() == null ? 0l : trackedRace.getStartOfRace().asMillis()); - jsonRace.put("end", trackedRace.getEndOfRace() == null ? 0l : trackedRace.getEndOfRace().asMillis()); - jsonRace.put("timeofnewestevent", trackedRace.getTimePointOfNewestEvent() == null ? 0l : trackedRace - .getTimePointOfNewestEvent().asMillis()); - jsonRace.put("timeoflastevent", trackedRace.getTimePointOfLastEvent() == null ? 0l : trackedRace - .getTimePointOfLastEvent().asMillis()); - jsonRace.put("updatecount", trackedRace.getUpdateCount()); - jsonRace.put("windaveragingintervalmillis", trackedRace.getMillisecondsOverWhichToAverageWind()); - jsonRace.put("speedaveragingintervalmillis", trackedRace.getMillisecondsOverWhichToAverageSpeed()); - Position positionForWind = null; - TrackedLeg currentLeg = trackedRace.getCurrentLeg(timePoint); - if (currentLeg != null) { - positionForWind = trackedRace.getOrCreateTrack(currentLeg.getLeg().getFrom().getMarks().iterator().next()) - .getEstimatedPosition(timePoint, false); - } - Wind currentWind = trackedRace.getWind(positionForWind, timePoint); - if (currentWind != null) { - JSONObject jsonWind = new JSONObject(); - jsonWind.put("truebearingdeg", currentWind.getBearing().getDegrees()); - jsonWind.put("knotspeed", currentWind.getKnots()); - jsonWind.put("meterspersecondspeed", currentWind.getMetersPerSecond()); - jsonRace.put("wind", jsonWind); - } - JSONArray jsonLegs = new JSONArray(); - for (TrackedLeg leg : trackedRace.getTrackedLegs()) { - JSONObject jsonLeg = new JSONObject(); - jsonLeg.put("from", leg.getLeg().getFrom().getName()); - jsonLeg.put("fromwaypointid", leg.getLeg().getFrom().getId()); - jsonLeg.put("to", leg.getLeg().getTo().getName()); - jsonLeg.put("towaypointid", leg.getLeg().getTo().getId()); - try { - jsonLeg.put("upordownwindleg", leg.isUpOrDownwindLeg(timePoint)); - } catch (NoWindException e) { - // no wind, then it's simply no upwind or downwind leg - jsonLeg.put("upordownwindleg", "false"); - } - JSONArray jsonCompetitors = new JSONArray(); - Map ranks = leg.getRanks(timePoint); - for (Competitor competitor : trackedRace.getRace().getCompetitors()) { - JSONObject jsonCompetitorInLeg = new JSONObject(); - TrackedLegOfCompetitor trackedLegOfCompetitor = leg.getTrackedLeg(competitor); - if (trackedLegOfCompetitor != null) { - jsonCompetitorInLeg.put("name", competitor.getName()); - Speed currentSpeedOverGround = trackedLegOfCompetitor.getSpeedOverGround(timePoint); - if (currentSpeedOverGround != null) { - jsonCompetitorInLeg.put("currentSpeedOverGroundInKnots", - currentSpeedOverGround == null ? null : currentSpeedOverGround.getKnots()); - jsonCompetitorInLeg.put("currentSpeedOverGroundInMetersPerSecond", - currentSpeedOverGround == null ? null : currentSpeedOverGround.getMetersPerSecond()); - } - Speed averageSpeedOverGround = trackedLegOfCompetitor.getAverageSpeedOverGround(timePoint); - if (averageSpeedOverGround != null) { - jsonCompetitorInLeg.put("averageSpeedOverGroundInKnots", - averageSpeedOverGround.getKnots()); - jsonCompetitorInLeg.put("averageSpeedOverGroundInMetersPerSecond", - averageSpeedOverGround.getMetersPerSecond()); - } - Speed currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinished = - trackedLegOfCompetitor.hasFinishedLeg(timePoint) ? - averageSpeedOverGround : currentSpeedOverGround; - if (currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinished != null) { - jsonCompetitorInLeg.put("currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots", - currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinished.getKnots()); - jsonCompetitorInLeg.put("currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond", - currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinished.getMetersPerSecond()); - } - Distance distanceTraveled = trackedLegOfCompetitor.getDistanceTraveled(timePoint); - if (distanceTraveled != null) { - jsonCompetitorInLeg.put("distanceTraveledOverGroundInMeters", - distanceTraveled.getMeters()); - } - Speed velocityMadeGood = trackedLegOfCompetitor.getVelocityMadeGood(timePoint, WindPositionMode.EXACT); - if (velocityMadeGood != null) { - jsonCompetitorInLeg.put("velocityMadeGoodInKnots", velocityMadeGood.getKnots()); - jsonCompetitorInLeg.put("velocityMadeGoodInMetersPerSecond", velocityMadeGood.getMetersPerSecond()); - } - Speed averageVelocityMadeGood = trackedLegOfCompetitor - .getAverageVelocityMadeGood(timePoint); - if (averageVelocityMadeGood != null) { - jsonCompetitorInLeg.put("averageVelocityMadeGoodInKnots", - averageVelocityMadeGood.getKnots()); - jsonCompetitorInLeg.put("averageVelocityMadeGoodInMetersPerSecond", - averageVelocityMadeGood.getMetersPerSecond()); - } - try { - Integer rank = ranks.get(competitor); - if (rank == null) { - logger.warning("Can't find rank of competitor "+competitor+" in leg "+leg); - } - jsonCompetitorInLeg.put("rank", ranks.get(competitor)); - } catch (RuntimeException re) { - if (re.getCause() != null && re.getCause() instanceof NoWindException) { - // well, we don't know the wind direction, so we can't compute a ranking - } else { - throw re; - } - } - jsonCompetitorInLeg.put("gapToLeaderInSeconds", - trackedLegOfCompetitor.getGapToLeader(timePoint, rankingInfo, WindPositionMode.LEG_MIDDLE)); - final Duration estimatedTimeToNextMarkInSeconds = trackedLegOfCompetitor - .getEstimatedTimeToNextMark(timePoint, WindPositionMode.EXACT); - jsonCompetitorInLeg.put("estimatedTimeToNextMarkInSeconds", - estimatedTimeToNextMarkInSeconds == null ? null : estimatedTimeToNextMarkInSeconds); - jsonCompetitorInLeg.put("windwardDistanceToGoInMeters", trackedLegOfCompetitor - .getWindwardDistanceToGo(timePoint, WindPositionMode.LEG_MIDDLE).getMeters()); - jsonCompetitorInLeg.put("started", trackedLegOfCompetitor.hasStartedLeg(timePoint)); - jsonCompetitorInLeg.put("finished", trackedLegOfCompetitor.hasFinishedLeg(timePoint)); - jsonCompetitors.add(jsonCompetitorInLeg); - } - } - jsonLeg.put("competitors", jsonCompetitors); - jsonLegs.add(jsonLeg); - } - jsonRace.put("legs", jsonLegs); - JSONArray jsonRaceRanking = new JSONArray(); - for (Competitor competitor : trackedRace.getRace().getCompetitors()) { - JSONObject competitorRank = new JSONObject(); - competitorRank.put("competitor", competitor.getName()); - competitorRank.put("rank", trackedRace.getRank(competitor, timePoint)); - jsonRaceRanking.add(competitorRank); - } - jsonRace.put("ranks", jsonRaceRanking); - setJsonResponseHeader(resp); - jsonRace.writeJSONString(resp.getWriter()); - } catch (InvalidDateException e) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Couldn't parse time specification " + e.getMessage()); - } - } - System.out.println("showrace took "+(System.currentTimeMillis()-start)+"ms"); - } - - private void listEvents(HttpServletResponse resp) throws IOException { - // TODO one a new top-level event will have been introduced, produce it here - JSONArray regattaList = new JSONArray(); - for (Regatta regatta : getService().getAllRegattas()) { - JSONObject jsonEvent = new JSONObject(); - jsonEvent.put("name", regatta.getName()); - if (regatta.getBoatClass() != null) { - jsonEvent.put("boatclass", regatta.getBoatClass().getName()); - } - JSONArray jsonCompetitors = new JSONArray(); - for (Competitor competitor : regatta.getAllCompetitors()) { - JSONObject jsonCompetitor = new JSONObject(); - jsonCompetitor.put("name", competitor.getName()); - jsonCompetitor.put("sailID", competitor.getBoat()==null?"":competitor.getBoat().getSailID()); - final Nationality nationality = competitor.getTeam().getNationality(); - jsonCompetitor.put("nationality", nationality == null ? "" : nationality.getThreeLetterIOCAcronym()); - CountryCode countryCode = nationality == null ? null : nationality.getCountryCode(); - jsonCompetitor.put("nationalityISO2", countryCode == null ? "" : countryCode.getTwoLetterISOCode()); - jsonCompetitor.put("nationalityISO3", countryCode == null ? "" : countryCode.getThreeLetterISOCode()); - JSONArray jsonTeam = new JSONArray(); - for (Person sailor : competitor.getTeam().getSailors()) { - JSONObject jsonSailor = new JSONObject(); - jsonSailor.put("name", sailor.getName()); - jsonSailor.put("description", sailor.getDescription()==null?"":sailor.getDescription()); - jsonTeam.add(jsonSailor); - } - jsonCompetitor.put("team", jsonTeam); - jsonCompetitors.add(jsonCompetitor); - } - jsonEvent.put("competitors", jsonCompetitors); - JSONArray jsonRaces = new JSONArray(); - for (RaceDefinition race : regatta.getAllRaces()) { - // don't wait for the arrival of a tracked race; just ignore it if it's not currently being tracked - TrackedRace trackedRace = getService().getOrCreateTrackedRegatta(regatta).getExistingTrackedRace(race); - if (trackedRace != null) { - JSONObject jsonRace = new JSONObject(); - jsonRace.put("name", race.getName()); - jsonRace.put("boatclass", race.getBoatClass() == null ? "" : race.getBoatClass().getName()); - TimePoint start = trackedRace.getStartOfRace(); - jsonRace.put("start", start == null ? Long.MAX_VALUE : start.asMillis()); - JSONArray jsonLegs = new JSONArray(); - for (Leg leg : race.getCourse().getLegs()) { - JSONObject jsonLeg = new JSONObject(); - jsonLeg.put("start", leg.getFrom().getName()); - jsonLeg.put("end", leg.getTo().getName()); - jsonLegs.add(jsonLeg); - } - jsonRace.put("legs", jsonLegs); - jsonRaces.add(jsonRace); - } - } - jsonEvent.put("races", jsonRaces); - regattaList.add(jsonEvent); - } - setJsonResponseHeader(resp); - regattaList.writeJSONString(resp.getWriter()); - } -} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattaEntryListJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattaEntryListJsonGetServlet.java deleted file mode 100644 index d5106713338..00000000000 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattaEntryListJsonGetServlet.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.sap.sailing.server.gateway.impl; - -import java.io.IOException; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.json.simple.JSONObject; - -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.common.RegattaName; -import com.sap.sailing.server.gateway.AbstractJsonHttpServlet; -import com.sap.sailing.server.gateway.serialization.JsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.CompetitorJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.NationalityJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.PersonJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.RegattaJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.TeamJsonSerializer; - -public class RegattaEntryListJsonGetServlet extends AbstractJsonHttpServlet { - private static final long serialVersionUID = -5280787192024697254L; - - private static final String PARAM_NAME_REGATTANAME = "name"; - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - String regattaName = request.getParameter(PARAM_NAME_REGATTANAME); - if (regattaName == null) { - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, - "Need to specify a regatta name using the " + PARAM_NAME_REGATTANAME + " parameter"); - } else { - Regatta regatta = getService().getRegatta(new RegattaName(regattaName)); - if (regatta == null) { - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Regatta " + regattaName - + " not found"); - } else { - NationalityJsonSerializer nationalityJsonSerializer = new NationalityJsonSerializer(); - CompetitorJsonSerializer competitorJsonSerializer = new CompetitorJsonSerializer( - new TeamJsonSerializer(new PersonJsonSerializer(nationalityJsonSerializer)), null); - JsonSerializer regattaSerializer = new RegattaJsonSerializer(null, competitorJsonSerializer); - JSONObject serializedRegatta = regattaSerializer.serialize(regatta); - - setJsonResponseHeader(response); - serializedRegatta.writeJSONString(response.getWriter()); - } - } - } -} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattaJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattaJsonGetServlet.java deleted file mode 100644 index 0340ef836d1..00000000000 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattaJsonGetServlet.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.sap.sailing.server.gateway.impl; - -import java.io.IOException; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.json.simple.JSONObject; - -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.common.RegattaName; -import com.sap.sailing.server.gateway.AbstractJsonHttpServlet; -import com.sap.sailing.server.gateway.serialization.JsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.ColorJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.FleetJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.RegattaJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.SeriesJsonSerializer; - -public class RegattaJsonGetServlet extends AbstractJsonHttpServlet { - private static final long serialVersionUID = -5280787192024697254L; - - private static final String PARAM_NAME_REGATTANAME = "name"; - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - String regattaName = request.getParameter(PARAM_NAME_REGATTANAME); - if (regattaName == null) { - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Need to specify a regatta name using the "+ - PARAM_NAME_REGATTANAME + " parameter"); - } else { - Regatta regatta = getService().getRegatta(new RegattaName(regattaName)); - if (regatta == null) { - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Regatta " + regattaName + " not found"); - } else { - SeriesJsonSerializer seriesJsonSerializer = new SeriesJsonSerializer(new FleetJsonSerializer(new ColorJsonSerializer())); - JsonSerializer regattaSerializer = new RegattaJsonSerializer(seriesJsonSerializer, null); - JSONObject serializedRegatta = regattaSerializer.serialize(regatta); - - setJsonResponseHeader(response); - serializedRegatta.writeJSONString(response.getWriter()); - } - } - } -} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattasJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattasJsonGetServlet.java deleted file mode 100644 index 1143d561bcf..00000000000 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/RegattasJsonGetServlet.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.sap.sailing.server.gateway.impl; - -import java.io.IOException; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.json.simple.JSONArray; - -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.server.gateway.AbstractJsonHttpServlet; -import com.sap.sailing.server.gateway.serialization.impl.RegattaJsonSerializer; - -public class RegattasJsonGetServlet extends AbstractJsonHttpServlet { - private static final long serialVersionUID = 1333207389294903999L; - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - RegattaJsonSerializer regattaJsonSerializer = new RegattaJsonSerializer(); - - JSONArray regattasJson = new JSONArray(); - for (Regatta regatta : getService().getAllRegattas()) { - regattasJson.add(regattaJsonSerializer.serialize(regatta)); - } - setJsonResponseHeader(resp); - regattasJson.writeJSONString(resp.getWriter()); - } -} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/WindJsonGetServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/WindJsonGetServlet.java deleted file mode 100644 index 01fe23d7280..00000000000 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/WindJsonGetServlet.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.sap.sailing.server.gateway.impl; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; - -import com.sap.sailing.domain.base.RaceDefinition; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.common.Wind; -import com.sap.sailing.domain.common.WindSource; -import com.sap.sailing.domain.common.WindSourceType; -import com.sap.sailing.domain.common.impl.WindSourceImpl; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.domain.tracking.WindTrack; -import com.sap.sailing.server.gateway.AbstractJsonHttpServlet; -import com.sap.sse.InvalidDateException; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public class WindJsonGetServlet extends AbstractJsonHttpServlet { - static final String ALL = "ALL"; - - private static final long serialVersionUID = -1408004464252437535L; - - private static final String PARAM_NAME_WINDSOURCE = "windsource"; - private static final String PARAM_NAME_WINDSOURCE_ID = "windsourceid"; - private static final String PARAM_NAME_FROM_TIME = "fromtime"; - private static final String PARAM_NAME_FROM_TIME_MILLIS = "fromtimeasmillis"; - private static final String PARAM_NAME_TO_TIME = "totime"; - private static final String PARAM_NAME_TO_TIME_MILLIS = "totimeasmillis"; - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - Regatta regatta = getRegatta(req); - if (regatta == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Regatta not found"); - } else { - RaceDefinition race = getRaceDefinition(req); - if (race == null) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Race not found"); - } else { - String windSourceParam = req.getParameter(PARAM_NAME_WINDSOURCE); - String windSourceToRead = windSourceParam != null ? windSourceParam : ALL; - String windSourceIdParam = req.getParameter(PARAM_NAME_WINDSOURCE_ID); - String windSourceIdToRead = windSourceIdParam != null ? windSourceIdParam : null; - - TrackedRace trackedRace = getService().getOrCreateTrackedRegatta(regatta).getTrackedRace(race); - - TimePoint from; - TimePoint to; - try { - from = readTimePointParam(req, PARAM_NAME_FROM_TIME, PARAM_NAME_FROM_TIME_MILLIS, - trackedRace.getStartOfRace() == null ? new MillisecondsTimePoint(0) : - /* 24h before race start */ new MillisecondsTimePoint(trackedRace.getStartOfRace().asMillis()-24*3600*1000)); - } catch (InvalidDateException e1) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not parse the 'from' time"); - return; - } - try { - to = readTimePointParam(req, PARAM_NAME_TO_TIME, PARAM_NAME_TO_TIME_MILLIS, MillisecondsTimePoint.now()); - } catch (InvalidDateException e1) { - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not parse the 'to' time"); - return; - } - - JSONObject jsonWindTracks = getResult(windSourceToRead, windSourceIdToRead, trackedRace, from, to); - setJsonResponseHeader(resp); - jsonWindTracks.writeJSONString(resp.getWriter()); - } - } - } - - JSONObject getResult(String windSourceToRead, String windSourceIdToRead, TrackedRace trackedRace, TimePoint from, TimePoint to) { - Map> fixes = getRelevantFixes(windSourceToRead, windSourceIdToRead, trackedRace, from, to); - JSONObject jsonWindTracks = getResultAsJsonObject(trackedRace, fixes); - return jsonWindTracks; - } - - private Map> getRelevantFixes(String windSourceToRead, String windSourceIdToRead, TrackedRace trackedRace, TimePoint from, - TimePoint to) { - // quickly extract relevant fixes to hold locks as shortly as possible; process later - List windSources = getAvailableWindSources(trackedRace); - Map> fixes = new HashMap<>(); - for (WindSource windSource : windSources) { - if (ALL.equals(windSourceToRead) || windSource.getType().name().equalsIgnoreCase(windSourceToRead)) { - if (windSourceIdToRead != null && windSource.getId() != null && !windSourceIdToRead.equalsIgnoreCase(windSource.getId().toString())) { - continue; - } - ArrayList fixesForWindSource = new ArrayList<>(); - fixes.put(windSource, fixesForWindSource); - WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource); - windTrack.lockForRead(); - try { - Iterator windIter = windTrack.getFixesIterator(from, /* inclusive */true); - while (windIter.hasNext()) { - Wind wind = windIter.next(); - if (wind.getTimePoint().compareTo(to) > 0) { - break; - } else { - fixesForWindSource.add(wind); - } - } - } finally { - windTrack.unlockAfterRead(); - } - } - } - return fixes; - } - - private JSONObject getResultAsJsonObject(TrackedRace trackedRace, Map> fixes) { - JSONObject jsonWindTracks = new JSONObject(); - JSONArray jsonWindSourcesDisplayed = new JSONArray(); - for (WindSource windSource : fixes.keySet()) { - JSONObject windSourceInformation = new JSONObject(); - windSourceInformation.put("typeName", windSource.getType().name()); - windSourceInformation.put("id", windSource.getId() != null ? windSource.getId().toString() : ""); - jsonWindSourcesDisplayed.add(windSourceInformation); - } - jsonWindTracks.put("availableWindSources", jsonWindSourcesDisplayed); - for (Map.Entry> e : fixes.entrySet()) { - WindSource windSource = e.getKey(); - JSONArray jsonWindArray = new JSONArray(); - WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource); - for (Wind wind : e.getValue()) { - JSONObject jsonWind = new JSONObject(); - jsonWind.put("truebearingdeg", wind.getBearing().getDegrees()); - jsonWind.put("knotspeed", wind.getKnots()); - jsonWind.put("meterspersecondspeed", wind.getMetersPerSecond()); - if (wind.getTimePoint() != null) { - jsonWind.put("timepoint", wind.getTimePoint().asMillis()); - final Wind averagedWind = windTrack.getAveragedWind(wind.getPosition(), wind.getTimePoint()); - jsonWind.put("dampenedtruebearingdeg", averagedWind.getBearing().getDegrees()); - jsonWind.put("dampenedknotspeed", averagedWind.getKnots()); - jsonWind.put("dampenedmeterspersecondspeed", averagedWind.getMetersPerSecond()); - } - if (wind.getPosition() != null) { - jsonWind.put("latdeg", wind.getPosition().getLatDeg()); - jsonWind.put("lngdeg", wind.getPosition().getLngDeg()); - } - jsonWindArray.add(jsonWind); - } - jsonWindTracks.put(windSource.toString()+(windSource.getId() != null ? "-"+windSource.getId().toString() : ""), jsonWindArray); - } - return jsonWindTracks; - } - - List getAvailableWindSources(TrackedRace trackedRace) { - List windSources = new ArrayList(); - for (WindSource windSource : trackedRace.getWindSources()) { - windSources.add(windSource); - } - for (WindSource windSourceToExclude : trackedRace.getWindSourcesToExclude()) { - windSources.remove(windSourceToExclude); - } - windSources.add(new WindSourceImpl(WindSourceType.COMBINED)); - return windSources; - } - -} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/AddEntryToRaceLogJsonPostServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/AddEntryToRaceLogJsonPostServlet.java index a5e56720082..fc534d63b6f 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/AddEntryToRaceLogJsonPostServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/AddEntryToRaceLogJsonPostServlet.java @@ -12,6 +12,7 @@ import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang.StringEscapeUtils; import org.json.simple.JSONObject; import org.json.simple.JSONValue; import org.json.simple.parser.ParseException; @@ -88,19 +89,19 @@ public class AddEntryToRaceLogJsonPostServlet extends AbstractJsonHttpServlet { Leaderboard leaderboard = service.getLeaderboardByName(leaderboardName); if (leaderboard == null) { - response.sendError(HttpServletResponse.SC_NOT_FOUND, "Leaderboard "+leaderboardName+" not found."); + response.sendError(HttpServletResponse.SC_NOT_FOUND, "Leaderboard "+StringEscapeUtils.escapeHtml(leaderboardName)+" not found."); return; } RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); if (raceColumn == null) { - response.sendError(HttpServletResponse.SC_NOT_FOUND, "Race column "+raceColumnName+" not found."); + response.sendError(HttpServletResponse.SC_NOT_FOUND, "Race column "+StringEscapeUtils.escapeHtml(raceColumnName)+" not found."); return; } Fleet fleet = raceColumn.getFleetByName(fleetName); if (fleet == null) { - response.sendError(HttpServletResponse.SC_NOT_FOUND, "Fleet "+fleetName+" not found."); + response.sendError(HttpServletResponse.SC_NOT_FOUND, "Fleet "+StringEscapeUtils.escapeHtml(fleetName)+" not found."); return; } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AbstractSailingServerResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AbstractSailingServerResource.java index 419781c0888..3571d969bfa 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AbstractSailingServerResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AbstractSailingServerResource.java @@ -76,8 +76,8 @@ public abstract class AbstractSailingServerResource { TrackedRace trackedRace = null; if (regatta != null && race != null) { DynamicTrackedRegatta trackedRegatta = getService().getTrackedRegatta(regatta); - if(trackedRegatta != null) { - trackedRace = trackedRegatta.getTrackedRace(race); + if (trackedRegatta != null) { + trackedRace = trackedRegatta.getExistingTrackedRace(race); } } return trackedRace; diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompetitorsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompetitorsResource.java index 5a9dba18631..0d9d084112d 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompetitorsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/CompetitorsResource.java @@ -21,6 +21,7 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.apache.commons.fileupload.util.Streams; +import org.apache.commons.lang.StringEscapeUtils; import org.json.simple.JSONObject; import com.sap.sailing.domain.base.Competitor; @@ -72,7 +73,7 @@ public class CompetitorsResource extends AbstractSailingServerResource { competitorIdAsString); if (competitor == null) { response = Response.status(Status.NOT_FOUND) - .entity("Could not find a competitor with id '" + competitorIdAsString + "'.") + .entity("Could not find a competitor with id '" + StringEscapeUtils.escapeHtml(competitorIdAsString) + "'.") .type(MediaType.TEXT_PLAIN).build(); } else { String jsonString = getCompetitorJSON(competitor).toJSONString(); @@ -89,7 +90,7 @@ public class CompetitorsResource extends AbstractSailingServerResource { if (competitor == null) { return Response.status(Status.NOT_FOUND) - .entity("Could not find a competitor with id '" + competitorId + "'.").type(MediaType.TEXT_PLAIN) + .entity("Could not find a competitor with id '" + StringEscapeUtils.escapeHtml(competitorId) + "'.").type(MediaType.TEXT_PLAIN) .build(); } @@ -97,7 +98,7 @@ public class CompetitorsResource extends AbstractSailingServerResource { if (team == null) { return Response.status(Status.NOT_FOUND) - .entity("Could not find a team associated with competitor '" + competitorId + "'.") + .entity("Could not find a team associated with competitor '" + StringEscapeUtils.escapeHtml(competitorId) + "'.") .type(MediaType.TEXT_PLAIN).build(); } @@ -127,9 +128,10 @@ public class CompetitorsResource extends AbstractSailingServerResource { CompetitorStore store = service.getCompetitorStore(); Competitor competitor = store.getExistingCompetitorByIdAsString(competitorId); if (competitor == null) { - logger.log(Level.INFO, "Could not find competitor to store image for: " + competitorId); + logger.log(Level.INFO, "Could not find competitor to store image for: " + StringEscapeUtils.escapeHtml(competitorId)); throw new WebApplicationException(Response.status(Status.BAD_REQUEST) - .entity("Could not find competitor with id " + competitorId).type(MediaType.TEXT_PLAIN).build()); + .entity("Could not find competitor with id " + + StringEscapeUtils.escapeHtml(competitorId)).type(MediaType.TEXT_PLAIN).build()); } String fileExtension = ""; diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/DataMiningResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/DataMiningResource.java index 9a53561cbb6..472489eaca3 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/DataMiningResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/DataMiningResource.java @@ -13,6 +13,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import org.apache.commons.lang.StringEscapeUtils; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.osgi.util.tracker.ServiceTracker; @@ -60,7 +61,7 @@ public class DataMiningResource extends AbstractSailingServerResource { } private Response getBadQueryDefinitionErrorResponse(ModifiableStatisticQueryDefinitionDTO queryDefinitionDTO) { - return Response.status(Status.BAD_REQUEST).entity("Couldn't create a query for definition '" + queryDefinitionDTO + "'.") + return Response.status(Status.BAD_REQUEST).entity("Couldn't create a query for definition '" + StringEscapeUtils.escapeHtml(queryDefinitionDTO.toString()) + "'.") .type(MediaType.TEXT_PLAIN).build(); } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java index 98860559abf..50404e507cf 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java @@ -11,6 +11,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import org.apache.commons.lang.StringEscapeUtils; import org.json.simple.JSONArray; import org.json.simple.JSONObject; @@ -29,7 +30,7 @@ import com.sap.sse.common.Util.Pair; public class EventsResource extends AbstractSailingServerResource { private Response getBadEventErrorResponse(String eventId) { - return Response.status(Status.NOT_FOUND).entity("Could not find an event with id '" + eventId + "'.").type(MediaType.TEXT_PLAIN).build(); + return Response.status(Status.NOT_FOUND).entity("Could not find an event with id '" + StringEscapeUtils.escapeHtml(eventId) + "'.").type(MediaType.TEXT_PLAIN).build(); } @GET diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardGroupsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardGroupsResource.java index b399c213d41..96d6fa865d3 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardGroupsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardGroupsResource.java @@ -16,6 +16,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import org.apache.commons.lang.StringEscapeUtils; import org.json.simple.JSONArray; import org.json.simple.JSONObject; @@ -61,7 +62,8 @@ public class LeaderboardGroupsResource extends AbstractSailingServerResource { Response response; LeaderboardGroup leaderboardGroup = getService().getLeaderboardGroupByName(leaderboardGroupName); if (leaderboardGroup == null) { - response = Response.status(Status.NOT_FOUND).entity("Could not find a leaderboard group with name '"+leaderboardGroupName+"'.").type(MediaType.TEXT_PLAIN).build(); + response = Response.status(Status.NOT_FOUND).entity("Could not find a leaderboard group with name '"+ + StringEscapeUtils.escapeHtml(leaderboardGroupName)+"'.").type(MediaType.TEXT_PLAIN).build(); } else { TimePoint timePoint = MillisecondsTimePoint.now(); JSONObject jsonLeaderboardGroup = new JSONObject(); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java index f3ddf72dfb8..79bcac5afd3 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java @@ -28,6 +28,7 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.xml.ws.http.HTTPException; +import org.apache.commons.lang.StringEscapeUtils; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.JSONValue; @@ -115,15 +116,14 @@ public class LeaderboardsResource extends AbstractSailingServerResource { @Path("{name}") public Response getLeaderboard(@PathParam("name") String leaderboardName, @DefaultValue("Live") @QueryParam("resultState") ResultStates resultState, - @DefaultValue("true") @QueryParam("useCache") boolean useCache, - @DefaultValue("1000") @QueryParam("maxCompetitorsCount") int maxCompetitorsCount) { + @QueryParam("maxCompetitorsCount") Integer maxCompetitorsCount) { Response response; TimePoint requestTimePoint = MillisecondsTimePoint.now(); Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); if (leaderboard == null) { response = Response.status(Status.NOT_FOUND) - .entity("Could not find a leaderboard with name '" + leaderboardName + "'.") + .entity("Could not find a leaderboard with name '" + StringEscapeUtils.escapeHtml(leaderboardName) + "'.") .type(MediaType.TEXT_PLAIN).build(); } else { try { @@ -517,11 +517,11 @@ public class LeaderboardsResource extends AbstractSailingServerResource { if (competitor == null) { response = Response.status(Status.NOT_FOUND) - .entity("Could not find a competitor with id '" + competitorIdAsString + "'.") + .entity("Could not find a competitor with id '" + StringEscapeUtils.escapeHtml(competitorIdAsString) + "'.") .type(MediaType.TEXT_PLAIN).build(); } else if (leaderboard == null) { response = Response.status(Status.NOT_FOUND) - .entity("Could not find a leaderboard with name '" + leaderboardName + "'.") + .entity("Could not find a leaderboard with name '" + StringEscapeUtils.escapeHtml(leaderboardName) + "'.") .type(MediaType.TEXT_PLAIN).build(); } else { JSONObject json = CompetitorsResource.getCompetitorJSON(competitor); @@ -541,7 +541,7 @@ public class LeaderboardsResource extends AbstractSailingServerResource { Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); if (leaderboard == null) { return Response.status(Status.NOT_FOUND) - .entity("Could not find a leaderboard with name '" + leaderboardName + "'.") + .entity("Could not find a leaderboard with name '" + StringEscapeUtils.escapeHtml(leaderboardName) + "'.") .type(MediaType.TEXT_PLAIN).build(); } @@ -568,14 +568,15 @@ public class LeaderboardsResource extends AbstractSailingServerResource { if (raceColumn == null) { return Response .status(Status.NOT_FOUND) - .entity("Could not find a race column '" + raceColumnName + "' in leaderboard '" - + leaderboardName + "'.").type(MediaType.TEXT_PLAIN).build(); + .entity("Could not find a race column '" + StringEscapeUtils.escapeHtml(raceColumnName) + "' in leaderboard '" + + StringEscapeUtils.escapeHtml(leaderboardName) + "'.").type(MediaType.TEXT_PLAIN).build(); } else if (fleetName != null) { Fleet fleet = raceColumn.getFleetByName(fleetName); if (fleet == null) { return Response .status(Status.NOT_FOUND) - .entity("Could not find fleet '" + fleetName + "' in leaderboard '" + leaderboardName + .entity("Could not find fleet '" + StringEscapeUtils.escapeHtml(fleetName) + "' in leaderboard '" + + StringEscapeUtils.escapeHtml(leaderboardName) + "'.").type(MediaType.TEXT_PLAIN).build(); } else { Util.addAll(raceColumn.getAllMarks(fleet), marks); @@ -631,7 +632,7 @@ public class LeaderboardsResource extends AbstractSailingServerResource { Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); if (leaderboard == null) { return Response.status(Status.NOT_FOUND) - .entity("Could not find a leaderboard with name '" + leaderboardName + "'.") + .entity("Could not find a leaderboard with name '" + StringEscapeUtils.escapeHtml(leaderboardName) + "'.") .type(MediaType.TEXT_PLAIN).build(); } @@ -640,13 +641,13 @@ public class LeaderboardsResource extends AbstractSailingServerResource { regattaLog = ((HasRegattaLike) leaderboard).getRegattaLike().getRegattaLog(); } else { return Response.status(Status.BAD_REQUEST) - .entity("Leaderboard '" + leaderboardName + "' does not have an attached RegattaLog.") + .entity("Leaderboard '" + StringEscapeUtils.escapeHtml(leaderboardName) + "' does not have an attached RegattaLog.") .type(MediaType.TEXT_PLAIN).build(); } Mark mark = getService().getBaseDomainFactory().getExistingMarkByIdAsString(markId); if (mark == null) { - return Response.status(Status.NOT_FOUND).entity("Could not find a mark with ID '" + markId + "'.") + return Response.status(Status.NOT_FOUND).entity("Could not find a mark with ID '" + StringEscapeUtils.escapeHtml(markId) + "'.") .type(MediaType.TEXT_PLAIN).build(); } TimePoint now = MillisecondsTimePoint.now(); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PolarResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PolarResource.java index a6633dc57e2..ec832b06767 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PolarResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/PolarResource.java @@ -14,6 +14,7 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.Status; +import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.math.analysis.polynomials.PolynomialFunction; import org.json.simple.JSONArray; @@ -134,11 +135,12 @@ public class PolarResource extends AbstractSailingServerResource { } private Response getBadRegattaErrorResponse(String regattaName) { - return Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN).build(); + return Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + StringEscapeUtils.escapeHtml(regattaName) + "'.").type(MediaType.TEXT_PLAIN).build(); } private Response getBadRaceErrorResponse(String regattaName, String raceName) { - return Response.status(Status.NOT_FOUND).entity("Could not find a race with name '" + raceName + "' in regatta '" + regattaName + "'.").type(MediaType.TEXT_PLAIN).build(); + return Response.status(Status.NOT_FOUND).entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + + "' in regatta '" + StringEscapeUtils.escapeHtml(regattaName) + "'.").type(MediaType.TEXT_PLAIN).build(); } @GET diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java index f20ceb6899e..e08f2deb2ce 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java @@ -20,6 +20,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import org.apache.commons.lang.StringEscapeUtils; import org.json.simple.JSONArray; import org.json.simple.JSONObject; @@ -76,26 +77,26 @@ public class RegattasResource extends AbstractSailingServerResource { private static final Logger logger = Logger.getLogger(RegattasResource.class.getName()); private Response getBadRegattaErrorResponse(String regattaName) { - return Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + regattaName + "'.") + return Response.status(Status.NOT_FOUND).entity("Could not find a regatta with name '" + StringEscapeUtils.escapeHtml(regattaName) + "'.") .type(MediaType.TEXT_PLAIN).build(); } private Response getBadRaceErrorResponse(String regattaName, String raceName) { return Response.status(Status.NOT_FOUND) - .entity("Could not find a race with name '" + raceName + "' in regatta '" + regattaName + "'.") + .entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "' in regatta '" + StringEscapeUtils.escapeHtml(regattaName) + "'.") .type(MediaType.TEXT_PLAIN).build(); } private Response getNoTrackedRaceErrorResponse(String regattaName, String raceName) { return Response.status(Status.NOT_FOUND) - .entity("No tracked race for race with name '" + raceName + "' in regatta '" + regattaName + "'.") + .entity("No tracked race for race with name '" + StringEscapeUtils.escapeHtml(raceName) + "' in regatta '" + StringEscapeUtils.escapeHtml(regattaName) + "'.") .type(MediaType.TEXT_PLAIN).build(); } private Response getNotEnoughDataAvailabeErrorResponse(String regattaName, String raceName) { return Response.status(Status.NOT_FOUND) - .entity("No wind or polar data for race with name '" + raceName + "' in regatta '" + regattaName + "'.") + .entity("No wind or polar data for race with name '" + StringEscapeUtils.escapeHtml(raceName) + "' in regatta '" + StringEscapeUtils.escapeHtml(regattaName) + "'.") .type(MediaType.TEXT_PLAIN).build(); } @@ -603,13 +604,13 @@ public class RegattasResource extends AbstractSailingServerResource { Regatta regatta = findRegattaByName(regattaName); if (regatta == null) { response = Response.status(Status.NOT_FOUND) - .entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN) + .entity("Could not find a regatta with name '" + StringEscapeUtils.escapeHtml(regattaName) + "'.").type(MediaType.TEXT_PLAIN) .build(); } else { RaceDefinition race = findRaceByName(regatta, raceName); if (race == null) { response = Response.status(Status.NOT_FOUND) - .entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN) + .entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.").type(MediaType.TEXT_PLAIN) .build(); } else { TrackedRace trackedRace = findTrackedRace(regattaName, raceName); @@ -640,13 +641,13 @@ public class RegattasResource extends AbstractSailingServerResource { Regatta regatta = findRegattaByName(regattaName); if (regatta == null) { response = Response.status(Status.NOT_FOUND) - .entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN) + .entity("Could not find a regatta with name '" + StringEscapeUtils.escapeHtml(regattaName) + "'.").type(MediaType.TEXT_PLAIN) .build(); } else { RaceDefinition race = findRaceByName(regatta, raceName); if (race == null) { response = Response.status(Status.NOT_FOUND) - .entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN) + .entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.").type(MediaType.TEXT_PLAIN) .build(); } else { TrackedRace trackedRace = findTrackedRace(regattaName, raceName); @@ -696,13 +697,13 @@ public class RegattasResource extends AbstractSailingServerResource { Regatta regatta = findRegattaByName(regattaName); if (regatta == null) { response = Response.status(Status.NOT_FOUND) - .entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN) + .entity("Could not find a regatta with name '" + StringEscapeUtils.escapeHtml(regattaName) + "'.").type(MediaType.TEXT_PLAIN) .build(); } else { RaceDefinition race = findRaceByName(regatta, raceName); if (race == null) { response = Response.status(Status.NOT_FOUND) - .entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN) + .entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.").type(MediaType.TEXT_PLAIN) .build(); } else { TrackedRace trackedRace = findTrackedRace(regattaName, raceName); @@ -736,13 +737,13 @@ public class RegattasResource extends AbstractSailingServerResource { Regatta regatta = findRegattaByName(regattaName); if (regatta == null) { response = Response.status(Status.NOT_FOUND) - .entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN) + .entity("Could not find a regatta with name '" + StringEscapeUtils.escapeHtml(regattaName) + "'.").type(MediaType.TEXT_PLAIN) .build(); } else { RaceDefinition race = findRaceByName(regatta, raceName); if (race == null) { response = Response.status(Status.NOT_FOUND) - .entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN) + .entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.").type(MediaType.TEXT_PLAIN) .build(); } else { TrackedRace trackedRace = findTrackedRace(regattaName, raceName); @@ -763,7 +764,7 @@ public class RegattasResource extends AbstractSailingServerResource { Regatta regatta = findRegattaByName(regattaName); if (regatta == null) { response = Response.status(Status.NOT_FOUND) - .entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN) + .entity("Could not find a regatta with name '" + StringEscapeUtils.escapeHtml(regattaName) + "'.").type(MediaType.TEXT_PLAIN) .build(); } else { JSONObject jsonRaceResults = new JSONObject(); @@ -791,13 +792,13 @@ public class RegattasResource extends AbstractSailingServerResource { Regatta regatta = findRegattaByName(regattaName); if (regatta == null) { response = Response.status(Status.NOT_FOUND) - .entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN) + .entity("Could not find a regatta with name '" + StringEscapeUtils.escapeHtml(regattaName) + "'.").type(MediaType.TEXT_PLAIN) .build(); } else { RaceDefinition race = findRaceByName(regatta, raceName); if (race == null) { response = Response.status(Status.NOT_FOUND) - .entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN) + .entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.").type(MediaType.TEXT_PLAIN) .build(); } else { TrackedRace trackedRace = findTrackedRace(regattaName, raceName); @@ -939,13 +940,13 @@ public class RegattasResource extends AbstractSailingServerResource { Regatta regatta = findRegattaByName(regattaName); if (regatta == null) { response = Response.status(Status.NOT_FOUND) - .entity("Could not find a regatta with name '" + regattaName + "'.").type(MediaType.TEXT_PLAIN) + .entity("Could not find a regatta with name '" + StringEscapeUtils.escapeHtml(regattaName) + "'.").type(MediaType.TEXT_PLAIN) .build(); } else { RaceDefinition race = findRaceByName(regatta, raceName); if (race == null) { response = Response.status(Status.NOT_FOUND) - .entity("Could not find a race with name '" + raceName + "'.").type(MediaType.TEXT_PLAIN) + .entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.").type(MediaType.TEXT_PLAIN) .build(); } else { TrackedRace trackedRace = findTrackedRace(regattaName, raceName); diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/eventsExportDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/eventsExportDoc.html deleted file mode 100644 index 83d9c42ddca..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/eventsExportDoc.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - Deprecated Web Services API - - -

    SAP Sailing Analytics - Web Services

    -

    Name: events

    - -Description: -

    This service exports all existing events with their venues and course areas

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Webservice Type:REST
    Output format:Json
    Base-Url:http://www.sapsailing.com/sailingserver/events
    Mandatory parameters:None
    Optional parameters:None
    Request method:GET
    Example:http://www.sapsailing.com/sailingserver/events
    - -Back to Web Service Overview - - diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/eventsExportSample.json b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/eventsExportSample.json deleted file mode 100644 index 4f022aadc6f..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/eventsExportSample.json +++ /dev/null @@ -1 +0,0 @@ -[{"id":"760f2748-2e48-4bd2-93ff-3452da02b4c5","name":"TestEvent","publicationUrl":"","venue":{"name":"Kiel","courseAreas":[{"name":"Area 1","id":"687da3c2-4fe0-4a3d-a791-f4f34c27b704"},{"name":"Area 2","id":"974d7258-3664-455f-82a2-e9103db938c9"}]}}] \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/index.html b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/index.html deleted file mode 100644 index de3bd6fcd45..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/index.html +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - Deprecated Web Services API - - -

    SAP Sailing Analytics Deprecated Web Services API - Overview

    - -

    -The best entry point to navigating the contents of a server is usually the leaderboardgroups service, -listing the leaderboard groups offered by the server. For example, -http://www.sapsailing.com/sailingserver/leaderboardgroups -lists the names of the leaderboard groups that the server instance knows. The names returned in the list can then be used -as parameter for the leaderboardgroup service, e.g., as follows: -http://www.sapsailing.com/sailingserver/leaderboardgroup?leaderboardGroupName=Star%20IDM%202013. -Note, of course, that the name needs to be URL-encoded with special characters and white spaces correctly -translated into URL format. -

    -
    -

    -The leaderboard group service list the leaderboards in the group. There are essentially two types of leaderboard: -flexible leaderboards and regatta leaderboards. For flexible leaderboards, the service tells the list of races -in the leaderboard, and for each race in particular the regatta name and the tracked race name. These two attributes -can, in turn, be used as parameters for further services to identify the race for which information shall be retrieved and -to fetch information about the regatta with its structure such as series and fleets. -Again, note that those names need to be URL-encoded accordingly when used as parameter to a service call. -

    -
    -

    -When a service takes a time point as a parameter, there are usually two ways of specifying it: either as an -ISO time stamp, e.g., 2013-05-08T10:12:59Z or as Unix time stamp (milliseconds since 1970-01-01T00:00:00Z). The parameters -in the latter format are usually called ...asmillis. -

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Web ServiceDescriptionSupported formats
    /eventsGets all eventsJson
    /regattasGets all regattasJson
    /regattaGets a regattaJson
    /leaderboardgroupsGets all leaderboard groupsJson
    /leaderboardgroupGets a group of leaderboardsJson
    /leaderboardsGets all leaderboardsJson
    /leaderboardGets a leaderboardJson
    /windGets wind data for a raceJson
    -
    -Back to Web Services Home - - \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardExportDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardExportDoc.html deleted file mode 100644 index d2d99b9fbea..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardExportDoc.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - Deprecated Web Services API - - -

    SAP Sailing Analytics - Web Services

    -

    Name: leaderboard

    - -Description: -

    This service exports the data of a leaderboard with all races, including total points, points per race and total rank.

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Webservice Type:REST
    Output format:Json
    Base-Url:http://www.sapsailing.com/sailingserver/leaderboard
    Mandatory parameters:leaderboardName
    Optional parameters:resultState [Live = Default, Preliminary, Final]
     maxCompetitorsCount
    Request method:GET
    Example:http://www.sapsailing.com/sailingserver/leaderboard?leaderboardName=Extreme%20Sailing%20Series%202013%20Overall&resultState=Final
    - -Back to Web Service Overview - - diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardExportSample.json b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardExportSample.json deleted file mode 100644 index 5240338a5fd..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardExportSample.json +++ /dev/null @@ -1 +0,0 @@ -{"name":"Drachen IDM 2012","timepoint":"2013-03-22T11:42:56.310+0100","resultTimepoint":"2013-03-22T11:42:56.310+0100","requestTimepoint":"2013-03-22T11:42:56.310+0100","resultState":"Live","scoringComment":null,"lastScoringUpdate":null,"columnNames":["R1","R2","R3","R4","R5","R6"],"competitors":[{"name":"Scheinecker, Christian","displayName":"Scheinecker, Christian","id":"6dca639a-db47-11e1-867d-406186cbf87c","sailID":"AUT218","nationality":"AUT","countryCode":"AT","rank":1,"carriedPoints":0.0,"totalPoints":20.0,"raceScores":{"R1":{"fleet":"Default","netPoints":8.0,"totalPoints":8.0,"maxPointsReason":"NONE","rank":8,"raceRank":8,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":9.0,"totalPoints":9.0,"maxPointsReason":"NONE","rank":9,"raceRank":9,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":3.0,"totalPoints":3.0,"maxPointsReason":"NONE","rank":3,"raceRank":3,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"van Cauwenbergh, Ben","displayName":"van Cauwenbergh, Ben","id":"6d3c34da-db47-11e1-867d-406186cbf87c","sailID":"BEL80","nationality":"BEL","countryCode":"BE","rank":2,"carriedPoints":0.0,"totalPoints":21.0,"raceScores":{"R1":{"fleet":"Default","netPoints":5.0,"totalPoints":5.0,"maxPointsReason":"NONE","rank":5,"raceRank":5,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":15.0,"totalPoints":15.0,"maxPointsReason":"NONE","rank":15,"raceRank":15,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":1.0,"totalPoints":1.0,"maxPointsReason":"NONE","rank":1,"raceRank":1,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Brennecke, Marcus","displayName":"Brennecke, Marcus","id":"6ca649de-db47-11e1-867d-406186cbf87c","sailID":"GER10","nationality":"GER","countryCode":"DE","rank":3,"carriedPoints":0.0,"totalPoints":25.0,"raceScores":{"R1":{"fleet":"Default","netPoints":7.0,"totalPoints":7.0,"maxPointsReason":"NONE","rank":7,"raceRank":7,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":2.0,"totalPoints":2.0,"maxPointsReason":"NONE","rank":2,"raceRank":2,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":16.0,"totalPoints":16.0,"maxPointsReason":"NONE","rank":16,"raceRank":16,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Jacob, Florian","displayName":"Jacob, Florian","id":"7dc29358-db47-11e1-867d-406186cbf87c","sailID":"GER976","nationality":"GER","countryCode":"DE","rank":4,"carriedPoints":0.0,"totalPoints":28.0,"raceScores":{"R1":{"fleet":"Default","netPoints":12.0,"totalPoints":12.0,"maxPointsReason":"NONE","rank":12,"raceRank":12,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":7.0,"totalPoints":7.0,"maxPointsReason":"NONE","rank":7,"raceRank":7,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":9.0,"totalPoints":9.0,"maxPointsReason":"NONE","rank":9,"raceRank":9,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Glas, Matthias","displayName":"Glas, Matthias","id":"8a6df7fa-db47-11e1-867d-406186cbf87c","sailID":"GER1038","nationality":"GER","countryCode":"DE","rank":5,"carriedPoints":0.0,"totalPoints":31.0,"raceScores":{"R1":{"fleet":"Default","netPoints":9.0,"totalPoints":9.0,"maxPointsReason":"NONE","rank":9,"raceRank":9,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":10.0,"totalPoints":10.0,"maxPointsReason":"NONE","rank":10,"raceRank":10,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":12.0,"totalPoints":12.0,"maxPointsReason":"NONE","rank":12,"raceRank":12,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Müller, Thomas","displayName":"Müller, Thomas","id":"a8a9c294-db47-11e1-867d-406186cbf87c","sailID":"GER1123","nationality":"GER","countryCode":"DE","rank":6,"carriedPoints":0.0,"totalPoints":39.0,"raceScores":{"R1":{"fleet":"Default","netPoints":2.0,"totalPoints":2.0,"maxPointsReason":"NONE","rank":2,"raceRank":2,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":1.0,"totalPoints":1.0,"maxPointsReason":"NONE","rank":1,"raceRank":1,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":36.0,"totalPoints":36.0,"maxPointsReason":"NONE","rank":36,"raceRank":36,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Zachariassen, Hans-Werner","displayName":"Zachariassen, Hans-Werner","id":"73e7d438-db47-11e1-867d-406186cbf87c","sailID":"GER816","nationality":"GER","countryCode":"DE","rank":7,"carriedPoints":0.0,"totalPoints":41.0,"raceScores":{"R1":{"fleet":"Default","netPoints":4.0,"totalPoints":4.0,"maxPointsReason":"NONE","rank":4,"raceRank":4,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":4.0,"totalPoints":4.0,"maxPointsReason":"NONE","rank":4,"raceRank":4,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":33.0,"totalPoints":33.0,"maxPointsReason":"NONE","rank":33,"raceRank":33,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"David, Dirk","displayName":"David, Dirk","id":"91ed82ac-db47-11e1-867d-406186cbf87c","sailID":"GER1070","nationality":"GER","countryCode":"DE","rank":8,"carriedPoints":0.0,"totalPoints":43.0,"raceScores":{"R1":{"fleet":"Default","netPoints":25.0,"totalPoints":25.0,"maxPointsReason":"NONE","rank":25,"raceRank":25,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":8.0,"totalPoints":8.0,"maxPointsReason":"NONE","rank":8,"raceRank":8,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":10.0,"totalPoints":10.0,"maxPointsReason":"NONE","rank":10,"raceRank":10,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Dohse, Dr. Philip","displayName":"Dohse, Dr. Philip","id":"8bed5224-db47-11e1-867d-406186cbf87c","sailID":"GER1042","nationality":"GER","countryCode":"DE","rank":9,"carriedPoints":0.0,"totalPoints":45.0,"raceScores":{"R1":{"fleet":"Default","netPoints":31.0,"totalPoints":31.0,"maxPointsReason":"NONE","rank":31,"raceRank":31,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":12.0,"totalPoints":12.0,"maxPointsReason":"NONE","rank":12,"raceRank":12,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":2.0,"totalPoints":2.0,"maxPointsReason":"NONE","rank":2,"raceRank":2,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"ten Wolde, Wouter","displayName":"ten Wolde, Wouter","id":"701a4728-db47-11e1-867d-406186cbf87c","sailID":"NED411","nationality":"NED","countryCode":"NL","rank":10,"carriedPoints":0.0,"totalPoints":45.0,"raceScores":{"R1":{"fleet":"Default","netPoints":10.0,"totalPoints":10.0,"maxPointsReason":"NONE","rank":10,"raceRank":10,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":22.0,"totalPoints":22.0,"maxPointsReason":"NONE","rank":22,"raceRank":22,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":13.0,"totalPoints":13.0,"maxPointsReason":"NONE","rank":13,"raceRank":13,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Woortman, Jan","displayName":"Woortman, Jan","id":"937d43b4-db47-11e1-867d-406186cbf87c","sailID":"GER1074","nationality":"GER","countryCode":"DE","rank":11,"carriedPoints":0.0,"totalPoints":47.0,"raceScores":{"R1":{"fleet":"Default","netPoints":19.0,"totalPoints":19.0,"maxPointsReason":"NONE","rank":19,"raceRank":19,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":3.0,"totalPoints":3.0,"maxPointsReason":"NONE","rank":3,"raceRank":3,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":25.0,"totalPoints":25.0,"maxPointsReason":"NONE","rank":25,"raceRank":25,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Hoffmann, Jan","displayName":"Hoffmann, Jan","id":"7f33d724-db47-11e1-867d-406186cbf87c","sailID":"GER995","nationality":"GER","countryCode":"DE","rank":12,"carriedPoints":0.0,"totalPoints":50.0,"raceScores":{"R1":{"fleet":"Default","netPoints":16.0,"totalPoints":16.0,"maxPointsReason":"NONE","rank":16,"raceRank":16,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":27.0,"totalPoints":27.0,"maxPointsReason":"NONE","rank":27,"raceRank":27,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":7.0,"totalPoints":7.0,"maxPointsReason":"NONE","rank":7,"raceRank":7,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Engstfeld, Axel","displayName":"Engstfeld, Axel","id":"857d5e5c-db47-11e1-867d-406186cbf87c","sailID":"GER1005","nationality":"GER","countryCode":"DE","rank":13,"carriedPoints":0.0,"totalPoints":51.0,"raceScores":{"R1":{"fleet":"Default","netPoints":22.0,"totalPoints":22.0,"maxPointsReason":"NONE","rank":22,"raceRank":22,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":14.0,"totalPoints":14.0,"maxPointsReason":"NONE","rank":14,"raceRank":14,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":15.0,"totalPoints":15.0,"maxPointsReason":"NONE","rank":15,"raceRank":15,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Libor, Ulli","displayName":"Libor, Ulli","id":"6f2a907a-db47-11e1-867d-406186cbf87c","sailID":"SUI307","nationality":"SUI","countryCode":"CH","rank":14,"carriedPoints":0.0,"totalPoints":53.0,"raceScores":{"R1":{"fleet":"Default","netPoints":3.0,"totalPoints":3.0,"maxPointsReason":"NONE","rank":3,"raceRank":3,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":11.0,"totalPoints":11.0,"maxPointsReason":"NONE","rank":11,"raceRank":11,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":39.0,"totalPoints":39.0,"maxPointsReason":"NONE","rank":39,"raceRank":39,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Herpolsheimer, Manfred","displayName":"Herpolsheimer, Manfred","id":"77be7eea-db47-11e1-867d-406186cbf87c","sailID":"GER893","nationality":"GER","countryCode":"DE","rank":15,"carriedPoints":0.0,"totalPoints":54.0,"raceScores":{"R1":{"fleet":"Default","netPoints":29.0,"totalPoints":29.0,"maxPointsReason":"NONE","rank":29,"raceRank":29,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":21.0,"totalPoints":21.0,"maxPointsReason":"NONE","rank":21,"raceRank":21,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":4.0,"totalPoints":4.0,"maxPointsReason":"NONE","rank":4,"raceRank":4,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Bittner, Martin","displayName":"Bittner, Martin","id":"71efd522-db47-11e1-867d-406186cbf87c","sailID":"GER687","nationality":"GER","countryCode":"DE","rank":16,"carriedPoints":0.0,"totalPoints":54.0,"raceScores":{"R1":{"fleet":"Default","netPoints":24.0,"totalPoints":24.0,"maxPointsReason":"NONE","rank":24,"raceRank":24,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":25.0,"totalPoints":25.0,"maxPointsReason":"NONE","rank":25,"raceRank":25,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":5.0,"totalPoints":5.0,"maxPointsReason":"NONE","rank":5,"raceRank":5,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Ladehof, Tim","displayName":"Ladehof, Tim","id":"962f792e-db47-11e1-867d-406186cbf87c","sailID":"GER1078","nationality":"GER","countryCode":"DE","rank":17,"carriedPoints":0.0,"totalPoints":56.0,"raceScores":{"R1":{"fleet":"Default","netPoints":1.0,"totalPoints":1.0,"maxPointsReason":"NONE","rank":1,"raceRank":1,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":32.0,"totalPoints":32.0,"maxPointsReason":"NONE","rank":32,"raceRank":32,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":23.0,"totalPoints":23.0,"maxPointsReason":"NONE","rank":23,"raceRank":23,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Bauer, Florian","displayName":"Bauer, Florian","id":"88619a8e-db47-11e1-867d-406186cbf87c","sailID":"GER1026","nationality":"GER","countryCode":"DE","rank":18,"carriedPoints":0.0,"totalPoints":57.0,"raceScores":{"R1":{"fleet":"Default","netPoints":30.0,"totalPoints":30.0,"maxPointsReason":"NONE","rank":30,"raceRank":30,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":16.0,"totalPoints":16.0,"maxPointsReason":"NONE","rank":16,"raceRank":16,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":11.0,"totalPoints":11.0,"maxPointsReason":"NONE","rank":11,"raceRank":11,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Schaper, Michael","displayName":"Schaper, Michael","id":"90678a4a-db47-11e1-867d-406186cbf87c","sailID":"GER1058","nationality":"GER","countryCode":"DE","rank":19,"carriedPoints":0.0,"totalPoints":60.0,"raceScores":{"R1":{"fleet":"Default","netPoints":20.0,"totalPoints":20.0,"maxPointsReason":"NONE","rank":20,"raceRank":20,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":6.0,"totalPoints":6.0,"maxPointsReason":"NONE","rank":6,"raceRank":6,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":34.0,"totalPoints":34.0,"maxPointsReason":"NONE","rank":34,"raceRank":34,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Bergmann, Simon","displayName":"Bergmann, Simon","id":"765a3012-db47-11e1-867d-406186cbf87c","sailID":"GER870","nationality":"GER","countryCode":"DE","rank":20,"carriedPoints":0.0,"totalPoints":66.0,"raceScores":{"R1":{"fleet":"Default","netPoints":23.0,"totalPoints":23.0,"maxPointsReason":"NONE","rank":23,"raceRank":23,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":26.0,"totalPoints":26.0,"maxPointsReason":"NONE","rank":26,"raceRank":26,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":17.0,"totalPoints":17.0,"maxPointsReason":"NONE","rank":17,"raceRank":17,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Steingross, Benjamin","displayName":"Steingross, Benjamin","id":"9e21c2a4-db47-11e1-867d-406186cbf87c","sailID":"GER1100","nationality":"GER","countryCode":"DE","rank":21,"carriedPoints":0.0,"totalPoints":67.0,"raceScores":{"R1":{"fleet":"Default","netPoints":21.0,"totalPoints":21.0,"maxPointsReason":"NONE","rank":21,"raceRank":21,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":17.0,"totalPoints":17.0,"maxPointsReason":"NONE","rank":17,"raceRank":17,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":29.0,"totalPoints":29.0,"maxPointsReason":"NONE","rank":29,"raceRank":29,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Pochhammer, Dr. Andreas","displayName":"Pochhammer, Dr. Andreas","id":"8d6b65e6-db47-11e1-867d-406186cbf87c","sailID":"GER1050","nationality":"GER","countryCode":"DE","rank":22,"carriedPoints":0.0,"totalPoints":69.0,"raceScores":{"R1":{"fleet":"Default","netPoints":36.0,"totalPoints":36.0,"maxPointsReason":"NONE","rank":36,"raceRank":36,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":13.0,"totalPoints":13.0,"maxPointsReason":"NONE","rank":13,"raceRank":13,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":20.0,"totalPoints":20.0,"maxPointsReason":"NONE","rank":20,"raceRank":20,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Dankert, Claus","displayName":"Dankert, Claus","id":"a5921df4-db47-11e1-867d-406186cbf87c","sailID":"GER1118","nationality":"GER","countryCode":"DE","rank":23,"carriedPoints":0.0,"totalPoints":70.0,"raceScores":{"R1":{"fleet":"Default","netPoints":18.0,"totalPoints":18.0,"maxPointsReason":"NONE","rank":18,"raceRank":18,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":34.0,"totalPoints":34.0,"maxPointsReason":"NONE","rank":34,"raceRank":34,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":18.0,"totalPoints":18.0,"maxPointsReason":"NONE","rank":18,"raceRank":18,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Schümann, Ulrike","displayName":"Schümann, Ulrike","id":"72e01e7e-db47-11e1-867d-406186cbf87c","sailID":"GER801","nationality":"GER","countryCode":"DE","rank":24,"carriedPoints":0.0,"totalPoints":71.0,"raceScores":{"R1":{"fleet":"Default","netPoints":28.0,"totalPoints":28.0,"maxPointsReason":"NONE","rank":28,"raceRank":28,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":24.0,"totalPoints":24.0,"maxPointsReason":"NONE","rank":24,"raceRank":24,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":19.0,"totalPoints":19.0,"maxPointsReason":"NONE","rank":19,"raceRank":19,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Pramann, Dirk","displayName":"Pramann, Dirk","id":"9b282598-db47-11e1-867d-406186cbf87c","sailID":"GER1087","nationality":"GER","countryCode":"DE","rank":25,"carriedPoints":0.0,"totalPoints":72.0,"raceScores":{"R1":{"fleet":"Default","netPoints":27.0,"totalPoints":27.0,"maxPointsReason":"NONE","rank":27,"raceRank":27,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":18.0,"totalPoints":18.0,"maxPointsReason":"NONE","rank":18,"raceRank":18,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":27.0,"totalPoints":27.0,"maxPointsReason":"NONE","rank":27,"raceRank":27,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Link, Stephan","displayName":"Link, Stephan","id":"aa72f21c-db47-11e1-867d-406186cbf87c","sailID":"GER1125","nationality":"GER","countryCode":"DE","rank":26,"carriedPoints":0.0,"totalPoints":75.0,"raceScores":{"R1":{"fleet":"Default","netPoints":15.0,"totalPoints":15.0,"maxPointsReason":"NONE","rank":15,"raceRank":15,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":28.0,"totalPoints":28.0,"maxPointsReason":"NONE","rank":28,"raceRank":28,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":32.0,"totalPoints":32.0,"maxPointsReason":"NONE","rank":32,"raceRank":32,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Oldenburg, Dr. Dirk","displayName":"Oldenburg, Dr. Dirk","id":"3005bb98-db49-11e1-867d-406186cbf87c","sailID":"GER1014","nationality":"GER","countryCode":"DE","rank":27,"carriedPoints":0.0,"totalPoints":75.0,"raceScores":{"R1":{"fleet":"Default","netPoints":26.0,"totalPoints":26.0,"maxPointsReason":"NONE","rank":26,"raceRank":26,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":19.0,"totalPoints":19.0,"maxPointsReason":"NONE","rank":19,"raceRank":19,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":30.0,"totalPoints":30.0,"maxPointsReason":"NONE","rank":30,"raceRank":30,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Behrens, Walther","displayName":"Behrens, Walther","id":"9939d092-db47-11e1-867d-406186cbf87c","sailID":"GER1082","nationality":"GER","countryCode":"DE","rank":28,"carriedPoints":0.0,"totalPoints":77.0,"raceScores":{"R1":{"fleet":"Default","netPoints":38.0,"totalPoints":38.0,"maxPointsReason":"NONE","rank":38,"raceRank":38,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":33.0,"totalPoints":33.0,"maxPointsReason":"NONE","rank":33,"raceRank":33,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":6.0,"totalPoints":6.0,"maxPointsReason":"NONE","rank":6,"raceRank":6,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Schmidt, Michael","displayName":"Schmidt, Michael","id":"a72ceb58-db47-11e1-867d-406186cbf87c","sailID":"GER1120","nationality":"GER","countryCode":"DE","rank":29,"carriedPoints":0.0,"totalPoints":77.0,"raceScores":{"R1":{"fleet":"Default","netPoints":11.0,"totalPoints":11.0,"maxPointsReason":"NONE","rank":11,"raceRank":11,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":35.0,"totalPoints":35.0,"maxPointsReason":"NONE","rank":35,"raceRank":35,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":31.0,"totalPoints":31.0,"maxPointsReason":"NONE","rank":31,"raceRank":31,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Hohmann, Jörg","displayName":"Hohmann, Jörg","id":"7521b60c-db47-11e1-867d-406186cbf87c","sailID":"GER837","nationality":"GER","countryCode":"DE","rank":30,"carriedPoints":0.0,"totalPoints":78.0,"raceScores":{"R1":{"fleet":"Default","netPoints":33.0,"totalPoints":33.0,"maxPointsReason":"NONE","rank":33,"raceRank":33,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":37.0,"totalPoints":37.0,"maxPointsReason":"NONE","rank":37,"raceRank":37,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":8.0,"totalPoints":8.0,"maxPointsReason":"NONE","rank":8,"raceRank":8,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Ohst, Norbert","displayName":"Ohst, Norbert","id":"a1130d06-db47-11e1-867d-406186cbf87c","sailID":"GER1110","nationality":"GER","countryCode":"DE","rank":31,"carriedPoints":0.0,"totalPoints":78.0,"raceScores":{"R1":{"fleet":"Default","netPoints":14.0,"totalPoints":14.0,"maxPointsReason":"NONE","rank":14,"raceRank":14,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":38.0,"totalPoints":38.0,"maxPointsReason":"NONE","rank":38,"raceRank":38,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":26.0,"totalPoints":26.0,"maxPointsReason":"NONE","rank":26,"raceRank":26,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Kröber, Günther","displayName":"Kröber, Günther","id":"80fbc36e-db47-11e1-867d-406186cbf87c","sailID":"GER996","nationality":"GER","countryCode":"DE","rank":32,"carriedPoints":0.0,"totalPoints":80.0,"raceScores":{"R1":{"fleet":"Default","netPoints":37.0,"totalPoints":37.0,"maxPointsReason":"NONE","rank":37,"raceRank":37,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":5.0,"totalPoints":5.0,"maxPointsReason":"NONE","rank":5,"raceRank":5,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":38.0,"totalPoints":38.0,"maxPointsReason":"NONE","rank":38,"raceRank":38,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Hunger, Dr. Klaus","displayName":"Hunger, Dr. Klaus","id":"97bc5640-db47-11e1-867d-406186cbf87c","sailID":"GER1079","nationality":"GER","countryCode":"DE","rank":33,"carriedPoints":0.0,"totalPoints":81.0,"raceScores":{"R1":{"fleet":"Default","netPoints":6.0,"totalPoints":6.0,"maxPointsReason":"NONE","rank":6,"raceRank":6,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":40.0,"totalPoints":40.0,"maxPointsReason":"NONE","rank":40,"raceRank":40,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":35.0,"totalPoints":35.0,"maxPointsReason":"NONE","rank":35,"raceRank":35,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Hübner, Reinhard","displayName":"Hübner, Reinhard","id":"6e6add98-db47-11e1-867d-406186cbf87c","sailID":"SUI288","nationality":"SUI","countryCode":"CH","rank":34,"carriedPoints":0.0,"totalPoints":84.0,"raceScores":{"R1":{"fleet":"Default","netPoints":40.0,"totalPoints":40.0,"maxPointsReason":"NONE","rank":40,"raceRank":40,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":30.0,"totalPoints":30.0,"maxPointsReason":"NONE","rank":30,"raceRank":30,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":14.0,"totalPoints":14.0,"maxPointsReason":"NONE","rank":14,"raceRank":14,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Schmidt, Axel","displayName":"Schmidt, Axel","id":"9c9ca4f8-db47-11e1-867d-406186cbf87c","sailID":"GER1094","nationality":"GER","countryCode":"DE","rank":35,"carriedPoints":0.0,"totalPoints":86.0,"raceScores":{"R1":{"fleet":"Default","netPoints":39.0,"totalPoints":39.0,"maxPointsReason":"NONE","rank":39,"raceRank":39,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":23.0,"totalPoints":23.0,"maxPointsReason":"NONE","rank":23,"raceRank":23,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":24.0,"totalPoints":24.0,"maxPointsReason":"NONE","rank":24,"raceRank":24,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Schwieger, Hans-Christian","displayName":"Schwieger, Hans-Christian","id":"7ab675d0-db47-11e1-867d-406186cbf87c","sailID":"GER962","nationality":"GER","countryCode":"DE","rank":36,"carriedPoints":0.0,"totalPoints":91.0,"raceScores":{"R1":{"fleet":"Default","netPoints":34.0,"totalPoints":34.0,"maxPointsReason":"NONE","rank":34,"raceRank":34,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":29.0,"totalPoints":29.0,"maxPointsReason":"NONE","rank":29,"raceRank":29,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":28.0,"totalPoints":28.0,"maxPointsReason":"NONE","rank":28,"raceRank":28,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Kadelbach, Achim","displayName":"Kadelbach, Achim","id":"83ed7784-db47-11e1-867d-406186cbf87c","sailID":"GER999","nationality":"GER","countryCode":"DE","rank":37,"carriedPoints":0.0,"totalPoints":33.0,"raceScores":{"R1":{"fleet":"Default","netPoints":13.0,"totalPoints":13.0,"maxPointsReason":"NONE","rank":13,"raceRank":13,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":20.0,"totalPoints":20.0,"maxPointsReason":"NONE","rank":20,"raceRank":20,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"raceRank":0,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Springer, Dr. Stefan","displayName":"Springer, Dr. Stefan","id":"a40fb78e-db47-11e1-867d-406186cbf87c","sailID":"GER1117","nationality":"GER","countryCode":"DE","rank":38,"carriedPoints":0.0,"totalPoints":56.0,"raceScores":{"R1":{"fleet":"Default","netPoints":17.0,"totalPoints":17.0,"maxPointsReason":"NONE","rank":17,"raceRank":17,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":39.0,"totalPoints":39.0,"maxPointsReason":"NONE","rank":39,"raceRank":39,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"raceRank":0,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Langer-Langmaack, Peter","displayName":"Langer-Langmaack, Peter","id":"79453650-db47-11e1-867d-406186cbf87c","sailID":"GER958","nationality":"GER","countryCode":"DE","rank":39,"carriedPoints":0.0,"totalPoints":66.0,"raceScores":{"R1":{"fleet":"Default","netPoints":35.0,"totalPoints":35.0,"maxPointsReason":"NONE","rank":35,"raceRank":35,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":31.0,"totalPoints":31.0,"maxPointsReason":"NONE","rank":31,"raceRank":31,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"raceRank":0,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Plattner, Prof.Dr.Hasso","displayName":"Plattner, Prof.Dr.Hasso","id":"a295e82e-db47-11e1-867d-406186cbf87c","sailID":"GER1112","nationality":"GER","countryCode":"DE","rank":40,"carriedPoints":0.0,"totalPoints":73.0,"raceScores":{"R1":{"fleet":"Default","netPoints":32.0,"totalPoints":32.0,"maxPointsReason":"NONE","rank":32,"raceRank":32,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":41.0,"totalPoints":41.0,"maxPointsReason":"NONE","rank":41,"raceRank":41,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"raceRank":0,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Ehrlicher, Dr. Ingo","displayName":"Ehrlicher, Dr. Ingo","id":"7c35b84e-db47-11e1-867d-406186cbf87c","sailID":"GER965","nationality":"GER","countryCode":"DE","rank":41,"carriedPoints":0.0,"totalPoints":73.0,"raceScores":{"R1":{"fleet":"Default","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"raceRank":0,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":36.0,"totalPoints":36.0,"maxPointsReason":"NONE","rank":36,"raceRank":36,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":37.0,"totalPoints":37.0,"maxPointsReason":"NONE","rank":37,"raceRank":37,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Stamm, Gerhard","displayName":"Stamm, Gerhard","id":"8ef16834-db47-11e1-867d-406186cbf87c","sailID":"GER1056","nationality":"GER","countryCode":"DE","rank":42,"carriedPoints":0.0,"totalPoints":21.0,"raceScores":{"R1":{"fleet":"Default","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"raceRank":0,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"raceRank":0,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":21.0,"totalPoints":21.0,"maxPointsReason":"NONE","rank":21,"raceRank":21,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}},{"name":"Stade, Daniel","displayName":"Stade, Daniel","id":"70fd5806-db47-11e1-867d-406186cbf87c","sailID":"GER572","nationality":"GER","countryCode":"DE","rank":43,"carriedPoints":0.0,"totalPoints":22.0,"raceScores":{"R1":{"fleet":"Default","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"raceRank":0,"isDiscarded":false,"isCorrected":false},"R2":{"fleet":"Default","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"raceRank":0,"isDiscarded":false,"isCorrected":false},"R3":{"fleet":"Default","netPoints":22.0,"totalPoints":22.0,"maxPointsReason":"NONE","rank":22,"raceRank":22,"isDiscarded":false,"isCorrected":false},"R4":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R5":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false},"R6":{"fleet":"","netPoints":null,"totalPoints":null,"maxPointsReason":"NONE","rank":0,"isDiscarded":false,"isCorrected":false}}}]} \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardGroupExportDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardGroupExportDoc.html deleted file mode 100644 index 2d52a04ef56..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardGroupExportDoc.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - Deprecated Web Services API - - -

    SAP Sailing Analytics - Web Services

    -

    Name: leaderboardgroup

    - -Description: -

    This service exports the data of a group of leaderboards including all contained leaderboards.

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Webservice Type:REST
    Output format:Json
    Base-Url:http://www.sapsailing.com/sailingserver/leaderboardgroup
    Mandatory parameters:leaderboardGroupName
    Optional parameters:None
    Request method:GET
    Example:http://tw2013.sapsailing.com/sailingserver/leaderboardgroup?leaderboardGroupName=Travem%C3%BCnder%20Woche%202013
    - -Back to Web Service Overview - - - diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardGroupExportSample.json b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardGroupExportSample.json deleted file mode 100644 index ab48fa4d78e..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardGroupExportSample.json +++ /dev/null @@ -1,1210 +0,0 @@ -{ - - "name": "Extreme Sailing Series 2012", - "description": "Muscat - Qingdao - Istanbul - Porto - Cardiff - Nice - Rio de Janeiro", - "timepoint": "2013-08-13T14:57:40.369+0200", - "leaderboards": [ - { - "name": "Extreme 40 Muscat", - "displayName": "Muscat", - "isMetaLeaderboard": false, - "isRegattaLeaderboard": false, - "scoringComment": "final results after race 29", - "lastScoringUpdate": "Fri Aug 31 15:46:41 CEST 2012", - "scoringScheme": "HIGH_POINT", - "regattaName": null, - "series": [ - { - "name": "Default", - "isMedalSeries": null, - "fleets": [ - { - "name": "Default", - "color": null, - "ordering": 0, - "races": [ - { - "name": "R1", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R2", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R3", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R4", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R5", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R6", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R7", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R8", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R9", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R10", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R11", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R12", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R13", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R14", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R15", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R16", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R17", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R18", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R19", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R20", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R21", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R22", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R23", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R24", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R25", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R26", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R27", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R28", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R29", - "isMedalRace": true, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Muscat_Proxy", - "hasGpsData": false, - "hasWindData": false - } - ] - } - ] - } - ] - }, - { - "name": "Extreme 40 Qingdao", - "displayName": "Qingdao", - "isMetaLeaderboard": false, - "isRegattaLeaderboard": false, - "scoringComment": "final results after race 29", - "lastScoringUpdate": "Fri Aug 31 15:44:30 CEST 2012", - "scoringScheme": "HIGH_POINT", - "regattaName": null, - "series": [ - { - "name": "Default", - "isMedalSeries": null, - "fleets": [ - { - "name": "Default", - "color": null, - "ordering": 0, - "races": [ - { - "name": "R1", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R2", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R3", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R4", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R5", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R6", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R7", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R8", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R9", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R10", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R11", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R12", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R13", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R14", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R15", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R16", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R17", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R18", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R19", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R20", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R21", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R22", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R23", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R24", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R25", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R26", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R27", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R28", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R29", - "isMedalRace": true, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Qingdao_Proxy", - "hasGpsData": false, - "hasWindData": false - } - ] - } - ] - } - ] - }, - { - "name": "Extreme 40 Istanbul", - "displayName": "Istanbul", - "isMetaLeaderboard": false, - "isRegattaLeaderboard": false, - "scoringComment": "final results after race 29", - "lastScoringUpdate": "Fri Aug 31 15:41:56 CEST 2012", - "scoringScheme": "HIGH_POINT", - "regattaName": null, - "series": [ - { - "name": "Default", - "isMedalSeries": null, - "fleets": [ - { - "name": "Default", - "color": null, - "ordering": 0, - "races": [ - { - "name": "R1", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R2", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R3", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R4", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R5", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R6", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R7", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R8", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R9", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R10", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R11", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R12", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R13", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R14", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R15", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R16", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R17", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R18", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R19", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R20", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R21", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R22", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R23", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R24", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R25", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R26", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R27", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R28", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R29", - "isMedalRace": true, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Istanbul_Proxy", - "hasGpsData": false, - "hasWindData": false - } - ] - } - ] - } - ] - }, - { - "name": "Extreme 40 Porto", - "displayName": "Porto", - "isMetaLeaderboard": false, - "isRegattaLeaderboard": false, - "scoringComment": "final results after race 32", - "lastScoringUpdate": "Fri Aug 31 15:43:29 CEST 2012", - "scoringScheme": "HIGH_POINT", - "regattaName": null, - "series": [ - { - "name": "Default", - "isMedalSeries": null, - "fleets": [ - { - "name": "Default", - "color": null, - "ordering": 0, - "races": [ - { - "name": "R1", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R2", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R3", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R4", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R5", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R6", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R7", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R8", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R9", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R10", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R11", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R12", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R13", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R14", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R15", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R16", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R17", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R18", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R19", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R20", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R21", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R22", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R23", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R24", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R25", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R26", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R27", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R28", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R29", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R30", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R31", - "isMedalRace": false, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - }, - { - "name": "R32", - "isMedalRace": true, - "isTracked": true, - "regattaName": "Extreme Sailing Series (Extreme40)", - "trackedRaceName": "Porto_Proxy", - "hasGpsData": false, - "hasWindData": false - } - ] - } - ] - } - ] - }, - { - "name": "ESS 2012 Cardiff (Extreme40)", - "displayName": "Cardiff", - "isMetaLeaderboard": false, - "isRegattaLeaderboard": true, - "scoringComment": "", - "lastScoringUpdate": "Sat Aug 10 23:27:50 CEST 2013", - "scoringScheme": "HIGH_POINT", - "regattaName": "ESS 2012 Cardiff (Extreme40)" - }, - { - "name": "ESS 2012 Nice (Extreme40)", - "displayName": "Nice", - "isMetaLeaderboard": false, - "isRegattaLeaderboard": true, - "scoringComment": null, - "lastScoringUpdate": null, - "scoringScheme": "HIGH_POINT", - "regattaName": "ESS 2012 Nice (Extreme40)" - }, - { - "name": "ESS 2012 Rio (Extreme40)", - "displayName": "Rio", - "isMetaLeaderboard": false, - "isRegattaLeaderboard": true, - "scoringComment": null, - "lastScoringUpdate": null, - "scoringScheme": "HIGH_POINT", - "regattaName": "ESS 2012 Rio (Extreme40)" - } - ] - -} \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardGroupsExportDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardGroupsExportDoc.html deleted file mode 100644 index 493e5d50173..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardGroupsExportDoc.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - Deprecated Web Services API - - -

    SAP Sailing Analytics - Web Services

    -

    Name: leaderboardgroups

    - -Description: -

    This service exports the names of all existing leaderboard groups

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Webservice Type:REST
    Output format:Json
    Base-Url:http://www.sapsailing.com/sailingserver/leaderboardgroups
    Mandatory parameters:None
    Optional parameters:None
    Request method:GET
    Example:http://www.sapsailing.com/sailingserver/leaderboardgroups
    - -Back to Web Service Overview - - diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardGroupsExportSample.json b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardGroupsExportSample.json deleted file mode 100644 index a86396e8e2d..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardGroupsExportSample.json +++ /dev/null @@ -1 +0,0 @@ -["BMW Cup","Test","Extreme Sailing Series","49er","STG-Berlin-MR2012","ESS All test","BMW Cup Overall","ESS 2013 Muscat","Test And Wech","49er Europeans 2012","Reverse display test"] \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardsExportDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardsExportDoc.html deleted file mode 100644 index 0b85ad724e7..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardsExportDoc.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - Deprecated Web Services API - - -

    SAP Sailing Analytics - Web Services

    -

    Name: leaderboards

    - -Description: -

    This service exports the names of all existing leaderboards

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Webservice Type:REST
    Output format:Json
    Base-Url:http://www.sapsailing.com/sailingserver/leaderboards
    Mandatory parameters:None
    Optional parameters:None
    Request method:GET
    Example:http://www.sapsailing.com/sailingserver/leaderboards
    - -Back to Web Service Overview - - diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardsExportSample.json b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardsExportSample.json deleted file mode 100644 index 91f8dc3e3d9..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/leaderboardsExportSample.json +++ /dev/null @@ -1 +0,0 @@ -["ESS 2013 Muscat (Extreme40)","Extreme 40 Nice","Silbernes Band","49er Training Palma 2013","ESS 2013 singapure (Extreme40)","Star IDM 2013","SwissTimingTest","Extreme 40 Muscat","Drachen IDM 2012","Extreme Sailing Series Overall","BMW Cup Overall Overall","ESS 2012 Cardiff (Extreme40)","KW 2013 Laser Radial (Laser Radial)","BMW Cup","Extreme 40 Rio","505 WM Barbados 2013 (505)","ESS All test Overall","Test Overall","Extreme 40 Cardiff","Extreme 40 Porto","Nixdorf Pokal 2013","KW 2013 Laser (Laser)","505 Worlds 2012"] \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/regattaExportDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/regattaExportDoc.html deleted file mode 100644 index a52ad30ad90..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/regattaExportDoc.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - Deprecated Web Services API - - -

    SAP Sailing Analytics - Web Services

    -

    Name: regatta

    - -Description: -

    This service exports the structure (series, fleets, races) of a regatta

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Webservice Type:REST
    Output format:Json
    Base-Url:http://www.sapsailing.com/sailingserver/regatta
    Mandatory parameters:name
    Optional parameters:None
    Request method:GET
    Example:http://tw2013.sapsailing.com/sailingserver/regatta?name=TW%202013%20%28Hobie%20Tiger%29
    - -Back to Web Service Overview - - diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/regattaExportSample.json b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/regattaExportSample.json deleted file mode 100644 index 5a3e09e3e3d..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/regattaExportSample.json +++ /dev/null @@ -1 +0,0 @@ -{"name":"TW 2013 (Hobie Tiger)","scoringSystem":"LOW_POINT","boatclass":"Hobie Tiger","series":[{"name":"Default","isMedalSeries":false,"startsWithZeroScore":false,"fleets":[{"name":"Default","ordering":0}],"races":["R1","R2","R3","R4","R5","R6","R7","R8","R9","R10","R11","R12","R13","R14","R15","R16","R17","R18"],"trackedRaces":{"fleets":[{"name":"Default","races":[{"name":"R1","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R2","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R3","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R4","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R5","isMedalRace":false,"isLive":false,"isTracked":true,"trackedRaceName":"Hobie Tiger - Race 5","hasGpsData":true,"hasWindData":true},{"name":"R6","isMedalRace":false,"isLive":false,"isTracked":true,"trackedRaceName":"Hobie Tiger - Race 6","hasGpsData":true,"hasWindData":true},{"name":"R7","isMedalRace":false,"isLive":true,"isTracked":true,"trackedRaceName":"Hobie Tiger - Race 7","hasGpsData":true,"hasWindData":true},{"name":"R8","isMedalRace":false,"isLive":false,"isTracked":true,"trackedRaceName":"Hobie Tiger - Race 8","hasGpsData":false,"hasWindData":false},{"name":"R9","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R10","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R11","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R12","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R13","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R14","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R15","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R16","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R17","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false},{"name":"R18","isMedalRace":false,"isLive":false,"isTracked":false,"trackedRaceName":null,"hasGpsData":false,"hasWindData":false}]}]}}]} \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/regattasExportDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/regattasExportDoc.html deleted file mode 100644 index c9ebb25f7a4..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/regattasExportDoc.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - Deprecated Web Services API - - -

    SAP Sailing Analytics - Web Services

    -

    Name: regattas

    - -Description: -

    This service exports the names of all existing regattas

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Webservice Type:REST
    Output format:Json
    Base-Url:http://www.sapsailing.com/sailingserver/regattas
    Mandatory parameters:None
    Optional parameters:None
    Request method:GET
    Example:http://www.sapsailing.com/sailingserver/regattas
    - -Back to Web Service Overview - - diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/regattasExportSample.json b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/regattasExportSample.json deleted file mode 100644 index 0e954f4d8e1..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/regattasExportSample.json +++ /dev/null @@ -1 +0,0 @@ -[{"name":"ESS 2012 Cardiff (Extreme40)","boatclass":"Extreme40","competitors":[],"races":[]},{"name":"Bla (49er)","boatclass":"49er","competitors":[],"races":[]},{"name":"Wannsee Zwischenrunde (Laser SB3)","boatclass":"Laser SB3","competitors":[],"races":[]},{"name":"Regatta With 2 series but no fleets (Laser)","boatclass":"Laser","competitors":[],"races":[]},{"name":"Wannsee Vorrunde (Laser SB3)","boatclass":"Laser SB3","competitors":[],"races":[]},{"name":"Test And Wech (Extreme 40)","boatclass":"Extreme 40","competitors":[],"races":[]},{"name":"49er 3 Rounds (49er)","boatclass":"49er","competitors":[],"races":[]},{"name":"49er Qualification Round 2 (49er)","boatclass":"49er","competitors":[],"races":[]},{"name":"Long regatta test (Extreme 40)","boatclass":"Extreme 40","competitors":[],"races":[]},{"name":"49er Qualification Round 1\/2 (49er)","boatclass":"49er","competitors":[],"races":[]},{"name":"ESS 2013 Muscat (Extreme40)","boatclass":"Extreme40","competitors":[],"races":[]},{"name":"49er Qualification Round 1 (49er)","boatclass":"49er","competitors":[],"races":[]}] \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/showboatpositionsDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/showboatpositionsDoc.html deleted file mode 100755 index b14471f95f7..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/showboatpositionsDoc.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - Deprecated Web Services API - - -

    SAP Sailing Analytics - Web Services

    -

    Name: wind

    - -Description: -

    This service lists GPS the fixes available for a race's competitors. A time range can be provided using -optional parameters. It those are not used, all available GPS fixes are returned. Fixes classified as outlier -(obviously wrong fixes due to tracker or transmission failure) will not show in the result.

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Webservice Type:REST
    Output format:Json
    Base-Url:http://www.sapsailing.com/sailingserver/moderator?action=showboatpositions
    Mandatory parameters: -
    regattaname
    -
    racename
    -
    Optional parameters: -
    since
    -
    sinceasmillis
    -
    to
    -
    toasmillis
    -
    Request method:GET
    Example:http://www.sapsailing.com/sailingserver/moderator?action=showboatpositions&regattaname=ESS%202012%20Cardiff%20%28Extreme40%29&racename=Cardiff%20Race12
    - -Back to Web Service Overview - - diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/showboatpositionsSample.json b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/showboatpositionsSample.json deleted file mode 100755 index c3c1e50f272..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/showboatpositionsSample.json +++ /dev/null @@ -1,31004 +0,0 @@ -{ - - "name": "Cardiff Race12", - "competitors": [ - { - "name": "The Wave Muscat", - "track": [ - { - "timepoint": 1346422554000, - "latdeg": 51.461372, - "lngdeg": -3.163143, - "truebearingdeg": 313, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346422556000, - "latdeg": 51.46141, - "lngdeg": -3.163208, - "truebearingdeg": 312, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346422558000, - "latdeg": 51.461451999999994, - "lngdeg": -3.1632819999999997, - "truebearingdeg": 310, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346422560000, - "latdeg": 51.46149, - "lngdeg": -3.1633649999999998, - "truebearingdeg": 306, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346422562000, - "latdeg": 51.461524999999995, - "lngdeg": -3.163452, - "truebearingdeg": 303, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346422564000, - "latdeg": 51.461555, - "lngdeg": -3.1635299999999997, - "truebearingdeg": 301, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346422566000, - "latdeg": 51.461582, - "lngdeg": -3.1635999999999997, - "truebearingdeg": 302, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346422568000, - "latdeg": 51.461608, - "lngdeg": -3.163653, - "truebearingdeg": 320, - "knotspeed": 4.319654427645788, - "tack": "PORT" - }, - { - "timepoint": 1346422571000, - "latdeg": 51.461647, - "lngdeg": -3.16369, - "truebearingdeg": 331, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346422579000, - "latdeg": 51.461883, - "lngdeg": -3.163675, - "truebearingdeg": 45, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346422581000, - "latdeg": 51.461906899999995, - "lngdeg": -3.163592, - "truebearingdeg": 69, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346422583000, - "latdeg": 51.461918, - "lngdeg": -3.16351, - "truebearingdeg": 92, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422585000, - "latdeg": 51.461906899999995, - "lngdeg": -3.1634349999999998, - "truebearingdeg": 107, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422587000, - "latdeg": 51.461892, - "lngdeg": -3.1633679999999997, - "truebearingdeg": 119, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422589000, - "latdeg": 51.461859999999994, - "lngdeg": -3.16331, - "truebearingdeg": 135, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422591000, - "latdeg": 51.46183, - "lngdeg": -3.163255, - "truebearingdeg": 129, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422593000, - "latdeg": 51.461788, - "lngdeg": -3.163198, - "truebearingdeg": 146, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422595000, - "latdeg": 51.461765, - "lngdeg": -3.163143, - "truebearingdeg": 131, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422597000, - "latdeg": 51.461704999999995, - "lngdeg": -3.16308, - "truebearingdeg": 133, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422599000, - "latdeg": 51.46167, - "lngdeg": -3.163007, - "truebearingdeg": 130, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422601000, - "latdeg": 51.461616899999996, - "lngdeg": -3.1629649, - "truebearingdeg": 160, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422603000, - "latdeg": 51.461577999999996, - "lngdeg": -3.162937, - "truebearingdeg": 144, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422605000, - "latdeg": 51.46154, - "lngdeg": -3.1628949, - "truebearingdeg": 151, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422607000, - "latdeg": 51.461498, - "lngdeg": -3.162878, - "truebearingdeg": 170, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422609000, - "latdeg": 51.461483, - "lngdeg": -3.162865, - "truebearingdeg": 152, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422611000, - "latdeg": 51.461456999999996, - "lngdeg": -3.162842, - "truebearingdeg": 143, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422613000, - "latdeg": 51.461433, - "lngdeg": -3.162827, - "truebearingdeg": 171, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422615000, - "latdeg": 51.461414999999995, - "lngdeg": -3.162823, - "truebearingdeg": 180, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422617000, - "latdeg": 51.461397999999996, - "lngdeg": -3.162833, - "truebearingdeg": 183, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422619000, - "latdeg": 51.461388, - "lngdeg": -3.1628469999999997, - "truebearingdeg": 215, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422621000, - "latdeg": 51.46138, - "lngdeg": -3.1628599, - "truebearingdeg": 217, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422623000, - "latdeg": 51.46138, - "lngdeg": -3.1628719999999997, - "truebearingdeg": 220, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422625000, - "latdeg": 51.461377, - "lngdeg": -3.1628749999999997, - "truebearingdeg": 220, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422629000, - "latdeg": 51.461362, - "lngdeg": -3.16289, - "truebearingdeg": 219, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422631000, - "latdeg": 51.461349999999996, - "lngdeg": -3.162915, - "truebearingdeg": 226, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422633000, - "latdeg": 51.461329899999996, - "lngdeg": -3.1629479999999996, - "truebearingdeg": 229, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422636000, - "latdeg": 51.461323, - "lngdeg": -3.1629799999999997, - "truebearingdeg": 256, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422643000, - "latdeg": 51.461287999999996, - "lngdeg": -3.1630469999999997, - "truebearingdeg": 210, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422645000, - "latdeg": 51.461279999999995, - "lngdeg": -3.1630599999999998, - "truebearingdeg": 213, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422647000, - "latdeg": 51.461276999999995, - "lngdeg": -3.1630749999999996, - "truebearingdeg": 216, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422651000, - "latdeg": 51.46127, - "lngdeg": -3.1631099999999996, - "truebearingdeg": 224, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422655000, - "latdeg": 51.461237999999994, - "lngdeg": -3.16315, - "truebearingdeg": 226, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422657000, - "latdeg": 51.461227, - "lngdeg": -3.163172, - "truebearingdeg": 225, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422659000, - "latdeg": 51.461217, - "lngdeg": -3.163185, - "truebearingdeg": 224, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422661000, - "latdeg": 51.461205, - "lngdeg": -3.163198, - "truebearingdeg": 222, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422663000, - "latdeg": 51.461189999999995, - "lngdeg": -3.1632119999999997, - "truebearingdeg": 207, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422665000, - "latdeg": 51.461169999999996, - "lngdeg": -3.163232, - "truebearingdeg": 212, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422667000, - "latdeg": 51.461146899999996, - "lngdeg": -3.163262, - "truebearingdeg": 222, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422669000, - "latdeg": 51.46112, - "lngdeg": -3.163293, - "truebearingdeg": 214, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422671000, - "latdeg": 51.46109, - "lngdeg": -3.1633229999999997, - "truebearingdeg": 220, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422673000, - "latdeg": 51.461062999999996, - "lngdeg": -3.1633549999999997, - "truebearingdeg": 211, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422675000, - "latdeg": 51.461039899999996, - "lngdeg": -3.163383, - "truebearingdeg": 207, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422677000, - "latdeg": 51.461012999999994, - "lngdeg": -3.163392, - "truebearingdeg": 190, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422679000, - "latdeg": 51.460986999999996, - "lngdeg": -3.163398, - "truebearingdeg": 182, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422681000, - "latdeg": 51.460944999999995, - "lngdeg": -3.163395, - "truebearingdeg": 182, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422683000, - "latdeg": 51.460892, - "lngdeg": -3.1633999999999998, - "truebearingdeg": 188, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422685000, - "latdeg": 51.460812, - "lngdeg": -3.1634129, - "truebearingdeg": 193, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422687000, - "latdeg": 51.460747, - "lngdeg": -3.1634469999999997, - "truebearingdeg": 195, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422689000, - "latdeg": 51.460677999999994, - "lngdeg": -3.1634849999999997, - "truebearingdeg": 196, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422691000, - "latdeg": 51.460595, - "lngdeg": -3.163528, - "truebearingdeg": 199, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422693000, - "latdeg": 51.460507, - "lngdeg": -3.163588, - "truebearingdeg": 202, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422695000, - "latdeg": 51.460406899999995, - "lngdeg": -3.1636469999999997, - "truebearingdeg": 202, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422697000, - "latdeg": 51.460299899999995, - "lngdeg": -3.163713, - "truebearingdeg": 201, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422700000, - "latdeg": 51.460192899999996, - "lngdeg": -3.163795, - "truebearingdeg": 208, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422708000, - "latdeg": 51.459717, - "lngdeg": -3.164255, - "truebearingdeg": 213, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422710000, - "latdeg": 51.459618, - "lngdeg": -3.164367, - "truebearingdeg": 214, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422712000, - "latdeg": 51.459522, - "lngdeg": -3.1644919999999996, - "truebearingdeg": 221, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422714000, - "latdeg": 51.459419999999994, - "lngdeg": -3.1646199999999998, - "truebearingdeg": 216, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422716000, - "latdeg": 51.459305, - "lngdeg": -3.164752, - "truebearingdeg": 214, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422718000, - "latdeg": 51.459187, - "lngdeg": -3.164873, - "truebearingdeg": 208, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422720000, - "latdeg": 51.45906, - "lngdeg": -3.1649849999999997, - "truebearingdeg": 205, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422722000, - "latdeg": 51.458923, - "lngdeg": -3.165088, - "truebearingdeg": 205, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422724000, - "latdeg": 51.458787, - "lngdeg": -3.1652, - "truebearingdeg": 204, - "knotspeed": 17.278617710583152, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422726000, - "latdeg": 51.45863, - "lngdeg": -3.1653119999999997, - "truebearingdeg": 206, - "knotspeed": 17.818574514038875, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422728000, - "latdeg": 51.458481899999995, - "lngdeg": -3.1654429, - "truebearingdeg": 210, - "knotspeed": 17.818574514038875, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422730000, - "latdeg": 51.458332, - "lngdeg": -3.1655669, - "truebearingdeg": 208, - "knotspeed": 17.278617710583152, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422732000, - "latdeg": 51.458194999999996, - "lngdeg": -3.165685, - "truebearingdeg": 208, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422734000, - "latdeg": 51.458065, - "lngdeg": -3.1657919999999997, - "truebearingdeg": 206, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422736000, - "latdeg": 51.457949899999996, - "lngdeg": -3.1658879, - "truebearingdeg": 207, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422738000, - "latdeg": 51.45784, - "lngdeg": -3.165977, - "truebearingdeg": 206, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422740000, - "latdeg": 51.45773, - "lngdeg": -3.166067, - "truebearingdeg": 206, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422742000, - "latdeg": 51.457623, - "lngdeg": -3.166152, - "truebearingdeg": 207, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422744000, - "latdeg": 51.457519999999995, - "lngdeg": -3.1662369999999997, - "truebearingdeg": 206, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422746000, - "latdeg": 51.45742, - "lngdeg": -3.166325, - "truebearingdeg": 207, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422748000, - "latdeg": 51.457325, - "lngdeg": -3.1664019999999997, - "truebearingdeg": 205, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422750000, - "latdeg": 51.457225, - "lngdeg": -3.16648, - "truebearingdeg": 207, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422752000, - "latdeg": 51.45713, - "lngdeg": -3.166565, - "truebearingdeg": 210, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422754000, - "latdeg": 51.457035, - "lngdeg": -3.1666599, - "truebearingdeg": 209, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422756000, - "latdeg": 51.456928, - "lngdeg": -3.1667549999999998, - "truebearingdeg": 206, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422758000, - "latdeg": 51.456818, - "lngdeg": -3.166843, - "truebearingdeg": 205, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422760000, - "latdeg": 51.456703, - "lngdeg": -3.166937, - "truebearingdeg": 206, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422762000, - "latdeg": 51.456584899999996, - "lngdeg": -3.167027, - "truebearingdeg": 206, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422765000, - "latdeg": 51.456463, - "lngdeg": -3.167115, - "truebearingdeg": 204, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422773000, - "latdeg": 51.455932999999995, - "lngdeg": -3.167502, - "truebearingdeg": 203, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422775000, - "latdeg": 51.455825, - "lngdeg": -3.1675749, - "truebearingdeg": 203, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422777000, - "latdeg": 51.455712, - "lngdeg": -3.167658, - "truebearingdeg": 203, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422779000, - "latdeg": 51.455593, - "lngdeg": -3.167745, - "truebearingdeg": 202, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422781000, - "latdeg": 51.455475, - "lngdeg": -3.167827, - "truebearingdeg": 203, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422783000, - "latdeg": 51.455357, - "lngdeg": -3.1679049999999997, - "truebearingdeg": 201, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422785000, - "latdeg": 51.455234999999995, - "lngdeg": -3.167985, - "truebearingdeg": 200, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422787000, - "latdeg": 51.455107999999996, - "lngdeg": -3.168053, - "truebearingdeg": 195, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422789000, - "latdeg": 51.454986999999996, - "lngdeg": -3.168123, - "truebearingdeg": 205, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422791000, - "latdeg": 51.454865, - "lngdeg": -3.168228, - "truebearingdeg": 211, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422793000, - "latdeg": 51.454752899999995, - "lngdeg": -3.168325, - "truebearingdeg": 208, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422795000, - "latdeg": 51.454667, - "lngdeg": -3.168415, - "truebearingdeg": 207, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422797000, - "latdeg": 51.454578, - "lngdeg": -3.1684919999999996, - "truebearingdeg": 209, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422799000, - "latdeg": 51.454482999999996, - "lngdeg": -3.1685819, - "truebearingdeg": 212, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422801000, - "latdeg": 51.454395, - "lngdeg": -3.1686929999999998, - "truebearingdeg": 222, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422803000, - "latdeg": 51.454315, - "lngdeg": -3.168803, - "truebearingdeg": 217, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422805000, - "latdeg": 51.454237899999995, - "lngdeg": -3.1688769999999997, - "truebearingdeg": 204, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422807000, - "latdeg": 51.454163, - "lngdeg": -3.1689029, - "truebearingdeg": 177, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422809000, - "latdeg": 51.454093, - "lngdeg": -3.1688679, - "truebearingdeg": 147, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422811000, - "latdeg": 51.454021999999995, - "lngdeg": -3.168793, - "truebearingdeg": 147, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422813000, - "latdeg": 51.453938, - "lngdeg": -3.1686929999999998, - "truebearingdeg": 136, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422815000, - "latdeg": 51.453865, - "lngdeg": -3.16857, - "truebearingdeg": 133, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422817000, - "latdeg": 51.453781899999996, - "lngdeg": -3.1684419999999998, - "truebearingdeg": 138, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422819000, - "latdeg": 51.453692999999994, - "lngdeg": -3.1683049999999997, - "truebearingdeg": 134, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422821000, - "latdeg": 51.453604999999996, - "lngdeg": -3.168145, - "truebearingdeg": 129, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422823000, - "latdeg": 51.453513, - "lngdeg": -3.167963, - "truebearingdeg": 127, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422825000, - "latdeg": 51.45343, - "lngdeg": -3.167767, - "truebearingdeg": 123, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422827000, - "latdeg": 51.453362, - "lngdeg": -3.167567, - "truebearingdeg": 114, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422830000, - "latdeg": 51.453326999999994, - "lngdeg": -3.16737, - "truebearingdeg": 93, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422838000, - "latdeg": 51.453559999999996, - "lngdeg": -3.1670019999999997, - "truebearingdeg": 21, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422840000, - "latdeg": 51.453637, - "lngdeg": -3.16695, - "truebearingdeg": 19, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422842000, - "latdeg": 51.453737999999994, - "lngdeg": -3.166902, - "truebearingdeg": 18, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422844000, - "latdeg": 51.453835, - "lngdeg": -3.1668399999999997, - "truebearingdeg": 25, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422846000, - "latdeg": 51.453938, - "lngdeg": -3.1667669999999997, - "truebearingdeg": 26, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422848000, - "latdeg": 51.454032999999995, - "lngdeg": -3.1666849999999998, - "truebearingdeg": 30, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422850000, - "latdeg": 51.454128, - "lngdeg": -3.1666019999999997, - "truebearingdeg": 28, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422852000, - "latdeg": 51.454223, - "lngdeg": -3.1665129999999997, - "truebearingdeg": 33, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422854000, - "latdeg": 51.454341899999996, - "lngdeg": -3.166397, - "truebearingdeg": 39, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422856000, - "latdeg": 51.454445, - "lngdeg": -3.166258, - "truebearingdeg": 39, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346422858000, - "latdeg": 51.454552, - "lngdeg": -3.16613, - "truebearingdeg": 35, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346422860000, - "latdeg": 51.454657999999995, - "lngdeg": -3.16601, - "truebearingdeg": 35, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346422862000, - "latdeg": 51.454761999999995, - "lngdeg": -3.165877, - "truebearingdeg": 44, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346422864000, - "latdeg": 51.454826999999995, - "lngdeg": -3.165723, - "truebearingdeg": 59, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422866000, - "latdeg": 51.454842, - "lngdeg": -3.165565, - "truebearingdeg": 94, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422868000, - "latdeg": 51.454803, - "lngdeg": -3.16546, - "truebearingdeg": 121, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422870000, - "latdeg": 51.454752899999995, - "lngdeg": -3.165383, - "truebearingdeg": 134, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422872000, - "latdeg": 51.454699999999995, - "lngdeg": -3.165288, - "truebearingdeg": 135, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422874000, - "latdeg": 51.454634999999996, - "lngdeg": -3.165213, - "truebearingdeg": 138, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422876000, - "latdeg": 51.454567, - "lngdeg": -3.1651249999999997, - "truebearingdeg": 139, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422878000, - "latdeg": 51.454487, - "lngdeg": -3.1650269, - "truebearingdeg": 146, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422880000, - "latdeg": 51.454406999999996, - "lngdeg": -3.1649249999999998, - "truebearingdeg": 135, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422882000, - "latdeg": 51.454367999999995, - "lngdeg": -3.164792, - "truebearingdeg": 107, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422884000, - "latdeg": 51.454364999999996, - "lngdeg": -3.1646769999999997, - "truebearingdeg": 77, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346422886000, - "latdeg": 51.454387999999994, - "lngdeg": -3.1645999999999996, - "truebearingdeg": 55, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346422888000, - "latdeg": 51.454425, - "lngdeg": -3.1645429999999997, - "truebearingdeg": 37, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346422890000, - "latdeg": 51.454471999999996, - "lngdeg": -3.1645149999999997, - "truebearingdeg": 5, - "knotspeed": 4.319654427645788, - "tack": "PORT" - }, - { - "timepoint": 1346422892000, - "latdeg": 51.454539999999994, - "lngdeg": -3.164523, - "truebearingdeg": 349, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346422895000, - "latdeg": 51.454605, - "lngdeg": -3.1645629, - "truebearingdeg": 338, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422903000, - "latdeg": 51.454868, - "lngdeg": -3.164958, - "truebearingdeg": 316, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422905000, - "latdeg": 51.454932899999996, - "lngdeg": -3.165053, - "truebearingdeg": 315, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422907000, - "latdeg": 51.454994899999996, - "lngdeg": -3.165165, - "truebearingdeg": 312, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422909000, - "latdeg": 51.455058, - "lngdeg": -3.1652899999999997, - "truebearingdeg": 308, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422911000, - "latdeg": 51.45512, - "lngdeg": -3.165412, - "truebearingdeg": 307, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422913000, - "latdeg": 51.455177, - "lngdeg": -3.1655379999999997, - "truebearingdeg": 305, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422915000, - "latdeg": 51.455227, - "lngdeg": -3.1656619999999998, - "truebearingdeg": 307, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422917000, - "latdeg": 51.455282999999994, - "lngdeg": -3.165785, - "truebearingdeg": 307, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422919000, - "latdeg": 51.455341999999995, - "lngdeg": -3.1659099, - "truebearingdeg": 304, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422921000, - "latdeg": 51.455394999999996, - "lngdeg": -3.1660369999999998, - "truebearingdeg": 308, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422923000, - "latdeg": 51.455447899999996, - "lngdeg": -3.1661699999999997, - "truebearingdeg": 303, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422925000, - "latdeg": 51.455509899999996, - "lngdeg": -3.1662849, - "truebearingdeg": 314, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422927000, - "latdeg": 51.455577999999996, - "lngdeg": -3.166395, - "truebearingdeg": 309, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422929000, - "latdeg": 51.455638, - "lngdeg": -3.1665069999999997, - "truebearingdeg": 313, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422931000, - "latdeg": 51.455718, - "lngdeg": -3.166587, - "truebearingdeg": 332, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422933000, - "latdeg": 51.455807, - "lngdeg": -3.16668, - "truebearingdeg": 320, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422935000, - "latdeg": 51.455883, - "lngdeg": -3.1668019999999997, - "truebearingdeg": 305, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422937000, - "latdeg": 51.455887, - "lngdeg": -3.1669299, - "truebearingdeg": 267, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422939000, - "latdeg": 51.455872, - "lngdeg": -3.167005, - "truebearingdeg": 235, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422941000, - "latdeg": 51.455859999999994, - "lngdeg": -3.167033, - "truebearingdeg": 220, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422943000, - "latdeg": 51.455841899999996, - "lngdeg": -3.167065, - "truebearingdeg": 218, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422945000, - "latdeg": 51.455791999999995, - "lngdeg": -3.167135, - "truebearingdeg": 222, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422947000, - "latdeg": 51.455718, - "lngdeg": -3.167227, - "truebearingdeg": 218, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422949000, - "latdeg": 51.455635, - "lngdeg": -3.1673269, - "truebearingdeg": 215, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422951000, - "latdeg": 51.455528, - "lngdeg": -3.1674249999999997, - "truebearingdeg": 209, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422953000, - "latdeg": 51.455422, - "lngdeg": -3.167525, - "truebearingdeg": 209, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422955000, - "latdeg": 51.455315, - "lngdeg": -3.1676129, - "truebearingdeg": 208, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422957000, - "latdeg": 51.455208, - "lngdeg": -3.1677019, - "truebearingdeg": 207, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422960000, - "latdeg": 51.455104999999996, - "lngdeg": -3.1677899999999997, - "truebearingdeg": 207, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422967000, - "latdeg": 51.454667, - "lngdeg": -3.168147, - "truebearingdeg": 206, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422969000, - "latdeg": 51.454543, - "lngdeg": -3.168247, - "truebearingdeg": 209, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422971000, - "latdeg": 51.454432999999995, - "lngdeg": -3.168348, - "truebearingdeg": 211, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422973000, - "latdeg": 51.454327, - "lngdeg": -3.1684349999999997, - "truebearingdeg": 198, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422975000, - "latdeg": 51.454223, - "lngdeg": -3.168523, - "truebearingdeg": 213, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422977000, - "latdeg": 51.454147, - "lngdeg": -3.168618, - "truebearingdeg": 217, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422979000, - "latdeg": 51.454082, - "lngdeg": -3.168698, - "truebearingdeg": 215, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422981000, - "latdeg": 51.454021999999995, - "lngdeg": -3.1687399999999997, - "truebearingdeg": 190, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422983000, - "latdeg": 51.453945, - "lngdeg": -3.1687369999999997, - "truebearingdeg": 172, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422985000, - "latdeg": 51.453849999999996, - "lngdeg": -3.168688, - "truebearingdeg": 160, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422987000, - "latdeg": 51.453719899999996, - "lngdeg": -3.168608, - "truebearingdeg": 155, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422989000, - "latdeg": 51.453613, - "lngdeg": -3.16845, - "truebearingdeg": 122, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422991000, - "latdeg": 51.453562999999995, - "lngdeg": -3.1682419, - "truebearingdeg": 103, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422993000, - "latdeg": 51.453525, - "lngdeg": -3.168028, - "truebearingdeg": 111, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422995000, - "latdeg": 51.453457, - "lngdeg": -3.167842, - "truebearingdeg": 124, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422997000, - "latdeg": 51.453373, - "lngdeg": -3.1676379999999997, - "truebearingdeg": 123, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422999000, - "latdeg": 51.453278, - "lngdeg": -3.167433, - "truebearingdeg": 126, - "knotspeed": 17.278617710583152, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423001000, - "latdeg": 51.453178, - "lngdeg": -3.167207, - "truebearingdeg": 120, - "knotspeed": 18.898488120950326, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423003000, - "latdeg": 51.453089999999996, - "lngdeg": -3.166918, - "truebearingdeg": 110, - "knotspeed": 22.138228941684666, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423005000, - "latdeg": 51.453047999999995, - "lngdeg": -3.166603, - "truebearingdeg": 95, - "knotspeed": 19.978401727861772, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423007000, - "latdeg": 51.453067999999995, - "lngdeg": -3.1663699999999997, - "truebearingdeg": 72, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423009000, - "latdeg": 51.453133, - "lngdeg": -3.166185, - "truebearingdeg": 53, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423011000, - "latdeg": 51.453213, - "lngdeg": -3.166045, - "truebearingdeg": 43, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423013000, - "latdeg": 51.453311899999996, - "lngdeg": -3.165937, - "truebearingdeg": 31, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423015000, - "latdeg": 51.453427, - "lngdeg": -3.1658399999999998, - "truebearingdeg": 30, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423017000, - "latdeg": 51.453537, - "lngdeg": -3.165742, - "truebearingdeg": 28, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423019000, - "latdeg": 51.453652, - "lngdeg": -3.165645, - "truebearingdeg": 28, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423021000, - "latdeg": 51.453767, - "lngdeg": -3.165558, - "truebearingdeg": 23, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423024000, - "latdeg": 51.453883, - "lngdeg": -3.16547, - "truebearingdeg": 27, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423032000, - "latdeg": 51.45433, - "lngdeg": -3.1648, - "truebearingdeg": 61, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423034000, - "latdeg": 51.454383, - "lngdeg": -3.164622, - "truebearingdeg": 58, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423036000, - "latdeg": 51.454448, - "lngdeg": -3.164513, - "truebearingdeg": 31, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423038000, - "latdeg": 51.454524899999996, - "lngdeg": -3.1644829999999997, - "truebearingdeg": 3, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423040000, - "latdeg": 51.454605, - "lngdeg": -3.164507, - "truebearingdeg": 344, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423042000, - "latdeg": 51.454693, - "lngdeg": -3.1645779999999997, - "truebearingdeg": 323, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423045000, - "latdeg": 51.454788, - "lngdeg": -3.1647019999999997, - "truebearingdeg": 315, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423047000, - "latdeg": 51.454871999999995, - "lngdeg": -3.16485, - "truebearingdeg": 315, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423048000, - "latdeg": 51.454963, - "lngdeg": -3.1649819999999997, - "truebearingdeg": 321, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423050000, - "latdeg": 51.45507, - "lngdeg": -3.165105, - "truebearingdeg": 327, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423052000, - "latdeg": 51.4552, - "lngdeg": -3.1652449999999996, - "truebearingdeg": 324, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423058000, - "latdeg": 51.455635, - "lngdeg": -3.165673, - "truebearingdeg": 331, - "knotspeed": 18.898488120950326, - "tack": "PORT" - }, - { - "timepoint": 1346423060000, - "latdeg": 51.455782899999996, - "lngdeg": -3.1657979999999997, - "truebearingdeg": 335, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423062000, - "latdeg": 51.455937, - "lngdeg": -3.165905, - "truebearingdeg": 338, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423064000, - "latdeg": 51.456084999999995, - "lngdeg": -3.166013, - "truebearingdeg": 336, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423066000, - "latdeg": 51.45623, - "lngdeg": -3.166117, - "truebearingdeg": 338, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423068000, - "latdeg": 51.456371999999995, - "lngdeg": -3.1662079999999997, - "truebearingdeg": 337, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423070000, - "latdeg": 51.456508, - "lngdeg": -3.166295, - "truebearingdeg": 339, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423072000, - "latdeg": 51.456646899999996, - "lngdeg": -3.1663729999999997, - "truebearingdeg": 339, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423074000, - "latdeg": 51.456775, - "lngdeg": -3.166433, - "truebearingdeg": 344, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423076000, - "latdeg": 51.456902, - "lngdeg": -3.16648, - "truebearingdeg": 349, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423078000, - "latdeg": 51.45702, - "lngdeg": -3.16647, - "truebearingdeg": 10, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423080000, - "latdeg": 51.45713, - "lngdeg": -3.166382, - "truebearingdeg": 36, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423082000, - "latdeg": 51.457218, - "lngdeg": -3.1662179999999998, - "truebearingdeg": 51, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423084000, - "latdeg": 51.457302, - "lngdeg": -3.1660149, - "truebearingdeg": 57, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423086000, - "latdeg": 51.457392999999996, - "lngdeg": -3.165813, - "truebearingdeg": 48, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423089000, - "latdeg": 51.457499999999996, - "lngdeg": -3.165642, - "truebearingdeg": 40, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423097000, - "latdeg": 51.458014999999996, - "lngdeg": -3.1648829999999997, - "truebearingdeg": 70, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423099000, - "latdeg": 51.45802, - "lngdeg": -3.1647, - "truebearingdeg": 101, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423101000, - "latdeg": 51.457988, - "lngdeg": -3.16457, - "truebearingdeg": 120, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423103000, - "latdeg": 51.457932, - "lngdeg": -3.164478, - "truebearingdeg": 129, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423105000, - "latdeg": 51.457878, - "lngdeg": -3.164367, - "truebearingdeg": 120, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423107000, - "latdeg": 51.45784, - "lngdeg": -3.1642219999999996, - "truebearingdeg": 115, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423109000, - "latdeg": 51.457795, - "lngdeg": -3.164068, - "truebearingdeg": 113, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423111000, - "latdeg": 51.457757, - "lngdeg": -3.1639079999999997, - "truebearingdeg": 112, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423113000, - "latdeg": 51.457713, - "lngdeg": -3.1637369, - "truebearingdeg": 110, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423115000, - "latdeg": 51.457679999999996, - "lngdeg": -3.16357, - "truebearingdeg": 106, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423117000, - "latdeg": 51.457653, - "lngdeg": -3.1634149999999996, - "truebearingdeg": 105, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423119000, - "latdeg": 51.457632999999994, - "lngdeg": -3.1632699, - "truebearingdeg": 105, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423121000, - "latdeg": 51.457603, - "lngdeg": -3.163147, - "truebearingdeg": 114, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423123000, - "latdeg": 51.457561999999996, - "lngdeg": -3.163048, - "truebearingdeg": 136, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423125000, - "latdeg": 51.457505, - "lngdeg": -3.1630219, - "truebearingdeg": 173, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423127000, - "latdeg": 51.457457999999995, - "lngdeg": -3.1630379, - "truebearingdeg": 197, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423129000, - "latdeg": 51.457408, - "lngdeg": -3.16309, - "truebearingdeg": 219, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423131000, - "latdeg": 51.457363, - "lngdeg": -3.163185, - "truebearingdeg": 237, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423133000, - "latdeg": 51.457325, - "lngdeg": -3.163312, - "truebearingdeg": 252, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423135000, - "latdeg": 51.457297999999994, - "lngdeg": -3.1634379999999998, - "truebearingdeg": 252, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423137000, - "latdeg": 51.457274999999996, - "lngdeg": -3.1635519999999997, - "truebearingdeg": 249, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423139000, - "latdeg": 51.457248, - "lngdeg": -3.1636729999999997, - "truebearingdeg": 252, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423141000, - "latdeg": 51.457206899999996, - "lngdeg": -3.1637969999999997, - "truebearingdeg": 239, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423143000, - "latdeg": 51.457156999999995, - "lngdeg": -3.1639079999999997, - "truebearingdeg": 234, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423145000, - "latdeg": 51.457099899999996, - "lngdeg": -3.1640229, - "truebearingdeg": 232, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423147000, - "latdeg": 51.457038, - "lngdeg": -3.164142, - "truebearingdeg": 229, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423149000, - "latdeg": 51.456978, - "lngdeg": -3.164252, - "truebearingdeg": 221, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423151000, - "latdeg": 51.456902, - "lngdeg": -3.164342, - "truebearingdeg": 212, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423154000, - "latdeg": 51.456821999999995, - "lngdeg": -3.1644129999999997, - "truebearingdeg": 205, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423162000, - "latdeg": 51.456413, - "lngdeg": -3.164733, - "truebearingdeg": 204, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423164000, - "latdeg": 51.456312999999994, - "lngdeg": -3.164822, - "truebearingdeg": 214, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423166000, - "latdeg": 51.456222999999994, - "lngdeg": -3.164923, - "truebearingdeg": 216, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423168000, - "latdeg": 51.456153, - "lngdeg": -3.1650329999999998, - "truebearingdeg": 222, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423170000, - "latdeg": 51.456069899999996, - "lngdeg": -3.1651629999999997, - "truebearingdeg": 225, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423172000, - "latdeg": 51.455987, - "lngdeg": -3.165305, - "truebearingdeg": 226, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423174000, - "latdeg": 51.455909999999996, - "lngdeg": -3.165453, - "truebearingdeg": 234, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423176000, - "latdeg": 51.45588, - "lngdeg": -3.1656049, - "truebearingdeg": 267, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423178000, - "latdeg": 51.455898, - "lngdeg": -3.165685, - "truebearingdeg": 305, - "knotspeed": 4.319654427645788, - "tack": "PORT" - }, - { - "timepoint": 1346423180000, - "latdeg": 51.455925, - "lngdeg": -3.16572, - "truebearingdeg": 321, - "knotspeed": 4.319654427645788, - "tack": "PORT" - }, - { - "timepoint": 1346423182000, - "latdeg": 51.45597, - "lngdeg": -3.1657729999999997, - "truebearingdeg": 319, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423184000, - "latdeg": 51.456027999999996, - "lngdeg": -3.165842, - "truebearingdeg": 319, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423186000, - "latdeg": 51.456114899999996, - "lngdeg": -3.165952, - "truebearingdeg": 320, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423188000, - "latdeg": 51.456215, - "lngdeg": -3.1660749999999998, - "truebearingdeg": 323, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423190000, - "latdeg": 51.456309999999995, - "lngdeg": -3.1661929, - "truebearingdeg": 319, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423192000, - "latdeg": 51.45641, - "lngdeg": -3.166315, - "truebearingdeg": 326, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423194000, - "latdeg": 51.456537999999995, - "lngdeg": -3.166433, - "truebearingdeg": 325, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423196000, - "latdeg": 51.456646899999996, - "lngdeg": -3.16655, - "truebearingdeg": 325, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423198000, - "latdeg": 51.456765, - "lngdeg": -3.166655, - "truebearingdeg": 330, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423200000, - "latdeg": 51.456902, - "lngdeg": -3.1667549999999998, - "truebearingdeg": 343, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423202000, - "latdeg": 51.45702, - "lngdeg": -3.16675, - "truebearingdeg": 7, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423204000, - "latdeg": 51.457135, - "lngdeg": -3.166677, - "truebearingdeg": 23, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423206000, - "latdeg": 51.457274999999996, - "lngdeg": -3.1665669999999997, - "truebearingdeg": 27, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423208000, - "latdeg": 51.457417, - "lngdeg": -3.166432, - "truebearingdeg": 32, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423210000, - "latdeg": 51.45755, - "lngdeg": -3.166287, - "truebearingdeg": 35, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423212000, - "latdeg": 51.457671999999995, - "lngdeg": -3.166153, - "truebearingdeg": 30, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423214000, - "latdeg": 51.457778, - "lngdeg": -3.166038, - "truebearingdeg": 29, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423216000, - "latdeg": 51.457904899999996, - "lngdeg": -3.1659479999999998, - "truebearingdeg": 20, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423219000, - "latdeg": 51.458026999999994, - "lngdeg": -3.165877, - "truebearingdeg": 18, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423227000, - "latdeg": 51.458769999999994, - "lngdeg": -3.16546, - "truebearingdeg": 13, - "knotspeed": 19.978401727861772, - "tack": "PORT" - }, - { - "timepoint": 1346423229000, - "latdeg": 51.458946999999995, - "lngdeg": -3.165397, - "truebearingdeg": 13, - "knotspeed": 18.898488120950326, - "tack": "PORT" - }, - { - "timepoint": 1346423231000, - "latdeg": 51.459112999999995, - "lngdeg": -3.1653319, - "truebearingdeg": 13, - "knotspeed": 18.898488120950326, - "tack": "PORT" - }, - { - "timepoint": 1346423233000, - "latdeg": 51.459278, - "lngdeg": -3.165278, - "truebearingdeg": 8, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423235000, - "latdeg": 51.459419999999994, - "lngdeg": -3.1652429, - "truebearingdeg": 10, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423237000, - "latdeg": 51.459545, - "lngdeg": -3.1651979999999997, - "truebearingdeg": 15, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423239000, - "latdeg": 51.459663, - "lngdeg": -3.1651379, - "truebearingdeg": 17, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423241000, - "latdeg": 51.459804999999996, - "lngdeg": -3.16507, - "truebearingdeg": 14, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423243000, - "latdeg": 51.459962, - "lngdeg": -3.165015, - "truebearingdeg": 8, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423245000, - "latdeg": 51.460125, - "lngdeg": -3.1649629999999997, - "truebearingdeg": 14, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423247000, - "latdeg": 51.460293, - "lngdeg": -3.1648829999999997, - "truebearingdeg": 18, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423249000, - "latdeg": 51.460468, - "lngdeg": -3.16479, - "truebearingdeg": 18, - "knotspeed": 18.898488120950326, - "tack": "PORT" - }, - { - "timepoint": 1346423251000, - "latdeg": 51.460643, - "lngdeg": -3.1646769999999997, - "truebearingdeg": 23, - "knotspeed": 18.898488120950326, - "tack": "PORT" - }, - { - "timepoint": 1346423253000, - "latdeg": 51.460767, - "lngdeg": -3.164577, - "truebearingdeg": 21, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423255000, - "latdeg": 51.460873, - "lngdeg": -3.16451, - "truebearingdeg": 19, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423257000, - "latdeg": 51.460972, - "lngdeg": -3.164463, - "truebearingdeg": 14, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423259000, - "latdeg": 51.461062999999996, - "lngdeg": -3.164433, - "truebearingdeg": 14, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423261000, - "latdeg": 51.461146899999996, - "lngdeg": -3.164393, - "truebearingdeg": 16, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423263000, - "latdeg": 51.461227, - "lngdeg": -3.164363, - "truebearingdeg": 12, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423265000, - "latdeg": 51.461296999999995, - "lngdeg": -3.164338, - "truebearingdeg": 10, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423267000, - "latdeg": 51.461362, - "lngdeg": -3.164322, - "truebearingdeg": 7, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423269000, - "latdeg": 51.461445, - "lngdeg": -3.1643019999999997, - "truebearingdeg": 8, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423271000, - "latdeg": 51.461537, - "lngdeg": -3.1642769, - "truebearingdeg": 11, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423273000, - "latdeg": 51.461639999999996, - "lngdeg": -3.164247, - "truebearingdeg": 11, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423275000, - "latdeg": 51.461757999999996, - "lngdeg": -3.1642029999999997, - "truebearingdeg": 18, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423277000, - "latdeg": 51.461877, - "lngdeg": -3.1641049999999997, - "truebearingdeg": 36, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423279000, - "latdeg": 51.461906899999995, - "lngdeg": -3.1639779999999997, - "truebearingdeg": 81, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423281000, - "latdeg": 51.461906899999995, - "lngdeg": -3.163872, - "truebearingdeg": 110, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423284000, - "latdeg": 51.461867999999996, - "lngdeg": -3.163798, - "truebearingdeg": 130, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423291000, - "latdeg": 51.461566999999995, - "lngdeg": -3.1637749, - "truebearingdeg": 186, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423293000, - "latdeg": 51.461445, - "lngdeg": -3.163793, - "truebearingdeg": 187, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423295000, - "latdeg": 51.461323, - "lngdeg": -3.1638319999999998, - "truebearingdeg": 197, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423297000, - "latdeg": 51.461211999999996, - "lngdeg": -3.163897, - "truebearingdeg": 200, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423299000, - "latdeg": 51.461116999999994, - "lngdeg": -3.163957, - "truebearingdeg": 200, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423301000, - "latdeg": 51.461037, - "lngdeg": -3.164002, - "truebearingdeg": 197, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423303000, - "latdeg": 51.46096, - "lngdeg": -3.164033, - "truebearingdeg": 193, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423305000, - "latdeg": 51.460882999999995, - "lngdeg": -3.164055, - "truebearingdeg": 189, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423307000, - "latdeg": 51.460812, - "lngdeg": -3.164063, - "truebearingdeg": 181, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423309000, - "latdeg": 51.460738, - "lngdeg": -3.164052, - "truebearingdeg": 166, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423311000, - "latdeg": 51.46067, - "lngdeg": -3.1640219999999997, - "truebearingdeg": 162, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423313000, - "latdeg": 51.460598, - "lngdeg": -3.1639679999999997, - "truebearingdeg": 158, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423315000, - "latdeg": 51.460522, - "lngdeg": -3.163907, - "truebearingdeg": 147, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423317000, - "latdeg": 51.460432999999995, - "lngdeg": -3.1638029999999997, - "truebearingdeg": 147, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423319000, - "latdeg": 51.460311999999995, - "lngdeg": -3.16371, - "truebearingdeg": 159, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423321000, - "latdeg": 51.46019, - "lngdeg": -3.1636819999999997, - "truebearingdeg": 178, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423323000, - "latdeg": 51.460071899999996, - "lngdeg": -3.163662, - "truebearingdeg": 168, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423325000, - "latdeg": 51.45995, - "lngdeg": -3.1636249999999997, - "truebearingdeg": 176, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423327000, - "latdeg": 51.459843, - "lngdeg": -3.163598, - "truebearingdeg": 164, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423329000, - "latdeg": 51.459751999999995, - "lngdeg": -3.163513, - "truebearingdeg": 140, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423331000, - "latdeg": 51.459702, - "lngdeg": -3.1633929999999997, - "truebearingdeg": 113, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423333000, - "latdeg": 51.459689999999995, - "lngdeg": -3.163268, - "truebearingdeg": 86, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423335000, - "latdeg": 51.45971, - "lngdeg": -3.1632029999999998, - "truebearingdeg": 66, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346423337000, - "latdeg": 51.459747, - "lngdeg": -3.1631069999999997, - "truebearingdeg": 50, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423339000, - "latdeg": 51.459793, - "lngdeg": -3.163033, - "truebearingdeg": 30, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423341000, - "latdeg": 51.459846999999996, - "lngdeg": -3.1630029, - "truebearingdeg": 7, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346423343000, - "latdeg": 51.459903, - "lngdeg": -3.163013, - "truebearingdeg": 345, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346423345000, - "latdeg": 51.459972, - "lngdeg": -3.1630469999999997, - "truebearingdeg": 342, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423348000, - "latdeg": 51.460057, - "lngdeg": -3.163087, - "truebearingdeg": 344, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423356000, - "latdeg": 51.460617, - "lngdeg": -3.1633199999999997, - "truebearingdeg": 343, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423358000, - "latdeg": 51.460732, - "lngdeg": -3.1633799999999996, - "truebearingdeg": 338, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423360000, - "latdeg": 51.460842, - "lngdeg": -3.163452, - "truebearingdeg": 340, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423362000, - "latdeg": 51.460944999999995, - "lngdeg": -3.1634919999999997, - "truebearingdeg": 344, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423364000, - "latdeg": 51.461037, - "lngdeg": -3.163522, - "truebearingdeg": 349, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423366000, - "latdeg": 51.461116999999994, - "lngdeg": -3.163542, - "truebearingdeg": 351, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423368000, - "latdeg": 51.461192999999994, - "lngdeg": -3.1635649999999997, - "truebearingdeg": 350, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423370000, - "latdeg": 51.461265, - "lngdeg": -3.163588, - "truebearingdeg": 348, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423372000, - "latdeg": 51.461341999999995, - "lngdeg": -3.1636219999999997, - "truebearingdeg": 339, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423374000, - "latdeg": 51.461403, - "lngdeg": -3.16369, - "truebearingdeg": 313, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423376000, - "latdeg": 51.461422, - "lngdeg": -3.1637749, - "truebearingdeg": 284, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346423378000, - "latdeg": 51.461422, - "lngdeg": -3.163827, - "truebearingdeg": 252, - "knotspeed": 3.239740820734341, - "tack": "PORT" - }, - { - "timepoint": 1346423380000, - "latdeg": 51.461406999999994, - "lngdeg": -3.1638699999999997, - "truebearingdeg": 235, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423382000, - "latdeg": 51.461383, - "lngdeg": -3.163907, - "truebearingdeg": 227, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423384000, - "latdeg": 51.461357, - "lngdeg": -3.163942, - "truebearingdeg": 212, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423386000, - "latdeg": 51.461307999999995, - "lngdeg": -3.16398, - "truebearingdeg": 199, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423388000, - "latdeg": 51.461237999999994, - "lngdeg": -3.1639969999999997, - "truebearingdeg": 181, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423390000, - "latdeg": 51.461155, - "lngdeg": -3.163982, - "truebearingdeg": 169, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423392000, - "latdeg": 51.461062999999996, - "lngdeg": -3.16395, - "truebearingdeg": 160, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423394000, - "latdeg": 51.460972, - "lngdeg": -3.163897, - "truebearingdeg": 154, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423396000, - "latdeg": 51.460892, - "lngdeg": -3.163833, - "truebearingdeg": 153, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423398000, - "latdeg": 51.460812, - "lngdeg": -3.16377, - "truebearingdeg": 154, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423400000, - "latdeg": 51.460743, - "lngdeg": -3.163713, - "truebearingdeg": 155, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423402000, - "latdeg": 51.460677999999994, - "lngdeg": -3.1636669, - "truebearingdeg": 163, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423404000, - "latdeg": 51.460605, - "lngdeg": -3.163652, - "truebearingdeg": 170, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423406000, - "latdeg": 51.460539999999995, - "lngdeg": -3.1636249999999997, - "truebearingdeg": 168, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423408000, - "latdeg": 51.460482999999996, - "lngdeg": -3.163607, - "truebearingdeg": 167, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423410000, - "latdeg": 51.460423, - "lngdeg": -3.163592, - "truebearingdeg": 166, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423413000, - "latdeg": 51.460364999999996, - "lngdeg": -3.163572, - "truebearingdeg": 164, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423423000, - "latdeg": 51.459995, - "lngdeg": -3.16342, - "truebearingdeg": 166, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423425000, - "latdeg": 51.459914999999995, - "lngdeg": -3.1633999999999998, - "truebearingdeg": 174, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423427000, - "latdeg": 51.459843, - "lngdeg": -3.163385, - "truebearingdeg": 176, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423429000, - "latdeg": 51.459778, - "lngdeg": -3.1633779, - "truebearingdeg": 177, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423431000, - "latdeg": 51.459717, - "lngdeg": -3.1633679999999997, - "truebearingdeg": 176, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423433000, - "latdeg": 51.459647999999994, - "lngdeg": -3.163357, - "truebearingdeg": 176, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423435000, - "latdeg": 51.459575, - "lngdeg": -3.163348, - "truebearingdeg": 176, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423437000, - "latdeg": 51.459514999999996, - "lngdeg": -3.1633429, - "truebearingdeg": 176, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423439000, - "latdeg": 51.459447, - "lngdeg": -3.163337, - "truebearingdeg": 175, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423441000, - "latdeg": 51.459367, - "lngdeg": -3.1633269999999998, - "truebearingdeg": 174, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423443000, - "latdeg": 51.459278, - "lngdeg": -3.163315, - "truebearingdeg": 174, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423445000, - "latdeg": 51.459182999999996, - "lngdeg": -3.1633079, - "truebearingdeg": 179, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423447000, - "latdeg": 51.45908, - "lngdeg": -3.163318, - "truebearingdeg": 190, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423449000, - "latdeg": 51.458999999999996, - "lngdeg": -3.1633679999999997, - "truebearingdeg": 208, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423451000, - "latdeg": 51.458937999999996, - "lngdeg": -3.163442, - "truebearingdeg": 219, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423453000, - "latdeg": 51.458892999999996, - "lngdeg": -3.1635169999999997, - "truebearingdeg": 230, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423455000, - "latdeg": 51.458858, - "lngdeg": -3.1635779, - "truebearingdeg": 224, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423457000, - "latdeg": 51.458827899999996, - "lngdeg": -3.1636279999999997, - "truebearingdeg": 227, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423459000, - "latdeg": 51.458802, - "lngdeg": -3.163678, - "truebearingdeg": 225, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423461000, - "latdeg": 51.458774999999996, - "lngdeg": -3.163723, - "truebearingdeg": 229, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423463000, - "latdeg": 51.458751899999996, - "lngdeg": -3.16377, - "truebearingdeg": 229, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423465000, - "latdeg": 51.458732999999995, - "lngdeg": -3.1638099, - "truebearingdeg": 228, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423467000, - "latdeg": 51.458712999999996, - "lngdeg": -3.16384, - "truebearingdeg": 228, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423469000, - "latdeg": 51.458701999999995, - "lngdeg": -3.163865, - "truebearingdeg": 228, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423471000, - "latdeg": 51.45869, - "lngdeg": -3.1638799, - "truebearingdeg": 228, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423478000, - "latdeg": 51.458662999999994, - "lngdeg": -3.1638819999999996, - "truebearingdeg": 214, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423485000, - "latdeg": 51.458591999999996, - "lngdeg": -3.163887, - "truebearingdeg": 199, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423487000, - "latdeg": 51.458571899999995, - "lngdeg": -3.1638979999999997, - "truebearingdeg": 202, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423489000, - "latdeg": 51.458552999999995, - "lngdeg": -3.163912, - "truebearingdeg": 203, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423491000, - "latdeg": 51.458538, - "lngdeg": -3.163923, - "truebearingdeg": 202, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423493000, - "latdeg": 51.458526899999995, - "lngdeg": -3.163928, - "truebearingdeg": 200, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423495000, - "latdeg": 51.458512, - "lngdeg": -3.1639269999999997, - "truebearingdeg": 197, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423497000, - "latdeg": 51.458487999999996, - "lngdeg": -3.1639269999999997, - "truebearingdeg": 178, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423499000, - "latdeg": 51.458464899999996, - "lngdeg": -3.163932, - "truebearingdeg": 180, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423501000, - "latdeg": 51.458447, - "lngdeg": -3.163935, - "truebearingdeg": 184, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423503000, - "latdeg": 51.458419899999996, - "lngdeg": -3.163938, - "truebearingdeg": 192, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423505000, - "latdeg": 51.458397, - "lngdeg": -3.163942, - "truebearingdeg": 187, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423507000, - "latdeg": 51.458382, - "lngdeg": -3.163945, - "truebearingdeg": 187, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423509000, - "latdeg": 51.458366999999996, - "lngdeg": -3.163942, - "truebearingdeg": 186, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423511000, - "latdeg": 51.458352, - "lngdeg": -3.1639429999999997, - "truebearingdeg": 195, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423513000, - "latdeg": 51.458332, - "lngdeg": -3.163947, - "truebearingdeg": 194, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423515000, - "latdeg": 51.458317, - "lngdeg": -3.16395, - "truebearingdeg": 193, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423517000, - "latdeg": 51.45831, - "lngdeg": -3.1639529, - "truebearingdeg": 192, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423519000, - "latdeg": 51.458298, - "lngdeg": -3.1639519999999997, - "truebearingdeg": 190, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423521000, - "latdeg": 51.458287, - "lngdeg": -3.163945, - "truebearingdeg": 186, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423523000, - "latdeg": 51.458272, - "lngdeg": -3.163932, - "truebearingdeg": 161, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423525000, - "latdeg": 51.458248, - "lngdeg": -3.163925, - "truebearingdeg": 179, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423527000, - "latdeg": 51.458217999999995, - "lngdeg": -3.1639299999999997, - "truebearingdeg": 187, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423529000, - "latdeg": 51.458183, - "lngdeg": -3.163947, - "truebearingdeg": 199, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423531000, - "latdeg": 51.458152999999996, - "lngdeg": -3.1639649999999997, - "truebearingdeg": 202, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423533000, - "latdeg": 51.45813, - "lngdeg": -3.163985, - "truebearingdeg": 203, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423535000, - "latdeg": 51.458107, - "lngdeg": -3.163995, - "truebearingdeg": 193, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423537000, - "latdeg": 51.458087899999995, - "lngdeg": -3.163998, - "truebearingdeg": 184, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423539000, - "latdeg": 51.458068, - "lngdeg": -3.1640029999999997, - "truebearingdeg": 185, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423542000, - "latdeg": 51.458047, - "lngdeg": -3.164012, - "truebearingdeg": 193, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423549000, - "latdeg": 51.457955, - "lngdeg": -3.164062, - "truebearingdeg": 203, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423551000, - "latdeg": 51.457938, - "lngdeg": -3.164075, - "truebearingdeg": 203, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423553000, - "latdeg": 51.457923, - "lngdeg": -3.164082, - "truebearingdeg": 202, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423555000, - "latdeg": 51.457907999999996, - "lngdeg": -3.164087, - "truebearingdeg": 201, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423557000, - "latdeg": 51.457893, - "lngdeg": -3.164093, - "truebearingdeg": 196, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423559000, - "latdeg": 51.457874999999994, - "lngdeg": -3.164107, - "truebearingdeg": 205, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423561000, - "latdeg": 51.457854999999995, - "lngdeg": -3.164122, - "truebearingdeg": 208, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423563000, - "latdeg": 51.45784, - "lngdeg": -3.1641369999999998, - "truebearingdeg": 208, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423565000, - "latdeg": 51.457828, - "lngdeg": -3.164148, - "truebearingdeg": 207, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423567000, - "latdeg": 51.457812999999994, - "lngdeg": -3.164157, - "truebearingdeg": 205, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423569000, - "latdeg": 51.457797899999996, - "lngdeg": -3.16416, - "truebearingdeg": 203, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423571000, - "latdeg": 51.457783, - "lngdeg": -3.16416, - "truebearingdeg": 200, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - } - ] - }, - { - "name": "ZouLou", - "track": [ - { - "timepoint": 1346422553000, - "latdeg": 51.46006, - "lngdeg": -3.161962, - "truebearingdeg": 19, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346422555000, - "latdeg": 51.46022, - "lngdeg": -3.1618619999999997, - "truebearingdeg": 24, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346422557000, - "latdeg": 51.460361999999996, - "lngdeg": -3.1617799, - "truebearingdeg": 14, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346422560000, - "latdeg": 51.460482999999996, - "lngdeg": -3.1617799, - "truebearingdeg": 348, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422568000, - "latdeg": 51.460812, - "lngdeg": -3.162163, - "truebearingdeg": 329, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422570000, - "latdeg": 51.460892, - "lngdeg": -3.162245, - "truebearingdeg": 328, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422572000, - "latdeg": 51.460968, - "lngdeg": -3.162337, - "truebearingdeg": 320, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422574000, - "latdeg": 51.461037, - "lngdeg": -3.1624369999999997, - "truebearingdeg": 314, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422576000, - "latdeg": 51.461093, - "lngdeg": -3.162533, - "truebearingdeg": 310, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422578000, - "latdeg": 51.461146899999996, - "lngdeg": -3.162642, - "truebearingdeg": 307, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422580000, - "latdeg": 51.461197, - "lngdeg": -3.162737, - "truebearingdeg": 315, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422582000, - "latdeg": 51.461262, - "lngdeg": -3.162827, - "truebearingdeg": 317, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422584000, - "latdeg": 51.461327, - "lngdeg": -3.162927, - "truebearingdeg": 312, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422586000, - "latdeg": 51.461383, - "lngdeg": -3.163035, - "truebearingdeg": 312, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422588000, - "latdeg": 51.461445, - "lngdeg": -3.163122, - "truebearingdeg": 315, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422590000, - "latdeg": 51.46149, - "lngdeg": -3.163198, - "truebearingdeg": 311, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346422592000, - "latdeg": 51.461524999999995, - "lngdeg": -3.1632599999999997, - "truebearingdeg": 292, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346422594000, - "latdeg": 51.461527999999994, - "lngdeg": -3.163332, - "truebearingdeg": 264, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422596000, - "latdeg": 51.461509899999996, - "lngdeg": -3.163385, - "truebearingdeg": 239, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422598000, - "latdeg": 51.461498, - "lngdeg": -3.1634279999999997, - "truebearingdeg": 231, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422600000, - "latdeg": 51.461483, - "lngdeg": -3.1634699, - "truebearingdeg": 227, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422602000, - "latdeg": 51.461472, - "lngdeg": -3.1635, - "truebearingdeg": 245, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422604000, - "latdeg": 51.461467999999996, - "lngdeg": -3.163535, - "truebearingdeg": 252, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422606000, - "latdeg": 51.461456999999996, - "lngdeg": -3.16356, - "truebearingdeg": 216, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422608000, - "latdeg": 51.461445, - "lngdeg": -3.1635779, - "truebearingdeg": 242, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422610000, - "latdeg": 51.461436899999995, - "lngdeg": -3.163602, - "truebearingdeg": 244, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422612000, - "latdeg": 51.46143, - "lngdeg": -3.163637, - "truebearingdeg": 251, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422614000, - "latdeg": 51.461417999999995, - "lngdeg": -3.16369, - "truebearingdeg": 252, - "knotspeed": 2.699784017278618, - "tack": "PORT" - }, - { - "timepoint": 1346422616000, - "latdeg": 51.461417999999995, - "lngdeg": -3.163723, - "truebearingdeg": 270, - "knotspeed": 2.159827213822894, - "tack": "PORT" - }, - { - "timepoint": 1346422618000, - "latdeg": 51.461425, - "lngdeg": -3.16375, - "truebearingdeg": 279, - "knotspeed": 1.6198704103671706, - "tack": "PORT" - }, - { - "timepoint": 1346422620000, - "latdeg": 51.46143, - "lngdeg": -3.1637749, - "truebearingdeg": 279, - "knotspeed": 1.079913606911447, - "tack": "PORT" - }, - { - "timepoint": 1346422622000, - "latdeg": 51.461425, - "lngdeg": -3.163798, - "truebearingdeg": 279, - "knotspeed": 1.079913606911447, - "tack": "PORT" - }, - { - "timepoint": 1346422625000, - "latdeg": 51.461422, - "lngdeg": -3.1638129999999998, - "truebearingdeg": 277, - "knotspeed": 0.5399568034557235, - "tack": "PORT" - }, - { - "timepoint": 1346422632000, - "latdeg": 51.461417999999995, - "lngdeg": -3.163842, - "truebearingdeg": 262, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422634000, - "latdeg": 51.461414999999995, - "lngdeg": -3.1638479999999998, - "truebearingdeg": 256, - "knotspeed": 0, - "tack": "PORT" - }, - { - "timepoint": 1346422636000, - "latdeg": 51.461417999999995, - "lngdeg": -3.16385, - "truebearingdeg": 253, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422638000, - "latdeg": 51.461414999999995, - "lngdeg": -3.1638479999999998, - "truebearingdeg": 247, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422640000, - "latdeg": 51.461414999999995, - "lngdeg": -3.163847, - "truebearingdeg": 241, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422642000, - "latdeg": 51.46141, - "lngdeg": -3.163843, - "truebearingdeg": 234, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422644000, - "latdeg": 51.461406999999994, - "lngdeg": -3.1638379999999997, - "truebearingdeg": 216, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422646000, - "latdeg": 51.461406999999994, - "lngdeg": -3.16383, - "truebearingdeg": 199, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422648000, - "latdeg": 51.461406999999994, - "lngdeg": -3.1638219999999997, - "truebearingdeg": 182, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422650000, - "latdeg": 51.461406999999994, - "lngdeg": -3.163817, - "truebearingdeg": 169, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422652000, - "latdeg": 51.461406999999994, - "lngdeg": -3.163817, - "truebearingdeg": 158, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422654000, - "latdeg": 51.461406999999994, - "lngdeg": -3.1638099, - "truebearingdeg": 147, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422660000, - "latdeg": 51.461417999999995, - "lngdeg": -3.163783, - "truebearingdeg": 121, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422668000, - "latdeg": 51.46141, - "lngdeg": -3.163715, - "truebearingdeg": 110, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422670000, - "latdeg": 51.46141, - "lngdeg": -3.1637019, - "truebearingdeg": 109, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422672000, - "latdeg": 51.461406999999994, - "lngdeg": -3.163688, - "truebearingdeg": 107, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422674000, - "latdeg": 51.461406999999994, - "lngdeg": -3.163677, - "truebearingdeg": 104, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422676000, - "latdeg": 51.46141, - "lngdeg": -3.163672, - "truebearingdeg": 103, - "knotspeed": 0.5399568034557235, - "tack": "PORT" - }, - { - "timepoint": 1346422686000, - "latdeg": 51.461388, - "lngdeg": -3.1636819999999997, - "truebearingdeg": 137, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422689000, - "latdeg": 51.461383, - "lngdeg": -3.163687, - "truebearingdeg": 144, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422697000, - "latdeg": 51.461368, - "lngdeg": -3.163707, - "truebearingdeg": 167, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422699000, - "latdeg": 51.461362, - "lngdeg": -3.163707, - "truebearingdeg": 169, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422701000, - "latdeg": 51.461352999999995, - "lngdeg": -3.1637079999999997, - "truebearingdeg": 172, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422703000, - "latdeg": 51.461344999999994, - "lngdeg": -3.1637079999999997, - "truebearingdeg": 173, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422707000, - "latdeg": 51.461327, - "lngdeg": -3.163697, - "truebearingdeg": 180, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422709000, - "latdeg": 51.461296999999995, - "lngdeg": -3.163697, - "truebearingdeg": 191, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422711000, - "latdeg": 51.46127, - "lngdeg": -3.16369, - "truebearingdeg": 185, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422713000, - "latdeg": 51.461227, - "lngdeg": -3.163712, - "truebearingdeg": 197, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422715000, - "latdeg": 51.461182, - "lngdeg": -3.1637369, - "truebearingdeg": 195, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422717000, - "latdeg": 51.46112, - "lngdeg": -3.163773, - "truebearingdeg": 201, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422719000, - "latdeg": 51.461045, - "lngdeg": -3.163823, - "truebearingdeg": 203, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422721000, - "latdeg": 51.460968, - "lngdeg": -3.1638699999999997, - "truebearingdeg": 203, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422723000, - "latdeg": 51.460892, - "lngdeg": -3.1639299999999997, - "truebearingdeg": 205, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422725000, - "latdeg": 51.4608, - "lngdeg": -3.1640069, - "truebearingdeg": 206, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422727000, - "latdeg": 51.460693, - "lngdeg": -3.16409, - "truebearingdeg": 204, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422729000, - "latdeg": 51.460583, - "lngdeg": -3.1641679999999996, - "truebearingdeg": 204, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422731000, - "latdeg": 51.460491999999995, - "lngdeg": -3.1642379999999997, - "truebearingdeg": 205, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422733000, - "latdeg": 51.460415, - "lngdeg": -3.164298, - "truebearingdeg": 212, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422735000, - "latdeg": 51.460327, - "lngdeg": -3.16436, - "truebearingdeg": 204, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422737000, - "latdeg": 51.460204999999995, - "lngdeg": -3.16443, - "truebearingdeg": 201, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422739000, - "latdeg": 51.460071899999996, - "lngdeg": -3.1645, - "truebearingdeg": 197, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422741000, - "latdeg": 51.459972, - "lngdeg": -3.1645999999999996, - "truebearingdeg": 207, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422743000, - "latdeg": 51.459846999999996, - "lngdeg": -3.164707, - "truebearingdeg": 206, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422745000, - "latdeg": 51.459682, - "lngdeg": -3.164803, - "truebearingdeg": 202, - "knotspeed": 16.73866090712743, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422747000, - "latdeg": 51.459559999999996, - "lngdeg": -3.164888, - "truebearingdeg": 198, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422749000, - "latdeg": 51.459427, - "lngdeg": -3.1649569, - "truebearingdeg": 199, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422751000, - "latdeg": 51.459317, - "lngdeg": -3.1650579999999997, - "truebearingdeg": 204, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422754000, - "latdeg": 51.459187, - "lngdeg": -3.1651249999999997, - "truebearingdeg": 202, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422762000, - "latdeg": 51.458732999999995, - "lngdeg": -3.1654549999999997, - "truebearingdeg": 205, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422764000, - "latdeg": 51.458607, - "lngdeg": -3.165533, - "truebearingdeg": 202, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422766000, - "latdeg": 51.45847, - "lngdeg": -3.165628, - "truebearingdeg": 204, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422768000, - "latdeg": 51.458355, - "lngdeg": -3.1657129999999998, - "truebearingdeg": 202, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422770000, - "latdeg": 51.458245, - "lngdeg": -3.1657819999999997, - "truebearingdeg": 200, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422772000, - "latdeg": 51.45813, - "lngdeg": -3.16585, - "truebearingdeg": 201, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422774000, - "latdeg": 51.458014999999996, - "lngdeg": -3.165927, - "truebearingdeg": 203, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422776000, - "latdeg": 51.457896999999996, - "lngdeg": -3.1660019999999998, - "truebearingdeg": 202, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422778000, - "latdeg": 51.457783, - "lngdeg": -3.16608, - "truebearingdeg": 204, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422780000, - "latdeg": 51.457657, - "lngdeg": -3.166168, - "truebearingdeg": 203, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422782000, - "latdeg": 51.457527, - "lngdeg": -3.1662499, - "truebearingdeg": 201, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422784000, - "latdeg": 51.457404999999994, - "lngdeg": -3.166337, - "truebearingdeg": 203, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422786000, - "latdeg": 51.457274999999996, - "lngdeg": -3.1664269999999997, - "truebearingdeg": 206, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422788000, - "latdeg": 51.457156999999995, - "lngdeg": -3.166528, - "truebearingdeg": 209, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422790000, - "latdeg": 51.457049999999995, - "lngdeg": -3.16663, - "truebearingdeg": 209, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422792000, - "latdeg": 51.456947, - "lngdeg": -3.166722, - "truebearingdeg": 208, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422794000, - "latdeg": 51.456852, - "lngdeg": -3.1668019999999997, - "truebearingdeg": 207, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422796000, - "latdeg": 51.456765, - "lngdeg": -3.1668719999999997, - "truebearingdeg": 205, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422798000, - "latdeg": 51.456668, - "lngdeg": -3.1669419999999997, - "truebearingdeg": 203, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422800000, - "latdeg": 51.456567, - "lngdeg": -3.167027, - "truebearingdeg": 209, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422802000, - "latdeg": 51.456458, - "lngdeg": -3.1671269, - "truebearingdeg": 212, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422804000, - "latdeg": 51.456348, - "lngdeg": -3.1672369999999996, - "truebearingdeg": 214, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422806000, - "latdeg": 51.456238, - "lngdeg": -3.16735, - "truebearingdeg": 211, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422808000, - "latdeg": 51.456114899999996, - "lngdeg": -3.1674629999999997, - "truebearingdeg": 211, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422810000, - "latdeg": 51.456008, - "lngdeg": -3.1675579999999997, - "truebearingdeg": 207, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422812000, - "latdeg": 51.455925, - "lngdeg": -3.167637, - "truebearingdeg": 205, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422814000, - "latdeg": 51.455844899999995, - "lngdeg": -3.16769, - "truebearingdeg": 203, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422816000, - "latdeg": 51.455737899999995, - "lngdeg": -3.16776, - "truebearingdeg": 203, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422819000, - "latdeg": 51.455631999999994, - "lngdeg": -3.167833, - "truebearingdeg": 204, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422826000, - "latdeg": 51.455282999999994, - "lngdeg": -3.168085, - "truebearingdeg": 206, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422828000, - "latdeg": 51.455169999999995, - "lngdeg": -3.16817, - "truebearingdeg": 207, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422830000, - "latdeg": 51.455054999999994, - "lngdeg": -3.1682579, - "truebearingdeg": 208, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422832000, - "latdeg": 51.454937, - "lngdeg": -3.168352, - "truebearingdeg": 206, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422834000, - "latdeg": 51.454833, - "lngdeg": -3.1684549, - "truebearingdeg": 211, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422836000, - "latdeg": 51.454722999999994, - "lngdeg": -3.168548, - "truebearingdeg": 206, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422838000, - "latdeg": 51.454617, - "lngdeg": -3.1686329, - "truebearingdeg": 206, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422840000, - "latdeg": 51.454522, - "lngdeg": -3.1687149999999997, - "truebearingdeg": 203, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422842000, - "latdeg": 51.454425, - "lngdeg": -3.168767, - "truebearingdeg": 197, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422844000, - "latdeg": 51.454318, - "lngdeg": -3.168818, - "truebearingdeg": 193, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422846000, - "latdeg": 51.4542, - "lngdeg": -3.1688479999999997, - "truebearingdeg": 183, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422848000, - "latdeg": 51.454066999999995, - "lngdeg": -3.1688579999999997, - "truebearingdeg": 178, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422850000, - "latdeg": 51.453938, - "lngdeg": -3.168793, - "truebearingdeg": 150, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422852000, - "latdeg": 51.45388, - "lngdeg": -3.1686699999999997, - "truebearingdeg": 127, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422854000, - "latdeg": 51.45382, - "lngdeg": -3.168522, - "truebearingdeg": 123, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422856000, - "latdeg": 51.45377, - "lngdeg": -3.1683749999999997, - "truebearingdeg": 126, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422858000, - "latdeg": 51.453705, - "lngdeg": -3.1682829999999997, - "truebearingdeg": 137, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422860000, - "latdeg": 51.453632, - "lngdeg": -3.168177, - "truebearingdeg": 142, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422862000, - "latdeg": 51.453551999999995, - "lngdeg": -3.168075, - "truebearingdeg": 141, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422864000, - "latdeg": 51.45346, - "lngdeg": -3.167958, - "truebearingdeg": 137, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422866000, - "latdeg": 51.453376999999996, - "lngdeg": -3.1677969999999998, - "truebearingdeg": 131, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422868000, - "latdeg": 51.453285, - "lngdeg": -3.167617, - "truebearingdeg": 128, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422870000, - "latdeg": 51.453193, - "lngdeg": -3.167423, - "truebearingdeg": 126, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422872000, - "latdeg": 51.453109999999995, - "lngdeg": -3.1672179999999996, - "truebearingdeg": 120, - "knotspeed": 16.73866090712743, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422874000, - "latdeg": 51.453033, - "lngdeg": -3.166998, - "truebearingdeg": 117, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422876000, - "latdeg": 51.452977, - "lngdeg": -3.166788, - "truebearingdeg": 111, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422878000, - "latdeg": 51.452953, - "lngdeg": -3.166585, - "truebearingdeg": 93, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346422880000, - "latdeg": 51.452965, - "lngdeg": -3.166425, - "truebearingdeg": 79, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422883000, - "latdeg": 51.452991999999995, - "lngdeg": -3.166305, - "truebearingdeg": 61, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422891000, - "latdeg": 51.453311899999996, - "lngdeg": -3.165915, - "truebearingdeg": 36, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422893000, - "latdeg": 51.453415, - "lngdeg": -3.1657919999999997, - "truebearingdeg": 33, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346422895000, - "latdeg": 51.453525, - "lngdeg": -3.165667, - "truebearingdeg": 35, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346422897000, - "latdeg": 51.453637, - "lngdeg": -3.165527, - "truebearingdeg": 38, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346422899000, - "latdeg": 51.453747, - "lngdeg": -3.165362, - "truebearingdeg": 44, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346422901000, - "latdeg": 51.453857, - "lngdeg": -3.1651819999999997, - "truebearingdeg": 47, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346422903000, - "latdeg": 51.453956999999996, - "lngdeg": -3.165, - "truebearingdeg": 52, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346422905000, - "latdeg": 51.454045, - "lngdeg": -3.164825, - "truebearingdeg": 49, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346422907000, - "latdeg": 51.454125, - "lngdeg": -3.1646669999999997, - "truebearingdeg": 47, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422909000, - "latdeg": 51.454226999999996, - "lngdeg": -3.1645499999999998, - "truebearingdeg": 26, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346422911000, - "latdeg": 51.45433, - "lngdeg": -3.164497, - "truebearingdeg": 13, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422913000, - "latdeg": 51.45446, - "lngdeg": -3.1644699999999997, - "truebearingdeg": 1, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346422915000, - "latdeg": 51.454581999999995, - "lngdeg": -3.16453, - "truebearingdeg": 330, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346422917000, - "latdeg": 51.454673, - "lngdeg": -3.16465, - "truebearingdeg": 307, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422919000, - "latdeg": 51.454712, - "lngdeg": -3.1647599, - "truebearingdeg": 303, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422921000, - "latdeg": 51.45475, - "lngdeg": -3.164847, - "truebearingdeg": 310, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422923000, - "latdeg": 51.454803, - "lngdeg": -3.164952, - "truebearingdeg": 308, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422925000, - "latdeg": 51.454859899999995, - "lngdeg": -3.165072, - "truebearingdeg": 307, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422927000, - "latdeg": 51.454921999999996, - "lngdeg": -3.165193, - "truebearingdeg": 307, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422929000, - "latdeg": 51.454975, - "lngdeg": -3.1653029999999998, - "truebearingdeg": 308, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422931000, - "latdeg": 51.455028, - "lngdeg": -3.165418, - "truebearingdeg": 305, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422933000, - "latdeg": 51.455084899999996, - "lngdeg": -3.16554, - "truebearingdeg": 307, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422935000, - "latdeg": 51.455143, - "lngdeg": -3.1656679999999997, - "truebearingdeg": 305, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422937000, - "latdeg": 51.4552, - "lngdeg": -3.1657919999999997, - "truebearingdeg": 309, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422939000, - "latdeg": 51.455265, - "lngdeg": -3.165923, - "truebearingdeg": 309, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422941000, - "latdeg": 51.455329899999995, - "lngdeg": -3.166042, - "truebearingdeg": 312, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422943000, - "latdeg": 51.455402, - "lngdeg": -3.166162, - "truebearingdeg": 313, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422945000, - "latdeg": 51.455462999999995, - "lngdeg": -3.1662749999999997, - "truebearingdeg": 306, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422948000, - "latdeg": 51.45552, - "lngdeg": -3.16641, - "truebearingdeg": 304, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422955000, - "latdeg": 51.455794999999995, - "lngdeg": -3.16688, - "truebearingdeg": 323, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422957000, - "latdeg": 51.455883, - "lngdeg": -3.167013, - "truebearingdeg": 308, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422959000, - "latdeg": 51.455906999999996, - "lngdeg": -3.1671549999999997, - "truebearingdeg": 263, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346422961000, - "latdeg": 51.455887, - "lngdeg": -3.1672249999999997, - "truebearingdeg": 225, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422963000, - "latdeg": 51.455867999999995, - "lngdeg": -3.1672499999999997, - "truebearingdeg": 219, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422965000, - "latdeg": 51.45583, - "lngdeg": -3.1673, - "truebearingdeg": 216, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422967000, - "latdeg": 51.45578, - "lngdeg": -3.1673799999999996, - "truebearingdeg": 222, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422969000, - "latdeg": 51.455715, - "lngdeg": -3.1674629999999997, - "truebearingdeg": 216, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422971000, - "latdeg": 51.455631999999994, - "lngdeg": -3.1675649999999997, - "truebearingdeg": 216, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422973000, - "latdeg": 51.455543, - "lngdeg": -3.1676599999999997, - "truebearingdeg": 213, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422975000, - "latdeg": 51.455462999999995, - "lngdeg": -3.1677519999999997, - "truebearingdeg": 211, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422977000, - "latdeg": 51.455383, - "lngdeg": -3.167823, - "truebearingdeg": 207, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422979000, - "latdeg": 51.455315, - "lngdeg": -3.1678829, - "truebearingdeg": 206, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422981000, - "latdeg": 51.455247, - "lngdeg": -3.1679299999999997, - "truebearingdeg": 202, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422983000, - "latdeg": 51.455172999999995, - "lngdeg": -3.167985, - "truebearingdeg": 206, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422985000, - "latdeg": 51.45509, - "lngdeg": -3.16805, - "truebearingdeg": 206, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422987000, - "latdeg": 51.454989999999995, - "lngdeg": -3.1681299999999997, - "truebearingdeg": 208, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422989000, - "latdeg": 51.454907, - "lngdeg": -3.16823, - "truebearingdeg": 211, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422991000, - "latdeg": 51.454811899999996, - "lngdeg": -3.1683149999999998, - "truebearingdeg": 209, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422993000, - "latdeg": 51.454715, - "lngdeg": -3.168398, - "truebearingdeg": 209, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422995000, - "latdeg": 51.454612999999995, - "lngdeg": -3.168487, - "truebearingdeg": 210, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422997000, - "latdeg": 51.454513, - "lngdeg": -3.16858, - "truebearingdeg": 208, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422999000, - "latdeg": 51.454398, - "lngdeg": -3.1686769999999997, - "truebearingdeg": 206, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423001000, - "latdeg": 51.454288, - "lngdeg": -3.1687499999999997, - "truebearingdeg": 203, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423003000, - "latdeg": 51.45417, - "lngdeg": -3.168812, - "truebearingdeg": 194, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423005000, - "latdeg": 51.454045, - "lngdeg": -3.168822, - "truebearingdeg": 173, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423007000, - "latdeg": 51.453938, - "lngdeg": -3.1687429999999996, - "truebearingdeg": 150, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423009000, - "latdeg": 51.453835, - "lngdeg": -3.168608, - "truebearingdeg": 135, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423012000, - "latdeg": 51.453747, - "lngdeg": -3.168415, - "truebearingdeg": 120, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423019000, - "latdeg": 51.453449899999995, - "lngdeg": -3.167598, - "truebearingdeg": 120, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423021000, - "latdeg": 51.453365, - "lngdeg": -3.1673799999999996, - "truebearingdeg": 118, - "knotspeed": 16.73866090712743, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423023000, - "latdeg": 51.453308, - "lngdeg": -3.1671269, - "truebearingdeg": 108, - "knotspeed": 16.73866090712743, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423025000, - "latdeg": 51.453285, - "lngdeg": -3.1669099999999997, - "truebearingdeg": 89, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423027000, - "latdeg": 51.453308, - "lngdeg": -3.166735, - "truebearingdeg": 70, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423029000, - "latdeg": 51.45335, - "lngdeg": -3.1665799999999997, - "truebearingdeg": 61, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423031000, - "latdeg": 51.453412, - "lngdeg": -3.166445, - "truebearingdeg": 48, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423033000, - "latdeg": 51.453492, - "lngdeg": -3.1663229999999998, - "truebearingdeg": 45, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423035000, - "latdeg": 51.453587, - "lngdeg": -3.1661669999999997, - "truebearingdeg": 45, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423037000, - "latdeg": 51.453692999999994, - "lngdeg": -3.166003, - "truebearingdeg": 42, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423039000, - "latdeg": 51.453807999999995, - "lngdeg": -3.1658429999999997, - "truebearingdeg": 42, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423041000, - "latdeg": 51.453927, - "lngdeg": -3.16566, - "truebearingdeg": 45, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423043000, - "latdeg": 51.454045, - "lngdeg": -3.1654619999999998, - "truebearingdeg": 48, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423045000, - "latdeg": 51.454136999999996, - "lngdeg": -3.165275, - "truebearingdeg": 53, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423047000, - "latdeg": 51.454207999999994, - "lngdeg": -3.165105, - "truebearingdeg": 57, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423049000, - "latdeg": 51.45427, - "lngdeg": -3.1649399999999996, - "truebearingdeg": 61, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423051000, - "latdeg": 51.454318, - "lngdeg": -3.1647879999999997, - "truebearingdeg": 63, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423053000, - "latdeg": 51.454356999999995, - "lngdeg": -3.1646669999999997, - "truebearingdeg": 61, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423055000, - "latdeg": 51.454398, - "lngdeg": -3.164558, - "truebearingdeg": 51, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423057000, - "latdeg": 51.454448, - "lngdeg": -3.164507, - "truebearingdeg": 25, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423059000, - "latdeg": 51.454522, - "lngdeg": -3.1644899, - "truebearingdeg": 2, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423061000, - "latdeg": 51.454597, - "lngdeg": -3.164513, - "truebearingdeg": 347, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423063000, - "latdeg": 51.454657999999995, - "lngdeg": -3.16459, - "truebearingdeg": 317, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423065000, - "latdeg": 51.454727, - "lngdeg": -3.164697, - "truebearingdeg": 315, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423067000, - "latdeg": 51.454811899999996, - "lngdeg": -3.164822, - "truebearingdeg": 317, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423069000, - "latdeg": 51.454907, - "lngdeg": -3.1649569, - "truebearingdeg": 320, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423071000, - "latdeg": 51.455009999999994, - "lngdeg": -3.1650799999999997, - "truebearingdeg": 323, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423073000, - "latdeg": 51.45512, - "lngdeg": -3.165222, - "truebearingdeg": 322, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423076000, - "latdeg": 51.455242, - "lngdeg": -3.165365, - "truebearingdeg": 325, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423084000, - "latdeg": 51.455833, - "lngdeg": -3.16587, - "truebearingdeg": 332, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423086000, - "latdeg": 51.45594, - "lngdeg": -3.1659599999999997, - "truebearingdeg": 331, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423088000, - "latdeg": 51.456043, - "lngdeg": -3.1660529, - "truebearingdeg": 332, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423090000, - "latdeg": 51.456142, - "lngdeg": -3.166133, - "truebearingdeg": 333, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423092000, - "latdeg": 51.456238, - "lngdeg": -3.1662019999999997, - "truebearingdeg": 337, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423094000, - "latdeg": 51.456348, - "lngdeg": -3.1662679999999996, - "truebearingdeg": 339, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423096000, - "latdeg": 51.456466999999996, - "lngdeg": -3.1663379999999997, - "truebearingdeg": 339, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423098000, - "latdeg": 51.456596999999995, - "lngdeg": -3.166403, - "truebearingdeg": 344, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423100000, - "latdeg": 51.456727, - "lngdeg": -3.166448, - "truebearingdeg": 349, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423102000, - "latdeg": 51.45686, - "lngdeg": -3.1664779999999997, - "truebearingdeg": 359, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423104000, - "latdeg": 51.45699, - "lngdeg": -3.166445, - "truebearingdeg": 17, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423106000, - "latdeg": 51.457122999999996, - "lngdeg": -3.166353, - "truebearingdeg": 27, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423108000, - "latdeg": 51.45726, - "lngdeg": -3.1662119, - "truebearingdeg": 36, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423110000, - "latdeg": 51.457401999999995, - "lngdeg": -3.1660049999999997, - "truebearingdeg": 49, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423112000, - "latdeg": 51.457485, - "lngdeg": -3.1657669999999998, - "truebearingdeg": 69, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423114000, - "latdeg": 51.457535, - "lngdeg": -3.16554, - "truebearingdeg": 69, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423116000, - "latdeg": 51.457584999999995, - "lngdeg": -3.165317, - "truebearingdeg": 66, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423118000, - "latdeg": 51.457645, - "lngdeg": -3.1651149999999997, - "truebearingdeg": 59, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423120000, - "latdeg": 51.457718, - "lngdeg": -3.164923, - "truebearingdeg": 55, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423122000, - "latdeg": 51.457795, - "lngdeg": -3.164752, - "truebearingdeg": 57, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423124000, - "latdeg": 51.457862999999996, - "lngdeg": -3.1645529999999997, - "truebearingdeg": 61, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423126000, - "latdeg": 51.457919999999994, - "lngdeg": -3.1643499999999998, - "truebearingdeg": 65, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423128000, - "latdeg": 51.457972999999996, - "lngdeg": -3.16416, - "truebearingdeg": 72, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423130000, - "latdeg": 51.458008, - "lngdeg": -3.163973, - "truebearingdeg": 77, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423132000, - "latdeg": 51.458026999999994, - "lngdeg": -3.1638319999999998, - "truebearingdeg": 80, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423134000, - "latdeg": 51.458037999999995, - "lngdeg": -3.163703, - "truebearingdeg": 79, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423136000, - "latdeg": 51.458056899999995, - "lngdeg": -3.1635769999999996, - "truebearingdeg": 74, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423138000, - "latdeg": 51.458087899999995, - "lngdeg": -3.1634819999999997, - "truebearingdeg": 54, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423141000, - "latdeg": 51.458132899999995, - "lngdeg": -3.1633969, - "truebearingdeg": 38, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423149000, - "latdeg": 51.458422999999996, - "lngdeg": -3.1634849999999997, - "truebearingdeg": 306, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423151000, - "latdeg": 51.458464899999996, - "lngdeg": -3.163592, - "truebearingdeg": 299, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423153000, - "latdeg": 51.4585, - "lngdeg": -3.163677, - "truebearingdeg": 308, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423155000, - "latdeg": 51.458549999999995, - "lngdeg": -3.1637779999999998, - "truebearingdeg": 307, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423157000, - "latdeg": 51.458597999999995, - "lngdeg": -3.163897, - "truebearingdeg": 302, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423159000, - "latdeg": 51.458647899999995, - "lngdeg": -3.164047, - "truebearingdeg": 295, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423161000, - "latdeg": 51.45869, - "lngdeg": -3.1641869999999996, - "truebearingdeg": 295, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423163000, - "latdeg": 51.458709999999996, - "lngdeg": -3.1643179999999997, - "truebearingdeg": 272, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423165000, - "latdeg": 51.458695, - "lngdeg": -3.164395, - "truebearingdeg": 237, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423167000, - "latdeg": 51.458667999999996, - "lngdeg": -3.1644379999999996, - "truebearingdeg": 209, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423169000, - "latdeg": 51.458633, - "lngdeg": -3.164462, - "truebearingdeg": 194, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423171000, - "latdeg": 51.458591999999996, - "lngdeg": -3.164488, - "truebearingdeg": 204, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423173000, - "latdeg": 51.458526899999995, - "lngdeg": -3.164545, - "truebearingdeg": 211, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423175000, - "latdeg": 51.458447, - "lngdeg": -3.1646199999999998, - "truebearingdeg": 210, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423177000, - "latdeg": 51.458357899999996, - "lngdeg": -3.1647, - "truebearingdeg": 208, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423179000, - "latdeg": 51.458262999999995, - "lngdeg": -3.164783, - "truebearingdeg": 210, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423181000, - "latdeg": 51.458165, - "lngdeg": -3.164872, - "truebearingdeg": 208, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423183000, - "latdeg": 51.458065, - "lngdeg": -3.164965, - "truebearingdeg": 211, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423185000, - "latdeg": 51.457958, - "lngdeg": -3.165072, - "truebearingdeg": 211, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423187000, - "latdeg": 51.457857999999995, - "lngdeg": -3.1651719999999997, - "truebearingdeg": 209, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423189000, - "latdeg": 51.457763, - "lngdeg": -3.1652549999999997, - "truebearingdeg": 210, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423191000, - "latdeg": 51.457671999999995, - "lngdeg": -3.1653469999999997, - "truebearingdeg": 211, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423193000, - "latdeg": 51.457577, - "lngdeg": -3.165428, - "truebearingdeg": 206, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423195000, - "latdeg": 51.457482, - "lngdeg": -3.165507, - "truebearingdeg": 208, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423197000, - "latdeg": 51.457389899999995, - "lngdeg": -3.1656, - "truebearingdeg": 211, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423199000, - "latdeg": 51.457297999999994, - "lngdeg": -3.1656969999999998, - "truebearingdeg": 216, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423201000, - "latdeg": 51.457218, - "lngdeg": -3.1658169999999997, - "truebearingdeg": 220, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423203000, - "latdeg": 51.45713, - "lngdeg": -3.16593, - "truebearingdeg": 216, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423206000, - "latdeg": 51.457052999999995, - "lngdeg": -3.16602, - "truebearingdeg": 212, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423214000, - "latdeg": 51.456877999999996, - "lngdeg": -3.1664979, - "truebearingdeg": 314, - "knotspeed": 3.239740820734341, - "tack": "PORT" - }, - { - "timepoint": 1346423216000, - "latdeg": 51.456902, - "lngdeg": -3.166508, - "truebearingdeg": 338, - "knotspeed": 2.699784017278618, - "tack": "PORT" - }, - { - "timepoint": 1346423218000, - "latdeg": 51.456931999999995, - "lngdeg": -3.166502, - "truebearingdeg": 359, - "knotspeed": 2.699784017278618, - "tack": "PORT" - }, - { - "timepoint": 1346423220000, - "latdeg": 51.456973, - "lngdeg": -3.166505, - "truebearingdeg": 354, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346423222000, - "latdeg": 51.457035, - "lngdeg": -3.1665169, - "truebearingdeg": 356, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423224000, - "latdeg": 51.457127, - "lngdeg": -3.166508, - "truebearingdeg": 9, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423226000, - "latdeg": 51.457252999999994, - "lngdeg": -3.1664649999999996, - "truebearingdeg": 17, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423228000, - "latdeg": 51.457381999999996, - "lngdeg": -3.166362, - "truebearingdeg": 27, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423230000, - "latdeg": 51.45755, - "lngdeg": -3.166207, - "truebearingdeg": 32, - "knotspeed": 21.05831533477322, - "tack": "PORT" - }, - { - "timepoint": 1346423232000, - "latdeg": 51.45773, - "lngdeg": -3.166038, - "truebearingdeg": 28, - "knotspeed": 22.138228941684666, - "tack": "PORT" - }, - { - "timepoint": 1346423234000, - "latdeg": 51.457904899999996, - "lngdeg": -3.1659129999999998, - "truebearingdeg": 24, - "knotspeed": 22.138228941684666, - "tack": "PORT" - }, - { - "timepoint": 1346423236000, - "latdeg": 51.458092, - "lngdeg": -3.165787, - "truebearingdeg": 20, - "knotspeed": 20.518358531317496, - "tack": "PORT" - }, - { - "timepoint": 1346423238000, - "latdeg": 51.458281899999996, - "lngdeg": -3.1656549999999997, - "truebearingdeg": 20, - "knotspeed": 19.43844492440605, - "tack": "PORT" - }, - { - "timepoint": 1346423240000, - "latdeg": 51.458458, - "lngdeg": -3.165562, - "truebearingdeg": 18, - "knotspeed": 18.898488120950326, - "tack": "PORT" - }, - { - "timepoint": 1346423242000, - "latdeg": 51.458642, - "lngdeg": -3.1654619999999998, - "truebearingdeg": 19, - "knotspeed": 20.518358531317496, - "tack": "PORT" - }, - { - "timepoint": 1346423244000, - "latdeg": 51.458839999999995, - "lngdeg": -3.1653629999999997, - "truebearingdeg": 15, - "knotspeed": 21.598272138228943, - "tack": "PORT" - }, - { - "timepoint": 1346423246000, - "latdeg": 51.459022999999995, - "lngdeg": -3.165278, - "truebearingdeg": 12, - "knotspeed": 21.05831533477322, - "tack": "PORT" - }, - { - "timepoint": 1346423248000, - "latdeg": 51.459232, - "lngdeg": -3.165215, - "truebearingdeg": 9, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423250000, - "latdeg": 51.45937, - "lngdeg": -3.165175, - "truebearingdeg": 10, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423252000, - "latdeg": 51.4595, - "lngdeg": -3.165133, - "truebearingdeg": 12, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423254000, - "latdeg": 51.459647999999994, - "lngdeg": -3.1650799999999997, - "truebearingdeg": 13, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423256000, - "latdeg": 51.459751999999995, - "lngdeg": -3.1650199999999997, - "truebearingdeg": 17, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423258000, - "latdeg": 51.459938, - "lngdeg": -3.1649719999999997, - "truebearingdeg": 15, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423260000, - "latdeg": 51.459976999999995, - "lngdeg": -3.1649149999999997, - "truebearingdeg": 18, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423262000, - "latdeg": 51.46006, - "lngdeg": -3.1648479999999997, - "truebearingdeg": 16, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423264000, - "latdeg": 51.460175, - "lngdeg": -3.164792, - "truebearingdeg": 15, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423266000, - "latdeg": 51.460299899999995, - "lngdeg": -3.1647399999999997, - "truebearingdeg": 15, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423268000, - "latdeg": 51.460426999999996, - "lngdeg": -3.1646899, - "truebearingdeg": 11, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423271000, - "latdeg": 51.460547999999996, - "lngdeg": -3.1646669999999997, - "truebearingdeg": 2, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423279000, - "latdeg": 51.460941999999996, - "lngdeg": -3.16458, - "truebearingdeg": 14, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423281000, - "latdeg": 51.460997899999995, - "lngdeg": -3.1645619999999997, - "truebearingdeg": 10, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423283000, - "latdeg": 51.461078, - "lngdeg": -3.1645429999999997, - "truebearingdeg": 10, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423285000, - "latdeg": 51.461152, - "lngdeg": -3.164523, - "truebearingdeg": 9, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423287000, - "latdeg": 51.46122, - "lngdeg": -3.1645, - "truebearingdeg": 11, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423289000, - "latdeg": 51.461287999999996, - "lngdeg": -3.164482, - "truebearingdeg": 5, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423291000, - "latdeg": 51.461349999999996, - "lngdeg": -3.1644729999999996, - "truebearingdeg": 5, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423293000, - "latdeg": 51.461422, - "lngdeg": -3.1644449999999997, - "truebearingdeg": 14, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423295000, - "latdeg": 51.461507, - "lngdeg": -3.164408, - "truebearingdeg": 12, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423297000, - "latdeg": 51.461616899999996, - "lngdeg": -3.164363, - "truebearingdeg": 15, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423299000, - "latdeg": 51.461732, - "lngdeg": -3.164282, - "truebearingdeg": 33, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423301000, - "latdeg": 51.46183, - "lngdeg": -3.164125, - "truebearingdeg": 50, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423303000, - "latdeg": 51.461895, - "lngdeg": -3.163928, - "truebearingdeg": 77, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423305000, - "latdeg": 51.46188, - "lngdeg": -3.163777, - "truebearingdeg": 116, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423307000, - "latdeg": 51.461842, - "lngdeg": -3.163668, - "truebearingdeg": 134, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423309000, - "latdeg": 51.461777, - "lngdeg": -3.1636319999999998, - "truebearingdeg": 168, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423311000, - "latdeg": 51.461712, - "lngdeg": -3.1636279999999997, - "truebearingdeg": 185, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423313000, - "latdeg": 51.461597, - "lngdeg": -3.163655, - "truebearingdeg": 189, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423315000, - "latdeg": 51.461475, - "lngdeg": -3.16369, - "truebearingdeg": 191, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423317000, - "latdeg": 51.461352999999995, - "lngdeg": -3.163735, - "truebearingdeg": 188, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423319000, - "latdeg": 51.461234999999995, - "lngdeg": -3.1637649999999997, - "truebearingdeg": 187, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423321000, - "latdeg": 51.461116999999994, - "lngdeg": -3.1637869999999997, - "truebearingdeg": 184, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423323000, - "latdeg": 51.461002, - "lngdeg": -3.163802, - "truebearingdeg": 184, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423325000, - "latdeg": 51.460895, - "lngdeg": -3.1638219999999997, - "truebearingdeg": 188, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423327000, - "latdeg": 51.460803, - "lngdeg": -3.1638379999999997, - "truebearingdeg": 185, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423329000, - "latdeg": 51.460727999999996, - "lngdeg": -3.1638379999999997, - "truebearingdeg": 175, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423331000, - "latdeg": 51.460643, - "lngdeg": -3.163842, - "truebearingdeg": 177, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423333000, - "latdeg": 51.460563, - "lngdeg": -3.16382, - "truebearingdeg": 174, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423336000, - "latdeg": 51.460479899999996, - "lngdeg": -3.163805, - "truebearingdeg": 177, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423344000, - "latdeg": 51.459992, - "lngdeg": -3.163722, - "truebearingdeg": 174, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423346000, - "latdeg": 51.459849999999996, - "lngdeg": -3.163668, - "truebearingdeg": 162, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423348000, - "latdeg": 51.459728, - "lngdeg": -3.163557, - "truebearingdeg": 141, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423350000, - "latdeg": 51.459621999999996, - "lngdeg": -3.1633899999999997, - "truebearingdeg": 130, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423352000, - "latdeg": 51.459587, - "lngdeg": -3.163173, - "truebearingdeg": 93, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423354000, - "latdeg": 51.45961, - "lngdeg": -3.1630019999999996, - "truebearingdeg": 61, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423356000, - "latdeg": 51.459663, - "lngdeg": -3.162927, - "truebearingdeg": 31, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423358000, - "latdeg": 51.45972, - "lngdeg": -3.1629069999999997, - "truebearingdeg": 1, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346423360000, - "latdeg": 51.45979, - "lngdeg": -3.1629229999999997, - "truebearingdeg": 349, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423362000, - "latdeg": 51.459897, - "lngdeg": -3.162963, - "truebearingdeg": 343, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423364000, - "latdeg": 51.460018, - "lngdeg": -3.163027, - "truebearingdeg": 344, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423366000, - "latdeg": 51.460152, - "lngdeg": -3.163077, - "truebearingdeg": 346, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423368000, - "latdeg": 51.460299899999995, - "lngdeg": -3.1631419999999997, - "truebearingdeg": 342, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423370000, - "latdeg": 51.46046, - "lngdeg": -3.163207, - "truebearingdeg": 345, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423372000, - "latdeg": 51.460622, - "lngdeg": -3.1632719999999996, - "truebearingdeg": 344, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423374000, - "latdeg": 51.460781999999995, - "lngdeg": -3.16335, - "truebearingdeg": 340, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423376000, - "latdeg": 51.460907, - "lngdeg": -3.1634319, - "truebearingdeg": 337, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423378000, - "latdeg": 51.461009999999995, - "lngdeg": -3.1634949999999997, - "truebearingdeg": 339, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423380000, - "latdeg": 51.46109, - "lngdeg": -3.163537, - "truebearingdeg": 342, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423382000, - "latdeg": 51.461155, - "lngdeg": -3.163567, - "truebearingdeg": 343, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423384000, - "latdeg": 51.46122, - "lngdeg": -3.1635999999999997, - "truebearingdeg": 337, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423386000, - "latdeg": 51.461279999999995, - "lngdeg": -3.163643, - "truebearingdeg": 335, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423388000, - "latdeg": 51.461349999999996, - "lngdeg": -3.1636979999999997, - "truebearingdeg": 321, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423390000, - "latdeg": 51.461397999999996, - "lngdeg": -3.163792, - "truebearingdeg": 299, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423392000, - "latdeg": 51.46141, - "lngdeg": -3.163888, - "truebearingdeg": 274, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346423394000, - "latdeg": 51.461406999999994, - "lngdeg": -3.163948, - "truebearingdeg": 260, - "knotspeed": 3.239740820734341, - "tack": "PORT" - }, - { - "timepoint": 1346423396000, - "latdeg": 51.461397999999996, - "lngdeg": -3.1639969999999997, - "truebearingdeg": 258, - "knotspeed": 2.699784017278618, - "tack": "PORT" - }, - { - "timepoint": 1346423401000, - "latdeg": 51.46138, - "lngdeg": -3.1640599999999997, - "truebearingdeg": 236, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423409000, - "latdeg": 51.461341999999995, - "lngdeg": -3.164152, - "truebearingdeg": 237, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423413000, - "latdeg": 51.461315, - "lngdeg": -3.164192, - "truebearingdeg": 218, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423415000, - "latdeg": 51.461296999999995, - "lngdeg": -3.164212, - "truebearingdeg": 217, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423417000, - "latdeg": 51.461262, - "lngdeg": -3.164243, - "truebearingdeg": 204, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423419000, - "latdeg": 51.461217, - "lngdeg": -3.1642569999999997, - "truebearingdeg": 176, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423421000, - "latdeg": 51.461155, - "lngdeg": -3.1642479999999997, - "truebearingdeg": 165, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423423000, - "latdeg": 51.46109, - "lngdeg": -3.1641999999999997, - "truebearingdeg": 145, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423425000, - "latdeg": 51.461037, - "lngdeg": -3.1641329999999996, - "truebearingdeg": 139, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423427000, - "latdeg": 51.460992, - "lngdeg": -3.1640569999999997, - "truebearingdeg": 131, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423429000, - "latdeg": 51.460944999999995, - "lngdeg": -3.16398, - "truebearingdeg": 134, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423431000, - "latdeg": 51.460895, - "lngdeg": -3.163903, - "truebearingdeg": 135, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423433000, - "latdeg": 51.460868, - "lngdeg": -3.1637969999999997, - "truebearingdeg": 123, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423435000, - "latdeg": 51.460826999999995, - "lngdeg": -3.1636849999999996, - "truebearingdeg": 130, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423437000, - "latdeg": 51.460772999999996, - "lngdeg": -3.163592, - "truebearingdeg": 133, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423439000, - "latdeg": 51.460719999999995, - "lngdeg": -3.163502, - "truebearingdeg": 131, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423441000, - "latdeg": 51.460667, - "lngdeg": -3.1634219999999997, - "truebearingdeg": 135, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423443000, - "latdeg": 51.460622, - "lngdeg": -3.163357, - "truebearingdeg": 134, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423445000, - "latdeg": 51.460567, - "lngdeg": -3.1632919999999998, - "truebearingdeg": 143, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423447000, - "latdeg": 51.460513, - "lngdeg": -3.1632499999999997, - "truebearingdeg": 160, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423449000, - "latdeg": 51.460477, - "lngdeg": -3.1632469999999997, - "truebearingdeg": 188, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423451000, - "latdeg": 51.460415, - "lngdeg": -3.1632879999999997, - "truebearingdeg": 221, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423453000, - "latdeg": 51.460384999999995, - "lngdeg": -3.163367, - "truebearingdeg": 235, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423455000, - "latdeg": 51.460367999999995, - "lngdeg": -3.1634219999999997, - "truebearingdeg": 247, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423457000, - "latdeg": 51.46035, - "lngdeg": -3.163478, - "truebearingdeg": 252, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423459000, - "latdeg": 51.46035, - "lngdeg": -3.1635049, - "truebearingdeg": 261, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423461000, - "latdeg": 51.46035, - "lngdeg": -3.163535, - "truebearingdeg": 261, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423463000, - "latdeg": 51.46035, - "lngdeg": -3.1635619999999998, - "truebearingdeg": 262, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423466000, - "latdeg": 51.460353, - "lngdeg": -3.163583, - "truebearingdeg": 264, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423474000, - "latdeg": 51.460364999999996, - "lngdeg": -3.163617, - "truebearingdeg": 267, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423476000, - "latdeg": 51.460364999999996, - "lngdeg": -3.1636219999999997, - "truebearingdeg": 267, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423478000, - "latdeg": 51.460367999999995, - "lngdeg": -3.1636249999999997, - "truebearingdeg": 272, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423480000, - "latdeg": 51.460367999999995, - "lngdeg": -3.1636279999999997, - "truebearingdeg": 263, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423482000, - "latdeg": 51.460367999999995, - "lngdeg": -3.16363, - "truebearingdeg": 245, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423484000, - "latdeg": 51.460367999999995, - "lngdeg": -3.163627, - "truebearingdeg": 245, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423486000, - "latdeg": 51.460367999999995, - "lngdeg": -3.163618, - "truebearingdeg": 246, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423488000, - "latdeg": 51.460367999999995, - "lngdeg": -3.163607, - "truebearingdeg": 246, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423490000, - "latdeg": 51.460367999999995, - "lngdeg": -3.1635969999999998, - "truebearingdeg": 226, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423492000, - "latdeg": 51.460367999999995, - "lngdeg": -3.163585, - "truebearingdeg": 208, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423494000, - "latdeg": 51.460373, - "lngdeg": -3.16356, - "truebearingdeg": 191, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423496000, - "latdeg": 51.460377, - "lngdeg": -3.163537, - "truebearingdeg": 177, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423498000, - "latdeg": 51.460384999999995, - "lngdeg": -3.16351, - "truebearingdeg": 165, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423500000, - "latdeg": 51.460387999999995, - "lngdeg": -3.163487, - "truebearingdeg": 66, - "knotspeed": 1.6198704103671706, - "tack": "PORT" - }, - { - "timepoint": 1346423502000, - "latdeg": 51.460384999999995, - "lngdeg": -3.1634699, - "truebearingdeg": 66, - "knotspeed": 1.079913606911447, - "tack": "PORT" - }, - { - "timepoint": 1346423504000, - "latdeg": 51.460387999999995, - "lngdeg": -3.163462, - "truebearingdeg": 68, - "knotspeed": 0.5399568034557235, - "tack": "PORT" - }, - { - "timepoint": 1346423506000, - "latdeg": 51.460387999999995, - "lngdeg": -3.1634499999999997, - "truebearingdeg": 68, - "knotspeed": 1.079913606911447, - "tack": "PORT" - }, - { - "timepoint": 1346423510000, - "latdeg": 51.460395, - "lngdeg": -3.1634249999999997, - "truebearingdeg": 72, - "knotspeed": 0.5399568034557235, - "tack": "PORT" - }, - { - "timepoint": 1346423512000, - "latdeg": 51.4604, - "lngdeg": -3.1634149999999996, - "truebearingdeg": 74, - "knotspeed": 0.5399568034557235, - "tack": "PORT" - }, - { - "timepoint": 1346423514000, - "latdeg": 51.4604, - "lngdeg": -3.163405, - "truebearingdeg": 76, - "knotspeed": 0, - "tack": "PORT" - }, - { - "timepoint": 1346423516000, - "latdeg": 51.4604, - "lngdeg": -3.163402, - "truebearingdeg": 84, - "knotspeed": 0, - "tack": "PORT" - }, - { - "timepoint": 1346423518000, - "latdeg": 51.4604, - "lngdeg": -3.163392, - "truebearingdeg": 90, - "knotspeed": 0, - "tack": "PORT" - }, - { - "timepoint": 1346423520000, - "latdeg": 51.460395, - "lngdeg": -3.163388, - "truebearingdeg": 91, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423522000, - "latdeg": 51.460392, - "lngdeg": -3.163383, - "truebearingdeg": 96, - "knotspeed": 0, - "tack": "PORT" - }, - { - "timepoint": 1346423524000, - "latdeg": 51.460392, - "lngdeg": -3.1633799999999996, - "truebearingdeg": 97, - "knotspeed": 0.5399568034557235, - "tack": "PORT" - }, - { - "timepoint": 1346423526000, - "latdeg": 51.460387999999995, - "lngdeg": -3.163367, - "truebearingdeg": 97, - "knotspeed": 0.5399568034557235, - "tack": "PORT" - }, - { - "timepoint": 1346423528000, - "latdeg": 51.460387999999995, - "lngdeg": -3.1633549999999997, - "truebearingdeg": 96, - "knotspeed": 0.5399568034557235, - "tack": "PORT" - }, - { - "timepoint": 1346423531000, - "latdeg": 51.460392, - "lngdeg": -3.163337, - "truebearingdeg": 76, - "knotspeed": 1.079913606911447, - "tack": "PORT" - }, - { - "timepoint": 1346423536000, - "latdeg": 51.460377, - "lngdeg": -3.163325, - "truebearingdeg": 87, - "knotspeed": 0, - "tack": "PORT" - }, - { - "timepoint": 1346423538000, - "latdeg": 51.460377, - "lngdeg": -3.163325, - "truebearingdeg": 84, - "knotspeed": 0, - "tack": "PORT" - }, - { - "timepoint": 1346423540000, - "latdeg": 51.460377, - "lngdeg": -3.163325, - "truebearingdeg": 92, - "knotspeed": 0, - "tack": "PORT" - }, - { - "timepoint": 1346423548000, - "latdeg": 51.460358, - "lngdeg": -3.163337, - "truebearingdeg": 123, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423550000, - "latdeg": 51.46035, - "lngdeg": -3.163345, - "truebearingdeg": 133, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423552000, - "latdeg": 51.460342, - "lngdeg": -3.163363, - "truebearingdeg": 145, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423554000, - "latdeg": 51.460338, - "lngdeg": -3.1633769999999997, - "truebearingdeg": 157, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423556000, - "latdeg": 51.460335, - "lngdeg": -3.163388, - "truebearingdeg": 169, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423558000, - "latdeg": 51.460335, - "lngdeg": -3.1633999999999998, - "truebearingdeg": 179, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423560000, - "latdeg": 51.460332, - "lngdeg": -3.16341, - "truebearingdeg": 188, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423562000, - "latdeg": 51.460332, - "lngdeg": -3.1634149999999996, - "truebearingdeg": 198, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423564000, - "latdeg": 51.460332, - "lngdeg": -3.163417, - "truebearingdeg": 209, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423566000, - "latdeg": 51.460335, - "lngdeg": -3.163418, - "truebearingdeg": 223, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423572000, - "latdeg": 51.46035, - "lngdeg": -3.1633969, - "truebearingdeg": 279, - "knotspeed": 0.5399568034557235, - "tack": "PORT" - } - ] - }, - { - "name": "SAP Extreme Sailing Team", - "track": [ - { - "timepoint": 1346422553000, - "latdeg": 51.459652, - "lngdeg": -3.1628149999999997, - "truebearingdeg": 216, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422555000, - "latdeg": 51.45961, - "lngdeg": -3.162918, - "truebearingdeg": 255, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422557000, - "latdeg": 51.45961, - "lngdeg": -3.162997, - "truebearingdeg": 280, - "knotspeed": 4.319654427645788, - "tack": "PORT" - }, - { - "timepoint": 1346422559000, - "latdeg": 51.459618, - "lngdeg": -3.163033, - "truebearingdeg": 309, - "knotspeed": 2.159827213822894, - "tack": "PORT" - }, - { - "timepoint": 1346422561000, - "latdeg": 51.459637, - "lngdeg": -3.1630529999999997, - "truebearingdeg": 319, - "knotspeed": 2.159827213822894, - "tack": "PORT" - }, - { - "timepoint": 1346422563000, - "latdeg": 51.459655, - "lngdeg": -3.16308, - "truebearingdeg": 313, - "knotspeed": 2.699784017278618, - "tack": "PORT" - }, - { - "timepoint": 1346422565000, - "latdeg": 51.459677899999996, - "lngdeg": -3.1631229999999997, - "truebearingdeg": 306, - "knotspeed": 3.7796976241900646, - "tack": "PORT" - }, - { - "timepoint": 1346422567000, - "latdeg": 51.459702, - "lngdeg": -3.163183, - "truebearingdeg": 302, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346422569000, - "latdeg": 51.459731999999995, - "lngdeg": -3.163242, - "truebearingdeg": 320, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346422571000, - "latdeg": 51.45979, - "lngdeg": -3.1632979999999997, - "truebearingdeg": 335, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346422574000, - "latdeg": 51.459872999999995, - "lngdeg": -3.16334, - "truebearingdeg": 349, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422582000, - "latdeg": 51.460308, - "lngdeg": -3.16303, - "truebearingdeg": 49, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422584000, - "latdeg": 51.460367999999995, - "lngdeg": -3.1629229999999997, - "truebearingdeg": 45, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422586000, - "latdeg": 51.460426999999996, - "lngdeg": -3.162832, - "truebearingdeg": 41, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422588000, - "latdeg": 51.460487, - "lngdeg": -3.1627419999999997, - "truebearingdeg": 40, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422590000, - "latdeg": 51.460547999999996, - "lngdeg": -3.1626719999999997, - "truebearingdeg": 26, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422592000, - "latdeg": 51.460628, - "lngdeg": -3.1626249, - "truebearingdeg": 21, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422594000, - "latdeg": 51.460712, - "lngdeg": -3.1625769999999997, - "truebearingdeg": 15, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422596000, - "latdeg": 51.460814899999995, - "lngdeg": -3.162563, - "truebearingdeg": 358, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422598000, - "latdeg": 51.460927, - "lngdeg": -3.162578, - "truebearingdeg": 349, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346422600000, - "latdeg": 51.461045, - "lngdeg": -3.1626369999999997, - "truebearingdeg": 338, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346422602000, - "latdeg": 51.461143, - "lngdeg": -3.1627419999999997, - "truebearingdeg": 318, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422604000, - "latdeg": 51.461222899999996, - "lngdeg": -3.16283, - "truebearingdeg": 323, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422606000, - "latdeg": 51.461296999999995, - "lngdeg": -3.162915, - "truebearingdeg": 326, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346422608000, - "latdeg": 51.461357, - "lngdeg": -3.162973, - "truebearingdeg": 322, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346422610000, - "latdeg": 51.461406999999994, - "lngdeg": -3.163032, - "truebearingdeg": 325, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346422612000, - "latdeg": 51.461445, - "lngdeg": -3.1630949999999998, - "truebearingdeg": 304, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346422614000, - "latdeg": 51.461451999999994, - "lngdeg": -3.16316, - "truebearingdeg": 263, - "knotspeed": 3.7796976241900646, - "tack": "PORT" - }, - { - "timepoint": 1346422616000, - "latdeg": 51.461445, - "lngdeg": -3.1631999, - "truebearingdeg": 246, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422618000, - "latdeg": 51.461433, - "lngdeg": -3.1632249999999997, - "truebearingdeg": 245, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422620000, - "latdeg": 51.461414999999995, - "lngdeg": -3.163237, - "truebearingdeg": 240, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422622000, - "latdeg": 51.461391899999995, - "lngdeg": -3.163252, - "truebearingdeg": 209, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422624000, - "latdeg": 51.46138, - "lngdeg": -3.163287, - "truebearingdeg": 238, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422626000, - "latdeg": 51.461372, - "lngdeg": -3.163322, - "truebearingdeg": 252, - "knotspeed": 2.159827213822894, - "tack": "PORT" - }, - { - "timepoint": 1346422628000, - "latdeg": 51.461368, - "lngdeg": -3.1633549999999997, - "truebearingdeg": 270, - "knotspeed": 1.6198704103671706, - "tack": "PORT" - }, - { - "timepoint": 1346422632000, - "latdeg": 51.461368, - "lngdeg": -3.163382, - "truebearingdeg": 265, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422634000, - "latdeg": 51.461365, - "lngdeg": -3.163383, - "truebearingdeg": 260, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422636000, - "latdeg": 51.461362, - "lngdeg": -3.1633899999999997, - "truebearingdeg": 254, - "knotspeed": 1.079913606911447, - "tack": "PORT" - }, - { - "timepoint": 1346422639000, - "latdeg": 51.461365, - "lngdeg": -3.163402, - "truebearingdeg": 251, - "knotspeed": 1.079913606911447, - "tack": "PORT" - }, - { - "timepoint": 1346422647000, - "latdeg": 51.461323, - "lngdeg": -3.163502, - "truebearingdeg": 243, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422649000, - "latdeg": 51.461307999999995, - "lngdeg": -3.1635199999999997, - "truebearingdeg": 209, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422651000, - "latdeg": 51.461296999999995, - "lngdeg": -3.163532, - "truebearingdeg": 211, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422653000, - "latdeg": 51.461292, - "lngdeg": -3.163535, - "truebearingdeg": 209, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422659000, - "latdeg": 51.46127, - "lngdeg": -3.163547, - "truebearingdeg": 204, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422661000, - "latdeg": 51.461262, - "lngdeg": -3.163545, - "truebearingdeg": 201, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422663000, - "latdeg": 51.461247, - "lngdeg": -3.16355, - "truebearingdeg": 199, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422665000, - "latdeg": 51.461222899999996, - "lngdeg": -3.163553, - "truebearingdeg": 178, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422667000, - "latdeg": 51.461192999999994, - "lngdeg": -3.163553, - "truebearingdeg": 187, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422669000, - "latdeg": 51.461163, - "lngdeg": -3.16358, - "truebearingdeg": 226, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422671000, - "latdeg": 51.461146899999996, - "lngdeg": -3.163608, - "truebearingdeg": 240, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422673000, - "latdeg": 51.461132, - "lngdeg": -3.16364, - "truebearingdeg": 214, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422675000, - "latdeg": 51.461116999999994, - "lngdeg": -3.1636349999999998, - "truebearingdeg": 211, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422679000, - "latdeg": 51.461078, - "lngdeg": -3.163643, - "truebearingdeg": 177, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422681000, - "latdeg": 51.461059999999996, - "lngdeg": -3.163645, - "truebearingdeg": 185, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422683000, - "latdeg": 51.461028, - "lngdeg": -3.163662, - "truebearingdeg": 204, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422685000, - "latdeg": 51.460997899999995, - "lngdeg": -3.1636819999999997, - "truebearingdeg": 193, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422687000, - "latdeg": 51.460968, - "lngdeg": -3.163678, - "truebearingdeg": 189, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422689000, - "latdeg": 51.46093, - "lngdeg": -3.1636829, - "truebearingdeg": 193, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422691000, - "latdeg": 51.460882999999995, - "lngdeg": -3.1636919999999997, - "truebearingdeg": 192, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422693000, - "latdeg": 51.46082, - "lngdeg": -3.163715, - "truebearingdeg": 195, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422695000, - "latdeg": 51.460747, - "lngdeg": -3.163753, - "truebearingdeg": 196, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422697000, - "latdeg": 51.460667, - "lngdeg": -3.1638099, - "truebearingdeg": 204, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422699000, - "latdeg": 51.460575, - "lngdeg": -3.163877, - "truebearingdeg": 206, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422701000, - "latdeg": 51.460487, - "lngdeg": -3.163955, - "truebearingdeg": 206, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422704000, - "latdeg": 51.4604, - "lngdeg": -3.164027, - "truebearingdeg": 204, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422712000, - "latdeg": 51.460009899999996, - "lngdeg": -3.1643779999999997, - "truebearingdeg": 207, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422714000, - "latdeg": 51.45993, - "lngdeg": -3.1644419999999998, - "truebearingdeg": 208, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422716000, - "latdeg": 51.459846999999996, - "lngdeg": -3.16453, - "truebearingdeg": 213, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422718000, - "latdeg": 51.459742999999996, - "lngdeg": -3.1646199999999998, - "truebearingdeg": 207, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422720000, - "latdeg": 51.459647999999994, - "lngdeg": -3.1647249, - "truebearingdeg": 206, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422722000, - "latdeg": 51.459509999999995, - "lngdeg": -3.1648479999999997, - "truebearingdeg": 207, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422724000, - "latdeg": 51.459357999999995, - "lngdeg": -3.164965, - "truebearingdeg": 207, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422726000, - "latdeg": 51.459213, - "lngdeg": -3.1650799999999997, - "truebearingdeg": 208, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422728000, - "latdeg": 51.459083, - "lngdeg": -3.165177, - "truebearingdeg": 203, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422730000, - "latdeg": 51.458962, - "lngdeg": -3.1652519999999997, - "truebearingdeg": 199, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422732000, - "latdeg": 51.458832, - "lngdeg": -3.165323, - "truebearingdeg": 199, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422734000, - "latdeg": 51.45868, - "lngdeg": -3.1654269999999998, - "truebearingdeg": 205, - "knotspeed": 16.73866090712743, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422736000, - "latdeg": 51.458552999999995, - "lngdeg": -3.1655279999999997, - "truebearingdeg": 203, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422738000, - "latdeg": 51.458417, - "lngdeg": -3.1656079999999998, - "truebearingdeg": 203, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422740000, - "latdeg": 51.458275, - "lngdeg": -3.1656929999999996, - "truebearingdeg": 204, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422742000, - "latdeg": 51.45816, - "lngdeg": -3.1657949999999997, - "truebearingdeg": 203, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422744000, - "latdeg": 51.458053, - "lngdeg": -3.1658749999999998, - "truebearingdeg": 203, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422746000, - "latdeg": 51.457947, - "lngdeg": -3.165955, - "truebearingdeg": 203, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422748000, - "latdeg": 51.457851999999995, - "lngdeg": -3.166032, - "truebearingdeg": 208, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422750000, - "latdeg": 51.457757, - "lngdeg": -3.1661069, - "truebearingdeg": 208, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422752000, - "latdeg": 51.457668, - "lngdeg": -3.166178, - "truebearingdeg": 206, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422754000, - "latdeg": 51.45758, - "lngdeg": -3.166252, - "truebearingdeg": 207, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422756000, - "latdeg": 51.457496899999995, - "lngdeg": -3.166317, - "truebearingdeg": 203, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422758000, - "latdeg": 51.457404999999994, - "lngdeg": -3.1663769999999998, - "truebearingdeg": 205, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422760000, - "latdeg": 51.457316999999996, - "lngdeg": -3.166433, - "truebearingdeg": 204, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422762000, - "latdeg": 51.457218, - "lngdeg": -3.1665229999999998, - "truebearingdeg": 208, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422764000, - "latdeg": 51.457088, - "lngdeg": -3.166617, - "truebearingdeg": 206, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422766000, - "latdeg": 51.45697, - "lngdeg": -3.1667169, - "truebearingdeg": 206, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422769000, - "latdeg": 51.456863, - "lngdeg": -3.1668119999999997, - "truebearingdeg": 208, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422777000, - "latdeg": 51.456413, - "lngdeg": -3.167202, - "truebearingdeg": 202, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422779000, - "latdeg": 51.456322, - "lngdeg": -3.167268, - "truebearingdeg": 201, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422781000, - "latdeg": 51.45623, - "lngdeg": -3.167332, - "truebearingdeg": 201, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422783000, - "latdeg": 51.456134999999996, - "lngdeg": -3.167383, - "truebearingdeg": 201, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422785000, - "latdeg": 51.456035, - "lngdeg": -3.1674569999999997, - "truebearingdeg": 206, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422787000, - "latdeg": 51.455943, - "lngdeg": -3.1675269999999998, - "truebearingdeg": 205, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422789000, - "latdeg": 51.455844899999995, - "lngdeg": -3.167605, - "truebearingdeg": 211, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422791000, - "latdeg": 51.455752999999994, - "lngdeg": -3.167693, - "truebearingdeg": 210, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422793000, - "latdeg": 51.455647, - "lngdeg": -3.1677899999999997, - "truebearingdeg": 211, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422795000, - "latdeg": 51.455543, - "lngdeg": -3.1678829, - "truebearingdeg": 210, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422797000, - "latdeg": 51.455433, - "lngdeg": -3.1679879, - "truebearingdeg": 211, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422799000, - "latdeg": 51.455318, - "lngdeg": -3.1681, - "truebearingdeg": 211, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422801000, - "latdeg": 51.455208, - "lngdeg": -3.1681969999999997, - "truebearingdeg": 204, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422803000, - "latdeg": 51.455096999999995, - "lngdeg": -3.1682799999999998, - "truebearingdeg": 201, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422805000, - "latdeg": 51.455002, - "lngdeg": -3.168345, - "truebearingdeg": 200, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422807000, - "latdeg": 51.454895, - "lngdeg": -3.168402, - "truebearingdeg": 197, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422809000, - "latdeg": 51.454777, - "lngdeg": -3.1684549, - "truebearingdeg": 194, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422811000, - "latdeg": 51.454643, - "lngdeg": -3.1685079999999997, - "truebearingdeg": 196, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422813000, - "latdeg": 51.45451, - "lngdeg": -3.168587, - "truebearingdeg": 202, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422815000, - "latdeg": 51.45438, - "lngdeg": -3.168668, - "truebearingdeg": 202, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422817000, - "latdeg": 51.454262, - "lngdeg": -3.168745, - "truebearingdeg": 202, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422819000, - "latdeg": 51.454155, - "lngdeg": -3.168792, - "truebearingdeg": 191, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422821000, - "latdeg": 51.454054899999996, - "lngdeg": -3.1688, - "truebearingdeg": 175, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422823000, - "latdeg": 51.453956999999996, - "lngdeg": -3.1687529999999997, - "truebearingdeg": 155, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422825000, - "latdeg": 51.453877, - "lngdeg": -3.168647, - "truebearingdeg": 134, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422827000, - "latdeg": 51.4538, - "lngdeg": -3.168495, - "truebearingdeg": 123, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422829000, - "latdeg": 51.453731999999995, - "lngdeg": -3.1683269999999997, - "truebearingdeg": 127, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422831000, - "latdeg": 51.453655, - "lngdeg": -3.1681529999999998, - "truebearingdeg": 122, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422834000, - "latdeg": 51.453587, - "lngdeg": -3.1679529, - "truebearingdeg": 116, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422842000, - "latdeg": 51.453337999999995, - "lngdeg": -3.1669329999999998, - "truebearingdeg": 106, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422844000, - "latdeg": 51.4533, - "lngdeg": -3.1667199999999998, - "truebearingdeg": 106, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422846000, - "latdeg": 51.453261999999995, - "lngdeg": -3.166525, - "truebearingdeg": 107, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422848000, - "latdeg": 51.453224999999996, - "lngdeg": -3.1663349999999997, - "truebearingdeg": 109, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422850000, - "latdeg": 51.453182, - "lngdeg": -3.1661449999999998, - "truebearingdeg": 108, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422852000, - "latdeg": 51.453148, - "lngdeg": -3.165953, - "truebearingdeg": 108, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422854000, - "latdeg": 51.453122, - "lngdeg": -3.1657669999999998, - "truebearingdeg": 99, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422856000, - "latdeg": 51.453143, - "lngdeg": -3.1656049, - "truebearingdeg": 75, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422858000, - "latdeg": 51.453187, - "lngdeg": -3.165492, - "truebearingdeg": 55, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422860000, - "latdeg": 51.453243, - "lngdeg": -3.165412, - "truebearingdeg": 30, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422862000, - "latdeg": 51.453314999999996, - "lngdeg": -3.1653629999999997, - "truebearingdeg": 21, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422864000, - "latdeg": 51.453415, - "lngdeg": -3.1653029999999998, - "truebearingdeg": 20, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422866000, - "latdeg": 51.45353, - "lngdeg": -3.165235, - "truebearingdeg": 19, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346422868000, - "latdeg": 51.453637, - "lngdeg": -3.165178, - "truebearingdeg": 16, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422870000, - "latdeg": 51.453728, - "lngdeg": -3.1651279999999997, - "truebearingdeg": 15, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422872000, - "latdeg": 51.453807999999995, - "lngdeg": -3.1650929999999997, - "truebearingdeg": 15, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422874000, - "latdeg": 51.453862, - "lngdeg": -3.16504, - "truebearingdeg": 43, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346422876000, - "latdeg": 51.453891999999996, - "lngdeg": -3.164968, - "truebearingdeg": 70, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346422878000, - "latdeg": 51.453891999999996, - "lngdeg": -3.1649019999999997, - "truebearingdeg": 97, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422880000, - "latdeg": 51.453872999999994, - "lngdeg": -3.164847, - "truebearingdeg": 114, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422882000, - "latdeg": 51.453852999999995, - "lngdeg": -3.164805, - "truebearingdeg": 131, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422884000, - "latdeg": 51.453829999999996, - "lngdeg": -3.16477, - "truebearingdeg": 149, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422886000, - "latdeg": 51.453803, - "lngdeg": -3.164752, - "truebearingdeg": 155, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422888000, - "latdeg": 51.453781899999996, - "lngdeg": -3.1647249, - "truebearingdeg": 163, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422890000, - "latdeg": 51.453742999999996, - "lngdeg": -3.164717, - "truebearingdeg": 182, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422892000, - "latdeg": 51.453702, - "lngdeg": -3.1647429999999996, - "truebearingdeg": 211, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422894000, - "latdeg": 51.45364, - "lngdeg": -3.164808, - "truebearingdeg": 229, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422896000, - "latdeg": 51.453575, - "lngdeg": -3.164913, - "truebearingdeg": 233, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422899000, - "latdeg": 51.453522, - "lngdeg": -3.165013, - "truebearingdeg": 230, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422907000, - "latdeg": 51.453285, - "lngdeg": -3.165453, - "truebearingdeg": 228, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422909000, - "latdeg": 51.453227999999996, - "lngdeg": -3.165555, - "truebearingdeg": 229, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422911000, - "latdeg": 51.453167, - "lngdeg": -3.165663, - "truebearingdeg": 225, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422913000, - "latdeg": 51.453106999999996, - "lngdeg": -3.16575, - "truebearingdeg": 223, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422915000, - "latdeg": 51.453036999999995, - "lngdeg": -3.1658429999999997, - "truebearingdeg": 226, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422917000, - "latdeg": 51.452979899999995, - "lngdeg": -3.165937, - "truebearingdeg": 221, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422919000, - "latdeg": 51.452917899999996, - "lngdeg": -3.1660299999999997, - "truebearingdeg": 227, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422921000, - "latdeg": 51.45285, - "lngdeg": -3.16615, - "truebearingdeg": 226, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422923000, - "latdeg": 51.452777999999995, - "lngdeg": -3.166312, - "truebearingdeg": 238, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422925000, - "latdeg": 51.452743, - "lngdeg": -3.16647, - "truebearingdeg": 256, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422927000, - "latdeg": 51.452746999999995, - "lngdeg": -3.1666019999999997, - "truebearingdeg": 284, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422929000, - "latdeg": 51.452785, - "lngdeg": -3.1666749999999997, - "truebearingdeg": 321, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346422931000, - "latdeg": 51.452827, - "lngdeg": -3.166708, - "truebearingdeg": 337, - "knotspeed": 4.319654427645788, - "tack": "PORT" - }, - { - "timepoint": 1346422933000, - "latdeg": 51.45287, - "lngdeg": -3.166727, - "truebearingdeg": 332, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346422935000, - "latdeg": 51.452929999999995, - "lngdeg": -3.166788, - "truebearingdeg": 327, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422937000, - "latdeg": 51.453007, - "lngdeg": -3.166855, - "truebearingdeg": 329, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422939000, - "latdeg": 51.453086899999995, - "lngdeg": -3.16694, - "truebearingdeg": 323, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422941000, - "latdeg": 51.453174999999995, - "lngdeg": -3.167045, - "truebearingdeg": 320, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422943000, - "latdeg": 51.453255, - "lngdeg": -3.167153, - "truebearingdeg": 320, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422945000, - "latdeg": 51.453334999999996, - "lngdeg": -3.167268, - "truebearingdeg": 317, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422947000, - "latdeg": 51.453407, - "lngdeg": -3.1673929999999997, - "truebearingdeg": 311, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422949000, - "latdeg": 51.453483, - "lngdeg": -3.1675199999999997, - "truebearingdeg": 311, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422951000, - "latdeg": 51.453551999999995, - "lngdeg": -3.16765, - "truebearingdeg": 311, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422953000, - "latdeg": 51.453624999999995, - "lngdeg": -3.1677779999999998, - "truebearingdeg": 315, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422955000, - "latdeg": 51.453708, - "lngdeg": -3.167913, - "truebearingdeg": 321, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422957000, - "latdeg": 51.453803, - "lngdeg": -3.168018, - "truebearingdeg": 327, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422959000, - "latdeg": 51.453914999999995, - "lngdeg": -3.1681019999999998, - "truebearingdeg": 336, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422961000, - "latdeg": 51.454037, - "lngdeg": -3.168177, - "truebearingdeg": 338, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346422964000, - "latdeg": 51.454173, - "lngdeg": -3.168247, - "truebearingdeg": 340, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346422972000, - "latdeg": 51.454445, - "lngdeg": -3.168722, - "truebearingdeg": 213, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422974000, - "latdeg": 51.454415, - "lngdeg": -3.1687369999999997, - "truebearingdeg": 200, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422976000, - "latdeg": 51.454387999999994, - "lngdeg": -3.168757, - "truebearingdeg": 181, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422978000, - "latdeg": 51.454353, - "lngdeg": -3.1687599, - "truebearingdeg": 185, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422980000, - "latdeg": 51.454288, - "lngdeg": -3.168787, - "truebearingdeg": 185, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422982000, - "latdeg": 51.454197, - "lngdeg": -3.168805, - "truebearingdeg": 189, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422984000, - "latdeg": 51.45409, - "lngdeg": -3.168822, - "truebearingdeg": 176, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422986000, - "latdeg": 51.453983, - "lngdeg": -3.168765, - "truebearingdeg": 153, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422988000, - "latdeg": 51.453877, - "lngdeg": -3.1686349999999996, - "truebearingdeg": 140, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422990000, - "latdeg": 51.453773, - "lngdeg": -3.1684549, - "truebearingdeg": 130, - "knotspeed": 16.73866090712743, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422992000, - "latdeg": 51.453666999999996, - "lngdeg": -3.16825, - "truebearingdeg": 121, - "knotspeed": 17.278617710583152, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422994000, - "latdeg": 51.453587, - "lngdeg": -3.168037, - "truebearingdeg": 118, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422996000, - "latdeg": 51.453522, - "lngdeg": -3.167818, - "truebearingdeg": 118, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422998000, - "latdeg": 51.453449899999995, - "lngdeg": -3.167615, - "truebearingdeg": 122, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423000000, - "latdeg": 51.453368, - "lngdeg": -3.16744, - "truebearingdeg": 124, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423002000, - "latdeg": 51.453308, - "lngdeg": -3.167258, - "truebearingdeg": 113, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423004000, - "latdeg": 51.453272999999996, - "lngdeg": -3.167078, - "truebearingdeg": 95, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423006000, - "latdeg": 51.453297, - "lngdeg": -3.1669299, - "truebearingdeg": 69, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423008000, - "latdeg": 51.453337999999995, - "lngdeg": -3.1668369999999997, - "truebearingdeg": 49, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423010000, - "latdeg": 51.453392, - "lngdeg": -3.1667479999999997, - "truebearingdeg": 44, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423012000, - "latdeg": 51.453464999999994, - "lngdeg": -3.1666369999999997, - "truebearingdeg": 42, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423014000, - "latdeg": 51.453539899999996, - "lngdeg": -3.1665229999999998, - "truebearingdeg": 43, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423016000, - "latdeg": 51.453624999999995, - "lngdeg": -3.1663829999999997, - "truebearingdeg": 47, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423018000, - "latdeg": 51.453717, - "lngdeg": -3.1662119, - "truebearingdeg": 49, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423020000, - "latdeg": 51.453815, - "lngdeg": -3.16602, - "truebearingdeg": 49, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423022000, - "latdeg": 51.453922, - "lngdeg": -3.16582, - "truebearingdeg": 47, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423024000, - "latdeg": 51.454045, - "lngdeg": -3.1656519999999997, - "truebearingdeg": 33, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423026000, - "latdeg": 51.454181999999996, - "lngdeg": -3.165553, - "truebearingdeg": 18, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423029000, - "latdeg": 51.454322999999995, - "lngdeg": -3.1655379999999997, - "truebearingdeg": 351, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423045000, - "latdeg": 51.454322999999995, - "lngdeg": -3.1655379999999997, - "truebearingdeg": 351, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423049000, - "latdeg": 51.454415, - "lngdeg": -3.165683, - "truebearingdeg": 339, - "knotspeed": 1.079913606911447, - "tack": "PORT" - }, - { - "timepoint": 1346423051000, - "latdeg": 51.454409999999996, - "lngdeg": -3.16567, - "truebearingdeg": 143, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423053000, - "latdeg": 51.454395, - "lngdeg": -3.16565, - "truebearingdeg": 161, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423055000, - "latdeg": 51.454353, - "lngdeg": -3.1656329999999997, - "truebearingdeg": 162, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423057000, - "latdeg": 51.454282, - "lngdeg": -3.1655829, - "truebearingdeg": 149, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423059000, - "latdeg": 51.454192899999995, - "lngdeg": -3.165472, - "truebearingdeg": 133, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423061000, - "latdeg": 51.454131999999994, - "lngdeg": -3.1653219999999997, - "truebearingdeg": 113, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423063000, - "latdeg": 51.454113, - "lngdeg": -3.1651469999999997, - "truebearingdeg": 87, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423065000, - "latdeg": 51.454128, - "lngdeg": -3.165008, - "truebearingdeg": 67, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423067000, - "latdeg": 51.454173, - "lngdeg": -3.1649, - "truebearingdeg": 50, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423069000, - "latdeg": 51.454235, - "lngdeg": -3.1648129999999997, - "truebearingdeg": 35, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423071000, - "latdeg": 51.454322999999995, - "lngdeg": -3.164717, - "truebearingdeg": 35, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423073000, - "latdeg": 51.454417899999996, - "lngdeg": -3.1646069999999997, - "truebearingdeg": 30, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423075000, - "latdeg": 51.454533, - "lngdeg": -3.1645399999999997, - "truebearingdeg": 12, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423077000, - "latdeg": 51.454646999999994, - "lngdeg": -3.164555, - "truebearingdeg": 338, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423079000, - "latdeg": 51.454741999999996, - "lngdeg": -3.1646479999999997, - "truebearingdeg": 327, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423081000, - "latdeg": 51.454837999999995, - "lngdeg": -3.164745, - "truebearingdeg": 327, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423083000, - "latdeg": 51.454932899999996, - "lngdeg": -3.16484, - "truebearingdeg": 326, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423085000, - "latdeg": 51.455028, - "lngdeg": -3.164942, - "truebearingdeg": 326, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423087000, - "latdeg": 51.455135, - "lngdeg": -3.165047, - "truebearingdeg": 327, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423089000, - "latdeg": 51.455253, - "lngdeg": -3.1651629999999997, - "truebearingdeg": 325, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423091000, - "latdeg": 51.455374899999995, - "lngdeg": -3.1653029999999998, - "truebearingdeg": 325, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423094000, - "latdeg": 51.455509899999996, - "lngdeg": -3.16545, - "truebearingdeg": 327, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423100000, - "latdeg": 51.455993, - "lngdeg": -3.165928, - "truebearingdeg": 330, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423102000, - "latdeg": 51.456131899999995, - "lngdeg": -3.166042, - "truebearingdeg": 335, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423104000, - "latdeg": 51.456264999999995, - "lngdeg": -3.1661379999999997, - "truebearingdeg": 332, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423107000, - "latdeg": 51.45639, - "lngdeg": -3.166235, - "truebearingdeg": 336, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423109000, - "latdeg": 51.45657, - "lngdeg": -3.1663379999999997, - "truebearingdeg": 343, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423111000, - "latdeg": 51.456691899999996, - "lngdeg": -3.166398, - "truebearingdeg": 345, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423113000, - "latdeg": 51.456821999999995, - "lngdeg": -3.166442, - "truebearingdeg": 350, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423115000, - "latdeg": 51.456958, - "lngdeg": -3.166452, - "truebearingdeg": 5, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423117000, - "latdeg": 51.457099899999996, - "lngdeg": -3.1663569999999996, - "truebearingdeg": 29, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423119000, - "latdeg": 51.457206899999996, - "lngdeg": -3.166182, - "truebearingdeg": 54, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423121000, - "latdeg": 51.457287, - "lngdeg": -3.1659919999999997, - "truebearingdeg": 56, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423123000, - "latdeg": 51.457363, - "lngdeg": -3.165812, - "truebearingdeg": 55, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423125000, - "latdeg": 51.457443, - "lngdeg": -3.165638, - "truebearingdeg": 52, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423127000, - "latdeg": 51.457527, - "lngdeg": -3.1654649999999998, - "truebearingdeg": 52, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423129000, - "latdeg": 51.457614899999996, - "lngdeg": -3.165283, - "truebearingdeg": 52, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423131000, - "latdeg": 51.457695, - "lngdeg": -3.165098, - "truebearingdeg": 55, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423133000, - "latdeg": 51.457772, - "lngdeg": -3.16491, - "truebearingdeg": 60, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423135000, - "latdeg": 51.45784, - "lngdeg": -3.164727, - "truebearingdeg": 60, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423137000, - "latdeg": 51.457896999999996, - "lngdeg": -3.164548, - "truebearingdeg": 62, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423139000, - "latdeg": 51.457955, - "lngdeg": -3.1643749999999997, - "truebearingdeg": 63, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423141000, - "latdeg": 51.458003, - "lngdeg": -3.1642029999999997, - "truebearingdeg": 64, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423143000, - "latdeg": 51.45805, - "lngdeg": -3.164033, - "truebearingdeg": 66, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423145000, - "latdeg": 51.458092, - "lngdeg": -3.163878, - "truebearingdeg": 65, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423147000, - "latdeg": 51.45813, - "lngdeg": -3.163735, - "truebearingdeg": 66, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423149000, - "latdeg": 51.458168, - "lngdeg": -3.1636029999999997, - "truebearingdeg": 61, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423151000, - "latdeg": 51.458213, - "lngdeg": -3.1634949999999997, - "truebearingdeg": 51, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423153000, - "latdeg": 51.458272, - "lngdeg": -3.16342, - "truebearingdeg": 30, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423155000, - "latdeg": 51.458337, - "lngdeg": -3.1633929999999997, - "truebearingdeg": 357, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423158000, - "latdeg": 51.458393, - "lngdeg": -3.163433, - "truebearingdeg": 326, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423172000, - "latdeg": 51.458672, - "lngdeg": -3.164152, - "truebearingdeg": 299, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423174000, - "latdeg": 51.458712999999996, - "lngdeg": -3.1642699999999997, - "truebearingdeg": 298, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423176000, - "latdeg": 51.458748, - "lngdeg": -3.164395, - "truebearingdeg": 286, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423178000, - "latdeg": 51.458751899999996, - "lngdeg": -3.16451, - "truebearingdeg": 260, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423180000, - "latdeg": 51.458728, - "lngdeg": -3.164577, - "truebearingdeg": 223, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423182000, - "latdeg": 51.458698, - "lngdeg": -3.164592, - "truebearingdeg": 199, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423184000, - "latdeg": 51.458659999999995, - "lngdeg": -3.164627, - "truebearingdeg": 204, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423186000, - "latdeg": 51.458607, - "lngdeg": -3.164663, - "truebearingdeg": 204, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423188000, - "latdeg": 51.458529999999996, - "lngdeg": -3.164732, - "truebearingdeg": 206, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423190000, - "latdeg": 51.458434999999994, - "lngdeg": -3.1648099999999997, - "truebearingdeg": 206, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423192000, - "latdeg": 51.458337, - "lngdeg": -3.164892, - "truebearingdeg": 206, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423194000, - "latdeg": 51.458236899999996, - "lngdeg": -3.16498, - "truebearingdeg": 207, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423196000, - "latdeg": 51.45813, - "lngdeg": -3.1650769999999997, - "truebearingdeg": 209, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423198000, - "latdeg": 51.458023, - "lngdeg": -3.1651719999999997, - "truebearingdeg": 207, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423200000, - "latdeg": 51.457919999999994, - "lngdeg": -3.1652619, - "truebearingdeg": 208, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423202000, - "latdeg": 51.457825, - "lngdeg": -3.1653569999999998, - "truebearingdeg": 211, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423204000, - "latdeg": 51.45773, - "lngdeg": -3.165447, - "truebearingdeg": 210, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423206000, - "latdeg": 51.457632999999994, - "lngdeg": -3.165542, - "truebearingdeg": 210, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423208000, - "latdeg": 51.457541899999995, - "lngdeg": -3.165628, - "truebearingdeg": 207, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423210000, - "latdeg": 51.45744, - "lngdeg": -3.165723, - "truebearingdeg": 207, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423212000, - "latdeg": 51.457352, - "lngdeg": -3.1658329999999997, - "truebearingdeg": 222, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423214000, - "latdeg": 51.457263, - "lngdeg": -3.165982, - "truebearingdeg": 218, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423216000, - "latdeg": 51.457167999999996, - "lngdeg": -3.166083, - "truebearingdeg": 216, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423218000, - "latdeg": 51.457073, - "lngdeg": -3.166203, - "truebearingdeg": 220, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423220000, - "latdeg": 51.45697, - "lngdeg": -3.166318, - "truebearingdeg": 212, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423223000, - "latdeg": 51.45686, - "lngdeg": -3.166458, - "truebearingdeg": 223, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423226000, - "latdeg": 51.45686, - "lngdeg": -3.166458, - "truebearingdeg": 223, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423231000, - "latdeg": 51.456952, - "lngdeg": -3.1666849999999998, - "truebearingdeg": 4, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346423233000, - "latdeg": 51.45702, - "lngdeg": -3.166667, - "truebearingdeg": 19, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423235000, - "latdeg": 51.457111999999995, - "lngdeg": -3.166595, - "truebearingdeg": 28, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423237000, - "latdeg": 51.457242, - "lngdeg": -3.166483, - "truebearingdeg": 29, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423239000, - "latdeg": 51.457381999999996, - "lngdeg": -3.16635, - "truebearingdeg": 27, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423241000, - "latdeg": 51.457541899999995, - "lngdeg": -3.166228, - "truebearingdeg": 25, - "knotspeed": 18.898488120950326, - "tack": "PORT" - }, - { - "timepoint": 1346423243000, - "latdeg": 51.45771, - "lngdeg": -3.166118, - "truebearingdeg": 20, - "knotspeed": 18.898488120950326, - "tack": "PORT" - }, - { - "timepoint": 1346423245000, - "latdeg": 51.457878, - "lngdeg": -3.1660149, - "truebearingdeg": 20, - "knotspeed": 19.43844492440605, - "tack": "PORT" - }, - { - "timepoint": 1346423247000, - "latdeg": 51.458065, - "lngdeg": -3.165908, - "truebearingdeg": 20, - "knotspeed": 21.598272138228943, - "tack": "PORT" - }, - { - "timepoint": 1346423249000, - "latdeg": 51.458262999999995, - "lngdeg": -3.1658019, - "truebearingdeg": 16, - "knotspeed": 22.67818574514039, - "tack": "PORT" - }, - { - "timepoint": 1346423251000, - "latdeg": 51.458462, - "lngdeg": -3.1657129999999998, - "truebearingdeg": 14, - "knotspeed": 21.598272138228943, - "tack": "PORT" - }, - { - "timepoint": 1346423253000, - "latdeg": 51.458659999999995, - "lngdeg": -3.165638, - "truebearingdeg": 12, - "knotspeed": 21.598272138228943, - "tack": "PORT" - }, - { - "timepoint": 1346423255000, - "latdeg": 51.458855, - "lngdeg": -3.1655669, - "truebearingdeg": 14, - "knotspeed": 20.518358531317496, - "tack": "PORT" - }, - { - "timepoint": 1346423257000, - "latdeg": 51.459041899999995, - "lngdeg": -3.165472, - "truebearingdeg": 15, - "knotspeed": 20.518358531317496, - "tack": "PORT" - }, - { - "timepoint": 1346423259000, - "latdeg": 51.459216999999995, - "lngdeg": -3.165403, - "truebearingdeg": 11, - "knotspeed": 19.43844492440605, - "tack": "PORT" - }, - { - "timepoint": 1346423261000, - "latdeg": 51.459385, - "lngdeg": -3.1653599999999997, - "truebearingdeg": 8, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423263000, - "latdeg": 51.459545, - "lngdeg": -3.1653279999999997, - "truebearingdeg": 7, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423265000, - "latdeg": 51.459682, - "lngdeg": -3.165283, - "truebearingdeg": 14, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423267000, - "latdeg": 51.459796999999995, - "lngdeg": -3.165228, - "truebearingdeg": 18, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423269000, - "latdeg": 51.459911999999996, - "lngdeg": -3.165167, - "truebearingdeg": 17, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423271000, - "latdeg": 51.460024999999995, - "lngdeg": -3.1651119999999997, - "truebearingdeg": 15, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423273000, - "latdeg": 51.460147899999996, - "lngdeg": -3.165057, - "truebearingdeg": 16, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423275000, - "latdeg": 51.460277999999995, - "lngdeg": -3.1649979999999998, - "truebearingdeg": 15, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423277000, - "latdeg": 51.460423, - "lngdeg": -3.1649369999999997, - "truebearingdeg": 16, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423279000, - "latdeg": 51.46056, - "lngdeg": -3.1648679, - "truebearingdeg": 15, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423281000, - "latdeg": 51.460682, - "lngdeg": -3.164815, - "truebearingdeg": 14, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423283000, - "latdeg": 51.460777, - "lngdeg": -3.1647749999999997, - "truebearingdeg": 12, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423285000, - "latdeg": 51.460857, - "lngdeg": -3.164742, - "truebearingdeg": 15, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423288000, - "latdeg": 51.460921899999995, - "lngdeg": -3.164707, - "truebearingdeg": 19, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423296000, - "latdeg": 51.461169999999996, - "lngdeg": -3.164548, - "truebearingdeg": 26, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423298000, - "latdeg": 51.461222899999996, - "lngdeg": -3.1645, - "truebearingdeg": 29, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423300000, - "latdeg": 51.461276999999995, - "lngdeg": -3.164447, - "truebearingdeg": 30, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423302000, - "latdeg": 51.461338, - "lngdeg": -3.16439, - "truebearingdeg": 28, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423304000, - "latdeg": 51.461406999999994, - "lngdeg": -3.164333, - "truebearingdeg": 21, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423306000, - "latdeg": 51.461487, - "lngdeg": -3.16429, - "truebearingdeg": 14, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423308000, - "latdeg": 51.461582, - "lngdeg": -3.164252, - "truebearingdeg": 15, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423310000, - "latdeg": 51.461687999999995, - "lngdeg": -3.164193, - "truebearingdeg": 25, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423312000, - "latdeg": 51.461802999999996, - "lngdeg": -3.164085, - "truebearingdeg": 35, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423314000, - "latdeg": 51.461903, - "lngdeg": -3.1639179, - "truebearingdeg": 48, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423316000, - "latdeg": 51.461971999999996, - "lngdeg": -3.1637519999999997, - "truebearingdeg": 69, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423318000, - "latdeg": 51.461977999999995, - "lngdeg": -3.163602, - "truebearingdeg": 96, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423320000, - "latdeg": 51.461945, - "lngdeg": -3.1635329999999997, - "truebearingdeg": 131, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423322000, - "latdeg": 51.461895, - "lngdeg": -3.163493, - "truebearingdeg": 154, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423324000, - "latdeg": 51.461814999999994, - "lngdeg": -3.1634729999999998, - "truebearingdeg": 173, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423326000, - "latdeg": 51.46172, - "lngdeg": -3.1634629999999997, - "truebearingdeg": 176, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423328000, - "latdeg": 51.461602, - "lngdeg": -3.1634569999999997, - "truebearingdeg": 180, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423330000, - "latdeg": 51.461467999999996, - "lngdeg": -3.163478, - "truebearingdeg": 186, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423332000, - "latdeg": 51.461315, - "lngdeg": -3.163503, - "truebearingdeg": 184, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423334000, - "latdeg": 51.461158, - "lngdeg": -3.1635229999999996, - "truebearingdeg": 180, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423336000, - "latdeg": 51.461012999999994, - "lngdeg": -3.1635199999999997, - "truebearingdeg": 180, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423338000, - "latdeg": 51.460892, - "lngdeg": -3.163507, - "truebearingdeg": 176, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423340000, - "latdeg": 51.460797, - "lngdeg": -3.163493, - "truebearingdeg": 174, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423342000, - "latdeg": 51.460712, - "lngdeg": -3.1634849999999997, - "truebearingdeg": 175, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423344000, - "latdeg": 51.460628, - "lngdeg": -3.163483, - "truebearingdeg": 176, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423346000, - "latdeg": 51.460547999999996, - "lngdeg": -3.163477, - "truebearingdeg": 175, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423348000, - "latdeg": 51.460468, - "lngdeg": -3.163468, - "truebearingdeg": 176, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423350000, - "latdeg": 51.460387999999995, - "lngdeg": -3.163448, - "truebearingdeg": 176, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423353000, - "latdeg": 51.460285, - "lngdeg": -3.1634669, - "truebearingdeg": 187, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423361000, - "latdeg": 51.459796999999995, - "lngdeg": -3.163245, - "truebearingdeg": 119, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423363000, - "latdeg": 51.459782, - "lngdeg": -3.163077, - "truebearingdeg": 86, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423365000, - "latdeg": 51.459793, - "lngdeg": -3.162957, - "truebearingdeg": 57, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423367000, - "latdeg": 51.459838, - "lngdeg": -3.1628879999999997, - "truebearingdeg": 32, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423369000, - "latdeg": 51.459908, - "lngdeg": -3.162845, - "truebearingdeg": 13, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423371000, - "latdeg": 51.459998, - "lngdeg": -3.162827, - "truebearingdeg": 3, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423373000, - "latdeg": 51.460122, - "lngdeg": -3.1628149999999997, - "truebearingdeg": 1, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423375000, - "latdeg": 51.460257999999996, - "lngdeg": -3.1628149999999997, - "truebearingdeg": 358, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423377000, - "latdeg": 51.460403, - "lngdeg": -3.16283, - "truebearingdeg": 357, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423379000, - "latdeg": 51.460547999999996, - "lngdeg": -3.1628399999999997, - "truebearingdeg": 355, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423381000, - "latdeg": 51.460702, - "lngdeg": -3.16287, - "truebearingdeg": 351, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423383000, - "latdeg": 51.460829999999994, - "lngdeg": -3.162912, - "truebearingdeg": 346, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423385000, - "latdeg": 51.460944999999995, - "lngdeg": -3.1629519999999998, - "truebearingdeg": 346, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423387000, - "latdeg": 51.461028, - "lngdeg": -3.162978, - "truebearingdeg": 346, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423389000, - "latdeg": 51.461101899999996, - "lngdeg": -3.1630019999999996, - "truebearingdeg": 349, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423391000, - "latdeg": 51.461169999999996, - "lngdeg": -3.163027, - "truebearingdeg": 348, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423393000, - "latdeg": 51.461227, - "lngdeg": -3.163052, - "truebearingdeg": 348, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423395000, - "latdeg": 51.461287999999996, - "lngdeg": -3.163078, - "truebearingdeg": 340, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423397000, - "latdeg": 51.461362, - "lngdeg": -3.163118, - "truebearingdeg": 337, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423399000, - "latdeg": 51.461445, - "lngdeg": -3.163185, - "truebearingdeg": 328, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423401000, - "latdeg": 51.461524999999995, - "lngdeg": -3.163278, - "truebearingdeg": 319, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423403000, - "latdeg": 51.461577999999996, - "lngdeg": -3.163395, - "truebearingdeg": 297, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423405000, - "latdeg": 51.461597, - "lngdeg": -3.1635049, - "truebearingdeg": 277, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346423407000, - "latdeg": 51.461582, - "lngdeg": -3.163582, - "truebearingdeg": 240, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423409000, - "latdeg": 51.461563, - "lngdeg": -3.163617, - "truebearingdeg": 213, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423411000, - "latdeg": 51.461537, - "lngdeg": -3.163637, - "truebearingdeg": 201, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423413000, - "latdeg": 51.461507, - "lngdeg": -3.1636629999999997, - "truebearingdeg": 206, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423415000, - "latdeg": 51.461472, - "lngdeg": -3.163703, - "truebearingdeg": 214, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423418000, - "latdeg": 51.461433, - "lngdeg": -3.163747, - "truebearingdeg": 211, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423430000, - "latdeg": 51.461172999999995, - "lngdeg": -3.1640569999999997, - "truebearingdeg": 207, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423432000, - "latdeg": 51.461146899999996, - "lngdeg": -3.164097, - "truebearingdeg": 215, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423434000, - "latdeg": 51.461107999999996, - "lngdeg": -3.164135, - "truebearingdeg": 213, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423436000, - "latdeg": 51.461075, - "lngdeg": -3.164177, - "truebearingdeg": 216, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423438000, - "latdeg": 51.461033, - "lngdeg": -3.164217, - "truebearingdeg": 218, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423440000, - "latdeg": 51.460997899999995, - "lngdeg": -3.1642579, - "truebearingdeg": 214, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423442000, - "latdeg": 51.46096, - "lngdeg": -3.1643119, - "truebearingdeg": 219, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423444000, - "latdeg": 51.460921899999995, - "lngdeg": -3.1643749999999997, - "truebearingdeg": 227, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423446000, - "latdeg": 51.460892, - "lngdeg": -3.16443, - "truebearingdeg": 226, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423448000, - "latdeg": 51.460862, - "lngdeg": -3.164485, - "truebearingdeg": 223, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423450000, - "latdeg": 51.460829999999994, - "lngdeg": -3.164523, - "truebearingdeg": 223, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423452000, - "latdeg": 51.4608, - "lngdeg": -3.1645879999999997, - "truebearingdeg": 233, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423454000, - "latdeg": 51.460772999999996, - "lngdeg": -3.164628, - "truebearingdeg": 216, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423456000, - "latdeg": 51.460747, - "lngdeg": -3.1646669999999997, - "truebearingdeg": 220, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423458000, - "latdeg": 51.460719999999995, - "lngdeg": -3.1647, - "truebearingdeg": 212, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423460000, - "latdeg": 51.460693, - "lngdeg": -3.1647179999999997, - "truebearingdeg": 194, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423462000, - "latdeg": 51.46067, - "lngdeg": -3.164732, - "truebearingdeg": 188, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423464000, - "latdeg": 51.460651999999996, - "lngdeg": -3.164733, - "truebearingdeg": 176, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423466000, - "latdeg": 51.460609999999996, - "lngdeg": -3.1647219, - "truebearingdeg": 165, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423468000, - "latdeg": 51.46056, - "lngdeg": -3.164692, - "truebearingdeg": 148, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423470000, - "latdeg": 51.460498, - "lngdeg": -3.1646319999999997, - "truebearingdeg": 141, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423472000, - "latdeg": 51.460437999999996, - "lngdeg": -3.164567, - "truebearingdeg": 141, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423474000, - "latdeg": 51.4604, - "lngdeg": -3.164498, - "truebearingdeg": 136, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423476000, - "latdeg": 51.460367999999995, - "lngdeg": -3.164422, - "truebearingdeg": 129, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423478000, - "latdeg": 51.460335, - "lngdeg": -3.164342, - "truebearingdeg": 131, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423480000, - "latdeg": 51.460285, - "lngdeg": -3.164262, - "truebearingdeg": 138, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423483000, - "latdeg": 51.460228, - "lngdeg": -3.164202, - "truebearingdeg": 152, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423491000, - "latdeg": 51.460045, - "lngdeg": -3.164357, - "truebearingdeg": 240, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423493000, - "latdeg": 51.460032999999996, - "lngdeg": -3.1644069999999997, - "truebearingdeg": 239, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423507000, - "latdeg": 51.459942, - "lngdeg": -3.16456, - "truebearingdeg": 220, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423509000, - "latdeg": 51.459927, - "lngdeg": -3.164558, - "truebearingdeg": 215, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423511000, - "latdeg": 51.459917999999995, - "lngdeg": -3.164555, - "truebearingdeg": 212, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423513000, - "latdeg": 51.459903, - "lngdeg": -3.1645619999999997, - "truebearingdeg": 210, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423515000, - "latdeg": 51.459891999999996, - "lngdeg": -3.1645629, - "truebearingdeg": 207, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423519000, - "latdeg": 51.459872999999995, - "lngdeg": -3.1645619999999997, - "truebearingdeg": 201, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423521000, - "latdeg": 51.459862, - "lngdeg": -3.164558, - "truebearingdeg": 198, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423523000, - "latdeg": 51.459849999999996, - "lngdeg": -3.1645499999999998, - "truebearingdeg": 194, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423525000, - "latdeg": 51.459835, - "lngdeg": -3.1645499999999998, - "truebearingdeg": 191, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423527000, - "latdeg": 51.459817, - "lngdeg": -3.1645529999999997, - "truebearingdeg": 191, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423529000, - "latdeg": 51.459793, - "lngdeg": -3.1645619999999997, - "truebearingdeg": 198, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423531000, - "latdeg": 51.459782, - "lngdeg": -3.164565, - "truebearingdeg": 196, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423533000, - "latdeg": 51.459767, - "lngdeg": -3.164565, - "truebearingdeg": 188, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423535000, - "latdeg": 51.459747, - "lngdeg": -3.164565, - "truebearingdeg": 180, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423537000, - "latdeg": 51.45972, - "lngdeg": -3.164583, - "truebearingdeg": 191, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423539000, - "latdeg": 51.459686999999995, - "lngdeg": -3.164603, - "truebearingdeg": 199, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423541000, - "latdeg": 51.459647999999994, - "lngdeg": -3.16463, - "truebearingdeg": 205, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423543000, - "latdeg": 51.459613, - "lngdeg": -3.16466, - "truebearingdeg": 210, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423545000, - "latdeg": 51.459582999999995, - "lngdeg": -3.164688, - "truebearingdeg": 213, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423548000, - "latdeg": 51.459553, - "lngdeg": -3.164713, - "truebearingdeg": 210, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - } - ] - }, - { - "name": "Alinghi", - "track": [ - { - "timepoint": 1346422554000, - "latdeg": 51.461329899999996, - "lngdeg": -3.165028, - "truebearingdeg": 21, - "knotspeed": 3.7796976241900646, - "tack": "PORT" - }, - { - "timepoint": 1346422556000, - "latdeg": 51.461365, - "lngdeg": -3.164993, - "truebearingdeg": 37, - "knotspeed": 3.7796976241900646, - "tack": "PORT" - }, - { - "timepoint": 1346422558000, - "latdeg": 51.461391899999995, - "lngdeg": -3.1649469999999997, - "truebearingdeg": 41, - "knotspeed": 3.7796976241900646, - "tack": "PORT" - }, - { - "timepoint": 1346422560000, - "latdeg": 51.461406999999994, - "lngdeg": -3.16491, - "truebearingdeg": 46, - "knotspeed": 3.239740820734341, - "tack": "PORT" - }, - { - "timepoint": 1346422562000, - "latdeg": 51.46143, - "lngdeg": -3.164875, - "truebearingdeg": 49, - "knotspeed": 3.239740820734341, - "tack": "PORT" - }, - { - "timepoint": 1346422564000, - "latdeg": 51.461451999999994, - "lngdeg": -3.164837, - "truebearingdeg": 49, - "knotspeed": 3.239740820734341, - "tack": "PORT" - }, - { - "timepoint": 1346422566000, - "latdeg": 51.461472, - "lngdeg": -3.164803, - "truebearingdeg": 48, - "knotspeed": 2.699784017278618, - "tack": "PORT" - }, - { - "timepoint": 1346422568000, - "latdeg": 51.46149, - "lngdeg": -3.1647719999999997, - "truebearingdeg": 39, - "knotspeed": 2.699784017278618, - "tack": "PORT" - }, - { - "timepoint": 1346422571000, - "latdeg": 51.461517, - "lngdeg": -3.164758, - "truebearingdeg": 21, - "knotspeed": 2.699784017278618, - "tack": "PORT" - }, - { - "timepoint": 1346422578000, - "latdeg": 51.461613, - "lngdeg": -3.164703, - "truebearingdeg": 12, - "knotspeed": 2.699784017278618, - "tack": "PORT" - }, - { - "timepoint": 1346422580000, - "latdeg": 51.461652, - "lngdeg": -3.1646869, - "truebearingdeg": 12, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346422582000, - "latdeg": 51.4617, - "lngdeg": -3.164663, - "truebearingdeg": 16, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346422584000, - "latdeg": 51.461762, - "lngdeg": -3.16463, - "truebearingdeg": 26, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346422586000, - "latdeg": 51.461842, - "lngdeg": -3.1645779999999997, - "truebearingdeg": 28, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422588000, - "latdeg": 51.461918, - "lngdeg": -3.1645049999999997, - "truebearingdeg": 30, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422590000, - "latdeg": 51.462002, - "lngdeg": -3.164418, - "truebearingdeg": 31, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422592000, - "latdeg": 51.462089999999996, - "lngdeg": -3.1643279, - "truebearingdeg": 36, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422594000, - "latdeg": 51.462185, - "lngdeg": -3.164195, - "truebearingdeg": 46, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422596000, - "latdeg": 51.462241999999996, - "lngdeg": -3.164043, - "truebearingdeg": 70, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422599000, - "latdeg": 51.46225, - "lngdeg": -3.1639369999999998, - "truebearingdeg": 96, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422601000, - "latdeg": 51.462235, - "lngdeg": -3.1637999999999997, - "truebearingdeg": 98, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422603000, - "latdeg": 51.462222999999994, - "lngdeg": -3.1637199999999996, - "truebearingdeg": 99, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422605000, - "latdeg": 51.462207899999996, - "lngdeg": -3.1636379999999997, - "truebearingdeg": 111, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422607000, - "latdeg": 51.462188, - "lngdeg": -3.1635579999999996, - "truebearingdeg": 116, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422609000, - "latdeg": 51.462146999999995, - "lngdeg": -3.163493, - "truebearingdeg": 135, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422611000, - "latdeg": 51.462092999999996, - "lngdeg": -3.163452, - "truebearingdeg": 151, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422613000, - "latdeg": 51.462055, - "lngdeg": -3.163402, - "truebearingdeg": 145, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422615000, - "latdeg": 51.462002, - "lngdeg": -3.16334, - "truebearingdeg": 140, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422617000, - "latdeg": 51.461994999999995, - "lngdeg": -3.163275, - "truebearingdeg": 116, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422619000, - "latdeg": 51.461937, - "lngdeg": -3.163213, - "truebearingdeg": 155, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422621000, - "latdeg": 51.461838, - "lngdeg": -3.1631679999999998, - "truebearingdeg": 161, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422623000, - "latdeg": 51.461768, - "lngdeg": -3.163128, - "truebearingdeg": 160, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422625000, - "latdeg": 51.461697, - "lngdeg": -3.1631079, - "truebearingdeg": 181, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422627000, - "latdeg": 51.461642999999995, - "lngdeg": -3.163143, - "truebearingdeg": 207, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422629000, - "latdeg": 51.461577999999996, - "lngdeg": -3.163183, - "truebearingdeg": 207, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422631000, - "latdeg": 51.46154, - "lngdeg": -3.163242, - "truebearingdeg": 234, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422633000, - "latdeg": 51.461543, - "lngdeg": -3.1633049, - "truebearingdeg": 255, - "knotspeed": 2.699784017278618, - "tack": "PORT" - }, - { - "timepoint": 1346422636000, - "latdeg": 51.46154, - "lngdeg": -3.1633419999999997, - "truebearingdeg": 282, - "knotspeed": 1.6198704103671706, - "tack": "PORT" - }, - { - "timepoint": 1346422647000, - "latdeg": 51.46154, - "lngdeg": -3.163455, - "truebearingdeg": 262, - "knotspeed": 0, - "tack": "PORT" - }, - { - "timepoint": 1346422651000, - "latdeg": 51.461521999999995, - "lngdeg": -3.163445, - "truebearingdeg": 240, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422653000, - "latdeg": 51.461509899999996, - "lngdeg": -3.1634379999999998, - "truebearingdeg": 232, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422655000, - "latdeg": 51.46149, - "lngdeg": -3.163437, - "truebearingdeg": 177, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422657000, - "latdeg": 51.461475, - "lngdeg": -3.163433, - "truebearingdeg": 175, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422662000, - "latdeg": 51.461425, - "lngdeg": -3.1634029999999997, - "truebearingdeg": 163, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422664000, - "latdeg": 51.461368, - "lngdeg": -3.1633769999999997, - "truebearingdeg": 159, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422666000, - "latdeg": 51.461329899999996, - "lngdeg": -3.1633579999999997, - "truebearingdeg": 165, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422668000, - "latdeg": 51.461292, - "lngdeg": -3.1633549999999997, - "truebearingdeg": 183, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422670000, - "latdeg": 51.461242999999996, - "lngdeg": -3.163363, - "truebearingdeg": 188, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422672000, - "latdeg": 51.461189999999995, - "lngdeg": -3.163373, - "truebearingdeg": 192, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422674000, - "latdeg": 51.461127999999995, - "lngdeg": -3.1633999999999998, - "truebearingdeg": 208, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422676000, - "latdeg": 51.461101899999996, - "lngdeg": -3.1634379999999998, - "truebearingdeg": 206, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422678000, - "latdeg": 51.461071999999994, - "lngdeg": -3.1634629999999997, - "truebearingdeg": 192, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422680000, - "latdeg": 51.461033, - "lngdeg": -3.1634669, - "truebearingdeg": 185, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422682000, - "latdeg": 51.460994899999996, - "lngdeg": -3.163472, - "truebearingdeg": 186, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422684000, - "latdeg": 51.460941999999996, - "lngdeg": -3.163477, - "truebearingdeg": 179, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422686000, - "latdeg": 51.460895, - "lngdeg": -3.163502, - "truebearingdeg": 195, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422688000, - "latdeg": 51.460814899999995, - "lngdeg": -3.163532, - "truebearingdeg": 197, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422690000, - "latdeg": 51.460761999999995, - "lngdeg": -3.163572, - "truebearingdeg": 198, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422692000, - "latdeg": 51.460696999999996, - "lngdeg": -3.1636099, - "truebearingdeg": 203, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422694000, - "latdeg": 51.460622, - "lngdeg": -3.1636569999999997, - "truebearingdeg": 202, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422696000, - "latdeg": 51.460544999999996, - "lngdeg": -3.163725, - "truebearingdeg": 203, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422698000, - "latdeg": 51.460457, - "lngdeg": -3.163777, - "truebearingdeg": 207, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422701000, - "latdeg": 51.460361999999996, - "lngdeg": -3.1638379999999997, - "truebearingdeg": 204, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422708000, - "latdeg": 51.459987999999996, - "lngdeg": -3.1642129999999997, - "truebearingdeg": 209, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422710000, - "latdeg": 51.459891999999996, - "lngdeg": -3.1642929, - "truebearingdeg": 210, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422712000, - "latdeg": 51.45979, - "lngdeg": -3.1643819, - "truebearingdeg": 207, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422714000, - "latdeg": 51.459686999999995, - "lngdeg": -3.164485, - "truebearingdeg": 215, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422716000, - "latdeg": 51.459595, - "lngdeg": -3.1646129999999997, - "truebearingdeg": 216, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422718000, - "latdeg": 51.459477, - "lngdeg": -3.1647249, - "truebearingdeg": 208, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422721000, - "latdeg": 51.459342899999996, - "lngdeg": -3.164815, - "truebearingdeg": 208, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422723000, - "latdeg": 51.45916, - "lngdeg": -3.1649979999999998, - "truebearingdeg": 206, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422725000, - "latdeg": 51.45903, - "lngdeg": -3.1651149999999997, - "truebearingdeg": 205, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422727000, - "latdeg": 51.458878, - "lngdeg": -3.1652199999999997, - "truebearingdeg": 203, - "knotspeed": 16.73866090712743, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422729000, - "latdeg": 51.458737, - "lngdeg": -3.1653119999999997, - "truebearingdeg": 202, - "knotspeed": 16.73866090712743, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422731000, - "latdeg": 51.458588, - "lngdeg": -3.1654, - "truebearingdeg": 201, - "knotspeed": 17.818574514038875, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422733000, - "latdeg": 51.458438, - "lngdeg": -3.165498, - "truebearingdeg": 200, - "knotspeed": 16.73866090712743, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422735000, - "latdeg": 51.458281899999996, - "lngdeg": -3.1656269999999997, - "truebearingdeg": 207, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422737000, - "latdeg": 51.458144999999995, - "lngdeg": -3.165727, - "truebearingdeg": 203, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422739000, - "latdeg": 51.45803, - "lngdeg": -3.165815, - "truebearingdeg": 205, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422741000, - "latdeg": 51.457919999999994, - "lngdeg": -3.1659029999999997, - "truebearingdeg": 207, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422743000, - "latdeg": 51.457809999999995, - "lngdeg": -3.1660019999999998, - "truebearingdeg": 210, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422745000, - "latdeg": 51.45771, - "lngdeg": -3.166092, - "truebearingdeg": 206, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422747000, - "latdeg": 51.457617899999995, - "lngdeg": -3.166168, - "truebearingdeg": 208, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422749000, - "latdeg": 51.457527, - "lngdeg": -3.1662399999999997, - "truebearingdeg": 208, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422751000, - "latdeg": 51.45744, - "lngdeg": -3.166308, - "truebearingdeg": 205, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422753000, - "latdeg": 51.457352, - "lngdeg": -3.166378, - "truebearingdeg": 208, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422755000, - "latdeg": 51.45726, - "lngdeg": -3.166452, - "truebearingdeg": 206, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422757000, - "latdeg": 51.457164999999996, - "lngdeg": -3.166527, - "truebearingdeg": 207, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422759000, - "latdeg": 51.457057999999996, - "lngdeg": -3.1666149999999997, - "truebearingdeg": 208, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422761000, - "latdeg": 51.456952, - "lngdeg": -3.1666979, - "truebearingdeg": 205, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422763000, - "latdeg": 51.456837, - "lngdeg": -3.166788, - "truebearingdeg": 205, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422766000, - "latdeg": 51.456722, - "lngdeg": -3.1668819999999998, - "truebearingdeg": 208, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422773000, - "latdeg": 51.456233, - "lngdeg": -3.1672349, - "truebearingdeg": 203, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422775000, - "latdeg": 51.456131899999995, - "lngdeg": -3.167315, - "truebearingdeg": 206, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422777000, - "latdeg": 51.456032, - "lngdeg": -3.1673899999999997, - "truebearingdeg": 202, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422779000, - "latdeg": 51.455928, - "lngdeg": -3.1674569999999997, - "truebearingdeg": 203, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422782000, - "latdeg": 51.455822, - "lngdeg": -3.1675329999999997, - "truebearingdeg": 203, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422784000, - "latdeg": 51.455664999999996, - "lngdeg": -3.1676569999999997, - "truebearingdeg": 205, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422786000, - "latdeg": 51.455563, - "lngdeg": -3.1677429999999998, - "truebearingdeg": 207, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422788000, - "latdeg": 51.455455, - "lngdeg": -3.1678349999999997, - "truebearingdeg": 210, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422790000, - "latdeg": 51.455348, - "lngdeg": -3.1679369, - "truebearingdeg": 210, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422792000, - "latdeg": 51.455247, - "lngdeg": -3.168027, - "truebearingdeg": 207, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422794000, - "latdeg": 51.455132, - "lngdeg": -3.1681049999999997, - "truebearingdeg": 201, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422796000, - "latdeg": 51.455013, - "lngdeg": -3.16818, - "truebearingdeg": 204, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422798000, - "latdeg": 51.454898, - "lngdeg": -3.168268, - "truebearingdeg": 205, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422800000, - "latdeg": 51.454792, - "lngdeg": -3.168348, - "truebearingdeg": 205, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422802000, - "latdeg": 51.454693, - "lngdeg": -3.1684229999999998, - "truebearingdeg": 208, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422804000, - "latdeg": 51.454592999999996, - "lngdeg": -3.16851, - "truebearingdeg": 204, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422806000, - "latdeg": 51.454487, - "lngdeg": -3.16858, - "truebearingdeg": 202, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422808000, - "latdeg": 51.454361999999996, - "lngdeg": -3.168672, - "truebearingdeg": 208, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422810000, - "latdeg": 51.454235, - "lngdeg": -3.1687819999999998, - "truebearingdeg": 209, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422812000, - "latdeg": 51.454105, - "lngdeg": -3.168865, - "truebearingdeg": 195, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422814000, - "latdeg": 51.454012999999996, - "lngdeg": -3.168865, - "truebearingdeg": 167, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422816000, - "latdeg": 51.45393, - "lngdeg": -3.1687969999999996, - "truebearingdeg": 148, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422818000, - "latdeg": 51.453865, - "lngdeg": -3.1686899, - "truebearingdeg": 131, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422820000, - "latdeg": 51.453776999999995, - "lngdeg": -3.168535, - "truebearingdeg": 129, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422822000, - "latdeg": 51.453705, - "lngdeg": -3.168387, - "truebearingdeg": 130, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422824000, - "latdeg": 51.453624999999995, - "lngdeg": -3.168243, - "truebearingdeg": 129, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422826000, - "latdeg": 51.453545, - "lngdeg": -3.168065, - "truebearingdeg": 127, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422828000, - "latdeg": 51.453441999999995, - "lngdeg": -3.1678819999999996, - "truebearingdeg": 129, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422831000, - "latdeg": 51.453357999999994, - "lngdeg": -3.1676829, - "truebearingdeg": 121, - "knotspeed": 16.73866090712743, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422838000, - "latdeg": 51.453112999999995, - "lngdeg": -3.1667869, - "truebearingdeg": 109, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422840000, - "latdeg": 51.453083, - "lngdeg": -3.16656, - "truebearingdeg": 106, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422843000, - "latdeg": 51.453047999999995, - "lngdeg": -3.1663379999999997, - "truebearingdeg": 99, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422845000, - "latdeg": 51.453047999999995, - "lngdeg": -3.166047, - "truebearingdeg": 78, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346422847000, - "latdeg": 51.453083, - "lngdeg": -3.165918, - "truebearingdeg": 57, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422849000, - "latdeg": 51.453133, - "lngdeg": -3.165828, - "truebearingdeg": 40, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422851000, - "latdeg": 51.453202, - "lngdeg": -3.16574, - "truebearingdeg": 37, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422853000, - "latdeg": 51.453281999999994, - "lngdeg": -3.165635, - "truebearingdeg": 38, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422855000, - "latdeg": 51.453376999999996, - "lngdeg": -3.165507, - "truebearingdeg": 38, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422857000, - "latdeg": 51.453477, - "lngdeg": -3.165383, - "truebearingdeg": 36, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346422859000, - "latdeg": 51.453575, - "lngdeg": -3.165263, - "truebearingdeg": 35, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346422861000, - "latdeg": 51.453682, - "lngdeg": -3.1651499999999997, - "truebearingdeg": 39, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422863000, - "latdeg": 51.453758, - "lngdeg": -3.1650519999999998, - "truebearingdeg": 41, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422865000, - "latdeg": 51.453823, - "lngdeg": -3.1649279999999997, - "truebearingdeg": 65, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422867000, - "latdeg": 51.453838, - "lngdeg": -3.164825, - "truebearingdeg": 91, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422869000, - "latdeg": 51.453826899999996, - "lngdeg": -3.1647369999999997, - "truebearingdeg": 110, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422871000, - "latdeg": 51.453793, - "lngdeg": -3.164697, - "truebearingdeg": 133, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422875000, - "latdeg": 51.453722899999995, - "lngdeg": -3.164678, - "truebearingdeg": 201, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422877000, - "latdeg": 51.453692999999994, - "lngdeg": -3.164692, - "truebearingdeg": 189, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422879000, - "latdeg": 51.453655, - "lngdeg": -3.1647019999999997, - "truebearingdeg": 195, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422881000, - "latdeg": 51.45361, - "lngdeg": -3.16473, - "truebearingdeg": 205, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422883000, - "latdeg": 51.453559999999996, - "lngdeg": -3.164768, - "truebearingdeg": 218, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422885000, - "latdeg": 51.453522, - "lngdeg": -3.1648199999999997, - "truebearingdeg": 220, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422887000, - "latdeg": 51.453483, - "lngdeg": -3.1648769999999997, - "truebearingdeg": 221, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422889000, - "latdeg": 51.453441999999995, - "lngdeg": -3.164965, - "truebearingdeg": 232, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422891000, - "latdeg": 51.453396999999995, - "lngdeg": -3.1650679999999998, - "truebearingdeg": 232, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422893000, - "latdeg": 51.453342, - "lngdeg": -3.1651719999999997, - "truebearingdeg": 226, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422896000, - "latdeg": 51.453285, - "lngdeg": -3.165267, - "truebearingdeg": 223, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422904000, - "latdeg": 51.453047999999995, - "lngdeg": -3.1656999999999997, - "truebearingdeg": 229, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422906000, - "latdeg": 51.452968, - "lngdeg": -3.165858, - "truebearingdeg": 231, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422908000, - "latdeg": 51.452915, - "lngdeg": -3.165962, - "truebearingdeg": 227, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422910000, - "latdeg": 51.452858, - "lngdeg": -3.1660619999999997, - "truebearingdeg": 228, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422912000, - "latdeg": 51.452819999999996, - "lngdeg": -3.166168, - "truebearingdeg": 256, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422914000, - "latdeg": 51.452819999999996, - "lngdeg": -3.1662499, - "truebearingdeg": 282, - "knotspeed": 4.319654427645788, - "tack": "PORT" - }, - { - "timepoint": 1346422916000, - "latdeg": 51.452838, - "lngdeg": -3.166282, - "truebearingdeg": 329, - "knotspeed": 2.699784017278618, - "tack": "PORT" - }, - { - "timepoint": 1346422918000, - "latdeg": 51.452858, - "lngdeg": -3.1662879999999998, - "truebearingdeg": 347, - "knotspeed": 2.159827213822894, - "tack": "PORT" - }, - { - "timepoint": 1346422920000, - "latdeg": 51.452887999999994, - "lngdeg": -3.166327, - "truebearingdeg": 324, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346422922000, - "latdeg": 51.452942, - "lngdeg": -3.1663929, - "truebearingdeg": 321, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346422924000, - "latdeg": 51.453002999999995, - "lngdeg": -3.1664399999999997, - "truebearingdeg": 326, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422926000, - "latdeg": 51.453075, - "lngdeg": -3.166522, - "truebearingdeg": 331, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422928000, - "latdeg": 51.453167, - "lngdeg": -3.166595, - "truebearingdeg": 332, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422930000, - "latdeg": 51.453261999999995, - "lngdeg": -3.1666779999999997, - "truebearingdeg": 333, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422932000, - "latdeg": 51.453373, - "lngdeg": -3.166775, - "truebearingdeg": 330, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422934000, - "latdeg": 51.45348, - "lngdeg": -3.16688, - "truebearingdeg": 325, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422936000, - "latdeg": 51.453587, - "lngdeg": -3.1669869999999998, - "truebearingdeg": 322, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422938000, - "latdeg": 51.453682, - "lngdeg": -3.167128, - "truebearingdeg": 316, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346422940000, - "latdeg": 51.453781899999996, - "lngdeg": -3.167283, - "truebearingdeg": 314, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346422942000, - "latdeg": 51.453862, - "lngdeg": -3.1674349, - "truebearingdeg": 311, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346422944000, - "latdeg": 51.453953, - "lngdeg": -3.1675929999999997, - "truebearingdeg": 311, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346422946000, - "latdeg": 51.454045, - "lngdeg": -3.1677519999999997, - "truebearingdeg": 316, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346422948000, - "latdeg": 51.454142999999995, - "lngdeg": -3.1679079999999997, - "truebearingdeg": 315, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346422950000, - "latdeg": 51.454237899999995, - "lngdeg": -3.1680699999999997, - "truebearingdeg": 307, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346422952000, - "latdeg": 51.454315, - "lngdeg": -3.168243, - "truebearingdeg": 302, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422954000, - "latdeg": 51.454367999999995, - "lngdeg": -3.168408, - "truebearingdeg": 283, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422956000, - "latdeg": 51.454353, - "lngdeg": -3.168513, - "truebearingdeg": 247, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422958000, - "latdeg": 51.45433, - "lngdeg": -3.168555, - "truebearingdeg": 232, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422961000, - "latdeg": 51.454315, - "lngdeg": -3.1685819, - "truebearingdeg": 223, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422969000, - "latdeg": 51.453922, - "lngdeg": -3.168713, - "truebearingdeg": 164, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422971000, - "latdeg": 51.453838, - "lngdeg": -3.1686229999999997, - "truebearingdeg": 148, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422973000, - "latdeg": 51.453742999999996, - "lngdeg": -3.1684669999999997, - "truebearingdeg": 132, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422975000, - "latdeg": 51.453613, - "lngdeg": -3.1682919999999997, - "truebearingdeg": 127, - "knotspeed": 17.278617710583152, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422977000, - "latdeg": 51.45353, - "lngdeg": -3.168082, - "truebearingdeg": 115, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422979000, - "latdeg": 51.453464999999994, - "lngdeg": -3.1678599999999997, - "truebearingdeg": 112, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422981000, - "latdeg": 51.453399999999995, - "lngdeg": -3.1676379999999997, - "truebearingdeg": 113, - "knotspeed": 16.73866090712743, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422983000, - "latdeg": 51.453334999999996, - "lngdeg": -3.167408, - "truebearingdeg": 111, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422985000, - "latdeg": 51.453269999999996, - "lngdeg": -3.1671899999999997, - "truebearingdeg": 114, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422987000, - "latdeg": 51.453224999999996, - "lngdeg": -3.1669769999999997, - "truebearingdeg": 109, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422989000, - "latdeg": 51.453213, - "lngdeg": -3.1667679, - "truebearingdeg": 93, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422991000, - "latdeg": 51.453224999999996, - "lngdeg": -3.166597, - "truebearingdeg": 82, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422993000, - "latdeg": 51.453281999999994, - "lngdeg": -3.1664779999999997, - "truebearingdeg": 45, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422995000, - "latdeg": 51.453362, - "lngdeg": -3.166375, - "truebearingdeg": 40, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422997000, - "latdeg": 51.453449899999995, - "lngdeg": -3.166245, - "truebearingdeg": 43, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346422999000, - "latdeg": 51.453556999999996, - "lngdeg": -3.16608, - "truebearingdeg": 46, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423001000, - "latdeg": 51.453652, - "lngdeg": -3.165908, - "truebearingdeg": 49, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423003000, - "latdeg": 51.453737999999994, - "lngdeg": -3.1657479, - "truebearingdeg": 49, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423005000, - "latdeg": 51.45382, - "lngdeg": -3.1655919999999997, - "truebearingdeg": 48, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423007000, - "latdeg": 51.453902899999996, - "lngdeg": -3.1654419999999996, - "truebearingdeg": 45, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423009000, - "latdeg": 51.453995, - "lngdeg": -3.165295, - "truebearingdeg": 45, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423011000, - "latdeg": 51.454082, - "lngdeg": -3.16514, - "truebearingdeg": 47, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423013000, - "latdeg": 51.45417, - "lngdeg": -3.1649819999999997, - "truebearingdeg": 47, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423015000, - "latdeg": 51.454252999999994, - "lngdeg": -3.1648329, - "truebearingdeg": 49, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423017000, - "latdeg": 51.45433, - "lngdeg": -3.164692, - "truebearingdeg": 45, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423019000, - "latdeg": 51.454406999999996, - "lngdeg": -3.164583, - "truebearingdeg": 33, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423021000, - "latdeg": 51.454502, - "lngdeg": -3.1645179999999997, - "truebearingdeg": 15, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423023000, - "latdeg": 51.454597, - "lngdeg": -3.1645079999999997, - "truebearingdeg": 352, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423026000, - "latdeg": 51.45467, - "lngdeg": -3.1645619999999997, - "truebearingdeg": 326, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423036000, - "latdeg": 51.455292, - "lngdeg": -3.1652169999999997, - "truebearingdeg": 333, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423038000, - "latdeg": 51.455439999999996, - "lngdeg": -3.1653249999999997, - "truebearingdeg": 332, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423040000, - "latdeg": 51.455582, - "lngdeg": -3.1654579999999997, - "truebearingdeg": 327, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423042000, - "latdeg": 51.455718, - "lngdeg": -3.165597, - "truebearingdeg": 328, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423044000, - "latdeg": 51.455867999999995, - "lngdeg": -3.16572, - "truebearingdeg": 332, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423046000, - "latdeg": 51.456016999999996, - "lngdeg": -3.165842, - "truebearingdeg": 331, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423048000, - "latdeg": 51.456162, - "lngdeg": -3.165978, - "truebearingdeg": 333, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423050000, - "latdeg": 51.456306999999995, - "lngdeg": -3.166073, - "truebearingdeg": 339, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423052000, - "latdeg": 51.456432, - "lngdeg": -3.166175, - "truebearingdeg": 334, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423054000, - "latdeg": 51.456567, - "lngdeg": -3.16629, - "truebearingdeg": 330, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423056000, - "latdeg": 51.456691899999996, - "lngdeg": -3.16641, - "truebearingdeg": 336, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423058000, - "latdeg": 51.456824999999995, - "lngdeg": -3.16647, - "truebearingdeg": 347, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423060000, - "latdeg": 51.45697, - "lngdeg": -3.1664649999999996, - "truebearingdeg": 13, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423062000, - "latdeg": 51.457127, - "lngdeg": -3.166302, - "truebearingdeg": 42, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423064000, - "latdeg": 51.457198, - "lngdeg": -3.1660969999999997, - "truebearingdeg": 63, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423066000, - "latdeg": 51.457242, - "lngdeg": -3.165902, - "truebearingdeg": 66, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423068000, - "latdeg": 51.457252999999994, - "lngdeg": -3.165733, - "truebearingdeg": 72, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423070000, - "latdeg": 51.45731, - "lngdeg": -3.16559, - "truebearingdeg": 59, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423072000, - "latdeg": 51.457367, - "lngdeg": -3.165467, - "truebearingdeg": 46, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423074000, - "latdeg": 51.457432, - "lngdeg": -3.1653499999999997, - "truebearingdeg": 42, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423076000, - "latdeg": 51.457515, - "lngdeg": -3.16524, - "truebearingdeg": 39, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423078000, - "latdeg": 51.457614899999996, - "lngdeg": -3.1651279999999997, - "truebearingdeg": 37, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423080000, - "latdeg": 51.457713, - "lngdeg": -3.1650069999999997, - "truebearingdeg": 39, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423082000, - "latdeg": 51.457812999999994, - "lngdeg": -3.1648799999999997, - "truebearingdeg": 40, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423084000, - "latdeg": 51.457912, - "lngdeg": -3.164745, - "truebearingdeg": 41, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423086000, - "latdeg": 51.458014999999996, - "lngdeg": -3.1645979, - "truebearingdeg": 44, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423088000, - "latdeg": 51.458115, - "lngdeg": -3.1644379999999996, - "truebearingdeg": 48, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423091000, - "latdeg": 51.458197999999996, - "lngdeg": -3.164265, - "truebearingdeg": 56, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423106000, - "latdeg": 51.45784, - "lngdeg": -3.163197, - "truebearingdeg": 133, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423108000, - "latdeg": 51.45773, - "lngdeg": -3.1630849999999997, - "truebearingdeg": 141, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423110000, - "latdeg": 51.457629999999995, - "lngdeg": -3.1630379, - "truebearingdeg": 158, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423112000, - "latdeg": 51.457532, - "lngdeg": -3.163033, - "truebearingdeg": 182, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423114000, - "latdeg": 51.457417, - "lngdeg": -3.1630819999999997, - "truebearingdeg": 201, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423116000, - "latdeg": 51.457322, - "lngdeg": -3.1631769999999997, - "truebearingdeg": 215, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423118000, - "latdeg": 51.457232999999995, - "lngdeg": -3.163293, - "truebearingdeg": 224, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423120000, - "latdeg": 51.457153, - "lngdeg": -3.163423, - "truebearingdeg": 221, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423122000, - "latdeg": 51.457065, - "lngdeg": -3.1635549999999997, - "truebearingdeg": 226, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423124000, - "latdeg": 51.456993, - "lngdeg": -3.1636979999999997, - "truebearingdeg": 233, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423126000, - "latdeg": 51.456925, - "lngdeg": -3.163852, - "truebearingdeg": 236, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423128000, - "latdeg": 51.456886999999995, - "lngdeg": -3.164028, - "truebearingdeg": 245, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423130000, - "latdeg": 51.456832999999996, - "lngdeg": -3.164217, - "truebearingdeg": 251, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423132000, - "latdeg": 51.456772, - "lngdeg": -3.164392, - "truebearingdeg": 246, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423134000, - "latdeg": 51.456722, - "lngdeg": -3.164568, - "truebearingdeg": 244, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423136000, - "latdeg": 51.456677, - "lngdeg": -3.1647249, - "truebearingdeg": 243, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423138000, - "latdeg": 51.456657, - "lngdeg": -3.1648419999999997, - "truebearingdeg": 232, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423140000, - "latdeg": 51.456587999999996, - "lngdeg": -3.1649279999999997, - "truebearingdeg": 218, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423142000, - "latdeg": 51.456547, - "lngdeg": -3.16497, - "truebearingdeg": 213, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423144000, - "latdeg": 51.456492999999995, - "lngdeg": -3.165002, - "truebearingdeg": 203, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423146000, - "latdeg": 51.456421999999996, - "lngdeg": -3.1650799999999997, - "truebearingdeg": 218, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423148000, - "latdeg": 51.456348, - "lngdeg": -3.1651629999999997, - "truebearingdeg": 216, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423150000, - "latdeg": 51.456267999999994, - "lngdeg": -3.16526, - "truebearingdeg": 213, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423152000, - "latdeg": 51.456191999999994, - "lngdeg": -3.165352, - "truebearingdeg": 234, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423155000, - "latdeg": 51.456185, - "lngdeg": -3.16546, - "truebearingdeg": 263, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423158000, - "latdeg": 51.456185, - "lngdeg": -3.16546, - "truebearingdeg": 263, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423160000, - "latdeg": 51.456257, - "lngdeg": -3.1656019, - "truebearingdeg": 308, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423163000, - "latdeg": 51.456297899999996, - "lngdeg": -3.1656929999999996, - "truebearingdeg": 310, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423165000, - "latdeg": 51.45639, - "lngdeg": -3.165845, - "truebearingdeg": 314, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423167000, - "latdeg": 51.456466999999996, - "lngdeg": -3.1659569999999997, - "truebearingdeg": 322, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423169000, - "latdeg": 51.456558, - "lngdeg": -3.1660719999999998, - "truebearingdeg": 324, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423171000, - "latdeg": 51.456672999999995, - "lngdeg": -3.166217, - "truebearingdeg": 325, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423173000, - "latdeg": 51.456792, - "lngdeg": -3.166317, - "truebearingdeg": 333, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423175000, - "latdeg": 51.456947, - "lngdeg": -3.1664179999999997, - "truebearingdeg": 346, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423177000, - "latdeg": 51.457097, - "lngdeg": -3.166417, - "truebearingdeg": 7, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423179000, - "latdeg": 51.45726, - "lngdeg": -3.16635, - "truebearingdeg": 19, - "knotspeed": 19.43844492440605, - "tack": "PORT" - }, - { - "timepoint": 1346423181000, - "latdeg": 51.457423, - "lngdeg": -3.166225, - "truebearingdeg": 25, - "knotspeed": 18.898488120950326, - "tack": "PORT" - }, - { - "timepoint": 1346423183000, - "latdeg": 51.457584999999995, - "lngdeg": -3.166093, - "truebearingdeg": 24, - "knotspeed": 18.898488120950326, - "tack": "PORT" - }, - { - "timepoint": 1346423185000, - "latdeg": 51.457733, - "lngdeg": -3.165988, - "truebearingdeg": 21, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423187000, - "latdeg": 51.457882, - "lngdeg": -3.165905, - "truebearingdeg": 19, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423189000, - "latdeg": 51.45805, - "lngdeg": -3.165825, - "truebearingdeg": 16, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423191000, - "latdeg": 51.458206999999994, - "lngdeg": -3.1657629999999997, - "truebearingdeg": 14, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423193000, - "latdeg": 51.458377999999996, - "lngdeg": -3.165685, - "truebearingdeg": 15, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423195000, - "latdeg": 51.458541999999994, - "lngdeg": -3.1655979999999997, - "truebearingdeg": 17, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423197000, - "latdeg": 51.458712999999996, - "lngdeg": -3.165518, - "truebearingdeg": 16, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423199000, - "latdeg": 51.45887, - "lngdeg": -3.1654399, - "truebearingdeg": 19, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423201000, - "latdeg": 51.45903, - "lngdeg": -3.165343, - "truebearingdeg": 22, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423203000, - "latdeg": 51.459177999999994, - "lngdeg": -3.165238, - "truebearingdeg": 22, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423205000, - "latdeg": 51.459317, - "lngdeg": -3.1651529999999997, - "truebearingdeg": 22, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423207000, - "latdeg": 51.45943, - "lngdeg": -3.165085, - "truebearingdeg": 21, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423209000, - "latdeg": 51.459565, - "lngdeg": -3.165005, - "truebearingdeg": 20, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423211000, - "latdeg": 51.459705, - "lngdeg": -3.1649279999999997, - "truebearingdeg": 18, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423215000, - "latdeg": 51.460015, - "lngdeg": -3.164792, - "truebearingdeg": 13, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423217000, - "latdeg": 51.460187, - "lngdeg": -3.1647369999999997, - "truebearingdeg": 11, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423220000, - "latdeg": 51.460358, - "lngdeg": -3.164663, - "truebearingdeg": 13, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423228000, - "latdeg": 51.460853, - "lngdeg": -3.1644479999999997, - "truebearingdeg": 19, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423230000, - "latdeg": 51.460938, - "lngdeg": -3.164408, - "truebearingdeg": 13, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423232000, - "latdeg": 51.461033, - "lngdeg": -3.164377, - "truebearingdeg": 8, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423234000, - "latdeg": 51.461132, - "lngdeg": -3.1643619999999997, - "truebearingdeg": 3, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423236000, - "latdeg": 51.461234999999995, - "lngdeg": -3.16436, - "truebearingdeg": 0, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423238000, - "latdeg": 51.461341999999995, - "lngdeg": -3.164363, - "truebearingdeg": 357, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423240000, - "latdeg": 51.461445, - "lngdeg": -3.164373, - "truebearingdeg": 359, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423242000, - "latdeg": 51.461548, - "lngdeg": -3.164367, - "truebearingdeg": 5, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423244000, - "latdeg": 51.461642999999995, - "lngdeg": -3.164348, - "truebearingdeg": 10, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423246000, - "latdeg": 51.461732, - "lngdeg": -3.164303, - "truebearingdeg": 24, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423248000, - "latdeg": 51.461802999999996, - "lngdeg": -3.1642029999999997, - "truebearingdeg": 49, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423250000, - "latdeg": 51.461842, - "lngdeg": -3.164065, - "truebearingdeg": 75, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423252000, - "latdeg": 51.461853, - "lngdeg": -3.163948, - "truebearingdeg": 104, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423254000, - "latdeg": 51.461833, - "lngdeg": -3.163862, - "truebearingdeg": 115, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423256000, - "latdeg": 51.461794999999995, - "lngdeg": -3.163783, - "truebearingdeg": 137, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423258000, - "latdeg": 51.461757999999996, - "lngdeg": -3.163723, - "truebearingdeg": 144, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423260000, - "latdeg": 51.461697, - "lngdeg": -3.1637, - "truebearingdeg": 154, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423262000, - "latdeg": 51.461639999999996, - "lngdeg": -3.1636569999999997, - "truebearingdeg": 156, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423264000, - "latdeg": 51.461563, - "lngdeg": -3.163627, - "truebearingdeg": 169, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423266000, - "latdeg": 51.461487, - "lngdeg": -3.163607, - "truebearingdeg": 167, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423268000, - "latdeg": 51.46141, - "lngdeg": -3.1635779, - "truebearingdeg": 169, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423270000, - "latdeg": 51.461318, - "lngdeg": -3.1635429999999998, - "truebearingdeg": 167, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423272000, - "latdeg": 51.461242999999996, - "lngdeg": -3.163528, - "truebearingdeg": 172, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423274000, - "latdeg": 51.461163, - "lngdeg": -3.163502, - "truebearingdeg": 174, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423276000, - "latdeg": 51.461051999999995, - "lngdeg": -3.1634819999999997, - "truebearingdeg": 174, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423278000, - "latdeg": 51.460947999999995, - "lngdeg": -3.1634729999999998, - "truebearingdeg": 178, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423280000, - "latdeg": 51.460834999999996, - "lngdeg": -3.163478, - "truebearingdeg": 179, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423282000, - "latdeg": 51.460747, - "lngdeg": -3.1634979999999997, - "truebearingdeg": 184, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423285000, - "latdeg": 51.460651999999996, - "lngdeg": -3.163507, - "truebearingdeg": 181, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423293000, - "latdeg": 51.460086999999994, - "lngdeg": -3.1635229999999996, - "truebearingdeg": 184, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423295000, - "latdeg": 51.45995, - "lngdeg": -3.163537, - "truebearingdeg": 178, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423297000, - "latdeg": 51.459823, - "lngdeg": -3.16349, - "truebearingdeg": 157, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423299000, - "latdeg": 51.459778, - "lngdeg": -3.1633649999999998, - "truebearingdeg": 133, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423301000, - "latdeg": 51.459758, - "lngdeg": -3.1632349, - "truebearingdeg": 92, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423303000, - "latdeg": 51.459778, - "lngdeg": -3.1631269, - "truebearingdeg": 66, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346423305000, - "latdeg": 51.459804999999996, - "lngdeg": -3.163052, - "truebearingdeg": 43, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346423307000, - "latdeg": 51.459852999999995, - "lngdeg": -3.163008, - "truebearingdeg": 20, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346423309000, - "latdeg": 51.459903, - "lngdeg": -3.16301, - "truebearingdeg": 12, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346423311000, - "latdeg": 51.459956999999996, - "lngdeg": -3.162997, - "truebearingdeg": 3, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423313000, - "latdeg": 51.460024999999995, - "lngdeg": -3.1629799999999997, - "truebearingdeg": 359, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423315000, - "latdeg": 51.460107, - "lngdeg": -3.1629829999999997, - "truebearingdeg": 359, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423317000, - "latdeg": 51.460181999999996, - "lngdeg": -3.162985, - "truebearingdeg": 2, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423319000, - "latdeg": 51.460262, - "lngdeg": -3.162982, - "truebearingdeg": 1, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423321000, - "latdeg": 51.460338, - "lngdeg": -3.162982, - "truebearingdeg": 359, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423323000, - "latdeg": 51.460442, - "lngdeg": -3.1629869999999998, - "truebearingdeg": 356, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423325000, - "latdeg": 51.460598, - "lngdeg": -3.162975, - "truebearingdeg": 353, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423327000, - "latdeg": 51.460693, - "lngdeg": -3.163005, - "truebearingdeg": 353, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423329000, - "latdeg": 51.460784999999994, - "lngdeg": -3.163035, - "truebearingdeg": 351, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423331000, - "latdeg": 51.460879999999996, - "lngdeg": -3.1630629999999997, - "truebearingdeg": 350, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423333000, - "latdeg": 51.460964999999995, - "lngdeg": -3.163093, - "truebearingdeg": 347, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423335000, - "latdeg": 51.461037, - "lngdeg": -3.163115, - "truebearingdeg": 355, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423337000, - "latdeg": 51.461098, - "lngdeg": -3.1631199999999997, - "truebearingdeg": 358, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423339000, - "latdeg": 51.461166999999996, - "lngdeg": -3.163125, - "truebearingdeg": 355, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423341000, - "latdeg": 51.461242999999996, - "lngdeg": -3.163147, - "truebearingdeg": 345, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423343000, - "latdeg": 51.461327, - "lngdeg": -3.163185, - "truebearingdeg": 344, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423345000, - "latdeg": 51.461417999999995, - "lngdeg": -3.163232, - "truebearingdeg": 338, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423347000, - "latdeg": 51.461509899999996, - "lngdeg": -3.163287, - "truebearingdeg": 341, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423350000, - "latdeg": 51.461593, - "lngdeg": -3.16335, - "truebearingdeg": 334, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423358000, - "latdeg": 51.46194, - "lngdeg": -3.1637179, - "truebearingdeg": 299, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423360000, - "latdeg": 51.46196, - "lngdeg": -3.163818, - "truebearingdeg": 285, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346423362000, - "latdeg": 51.461963, - "lngdeg": -3.163887, - "truebearingdeg": 264, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423364000, - "latdeg": 51.461951899999995, - "lngdeg": -3.163932, - "truebearingdeg": 260, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423366000, - "latdeg": 51.461951899999995, - "lngdeg": -3.16397, - "truebearingdeg": 270, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423370000, - "latdeg": 51.461929999999995, - "lngdeg": -3.164018, - "truebearingdeg": 243, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423374000, - "latdeg": 51.461906899999995, - "lngdeg": -3.164033, - "truebearingdeg": 193, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423376000, - "latdeg": 51.461887, - "lngdeg": -3.164047, - "truebearingdeg": 212, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423378000, - "latdeg": 51.461864999999996, - "lngdeg": -3.164078, - "truebearingdeg": 222, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423380000, - "latdeg": 51.461859999999994, - "lngdeg": -3.16412, - "truebearingdeg": 233, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423382000, - "latdeg": 51.461856999999995, - "lngdeg": -3.1641529999999998, - "truebearingdeg": 259, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423384000, - "latdeg": 51.461844899999996, - "lngdeg": -3.16418, - "truebearingdeg": 243, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423386000, - "latdeg": 51.461814999999994, - "lngdeg": -3.164218, - "truebearingdeg": 212, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423388000, - "latdeg": 51.461794999999995, - "lngdeg": -3.1642419999999998, - "truebearingdeg": 202, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423390000, - "latdeg": 51.461777, - "lngdeg": -3.164252, - "truebearingdeg": 189, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423392000, - "latdeg": 51.461749999999995, - "lngdeg": -3.164265, - "truebearingdeg": 194, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423394000, - "latdeg": 51.461712, - "lngdeg": -3.1642829999999997, - "truebearingdeg": 205, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423396000, - "latdeg": 51.461655, - "lngdeg": -3.1643149999999998, - "truebearingdeg": 203, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423398000, - "latdeg": 51.461602, - "lngdeg": -3.1643649999999997, - "truebearingdeg": 202, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423400000, - "latdeg": 51.461537, - "lngdeg": -3.164402, - "truebearingdeg": 194, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423402000, - "latdeg": 51.461462999999995, - "lngdeg": -3.16443, - "truebearingdeg": 191, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423404000, - "latdeg": 51.461383, - "lngdeg": -3.1644419999999998, - "truebearingdeg": 178, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423406000, - "latdeg": 51.461315, - "lngdeg": -3.164427, - "truebearingdeg": 160, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423408000, - "latdeg": 51.461237999999994, - "lngdeg": -3.16437, - "truebearingdeg": 150, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423410000, - "latdeg": 51.461169999999996, - "lngdeg": -3.164303, - "truebearingdeg": 147, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423412000, - "latdeg": 51.461078, - "lngdeg": -3.164227, - "truebearingdeg": 161, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423415000, - "latdeg": 51.460997899999995, - "lngdeg": -3.164122, - "truebearingdeg": 142, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423423000, - "latdeg": 51.460705, - "lngdeg": -3.163707, - "truebearingdeg": 144, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423425000, - "latdeg": 51.460622, - "lngdeg": -3.1636479, - "truebearingdeg": 145, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423427000, - "latdeg": 51.460575, - "lngdeg": -3.1635929999999997, - "truebearingdeg": 146, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423429000, - "latdeg": 51.460533, - "lngdeg": -3.163532, - "truebearingdeg": 141, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423431000, - "latdeg": 51.460482999999996, - "lngdeg": -3.163453, - "truebearingdeg": 138, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423433000, - "latdeg": 51.460415, - "lngdeg": -3.1633769999999997, - "truebearingdeg": 146, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423435000, - "latdeg": 51.460347, - "lngdeg": -3.1633199999999997, - "truebearingdeg": 148, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423437000, - "latdeg": 51.460273, - "lngdeg": -3.163232, - "truebearingdeg": 143, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423439000, - "latdeg": 51.460178, - "lngdeg": -3.1631519999999997, - "truebearingdeg": 153, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423441000, - "latdeg": 51.460094999999995, - "lngdeg": -3.1631229999999997, - "truebearingdeg": 181, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423443000, - "latdeg": 51.460032999999996, - "lngdeg": -3.163173, - "truebearingdeg": 218, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423445000, - "latdeg": 51.459998, - "lngdeg": -3.163242, - "truebearingdeg": 250, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423447000, - "latdeg": 51.459992, - "lngdeg": -3.163297, - "truebearingdeg": 258, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423449000, - "latdeg": 51.459987999999996, - "lngdeg": -3.163328, - "truebearingdeg": 247, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423451000, - "latdeg": 51.459998, - "lngdeg": -3.16335, - "truebearingdeg": 272, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423453000, - "latdeg": 51.459998, - "lngdeg": -3.1633679999999997, - "truebearingdeg": 278, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423463000, - "latdeg": 51.459953, - "lngdeg": -3.1635079999999998, - "truebearingdeg": 250, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423465000, - "latdeg": 51.45995, - "lngdeg": -3.1635329999999997, - "truebearingdeg": 250, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423467000, - "latdeg": 51.459945, - "lngdeg": -3.1635549999999997, - "truebearingdeg": 251, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423469000, - "latdeg": 51.459942, - "lngdeg": -3.16357, - "truebearingdeg": 255, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423473000, - "latdeg": 51.45993, - "lngdeg": -3.163598, - "truebearingdeg": 259, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423477000, - "latdeg": 51.459927, - "lngdeg": -3.163618, - "truebearingdeg": 259, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423488000, - "latdeg": 51.459914999999995, - "lngdeg": -3.1636119999999996, - "truebearingdeg": 210, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423490000, - "latdeg": 51.459914999999995, - "lngdeg": -3.1635999999999997, - "truebearingdeg": 197, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423492000, - "latdeg": 51.459911999999996, - "lngdeg": -3.163592, - "truebearingdeg": 186, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423494000, - "latdeg": 51.459908, - "lngdeg": -3.1635779, - "truebearingdeg": 179, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423496000, - "latdeg": 51.459908, - "lngdeg": -3.163563, - "truebearingdeg": 176, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423498000, - "latdeg": 51.459903, - "lngdeg": -3.1635579999999996, - "truebearingdeg": 177, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423500000, - "latdeg": 51.4599, - "lngdeg": -3.1635679999999997, - "truebearingdeg": 181, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423502000, - "latdeg": 51.459897, - "lngdeg": -3.163582, - "truebearingdeg": 187, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423504000, - "latdeg": 51.459888, - "lngdeg": -3.1636029999999997, - "truebearingdeg": 193, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423506000, - "latdeg": 51.459869999999995, - "lngdeg": -3.1636379999999997, - "truebearingdeg": 227, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423508000, - "latdeg": 51.459865, - "lngdeg": -3.163655, - "truebearingdeg": 252, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423510000, - "latdeg": 51.459852999999995, - "lngdeg": -3.163668, - "truebearingdeg": 228, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423518000, - "latdeg": 51.459817, - "lngdeg": -3.1637649999999997, - "truebearingdeg": 254, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423520000, - "latdeg": 51.459812, - "lngdeg": -3.163773, - "truebearingdeg": 251, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423522000, - "latdeg": 51.459817, - "lngdeg": -3.1637749, - "truebearingdeg": 256, - "knotspeed": 0.5399568034557235, - "tack": "PORT" - }, - { - "timepoint": 1346423524000, - "latdeg": 51.45982, - "lngdeg": -3.163777, - "truebearingdeg": 259, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423526000, - "latdeg": 51.45982, - "lngdeg": -3.163773, - "truebearingdeg": 270, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423530000, - "latdeg": 51.459817, - "lngdeg": -3.163753, - "truebearingdeg": 232, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423532000, - "latdeg": 51.45982, - "lngdeg": -3.1637299999999997, - "truebearingdeg": 215, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423542000, - "latdeg": 51.459832, - "lngdeg": -3.163618, - "truebearingdeg": 82, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423545000, - "latdeg": 51.459817, - "lngdeg": -3.163627, - "truebearingdeg": 196, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423553000, - "latdeg": 51.459702, - "lngdeg": -3.1637549999999997, - "truebearingdeg": 226, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423555000, - "latdeg": 51.459686999999995, - "lngdeg": -3.163785, - "truebearingdeg": 220, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423557000, - "latdeg": 51.459675, - "lngdeg": -3.163802, - "truebearingdeg": 218, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423559000, - "latdeg": 51.459652, - "lngdeg": -3.1638219999999997, - "truebearingdeg": 211, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423561000, - "latdeg": 51.459624999999996, - "lngdeg": -3.163852, - "truebearingdeg": 219, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423563000, - "latdeg": 51.459598, - "lngdeg": -3.163885, - "truebearingdeg": 223, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423565000, - "latdeg": 51.459568, - "lngdeg": -3.1639269999999997, - "truebearingdeg": 222, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423567000, - "latdeg": 51.459542, - "lngdeg": -3.1639619999999997, - "truebearingdeg": 226, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423569000, - "latdeg": 51.459517999999996, - "lngdeg": -3.1639969999999997, - "truebearingdeg": 223, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423571000, - "latdeg": 51.459503, - "lngdeg": -3.164028, - "truebearingdeg": 227, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - } - ] - }, - { - "name": "Team Wales", - "track": [ - { - "timepoint": 1346422562000, - "latdeg": 51.462219999999995, - "lngdeg": -3.164167, - "truebearingdeg": 261, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422564000, - "latdeg": 51.462207899999996, - "lngdeg": -3.164218, - "truebearingdeg": 238, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422566000, - "latdeg": 51.462193, - "lngdeg": -3.164253, - "truebearingdeg": 228, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422568000, - "latdeg": 51.462173, - "lngdeg": -3.1642799999999998, - "truebearingdeg": 223, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422570000, - "latdeg": 51.46215, - "lngdeg": -3.1643, - "truebearingdeg": 202, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422572000, - "latdeg": 51.462128, - "lngdeg": -3.164307, - "truebearingdeg": 189, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422574000, - "latdeg": 51.462097, - "lngdeg": -3.1643119, - "truebearingdeg": 185, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422576000, - "latdeg": 51.462052, - "lngdeg": -3.1643079999999997, - "truebearingdeg": 173, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422578000, - "latdeg": 51.461997999999994, - "lngdeg": -3.164282, - "truebearingdeg": 154, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422580000, - "latdeg": 51.461945, - "lngdeg": -3.164218, - "truebearingdeg": 133, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422582000, - "latdeg": 51.461906899999995, - "lngdeg": -3.164128, - "truebearingdeg": 112, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422584000, - "latdeg": 51.461883, - "lngdeg": -3.1640319999999997, - "truebearingdeg": 110, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422586000, - "latdeg": 51.461864999999996, - "lngdeg": -3.163945, - "truebearingdeg": 110, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422588000, - "latdeg": 51.461842, - "lngdeg": -3.1638569999999997, - "truebearingdeg": 113, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422590000, - "latdeg": 51.461814999999994, - "lngdeg": -3.1637779999999998, - "truebearingdeg": 126, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422592000, - "latdeg": 51.461768, - "lngdeg": -3.163725, - "truebearingdeg": 148, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422594000, - "latdeg": 51.461712, - "lngdeg": -3.1636729999999997, - "truebearingdeg": 158, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422596000, - "latdeg": 51.461647, - "lngdeg": -3.16365, - "truebearingdeg": 177, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422598000, - "latdeg": 51.461586999999994, - "lngdeg": -3.163665, - "truebearingdeg": 192, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422601000, - "latdeg": 51.461512899999995, - "lngdeg": -3.163697, - "truebearingdeg": 199, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422603000, - "latdeg": 51.461425, - "lngdeg": -3.1637549999999997, - "truebearingdeg": 197, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422605000, - "latdeg": 51.461372, - "lngdeg": -3.1637779999999998, - "truebearingdeg": 190, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422607000, - "latdeg": 51.461315, - "lngdeg": -3.1637899999999997, - "truebearingdeg": 185, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422609000, - "latdeg": 51.461253, - "lngdeg": -3.1637969999999997, - "truebearingdeg": 181, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422611000, - "latdeg": 51.461189999999995, - "lngdeg": -3.163792, - "truebearingdeg": 174, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422613000, - "latdeg": 51.461116999999994, - "lngdeg": -3.1637749, - "truebearingdeg": 171, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422615000, - "latdeg": 51.461045, - "lngdeg": -3.16375, - "truebearingdeg": 167, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422618000, - "latdeg": 51.460972, - "lngdeg": -3.163713, - "truebearingdeg": 160, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422627000, - "latdeg": 51.460567, - "lngdeg": -3.1633079, - "truebearingdeg": 142, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422629000, - "latdeg": 51.460482999999996, - "lngdeg": -3.1632119999999997, - "truebearingdeg": 141, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422631000, - "latdeg": 51.460395, - "lngdeg": -3.16309, - "truebearingdeg": 137, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422633000, - "latdeg": 51.460299899999995, - "lngdeg": -3.162937, - "truebearingdeg": 131, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422635000, - "latdeg": 51.46022, - "lngdeg": -3.1627479999999997, - "truebearingdeg": 118, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422637000, - "latdeg": 51.460178, - "lngdeg": -3.1625579999999998, - "truebearingdeg": 100, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422639000, - "latdeg": 51.460192899999996, - "lngdeg": -3.1624119, - "truebearingdeg": 65, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422641000, - "latdeg": 51.46024, - "lngdeg": -3.16233, - "truebearingdeg": 40, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346422643000, - "latdeg": 51.460299899999995, - "lngdeg": -3.162282, - "truebearingdeg": 22, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346422645000, - "latdeg": 51.460377, - "lngdeg": -3.1622469, - "truebearingdeg": 11, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422647000, - "latdeg": 51.460477, - "lngdeg": -3.16221, - "truebearingdeg": 11, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422649000, - "latdeg": 51.460589999999996, - "lngdeg": -3.1621929, - "truebearingdeg": 358, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346422651000, - "latdeg": 51.460712, - "lngdeg": -3.1622119, - "truebearingdeg": 354, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346422653000, - "latdeg": 51.460823, - "lngdeg": -3.1622779999999997, - "truebearingdeg": 325, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422655000, - "latdeg": 51.460876999999996, - "lngdeg": -3.162387, - "truebearingdeg": 301, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422657000, - "latdeg": 51.460899999999995, - "lngdeg": -3.1624849999999998, - "truebearingdeg": 284, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346422659000, - "latdeg": 51.460895, - "lngdeg": -3.162538, - "truebearingdeg": 244, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422662000, - "latdeg": 51.460879999999996, - "lngdeg": -3.16256, - "truebearingdeg": 241, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422664000, - "latdeg": 51.460868, - "lngdeg": -3.162562, - "truebearingdeg": 234, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422666000, - "latdeg": 51.460853, - "lngdeg": -3.1625769999999997, - "truebearingdeg": 232, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422668000, - "latdeg": 51.460837999999995, - "lngdeg": -3.162592, - "truebearingdeg": 216, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422670000, - "latdeg": 51.460823, - "lngdeg": -3.16262, - "truebearingdeg": 230, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422672000, - "latdeg": 51.460808, - "lngdeg": -3.1626469999999998, - "truebearingdeg": 210, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422674000, - "latdeg": 51.460784999999994, - "lngdeg": -3.1626779999999997, - "truebearingdeg": 227, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422676000, - "latdeg": 51.460761999999995, - "lngdeg": -3.1627129999999997, - "truebearingdeg": 220, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422678000, - "latdeg": 51.460735, - "lngdeg": -3.162727, - "truebearingdeg": 196, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422680000, - "latdeg": 51.460696999999996, - "lngdeg": -3.16275, - "truebearingdeg": 200, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422683000, - "latdeg": 51.46064, - "lngdeg": -3.162778, - "truebearingdeg": 193, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422694000, - "latdeg": 51.459983, - "lngdeg": -3.1632029999999998, - "truebearingdeg": 206, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422696000, - "latdeg": 51.459835, - "lngdeg": -3.163315, - "truebearingdeg": 205, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422698000, - "latdeg": 51.459705, - "lngdeg": -3.1634129, - "truebearingdeg": 206, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422700000, - "latdeg": 51.459587, - "lngdeg": -3.163525, - "truebearingdeg": 207, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422702000, - "latdeg": 51.459472999999996, - "lngdeg": -3.1636279999999997, - "truebearingdeg": 208, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422704000, - "latdeg": 51.45935, - "lngdeg": -3.163735, - "truebearingdeg": 206, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422706000, - "latdeg": 51.459232, - "lngdeg": -3.163843, - "truebearingdeg": 209, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422708000, - "latdeg": 51.459112999999995, - "lngdeg": -3.163955, - "truebearingdeg": 211, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422710000, - "latdeg": 51.458991999999995, - "lngdeg": -3.164065, - "truebearingdeg": 211, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422712000, - "latdeg": 51.458878, - "lngdeg": -3.1641849, - "truebearingdeg": 211, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422714000, - "latdeg": 51.458751899999996, - "lngdeg": -3.1643, - "truebearingdeg": 211, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422716000, - "latdeg": 51.458636999999996, - "lngdeg": -3.1644229999999998, - "truebearingdeg": 210, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422718000, - "latdeg": 51.458515, - "lngdeg": -3.16453, - "truebearingdeg": 208, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422720000, - "latdeg": 51.458393, - "lngdeg": -3.164662, - "truebearingdeg": 213, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422723000, - "latdeg": 51.458259999999996, - "lngdeg": -3.164793, - "truebearingdeg": 210, - "knotspeed": 17.278617710583152, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422725000, - "latdeg": 51.45805, - "lngdeg": -3.1649949, - "truebearingdeg": 208, - "knotspeed": 16.73866090712743, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422727000, - "latdeg": 51.457919999999994, - "lngdeg": -3.1651119999999997, - "truebearingdeg": 207, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422729000, - "latdeg": 51.457786999999996, - "lngdeg": -3.165218, - "truebearingdeg": 207, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422731000, - "latdeg": 51.457657, - "lngdeg": -3.165327, - "truebearingdeg": 208, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422733000, - "latdeg": 51.457535, - "lngdeg": -3.1654329999999997, - "truebearingdeg": 208, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422735000, - "latdeg": 51.457432, - "lngdeg": -3.16552, - "truebearingdeg": 205, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422737000, - "latdeg": 51.457325, - "lngdeg": -3.1656079999999998, - "truebearingdeg": 206, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422739000, - "latdeg": 51.457215, - "lngdeg": -3.165698, - "truebearingdeg": 206, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422741000, - "latdeg": 51.457108, - "lngdeg": -3.1657819999999997, - "truebearingdeg": 205, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422743000, - "latdeg": 51.457, - "lngdeg": -3.1658619999999997, - "truebearingdeg": 206, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422745000, - "latdeg": 51.456893, - "lngdeg": -3.16594, - "truebearingdeg": 206, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422748000, - "latdeg": 51.456787, - "lngdeg": -3.166022, - "truebearingdeg": 206, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422755000, - "latdeg": 51.456359899999995, - "lngdeg": -3.166423, - "truebearingdeg": 214, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422757000, - "latdeg": 51.456257, - "lngdeg": -3.166533, - "truebearingdeg": 207, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422759000, - "latdeg": 51.456153, - "lngdeg": -3.1666249, - "truebearingdeg": 208, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422761000, - "latdeg": 51.456052, - "lngdeg": -3.1667169, - "truebearingdeg": 208, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422763000, - "latdeg": 51.455948, - "lngdeg": -3.1668049999999996, - "truebearingdeg": 207, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422765000, - "latdeg": 51.455844899999995, - "lngdeg": -3.166897, - "truebearingdeg": 209, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422767000, - "latdeg": 51.455745, - "lngdeg": -3.166988, - "truebearingdeg": 209, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422769000, - "latdeg": 51.455647, - "lngdeg": -3.167077, - "truebearingdeg": 209, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422771000, - "latdeg": 51.455552, - "lngdeg": -3.1671619, - "truebearingdeg": 210, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422773000, - "latdeg": 51.455455, - "lngdeg": -3.1672569999999998, - "truebearingdeg": 214, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422775000, - "latdeg": 51.455357, - "lngdeg": -3.1673649999999998, - "truebearingdeg": 215, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422777000, - "latdeg": 51.455262, - "lngdeg": -3.1674669, - "truebearingdeg": 214, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422779000, - "latdeg": 51.455169999999995, - "lngdeg": -3.16757, - "truebearingdeg": 212, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422781000, - "latdeg": 51.455075, - "lngdeg": -3.1676599999999997, - "truebearingdeg": 209, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422784000, - "latdeg": 51.454983, - "lngdeg": -3.167745, - "truebearingdeg": 211, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422786000, - "latdeg": 51.454845, - "lngdeg": -3.1678829, - "truebearingdeg": 216, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422788000, - "latdeg": 51.454761999999995, - "lngdeg": -3.16798, - "truebearingdeg": 216, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422790000, - "latdeg": 51.454682, - "lngdeg": -3.1680729999999997, - "truebearingdeg": 214, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422792000, - "latdeg": 51.454608, - "lngdeg": -3.1681529999999998, - "truebearingdeg": 213, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422794000, - "latdeg": 51.454536999999995, - "lngdeg": -3.1682319999999997, - "truebearingdeg": 214, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422796000, - "latdeg": 51.454462899999996, - "lngdeg": -3.168313, - "truebearingdeg": 216, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422798000, - "latdeg": 51.454392, - "lngdeg": -3.168408, - "truebearingdeg": 218, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422800000, - "latdeg": 51.454315, - "lngdeg": -3.168507, - "truebearingdeg": 220, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422802000, - "latdeg": 51.454232, - "lngdeg": -3.1686099999999997, - "truebearingdeg": 214, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422804000, - "latdeg": 51.454142999999995, - "lngdeg": -3.168695, - "truebearingdeg": 209, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422806000, - "latdeg": 51.454048, - "lngdeg": -3.168758, - "truebearingdeg": 192, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422808000, - "latdeg": 51.453959999999995, - "lngdeg": -3.168748, - "truebearingdeg": 168, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422810000, - "latdeg": 51.453868, - "lngdeg": -3.168685, - "truebearingdeg": 145, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422813000, - "latdeg": 51.4538, - "lngdeg": -3.168567, - "truebearingdeg": 128, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422820000, - "latdeg": 51.45348, - "lngdeg": -3.167913, - "truebearingdeg": 128, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422822000, - "latdeg": 51.453392, - "lngdeg": -3.167732, - "truebearingdeg": 127, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422824000, - "latdeg": 51.453311899999996, - "lngdeg": -3.167553, - "truebearingdeg": 124, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422826000, - "latdeg": 51.453247, - "lngdeg": -3.167363, - "truebearingdeg": 113, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422828000, - "latdeg": 51.453227999999996, - "lngdeg": -3.167173, - "truebearingdeg": 90, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422830000, - "latdeg": 51.453252, - "lngdeg": -3.1670149999999997, - "truebearingdeg": 66, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422832000, - "latdeg": 51.453297, - "lngdeg": -3.1669069999999997, - "truebearingdeg": 49, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422834000, - "latdeg": 51.453357999999994, - "lngdeg": -3.1668279999999998, - "truebearingdeg": 34, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422836000, - "latdeg": 51.45343, - "lngdeg": -3.166778, - "truebearingdeg": 21, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422838000, - "latdeg": 51.453506999999995, - "lngdeg": -3.166725, - "truebearingdeg": 25, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422840000, - "latdeg": 51.453578, - "lngdeg": -3.1666619999999996, - "truebearingdeg": 29, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422842000, - "latdeg": 51.453655, - "lngdeg": -3.1666049999999997, - "truebearingdeg": 28, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422845000, - "latdeg": 51.453734999999995, - "lngdeg": -3.1665479999999997, - "truebearingdeg": 23, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422847000, - "latdeg": 51.453862, - "lngdeg": -3.1664469, - "truebearingdeg": 29, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422849000, - "latdeg": 51.453945, - "lngdeg": -3.1663669999999997, - "truebearingdeg": 32, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422851000, - "latdeg": 51.454028, - "lngdeg": -3.166257, - "truebearingdeg": 38, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422853000, - "latdeg": 51.45411, - "lngdeg": -3.1661479999999997, - "truebearingdeg": 41, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422855000, - "latdeg": 51.454189899999996, - "lngdeg": -3.166032, - "truebearingdeg": 43, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422857000, - "latdeg": 51.45427, - "lngdeg": -3.165917, - "truebearingdeg": 36, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422859000, - "latdeg": 51.454353, - "lngdeg": -3.165785, - "truebearingdeg": 46, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346422861000, - "latdeg": 51.454432999999995, - "lngdeg": -3.165635, - "truebearingdeg": 49, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346422863000, - "latdeg": 51.454513, - "lngdeg": -3.165475, - "truebearingdeg": 54, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346422865000, - "latdeg": 51.454569899999996, - "lngdeg": -3.165317, - "truebearingdeg": 67, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422867000, - "latdeg": 51.454589999999996, - "lngdeg": -3.165167, - "truebearingdeg": 90, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422869000, - "latdeg": 51.454578, - "lngdeg": -3.165047, - "truebearingdeg": 108, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422871000, - "latdeg": 51.454543, - "lngdeg": -3.164952, - "truebearingdeg": 124, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422873000, - "latdeg": 51.454507, - "lngdeg": -3.164872, - "truebearingdeg": 126, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422875000, - "latdeg": 51.454468, - "lngdeg": -3.16478, - "truebearingdeg": 115, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422878000, - "latdeg": 51.45446, - "lngdeg": -3.164678, - "truebearingdeg": 82, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346422886000, - "latdeg": 51.454612999999995, - "lngdeg": -3.164573, - "truebearingdeg": 351, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346422888000, - "latdeg": 51.454662, - "lngdeg": -3.164602, - "truebearingdeg": 333, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346422890000, - "latdeg": 51.454727, - "lngdeg": -3.1646579999999997, - "truebearingdeg": 321, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422892000, - "latdeg": 51.454792, - "lngdeg": -3.1647279999999998, - "truebearingdeg": 319, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422894000, - "latdeg": 51.454856899999996, - "lngdeg": -3.1648199999999997, - "truebearingdeg": 317, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422896000, - "latdeg": 51.454921999999996, - "lngdeg": -3.1649279999999997, - "truebearingdeg": 308, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422898000, - "latdeg": 51.454975, - "lngdeg": -3.165032, - "truebearingdeg": 310, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422900000, - "latdeg": 51.455025, - "lngdeg": -3.1651279999999997, - "truebearingdeg": 313, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422902000, - "latdeg": 51.455082, - "lngdeg": -3.1652199999999997, - "truebearingdeg": 314, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422904000, - "latdeg": 51.455143, - "lngdeg": -3.16532, - "truebearingdeg": 312, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422907000, - "latdeg": 51.4552, - "lngdeg": -3.1654269999999998, - "truebearingdeg": 311, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422909000, - "latdeg": 51.455292, - "lngdeg": -3.1656019, - "truebearingdeg": 307, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422911000, - "latdeg": 51.455352999999995, - "lngdeg": -3.1657349999999997, - "truebearingdeg": 309, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422913000, - "latdeg": 51.455417999999995, - "lngdeg": -3.165863, - "truebearingdeg": 307, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422915000, - "latdeg": 51.455475, - "lngdeg": -3.165988, - "truebearingdeg": 308, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422917000, - "latdeg": 51.455543, - "lngdeg": -3.16613, - "truebearingdeg": 304, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422919000, - "latdeg": 51.455599899999996, - "lngdeg": -3.166263, - "truebearingdeg": 309, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422921000, - "latdeg": 51.455664999999996, - "lngdeg": -3.166388, - "truebearingdeg": 311, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422923000, - "latdeg": 51.455729999999996, - "lngdeg": -3.166492, - "truebearingdeg": 317, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422925000, - "latdeg": 51.455791999999995, - "lngdeg": -3.1666079999999996, - "truebearingdeg": 296, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422927000, - "latdeg": 51.455807, - "lngdeg": -3.166715, - "truebearingdeg": 256, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422929000, - "latdeg": 51.455788, - "lngdeg": -3.166765, - "truebearingdeg": 228, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422931000, - "latdeg": 51.455762, - "lngdeg": -3.166807, - "truebearingdeg": 226, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422933000, - "latdeg": 51.455729999999996, - "lngdeg": -3.1668569, - "truebearingdeg": 227, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422935000, - "latdeg": 51.455684999999995, - "lngdeg": -3.166935, - "truebearingdeg": 222, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422937000, - "latdeg": 51.455619999999996, - "lngdeg": -3.167013, - "truebearingdeg": 216, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422939000, - "latdeg": 51.455554899999996, - "lngdeg": -3.1670919, - "truebearingdeg": 216, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422942000, - "latdeg": 51.455475, - "lngdeg": -3.167185, - "truebearingdeg": 215, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422970000, - "latdeg": 51.454136999999996, - "lngdeg": -3.168723, - "truebearingdeg": 207, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422972000, - "latdeg": 51.454071899999995, - "lngdeg": -3.1687529999999997, - "truebearingdeg": 191, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422974000, - "latdeg": 51.453992, - "lngdeg": -3.1687629999999998, - "truebearingdeg": 180, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422976000, - "latdeg": 51.453894999999996, - "lngdeg": -3.168742, - "truebearingdeg": 165, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422978000, - "latdeg": 51.4538, - "lngdeg": -3.1686769999999997, - "truebearingdeg": 150, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422980000, - "latdeg": 51.453705, - "lngdeg": -3.1685629, - "truebearingdeg": 136, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422982000, - "latdeg": 51.45361, - "lngdeg": -3.1684099999999997, - "truebearingdeg": 133, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422984000, - "latdeg": 51.453522, - "lngdeg": -3.168212, - "truebearingdeg": 122, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422986000, - "latdeg": 51.453449899999995, - "lngdeg": -3.1680069, - "truebearingdeg": 115, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422988000, - "latdeg": 51.453396999999995, - "lngdeg": -3.167783, - "truebearingdeg": 110, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422990000, - "latdeg": 51.453342, - "lngdeg": -3.16757, - "truebearingdeg": 114, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422992000, - "latdeg": 51.453278, - "lngdeg": -3.167367, - "truebearingdeg": 115, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422994000, - "latdeg": 51.453213, - "lngdeg": -3.167148, - "truebearingdeg": 114, - "knotspeed": 16.73866090712743, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422996000, - "latdeg": 51.453154999999995, - "lngdeg": -3.1669129999999996, - "truebearingdeg": 106, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422998000, - "latdeg": 51.453128, - "lngdeg": -3.1666819999999998, - "truebearingdeg": 94, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423000000, - "latdeg": 51.45314, - "lngdeg": -3.1664879999999997, - "truebearingdeg": 73, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423002000, - "latdeg": 51.45319, - "lngdeg": -3.1663449999999997, - "truebearingdeg": 54, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423004000, - "latdeg": 51.453258, - "lngdeg": -3.166252, - "truebearingdeg": 37, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423007000, - "latdeg": 51.453353, - "lngdeg": -3.1661379999999997, - "truebearingdeg": 35, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423014000, - "latdeg": 51.453826899999996, - "lngdeg": -3.1656019, - "truebearingdeg": 37, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423016000, - "latdeg": 51.453945, - "lngdeg": -3.1654429, - "truebearingdeg": 42, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423018000, - "latdeg": 51.454054899999996, - "lngdeg": -3.165272, - "truebearingdeg": 44, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423020000, - "latdeg": 51.454152, - "lngdeg": -3.165113, - "truebearingdeg": 50, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423022000, - "latdeg": 51.454226999999996, - "lngdeg": -3.1649569, - "truebearingdeg": 52, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423024000, - "latdeg": 51.454291999999995, - "lngdeg": -3.1648199999999997, - "truebearingdeg": 53, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423026000, - "latdeg": 51.454353, - "lngdeg": -3.1646929999999998, - "truebearingdeg": 48, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423029000, - "latdeg": 51.454406999999996, - "lngdeg": -3.164608, - "truebearingdeg": 38, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423031000, - "latdeg": 51.454502, - "lngdeg": -3.1645269999999996, - "truebearingdeg": 17, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423033000, - "latdeg": 51.454567, - "lngdeg": -3.1645179999999997, - "truebearingdeg": 353, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423035000, - "latdeg": 51.454646999999994, - "lngdeg": -3.16457, - "truebearingdeg": 332, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423037000, - "latdeg": 51.454735, - "lngdeg": -3.1646579999999997, - "truebearingdeg": 324, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423039000, - "latdeg": 51.454845, - "lngdeg": -3.1647969999999996, - "truebearingdeg": 320, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423041000, - "latdeg": 51.45496, - "lngdeg": -3.164927, - "truebearingdeg": 324, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423043000, - "latdeg": 51.455078, - "lngdeg": -3.165073, - "truebearingdeg": 322, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423045000, - "latdeg": 51.455192999999994, - "lngdeg": -3.165205, - "truebearingdeg": 323, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423047000, - "latdeg": 51.455318, - "lngdeg": -3.165345, - "truebearingdeg": 325, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423049000, - "latdeg": 51.455445, - "lngdeg": -3.16548, - "truebearingdeg": 326, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423051000, - "latdeg": 51.455582, - "lngdeg": -3.1656049, - "truebearingdeg": 329, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423053000, - "latdeg": 51.455726999999996, - "lngdeg": -3.1657279999999997, - "truebearingdeg": 330, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423055000, - "latdeg": 51.455872, - "lngdeg": -3.16586, - "truebearingdeg": 330, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423057000, - "latdeg": 51.456019999999995, - "lngdeg": -3.165987, - "truebearingdeg": 332, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423059000, - "latdeg": 51.456162, - "lngdeg": -3.166092, - "truebearingdeg": 336, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423061000, - "latdeg": 51.456295, - "lngdeg": -3.166178, - "truebearingdeg": 337, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423063000, - "latdeg": 51.456424999999996, - "lngdeg": -3.1662719999999998, - "truebearingdeg": 335, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423065000, - "latdeg": 51.45655, - "lngdeg": -3.166362, - "truebearingdeg": 337, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423067000, - "latdeg": 51.456688, - "lngdeg": -3.1664399999999997, - "truebearingdeg": 341, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423069000, - "latdeg": 51.456832999999996, - "lngdeg": -3.166495, - "truebearingdeg": 349, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423072000, - "latdeg": 51.456978, - "lngdeg": -3.1664999999999996, - "truebearingdeg": 7, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423095000, - "latdeg": 51.457961999999995, - "lngdeg": -3.164437, - "truebearingdeg": 64, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423097000, - "latdeg": 51.458008, - "lngdeg": -3.1642919999999997, - "truebearingdeg": 66, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423099000, - "latdeg": 51.458062, - "lngdeg": -3.164155, - "truebearingdeg": 60, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423101000, - "latdeg": 51.458102999999994, - "lngdeg": -3.164012, - "truebearingdeg": 64, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423103000, - "latdeg": 51.458132899999995, - "lngdeg": -3.163887, - "truebearingdeg": 71, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423105000, - "latdeg": 51.458147999999994, - "lngdeg": -3.163782, - "truebearingdeg": 77, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423107000, - "latdeg": 51.458165, - "lngdeg": -3.1636819999999997, - "truebearingdeg": 74, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423109000, - "latdeg": 51.458183, - "lngdeg": -3.163582, - "truebearingdeg": 70, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423111000, - "latdeg": 51.458206999999994, - "lngdeg": -3.16349, - "truebearingdeg": 64, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423113000, - "latdeg": 51.458245, - "lngdeg": -3.163418, - "truebearingdeg": 41, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423115000, - "latdeg": 51.458298, - "lngdeg": -3.163385, - "truebearingdeg": 15, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346423117000, - "latdeg": 51.458346999999996, - "lngdeg": -3.1633929999999997, - "truebearingdeg": 347, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346423119000, - "latdeg": 51.458397, - "lngdeg": -3.163452, - "truebearingdeg": 321, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423121000, - "latdeg": 51.458434999999994, - "lngdeg": -3.163528, - "truebearingdeg": 306, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423123000, - "latdeg": 51.458487999999996, - "lngdeg": -3.1635929999999997, - "truebearingdeg": 317, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423125000, - "latdeg": 51.458541999999994, - "lngdeg": -3.163665, - "truebearingdeg": 316, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423127000, - "latdeg": 51.458594999999995, - "lngdeg": -3.163757, - "truebearingdeg": 309, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423129000, - "latdeg": 51.458636999999996, - "lngdeg": -3.163877, - "truebearingdeg": 289, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423131000, - "latdeg": 51.458636999999996, - "lngdeg": -3.1639899999999996, - "truebearingdeg": 255, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423133000, - "latdeg": 51.458617999999994, - "lngdeg": -3.164047, - "truebearingdeg": 226, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423206000, - "latdeg": 51.458222, - "lngdeg": -3.165838, - "truebearingdeg": 19, - "knotspeed": 19.43844492440605, - "tack": "PORT" - }, - { - "timepoint": 1346423209000, - "latdeg": 51.458397, - "lngdeg": -3.1657469999999996, - "truebearingdeg": 19, - "knotspeed": 19.43844492440605, - "tack": "PORT" - }, - { - "timepoint": 1346423211000, - "latdeg": 51.458659999999995, - "lngdeg": -3.165597, - "truebearingdeg": 19, - "knotspeed": 19.978401727861772, - "tack": "PORT" - }, - { - "timepoint": 1346423213000, - "latdeg": 51.458832, - "lngdeg": -3.165502, - "truebearingdeg": 20, - "knotspeed": 19.43844492440605, - "tack": "PORT" - }, - { - "timepoint": 1346423215000, - "latdeg": 51.459002999999996, - "lngdeg": -3.165402, - "truebearingdeg": 18, - "knotspeed": 18.898488120950326, - "tack": "PORT" - }, - { - "timepoint": 1346423217000, - "latdeg": 51.459157, - "lngdeg": -3.1653149999999997, - "truebearingdeg": 18, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423219000, - "latdeg": 51.459292999999995, - "lngdeg": -3.1652429, - "truebearingdeg": 15, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423221000, - "latdeg": 51.459438, - "lngdeg": -3.165177, - "truebearingdeg": 15, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423223000, - "latdeg": 51.459559999999996, - "lngdeg": -3.165117, - "truebearingdeg": 18, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423225000, - "latdeg": 51.459682, - "lngdeg": -3.16505, - "truebearingdeg": 20, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423228000, - "latdeg": 51.459817, - "lngdeg": -3.16497, - "truebearingdeg": 20, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423230000, - "latdeg": 51.460048, - "lngdeg": -3.164827, - "truebearingdeg": 21, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423232000, - "latdeg": 51.460204999999995, - "lngdeg": -3.1647279999999998, - "truebearingdeg": 21, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423234000, - "latdeg": 51.460361999999996, - "lngdeg": -3.164637, - "truebearingdeg": 19, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423236000, - "latdeg": 51.46051, - "lngdeg": -3.164558, - "truebearingdeg": 16, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423238000, - "latdeg": 51.460631899999996, - "lngdeg": -3.1645079999999997, - "truebearingdeg": 7, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423240000, - "latdeg": 51.460732, - "lngdeg": -3.164497, - "truebearingdeg": 0, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423242000, - "latdeg": 51.46082, - "lngdeg": -3.164503, - "truebearingdeg": 357, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423244000, - "latdeg": 51.460902999999995, - "lngdeg": -3.16451, - "truebearingdeg": 356, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423246000, - "latdeg": 51.460992, - "lngdeg": -3.164522, - "truebearingdeg": 358, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423248000, - "latdeg": 51.461075, - "lngdeg": -3.1645249, - "truebearingdeg": 3, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423250000, - "latdeg": 51.461166999999996, - "lngdeg": -3.164522, - "truebearingdeg": 4, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423252000, - "latdeg": 51.46127, - "lngdeg": -3.164517, - "truebearingdeg": 4, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423254000, - "latdeg": 51.46138, - "lngdeg": -3.1645, - "truebearingdeg": 6, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423256000, - "latdeg": 51.46149, - "lngdeg": -3.164478, - "truebearingdeg": 9, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423258000, - "latdeg": 51.46159, - "lngdeg": -3.164453, - "truebearingdeg": 10, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423260000, - "latdeg": 51.461684999999996, - "lngdeg": -3.164418, - "truebearingdeg": 16, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423262000, - "latdeg": 51.461788, - "lngdeg": -3.1643469, - "truebearingdeg": 34, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423264000, - "latdeg": 51.461872, - "lngdeg": -3.1642069999999998, - "truebearingdeg": 56, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423266000, - "latdeg": 51.461906899999995, - "lngdeg": -3.1640419, - "truebearingdeg": 84, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423268000, - "latdeg": 51.461895, - "lngdeg": -3.163903, - "truebearingdeg": 109, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423270000, - "latdeg": 51.461856999999995, - "lngdeg": -3.163827, - "truebearingdeg": 139, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423273000, - "latdeg": 51.461802999999996, - "lngdeg": -3.163785, - "truebearingdeg": 154, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423283000, - "latdeg": 51.461287999999996, - "lngdeg": -3.16371, - "truebearingdeg": 175, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423285000, - "latdeg": 51.461182, - "lngdeg": -3.16369, - "truebearingdeg": 174, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423287000, - "latdeg": 51.461078, - "lngdeg": -3.16368, - "truebearingdeg": 175, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423290000, - "latdeg": 51.460992, - "lngdeg": -3.1636699999999998, - "truebearingdeg": 178, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423292000, - "latdeg": 51.460873, - "lngdeg": -3.1636729999999997, - "truebearingdeg": 184, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423294000, - "latdeg": 51.460803, - "lngdeg": -3.16368, - "truebearingdeg": 184, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423296000, - "latdeg": 51.460735, - "lngdeg": -3.1636829, - "truebearingdeg": 181, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423298000, - "latdeg": 51.460667, - "lngdeg": -3.16368, - "truebearingdeg": 176, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423300000, - "latdeg": 51.460601999999994, - "lngdeg": -3.163672, - "truebearingdeg": 171, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423302000, - "latdeg": 51.460536999999995, - "lngdeg": -3.1636569999999997, - "truebearingdeg": 170, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423304000, - "latdeg": 51.46046, - "lngdeg": -3.1636319999999998, - "truebearingdeg": 170, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423306000, - "latdeg": 51.460367999999995, - "lngdeg": -3.163617, - "truebearingdeg": 179, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423308000, - "latdeg": 51.460266999999995, - "lngdeg": -3.163618, - "truebearingdeg": 183, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423310000, - "latdeg": 51.460152, - "lngdeg": -3.163637, - "truebearingdeg": 186, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423312000, - "latdeg": 51.460032999999996, - "lngdeg": -3.1636469999999997, - "truebearingdeg": 177, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423314000, - "latdeg": 51.459927, - "lngdeg": -3.163617, - "truebearingdeg": 162, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423316000, - "latdeg": 51.459832, - "lngdeg": -3.163537, - "truebearingdeg": 145, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423318000, - "latdeg": 51.459754999999994, - "lngdeg": -3.1634119999999997, - "truebearingdeg": 122, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423320000, - "latdeg": 51.459728, - "lngdeg": -3.1632719999999996, - "truebearingdeg": 95, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423322000, - "latdeg": 51.459742999999996, - "lngdeg": -3.1631449999999997, - "truebearingdeg": 71, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423324000, - "latdeg": 51.459778, - "lngdeg": -3.1630729, - "truebearingdeg": 41, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346423326000, - "latdeg": 51.45982, - "lngdeg": -3.1630569, - "truebearingdeg": 13, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346423328000, - "latdeg": 51.45988, - "lngdeg": -3.1630529999999997, - "truebearingdeg": 356, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423330000, - "latdeg": 51.459942, - "lngdeg": -3.163067, - "truebearingdeg": 351, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423332000, - "latdeg": 51.460024999999995, - "lngdeg": -3.1630849999999997, - "truebearingdeg": 353, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423334000, - "latdeg": 51.460107, - "lngdeg": -3.1630949999999998, - "truebearingdeg": 355, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423337000, - "latdeg": 51.46019, - "lngdeg": -3.1630979999999997, - "truebearingdeg": 356, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423346000, - "latdeg": 51.460732, - "lngdeg": -3.163087, - "truebearingdeg": 356, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423348000, - "latdeg": 51.460837999999995, - "lngdeg": -3.163102, - "truebearingdeg": 353, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423351000, - "latdeg": 51.460927, - "lngdeg": -3.163122, - "truebearingdeg": 354, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423353000, - "latdeg": 51.461033, - "lngdeg": -3.1631199999999997, - "truebearingdeg": 2, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423355000, - "latdeg": 51.461101899999996, - "lngdeg": -3.163113, - "truebearingdeg": 1, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423357000, - "latdeg": 51.461169999999996, - "lngdeg": -3.1631069999999997, - "truebearingdeg": 2, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423359000, - "latdeg": 51.461234999999995, - "lngdeg": -3.1631079, - "truebearingdeg": 358, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423361000, - "latdeg": 51.461315, - "lngdeg": -3.1631169999999997, - "truebearingdeg": 351, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423363000, - "latdeg": 51.461397999999996, - "lngdeg": -3.163138, - "truebearingdeg": 348, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423365000, - "latdeg": 51.461475, - "lngdeg": -3.1631579999999997, - "truebearingdeg": 355, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423367000, - "latdeg": 51.461552, - "lngdeg": -3.163207, - "truebearingdeg": 340, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423369000, - "latdeg": 51.461635, - "lngdeg": -3.163287, - "truebearingdeg": 329, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423371000, - "latdeg": 51.46172, - "lngdeg": -3.163345, - "truebearingdeg": 335, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423373000, - "latdeg": 51.461788, - "lngdeg": -3.1633999999999998, - "truebearingdeg": 334, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423375000, - "latdeg": 51.461856999999995, - "lngdeg": -3.163452, - "truebearingdeg": 335, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423377000, - "latdeg": 51.461906899999995, - "lngdeg": -3.163507, - "truebearingdeg": 333, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423379000, - "latdeg": 51.461957, - "lngdeg": -3.1635749, - "truebearingdeg": 322, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423381000, - "latdeg": 51.462013, - "lngdeg": -3.1636279999999997, - "truebearingdeg": 328, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423383000, - "latdeg": 51.462075, - "lngdeg": -3.16368, - "truebearingdeg": 324, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423385000, - "latdeg": 51.462128, - "lngdeg": -3.16375, - "truebearingdeg": 308, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423387000, - "latdeg": 51.462173, - "lngdeg": -3.1637999999999997, - "truebearingdeg": 319, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346423389000, - "latdeg": 51.462212, - "lngdeg": -3.163878, - "truebearingdeg": 312, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423391000, - "latdeg": 51.462252899999996, - "lngdeg": -3.163948, - "truebearingdeg": 302, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346423393000, - "latdeg": 51.462264999999995, - "lngdeg": -3.1639969999999997, - "truebearingdeg": 297, - "knotspeed": 4.319654427645788, - "tack": "PORT" - }, - { - "timepoint": 1346423395000, - "latdeg": 51.462272999999996, - "lngdeg": -3.1640599999999997, - "truebearingdeg": 281, - "knotspeed": 3.7796976241900646, - "tack": "PORT" - }, - { - "timepoint": 1346423397000, - "latdeg": 51.462277, - "lngdeg": -3.164107, - "truebearingdeg": 265, - "knotspeed": 2.699784017278618, - "tack": "PORT" - }, - { - "timepoint": 1346423399000, - "latdeg": 51.462272999999996, - "lngdeg": -3.1641399999999997, - "truebearingdeg": 258, - "knotspeed": 1.6198704103671706, - "tack": "PORT" - }, - { - "timepoint": 1346423402000, - "latdeg": 51.462267999999995, - "lngdeg": -3.164163, - "truebearingdeg": 257, - "knotspeed": 1.079913606911447, - "tack": "PORT" - }, - { - "timepoint": 1346423406000, - "latdeg": 51.462264999999995, - "lngdeg": -3.1641879, - "truebearingdeg": 256, - "knotspeed": 0, - "tack": "PORT" - }, - { - "timepoint": 1346423408000, - "latdeg": 51.462267999999995, - "lngdeg": -3.1641879, - "truebearingdeg": 266, - "knotspeed": 0, - "tack": "PORT" - }, - { - "timepoint": 1346423410000, - "latdeg": 51.462272999999996, - "lngdeg": -3.164183, - "truebearingdeg": 269, - "knotspeed": 1.079913606911447, - "tack": "PORT" - }, - { - "timepoint": 1346423413000, - "latdeg": 51.462264999999995, - "lngdeg": -3.16418, - "truebearingdeg": 256, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423415000, - "latdeg": 51.462257, - "lngdeg": -3.1641779999999997, - "truebearingdeg": 234, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423423000, - "latdeg": 51.462252899999996, - "lngdeg": -3.164122, - "truebearingdeg": 182, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423425000, - "latdeg": 51.46225, - "lngdeg": -3.164107, - "truebearingdeg": 175, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423427000, - "latdeg": 51.462241999999996, - "lngdeg": -3.164098, - "truebearingdeg": 172, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423429000, - "latdeg": 51.462227, - "lngdeg": -3.164098, - "truebearingdeg": 174, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423431000, - "latdeg": 51.462212, - "lngdeg": -3.164117, - "truebearingdeg": 219, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423433000, - "latdeg": 51.462196999999996, - "lngdeg": -3.1641429999999997, - "truebearingdeg": 222, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423435000, - "latdeg": 51.462176899999996, - "lngdeg": -3.164177, - "truebearingdeg": 227, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423437000, - "latdeg": 51.462165, - "lngdeg": -3.1641999999999997, - "truebearingdeg": 222, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423439000, - "latdeg": 51.462154999999996, - "lngdeg": -3.164218, - "truebearingdeg": 222, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423441000, - "latdeg": 51.462146999999995, - "lngdeg": -3.1642319999999997, - "truebearingdeg": 221, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423443000, - "latdeg": 51.462134999999996, - "lngdeg": -3.16424, - "truebearingdeg": 220, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423445000, - "latdeg": 51.462123, - "lngdeg": -3.1642479999999997, - "truebearingdeg": 219, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423447000, - "latdeg": 51.462108, - "lngdeg": -3.16426, - "truebearingdeg": 211, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423449000, - "latdeg": 51.462092999999996, - "lngdeg": -3.164287, - "truebearingdeg": 223, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423451000, - "latdeg": 51.46207, - "lngdeg": -3.16432, - "truebearingdeg": 230, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423453000, - "latdeg": 51.462052, - "lngdeg": -3.1643499999999998, - "truebearingdeg": 221, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423455000, - "latdeg": 51.462036999999995, - "lngdeg": -3.164377, - "truebearingdeg": 224, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423457000, - "latdeg": 51.462016999999996, - "lngdeg": -3.164402, - "truebearingdeg": 216, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423459000, - "latdeg": 51.461997999999994, - "lngdeg": -3.164425, - "truebearingdeg": 213, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423461000, - "latdeg": 51.461982899999995, - "lngdeg": -3.1644479999999997, - "truebearingdeg": 217, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423463000, - "latdeg": 51.461966999999994, - "lngdeg": -3.164465, - "truebearingdeg": 213, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423466000, - "latdeg": 51.461957, - "lngdeg": -3.1644769999999998, - "truebearingdeg": 211, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423476000, - "latdeg": 51.461864999999996, - "lngdeg": -3.16458, - "truebearingdeg": 216, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423478000, - "latdeg": 51.461827, - "lngdeg": -3.1646199999999998, - "truebearingdeg": 214, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423480000, - "latdeg": 51.461765, - "lngdeg": -3.1646699999999996, - "truebearingdeg": 209, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423482000, - "latdeg": 51.461697, - "lngdeg": -3.1647369999999997, - "truebearingdeg": 214, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423484000, - "latdeg": 51.461628, - "lngdeg": -3.164822, - "truebearingdeg": 214, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423486000, - "latdeg": 51.46154, - "lngdeg": -3.1648799999999997, - "truebearingdeg": 192, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423488000, - "latdeg": 51.461456999999996, - "lngdeg": -3.1648799999999997, - "truebearingdeg": 169, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423490000, - "latdeg": 51.461377, - "lngdeg": -3.164825, - "truebearingdeg": 150, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423492000, - "latdeg": 51.461303, - "lngdeg": -3.1647469999999998, - "truebearingdeg": 148, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423494000, - "latdeg": 51.461242999999996, - "lngdeg": -3.164685, - "truebearingdeg": 139, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423496000, - "latdeg": 51.461185, - "lngdeg": -3.164622, - "truebearingdeg": 137, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423498000, - "latdeg": 51.461113, - "lngdeg": -3.1645719999999997, - "truebearingdeg": 142, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423500000, - "latdeg": 51.461054999999995, - "lngdeg": -3.1645269999999996, - "truebearingdeg": 142, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423502000, - "latdeg": 51.461009999999995, - "lngdeg": -3.1644579999999998, - "truebearingdeg": 142, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423504000, - "latdeg": 51.460964999999995, - "lngdeg": -3.164392, - "truebearingdeg": 141, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423506000, - "latdeg": 51.460921899999995, - "lngdeg": -3.1643299999999996, - "truebearingdeg": 143, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423508000, - "latdeg": 51.460876999999996, - "lngdeg": -3.1642769, - "truebearingdeg": 145, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423510000, - "latdeg": 51.460834999999996, - "lngdeg": -3.1642229, - "truebearingdeg": 145, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423512000, - "latdeg": 51.460792999999995, - "lngdeg": -3.164163, - "truebearingdeg": 145, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423514000, - "latdeg": 51.460747, - "lngdeg": -3.1641019999999997, - "truebearingdeg": 145, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423516000, - "latdeg": 51.46069, - "lngdeg": -3.1640569999999997, - "truebearingdeg": 149, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423518000, - "latdeg": 51.46064, - "lngdeg": -3.1640069, - "truebearingdeg": 151, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423520000, - "latdeg": 51.460595, - "lngdeg": -3.16397, - "truebearingdeg": 150, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423522000, - "latdeg": 51.460533, - "lngdeg": -3.16392, - "truebearingdeg": 151, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423524000, - "latdeg": 51.460477, - "lngdeg": -3.1638669999999998, - "truebearingdeg": 152, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423526000, - "latdeg": 51.460415, - "lngdeg": -3.163815, - "truebearingdeg": 152, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423528000, - "latdeg": 51.460353, - "lngdeg": -3.16376, - "truebearingdeg": 148, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423531000, - "latdeg": 51.460293, - "lngdeg": -3.1637019, - "truebearingdeg": 147, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423539000, - "latdeg": 51.459857899999996, - "lngdeg": -3.163353, - "truebearingdeg": 155, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423541000, - "latdeg": 51.459739899999995, - "lngdeg": -3.163268, - "truebearingdeg": 157, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423543000, - "latdeg": 51.459618, - "lngdeg": -3.163232, - "truebearingdeg": 180, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423545000, - "latdeg": 51.459526999999994, - "lngdeg": -3.1632919999999998, - "truebearingdeg": 208, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423547000, - "latdeg": 51.459477, - "lngdeg": -3.1633519999999997, - "truebearingdeg": 220, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423549000, - "latdeg": 51.459441999999996, - "lngdeg": -3.163408, - "truebearingdeg": 223, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423551000, - "latdeg": 51.459415, - "lngdeg": -3.163452, - "truebearingdeg": 225, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423553000, - "latdeg": 51.459388, - "lngdeg": -3.163488, - "truebearingdeg": 220, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423555000, - "latdeg": 51.459367, - "lngdeg": -3.1635169999999997, - "truebearingdeg": 215, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423557000, - "latdeg": 51.459337999999995, - "lngdeg": -3.163542, - "truebearingdeg": 212, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423559000, - "latdeg": 51.459317, - "lngdeg": -3.1635679999999997, - "truebearingdeg": 217, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423561000, - "latdeg": 51.459292999999995, - "lngdeg": -3.163598, - "truebearingdeg": 221, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423563000, - "latdeg": 51.459275, - "lngdeg": -3.1636249999999997, - "truebearingdeg": 220, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423565000, - "latdeg": 51.459255, - "lngdeg": -3.1636569999999997, - "truebearingdeg": 233, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423567000, - "latdeg": 51.459243, - "lngdeg": -3.163687, - "truebearingdeg": 237, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423569000, - "latdeg": 51.459232, - "lngdeg": -3.163712, - "truebearingdeg": 224, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423571000, - "latdeg": 51.459216999999995, - "lngdeg": -3.163728, - "truebearingdeg": 223, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - } - ] - }, - { - "name": "Groupe Edmond de Rothschild", - "track": [ - { - "timepoint": 1346422553000, - "latdeg": 51.460442, - "lngdeg": -3.1630879999999997, - "truebearingdeg": 331, - "knotspeed": 1.079913606911447, - "tack": "PORT" - }, - { - "timepoint": 1346422555000, - "latdeg": 51.460453, - "lngdeg": -3.163103, - "truebearingdeg": 330, - "knotspeed": 1.6198704103671706, - "tack": "PORT" - }, - { - "timepoint": 1346422557000, - "latdeg": 51.460465, - "lngdeg": -3.163125, - "truebearingdeg": 327, - "knotspeed": 1.079913606911447, - "tack": "PORT" - }, - { - "timepoint": 1346422559000, - "latdeg": 51.460471999999996, - "lngdeg": -3.163143, - "truebearingdeg": 324, - "knotspeed": 1.079913606911447, - "tack": "PORT" - }, - { - "timepoint": 1346422561000, - "latdeg": 51.460482999999996, - "lngdeg": -3.1631619, - "truebearingdeg": 323, - "knotspeed": 1.6198704103671706, - "tack": "PORT" - }, - { - "timepoint": 1346422563000, - "latdeg": 51.460498, - "lngdeg": -3.1631869999999997, - "truebearingdeg": 304, - "knotspeed": 2.159827213822894, - "tack": "PORT" - }, - { - "timepoint": 1346422565000, - "latdeg": 51.46051, - "lngdeg": -3.163213, - "truebearingdeg": 310, - "knotspeed": 1.6198704103671706, - "tack": "PORT" - }, - { - "timepoint": 1346422567000, - "latdeg": 51.460524899999996, - "lngdeg": -3.16324, - "truebearingdeg": 306, - "knotspeed": 2.159827213822894, - "tack": "PORT" - }, - { - "timepoint": 1346422569000, - "latdeg": 51.460536999999995, - "lngdeg": -3.163267, - "truebearingdeg": 294, - "knotspeed": 2.159827213822894, - "tack": "PORT" - }, - { - "timepoint": 1346422571000, - "latdeg": 51.460544999999996, - "lngdeg": -3.1632949999999997, - "truebearingdeg": 291, - "knotspeed": 2.159827213822894, - "tack": "PORT" - }, - { - "timepoint": 1346422573000, - "latdeg": 51.460552, - "lngdeg": -3.163328, - "truebearingdeg": 287, - "knotspeed": 2.159827213822894, - "tack": "PORT" - }, - { - "timepoint": 1346422575000, - "latdeg": 51.460563, - "lngdeg": -3.163357, - "truebearingdeg": 306, - "knotspeed": 2.159827213822894, - "tack": "PORT" - }, - { - "timepoint": 1346422577000, - "latdeg": 51.460583, - "lngdeg": -3.1633869999999997, - "truebearingdeg": 321, - "knotspeed": 3.239740820734341, - "tack": "PORT" - }, - { - "timepoint": 1346422579000, - "latdeg": 51.460612999999995, - "lngdeg": -3.163427, - "truebearingdeg": 325, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346422581000, - "latdeg": 51.460662899999996, - "lngdeg": -3.163477, - "truebearingdeg": 335, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346422583000, - "latdeg": 51.460747, - "lngdeg": -3.163513, - "truebearingdeg": 354, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422585000, - "latdeg": 51.460847, - "lngdeg": -3.1635, - "truebearingdeg": 11, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422587000, - "latdeg": 51.460952999999996, - "lngdeg": -3.163445, - "truebearingdeg": 22, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422589000, - "latdeg": 51.461048, - "lngdeg": -3.163373, - "truebearingdeg": 26, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422591000, - "latdeg": 51.461132, - "lngdeg": -3.1632979999999997, - "truebearingdeg": 31, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422594000, - "latdeg": 51.461208, - "lngdeg": -3.1632249999999997, - "truebearingdeg": 29, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422596000, - "latdeg": 51.461323, - "lngdeg": -3.1631619, - "truebearingdeg": 16, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422598000, - "latdeg": 51.461391899999995, - "lngdeg": -3.163148, - "truebearingdeg": 358, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346422600000, - "latdeg": 51.461451999999994, - "lngdeg": -3.163153, - "truebearingdeg": 1, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346422602000, - "latdeg": 51.461512899999995, - "lngdeg": -3.163148, - "truebearingdeg": 5, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346422604000, - "latdeg": 51.461569999999995, - "lngdeg": -3.163138, - "truebearingdeg": 4, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346422610000, - "latdeg": 51.461777, - "lngdeg": -3.1631329999999998, - "truebearingdeg": 347, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422612000, - "latdeg": 51.46183, - "lngdeg": -3.163197, - "truebearingdeg": 307, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346422616000, - "latdeg": 51.461844899999996, - "lngdeg": -3.1633069999999996, - "truebearingdeg": 245, - "knotspeed": 2.699784017278618, - "tack": "PORT" - }, - { - "timepoint": 1346422618000, - "latdeg": 51.461838, - "lngdeg": -3.1633329999999997, - "truebearingdeg": 242, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422620000, - "latdeg": 51.46183, - "lngdeg": -3.163367, - "truebearingdeg": 251, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422622000, - "latdeg": 51.461827, - "lngdeg": -3.163398, - "truebearingdeg": 262, - "knotspeed": 2.159827213822894, - "tack": "PORT" - }, - { - "timepoint": 1346422624000, - "latdeg": 51.461822999999995, - "lngdeg": -3.16342, - "truebearingdeg": 252, - "knotspeed": 1.6198704103671706, - "tack": "PORT" - }, - { - "timepoint": 1346422626000, - "latdeg": 51.461814999999994, - "lngdeg": -3.163452, - "truebearingdeg": 251, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422628000, - "latdeg": 51.461802999999996, - "lngdeg": -3.16348, - "truebearingdeg": 225, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422630000, - "latdeg": 51.461799899999995, - "lngdeg": -3.1634979999999997, - "truebearingdeg": 228, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422632000, - "latdeg": 51.461794999999995, - "lngdeg": -3.1635, - "truebearingdeg": 229, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422634000, - "latdeg": 51.461791999999996, - "lngdeg": -3.1635049, - "truebearingdeg": 230, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422636000, - "latdeg": 51.461788, - "lngdeg": -3.1635169999999997, - "truebearingdeg": 232, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422642000, - "latdeg": 51.461777, - "lngdeg": -3.163553, - "truebearingdeg": 234, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422646000, - "latdeg": 51.461765, - "lngdeg": -3.16358, - "truebearingdeg": 232, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422648000, - "latdeg": 51.461749999999995, - "lngdeg": -3.163598, - "truebearingdeg": 226, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422650000, - "latdeg": 51.461737899999996, - "lngdeg": -3.1636319999999998, - "truebearingdeg": 239, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422652000, - "latdeg": 51.461726999999996, - "lngdeg": -3.163665, - "truebearingdeg": 235, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422657000, - "latdeg": 51.461697, - "lngdeg": -3.1637369, - "truebearingdeg": 236, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422663000, - "latdeg": 51.461658, - "lngdeg": -3.1638219999999997, - "truebearingdeg": 230, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422665000, - "latdeg": 51.461642999999995, - "lngdeg": -3.1638449, - "truebearingdeg": 230, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422667000, - "latdeg": 51.461631999999994, - "lngdeg": -3.1638629999999996, - "truebearingdeg": 229, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422698000, - "latdeg": 51.461247, - "lngdeg": -3.1639869999999997, - "truebearingdeg": 183, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422700000, - "latdeg": 51.461197, - "lngdeg": -3.1639879, - "truebearingdeg": 181, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422702000, - "latdeg": 51.46114, - "lngdeg": -3.163993, - "truebearingdeg": 185, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422705000, - "latdeg": 51.461002, - "lngdeg": -3.16404, - "truebearingdeg": 195, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422708000, - "latdeg": 51.46091, - "lngdeg": -3.1640949999999997, - "truebearingdeg": 202, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422710000, - "latdeg": 51.4608, - "lngdeg": -3.1641719999999998, - "truebearingdeg": 203, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422712000, - "latdeg": 51.460693, - "lngdeg": -3.164252, - "truebearingdeg": 202, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422717000, - "latdeg": 51.460502999999996, - "lngdeg": -3.164392, - "truebearingdeg": 203, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422719000, - "latdeg": 51.460384999999995, - "lngdeg": -3.1644729999999996, - "truebearingdeg": 202, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422721000, - "latdeg": 51.460311999999995, - "lngdeg": -3.1645179999999997, - "truebearingdeg": 202, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422724000, - "latdeg": 51.460167, - "lngdeg": -3.1646069999999997, - "truebearingdeg": 202, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422726000, - "latdeg": 51.46008, - "lngdeg": -3.164668, - "truebearingdeg": 203, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422728000, - "latdeg": 51.459962, - "lngdeg": -3.1647469999999998, - "truebearingdeg": 207, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422731000, - "latdeg": 51.459832, - "lngdeg": -3.1648449999999997, - "truebearingdeg": 205, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422733000, - "latdeg": 51.459705, - "lngdeg": -3.1649469999999997, - "truebearingdeg": 202, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422747000, - "latdeg": 51.458709999999996, - "lngdeg": -3.1655469999999997, - "truebearingdeg": 199, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422749000, - "latdeg": 51.45858, - "lngdeg": -3.165622, - "truebearingdeg": 200, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422751000, - "latdeg": 51.458453, - "lngdeg": -3.165695, - "truebearingdeg": 200, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422753000, - "latdeg": 51.45832, - "lngdeg": -3.165778, - "truebearingdeg": 201, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422755000, - "latdeg": 51.458183, - "lngdeg": -3.165867, - "truebearingdeg": 201, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422757000, - "latdeg": 51.458065, - "lngdeg": -3.165937, - "truebearingdeg": 197, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422759000, - "latdeg": 51.457947, - "lngdeg": -3.165993, - "truebearingdeg": 196, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422761000, - "latdeg": 51.457828, - "lngdeg": -3.166058, - "truebearingdeg": 200, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422763000, - "latdeg": 51.457713, - "lngdeg": -3.166128, - "truebearingdeg": 201, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422765000, - "latdeg": 51.457603, - "lngdeg": -3.166207, - "truebearingdeg": 203, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422767000, - "latdeg": 51.457493, - "lngdeg": -3.1662849, - "truebearingdeg": 205, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422769000, - "latdeg": 51.457378, - "lngdeg": -3.166368, - "truebearingdeg": 205, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422771000, - "latdeg": 51.45728, - "lngdeg": -3.166455, - "truebearingdeg": 206, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422773000, - "latdeg": 51.457172, - "lngdeg": -3.1665419999999997, - "truebearingdeg": 207, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422775000, - "latdeg": 51.457057999999996, - "lngdeg": -3.1666369999999997, - "truebearingdeg": 207, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422778000, - "latdeg": 51.456939999999996, - "lngdeg": -3.16673, - "truebearingdeg": 207, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422780000, - "latdeg": 51.456782999999994, - "lngdeg": -3.1668599, - "truebearingdeg": 206, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422782000, - "latdeg": 51.45668, - "lngdeg": -3.16694, - "truebearingdeg": 207, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422784000, - "latdeg": 51.456576999999996, - "lngdeg": -3.167027, - "truebearingdeg": 206, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422786000, - "latdeg": 51.456478, - "lngdeg": -3.1671, - "truebearingdeg": 204, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422788000, - "latdeg": 51.456387, - "lngdeg": -3.1671619, - "truebearingdeg": 203, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422790000, - "latdeg": 51.456286999999996, - "lngdeg": -3.167232, - "truebearingdeg": 207, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422792000, - "latdeg": 51.456188, - "lngdeg": -3.167318, - "truebearingdeg": 209, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422794000, - "latdeg": 51.456092999999996, - "lngdeg": -3.16741, - "truebearingdeg": 209, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422796000, - "latdeg": 51.455987, - "lngdeg": -3.167502, - "truebearingdeg": 207, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422798000, - "latdeg": 51.455875, - "lngdeg": -3.1675899999999997, - "truebearingdeg": 206, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422807000, - "latdeg": 51.455368, - "lngdeg": -3.1680379999999997, - "truebearingdeg": 208, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422809000, - "latdeg": 51.455265, - "lngdeg": -3.168123, - "truebearingdeg": 205, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422811000, - "latdeg": 51.455169999999995, - "lngdeg": -3.16819, - "truebearingdeg": 199, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422813000, - "latdeg": 51.455075, - "lngdeg": -3.168233, - "truebearingdeg": 194, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422815000, - "latdeg": 51.454972, - "lngdeg": -3.168278, - "truebearingdeg": 197, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422817000, - "latdeg": 51.454856899999996, - "lngdeg": -3.168335, - "truebearingdeg": 199, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422819000, - "latdeg": 51.454741999999996, - "lngdeg": -3.1684099999999997, - "truebearingdeg": 204, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422821000, - "latdeg": 51.45462, - "lngdeg": -3.168503, - "truebearingdeg": 206, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422823000, - "latdeg": 51.454498, - "lngdeg": -3.1686069999999997, - "truebearingdeg": 205, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422825000, - "latdeg": 51.454392, - "lngdeg": -3.168685, - "truebearingdeg": 203, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422827000, - "latdeg": 51.454299999999996, - "lngdeg": -3.168752, - "truebearingdeg": 201, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422829000, - "latdeg": 51.454212, - "lngdeg": -3.168803, - "truebearingdeg": 196, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422831000, - "latdeg": 51.454128, - "lngdeg": -3.168827, - "truebearingdeg": 187, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422833000, - "latdeg": 51.45404, - "lngdeg": -3.1688129999999997, - "truebearingdeg": 164, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422835000, - "latdeg": 51.453964899999995, - "lngdeg": -3.168742, - "truebearingdeg": 147, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422837000, - "latdeg": 51.453883, - "lngdeg": -3.1685999999999996, - "truebearingdeg": 125, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422840000, - "latdeg": 51.453826899999996, - "lngdeg": -3.1684199, - "truebearingdeg": 112, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422842000, - "latdeg": 51.453767, - "lngdeg": -3.168103, - "truebearingdeg": 104, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422844000, - "latdeg": 51.453734999999995, - "lngdeg": -3.167887, - "truebearingdeg": 99, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422846000, - "latdeg": 51.453719899999996, - "lngdeg": -3.167677, - "truebearingdeg": 95, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422848000, - "latdeg": 51.453708, - "lngdeg": -3.167472, - "truebearingdeg": 96, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422850000, - "latdeg": 51.453692999999994, - "lngdeg": -3.1672729, - "truebearingdeg": 100, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422852000, - "latdeg": 51.453666999999996, - "lngdeg": -3.167087, - "truebearingdeg": 104, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422854000, - "latdeg": 51.453637, - "lngdeg": -3.166918, - "truebearingdeg": 105, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422856000, - "latdeg": 51.453601899999995, - "lngdeg": -3.166762, - "truebearingdeg": 113, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422858000, - "latdeg": 51.453548, - "lngdeg": -3.1666079999999996, - "truebearingdeg": 120, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422860000, - "latdeg": 51.453483, - "lngdeg": -3.166445, - "truebearingdeg": 123, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422862000, - "latdeg": 51.453412, - "lngdeg": -3.16627, - "truebearingdeg": 123, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422870000, - "latdeg": 51.453337999999995, - "lngdeg": -3.165505, - "truebearingdeg": 44, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422872000, - "latdeg": 51.453412, - "lngdeg": -3.165432, - "truebearingdeg": 30, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422874000, - "latdeg": 51.453486999999996, - "lngdeg": -3.165375, - "truebearingdeg": 28, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422876000, - "latdeg": 51.453575, - "lngdeg": -3.165302, - "truebearingdeg": 26, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422878000, - "latdeg": 51.453663, - "lngdeg": -3.1652269, - "truebearingdeg": 28, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422880000, - "latdeg": 51.453747, - "lngdeg": -3.165148, - "truebearingdeg": 30, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422882000, - "latdeg": 51.453826899999996, - "lngdeg": -3.165072, - "truebearingdeg": 33, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422884000, - "latdeg": 51.4539, - "lngdeg": -3.164983, - "truebearingdeg": 47, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422886000, - "latdeg": 51.453938, - "lngdeg": -3.1648769999999997, - "truebearingdeg": 73, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422888000, - "latdeg": 51.453945, - "lngdeg": -3.164777, - "truebearingdeg": 100, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422890000, - "latdeg": 51.453927, - "lngdeg": -3.1647049999999997, - "truebearingdeg": 123, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422892000, - "latdeg": 51.453891999999996, - "lngdeg": -3.1646549999999998, - "truebearingdeg": 144, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422894000, - "latdeg": 51.453838, - "lngdeg": -3.164637, - "truebearingdeg": 175, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422896000, - "latdeg": 51.453784999999996, - "lngdeg": -3.164647, - "truebearingdeg": 185, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422898000, - "latdeg": 51.453719899999996, - "lngdeg": -3.1646799999999997, - "truebearingdeg": 204, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422901000, - "latdeg": 51.453666999999996, - "lngdeg": -3.164757, - "truebearingdeg": 229, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422903000, - "latdeg": 51.453595, - "lngdeg": -3.1649149999999997, - "truebearingdeg": 235, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422905000, - "latdeg": 51.453545, - "lngdeg": -3.165015, - "truebearingdeg": 232, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422907000, - "latdeg": 51.453494899999995, - "lngdeg": -3.165105, - "truebearingdeg": 228, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422909000, - "latdeg": 51.453441999999995, - "lngdeg": -3.1651949999999998, - "truebearingdeg": 224, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422911000, - "latdeg": 51.453387899999996, - "lngdeg": -3.165283, - "truebearingdeg": 224, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422913000, - "latdeg": 51.453331999999996, - "lngdeg": -3.165375, - "truebearingdeg": 226, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422915000, - "latdeg": 51.453269999999996, - "lngdeg": -3.1654619999999998, - "truebearingdeg": 221, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422917000, - "latdeg": 51.453196999999996, - "lngdeg": -3.1655499999999996, - "truebearingdeg": 217, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422919000, - "latdeg": 51.453122, - "lngdeg": -3.165645, - "truebearingdeg": 219, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422921000, - "latdeg": 51.453047999999995, - "lngdeg": -3.165733, - "truebearingdeg": 215, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422923000, - "latdeg": 51.452977, - "lngdeg": -3.165815, - "truebearingdeg": 218, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422925000, - "latdeg": 51.4529, - "lngdeg": -3.1659249999999997, - "truebearingdeg": 224, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422927000, - "latdeg": 51.452827, - "lngdeg": -3.1660519999999996, - "truebearingdeg": 231, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422942000, - "latdeg": 51.452607, - "lngdeg": -3.167118, - "truebearingdeg": 327, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346422944000, - "latdeg": 51.452655, - "lngdeg": -3.167157, - "truebearingdeg": 332, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346422946000, - "latdeg": 51.45272, - "lngdeg": -3.16721, - "truebearingdeg": 330, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422948000, - "latdeg": 51.452805, - "lngdeg": -3.1672949999999997, - "truebearingdeg": 325, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422950000, - "latdeg": 51.452897, - "lngdeg": -3.167405, - "truebearingdeg": 323, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422952000, - "latdeg": 51.452994999999994, - "lngdeg": -3.1675229999999996, - "truebearingdeg": 322, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422954000, - "latdeg": 51.453101999999994, - "lngdeg": -3.167653, - "truebearingdeg": 321, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346422956000, - "latdeg": 51.453196999999996, - "lngdeg": -3.167798, - "truebearingdeg": 314, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422958000, - "latdeg": 51.453288, - "lngdeg": -3.167945, - "truebearingdeg": 316, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346422960000, - "latdeg": 51.453387899999996, - "lngdeg": -3.168072, - "truebearingdeg": 325, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346422963000, - "latdeg": 51.453494899999995, - "lngdeg": -3.1681719999999998, - "truebearingdeg": 332, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422965000, - "latdeg": 51.453689999999995, - "lngdeg": -3.1682949999999996, - "truebearingdeg": 342, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346422967000, - "latdeg": 51.453849999999996, - "lngdeg": -3.1683649999999997, - "truebearingdeg": 345, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346422969000, - "latdeg": 51.454021999999995, - "lngdeg": -3.1684229999999998, - "truebearingdeg": 346, - "knotspeed": 19.43844492440605, - "tack": "PORT" - }, - { - "timepoint": 1346422971000, - "latdeg": 51.454192899999995, - "lngdeg": -3.1685019999999997, - "truebearingdeg": 341, - "knotspeed": 18.898488120950326, - "tack": "PORT" - }, - { - "timepoint": 1346422973000, - "latdeg": 51.454356999999995, - "lngdeg": -3.1685429999999997, - "truebearingdeg": 5, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346422975000, - "latdeg": 51.454487, - "lngdeg": -3.168535, - "truebearingdeg": 358, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346422977000, - "latdeg": 51.454578, - "lngdeg": -3.168583, - "truebearingdeg": 325, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422979000, - "latdeg": 51.454612999999995, - "lngdeg": -3.168695, - "truebearingdeg": 279, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346422981000, - "latdeg": 51.454605, - "lngdeg": -3.168783, - "truebearingdeg": 242, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422983000, - "latdeg": 51.454589999999996, - "lngdeg": -3.168812, - "truebearingdeg": 214, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422985000, - "latdeg": 51.454563, - "lngdeg": -3.168825, - "truebearingdeg": 190, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422987000, - "latdeg": 51.454536999999995, - "lngdeg": -3.1688299, - "truebearingdeg": 188, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422989000, - "latdeg": 51.454487, - "lngdeg": -3.1688519, - "truebearingdeg": 196, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422991000, - "latdeg": 51.454409999999996, - "lngdeg": -3.168888, - "truebearingdeg": 196, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422999000, - "latdeg": 51.453975, - "lngdeg": -3.168948, - "truebearingdeg": 168, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423001000, - "latdeg": 51.453829999999996, - "lngdeg": -3.16886, - "truebearingdeg": 155, - "knotspeed": 16.73866090712743, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423003000, - "latdeg": 51.453692999999994, - "lngdeg": -3.1687179999999997, - "truebearingdeg": 141, - "knotspeed": 17.818574514038875, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423005000, - "latdeg": 51.453568, - "lngdeg": -3.168538, - "truebearingdeg": 133, - "knotspeed": 18.898488120950326, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423007000, - "latdeg": 51.453464999999994, - "lngdeg": -3.168323, - "truebearingdeg": 125, - "knotspeed": 17.818574514038875, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423009000, - "latdeg": 51.453376999999996, - "lngdeg": -3.16811, - "truebearingdeg": 122, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423011000, - "latdeg": 51.453305, - "lngdeg": -3.167903, - "truebearingdeg": 119, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423013000, - "latdeg": 51.453235, - "lngdeg": -3.1677049999999998, - "truebearingdeg": 121, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423015000, - "latdeg": 51.453159899999996, - "lngdeg": -3.1675049, - "truebearingdeg": 121, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423017000, - "latdeg": 51.453086899999995, - "lngdeg": -3.167297, - "truebearingdeg": 119, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423019000, - "latdeg": 51.453022, - "lngdeg": -3.1670849999999997, - "truebearingdeg": 114, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423021000, - "latdeg": 51.452971999999995, - "lngdeg": -3.1668819999999998, - "truebearingdeg": 108, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423024000, - "latdeg": 51.452937999999996, - "lngdeg": -3.166687, - "truebearingdeg": 103, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423026000, - "latdeg": 51.452934899999995, - "lngdeg": -3.1664369999999997, - "truebearingdeg": 74, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423028000, - "latdeg": 51.452965, - "lngdeg": -3.1663199, - "truebearingdeg": 58, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423030000, - "latdeg": 51.453024899999996, - "lngdeg": -3.1662119, - "truebearingdeg": 46, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423032000, - "latdeg": 51.453117, - "lngdeg": -3.1660779999999997, - "truebearingdeg": 41, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423034000, - "latdeg": 51.453232, - "lngdeg": -3.165918, - "truebearingdeg": 41, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423036000, - "latdeg": 51.453362, - "lngdeg": -3.1657469999999996, - "truebearingdeg": 37, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423038000, - "latdeg": 51.453503, - "lngdeg": -3.165593, - "truebearingdeg": 30, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423040000, - "latdeg": 51.453648, - "lngdeg": -3.1654579999999997, - "truebearingdeg": 27, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423042000, - "latdeg": 51.453787999999996, - "lngdeg": -3.1653319, - "truebearingdeg": 29, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423044000, - "latdeg": 51.453914999999995, - "lngdeg": -3.165222, - "truebearingdeg": 28, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423046000, - "latdeg": 51.454032999999995, - "lngdeg": -3.16507, - "truebearingdeg": 43, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423048000, - "latdeg": 51.454119999999996, - "lngdeg": -3.164895, - "truebearingdeg": 55, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423050000, - "latdeg": 51.4542, - "lngdeg": -3.1647219, - "truebearingdeg": 62, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423052000, - "latdeg": 51.454257999999996, - "lngdeg": -3.16456, - "truebearingdeg": 58, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423054000, - "latdeg": 51.454335, - "lngdeg": -3.16444, - "truebearingdeg": 39, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423056000, - "latdeg": 51.454417899999996, - "lngdeg": -3.164355, - "truebearingdeg": 23, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423064000, - "latdeg": 51.454811899999996, - "lngdeg": -3.16463, - "truebearingdeg": 317, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423066000, - "latdeg": 51.454924999999996, - "lngdeg": -3.1647849999999997, - "truebearingdeg": 318, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423068000, - "latdeg": 51.455037, - "lngdeg": -3.1649499999999997, - "truebearingdeg": 320, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423070000, - "latdeg": 51.455155, - "lngdeg": -3.165098, - "truebearingdeg": 325, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423072000, - "latdeg": 51.455276999999995, - "lngdeg": -3.16524, - "truebearingdeg": 321, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423074000, - "latdeg": 51.455394999999996, - "lngdeg": -3.165383, - "truebearingdeg": 323, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423076000, - "latdeg": 51.45552, - "lngdeg": -3.165523, - "truebearingdeg": 326, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423078000, - "latdeg": 51.45565, - "lngdeg": -3.1656619999999998, - "truebearingdeg": 326, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423080000, - "latdeg": 51.455788, - "lngdeg": -3.16578, - "truebearingdeg": 332, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423082000, - "latdeg": 51.455917, - "lngdeg": -3.165877, - "truebearingdeg": 336, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423085000, - "latdeg": 51.456039999999994, - "lngdeg": -3.165953, - "truebearingdeg": 338, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423087000, - "latdeg": 51.456215, - "lngdeg": -3.16606, - "truebearingdeg": 337, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423089000, - "latdeg": 51.456333, - "lngdeg": -3.166133, - "truebearingdeg": 338, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423091000, - "latdeg": 51.456447999999995, - "lngdeg": -3.1662119, - "truebearingdeg": 336, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423093000, - "latdeg": 51.456567, - "lngdeg": -3.166295, - "truebearingdeg": 335, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423095000, - "latdeg": 51.456683, - "lngdeg": -3.1663729999999997, - "truebearingdeg": 340, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423097000, - "latdeg": 51.456801999999996, - "lngdeg": -3.166432, - "truebearingdeg": 345, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423099000, - "latdeg": 51.456931999999995, - "lngdeg": -3.1664629, - "truebearingdeg": 0, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423101000, - "latdeg": 51.457069999999995, - "lngdeg": -3.166422, - "truebearingdeg": 16, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423103000, - "latdeg": 51.457198, - "lngdeg": -3.1663069999999998, - "truebearingdeg": 35, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423105000, - "latdeg": 51.457316999999996, - "lngdeg": -3.166112, - "truebearingdeg": 52, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423107000, - "latdeg": 51.457408, - "lngdeg": -3.165845, - "truebearingdeg": 63, - "knotspeed": 19.978401727861772, - "tack": "PORT" - }, - { - "timepoint": 1346423109000, - "latdeg": 51.457485, - "lngdeg": -3.1655819999999997, - "truebearingdeg": 67, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423111000, - "latdeg": 51.45755, - "lngdeg": -3.1653349, - "truebearingdeg": 65, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423113000, - "latdeg": 51.457626999999995, - "lngdeg": -3.165098, - "truebearingdeg": 62, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423115000, - "latdeg": 51.457695, - "lngdeg": -3.1648829999999997, - "truebearingdeg": 61, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423117000, - "latdeg": 51.457763, - "lngdeg": -3.164682, - "truebearingdeg": 63, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423119000, - "latdeg": 51.457825, - "lngdeg": -3.1644929, - "truebearingdeg": 63, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423121000, - "latdeg": 51.45789, - "lngdeg": -3.1643049999999997, - "truebearingdeg": 63, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423129000, - "latdeg": 51.458037999999995, - "lngdeg": -3.1635519999999997, - "truebearingdeg": 77, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423131000, - "latdeg": 51.458076999999996, - "lngdeg": -3.16342, - "truebearingdeg": 51, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423133000, - "latdeg": 51.458138, - "lngdeg": -3.163348, - "truebearingdeg": 20, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423135000, - "latdeg": 51.458213, - "lngdeg": -3.163328, - "truebearingdeg": 2, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423137000, - "latdeg": 51.45831, - "lngdeg": -3.1633419999999997, - "truebearingdeg": 341, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423139000, - "latdeg": 51.458377999999996, - "lngdeg": -3.163433, - "truebearingdeg": 313, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423141000, - "latdeg": 51.458434999999994, - "lngdeg": -3.163567, - "truebearingdeg": 306, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423143000, - "latdeg": 51.458484999999996, - "lngdeg": -3.1636979999999997, - "truebearingdeg": 305, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423146000, - "latdeg": 51.458538, - "lngdeg": -3.1638249999999997, - "truebearingdeg": 301, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423148000, - "latdeg": 51.458625, - "lngdeg": -3.1640129999999997, - "truebearingdeg": 313, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423150000, - "latdeg": 51.458706899999996, - "lngdeg": -3.164135, - "truebearingdeg": 314, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423152000, - "latdeg": 51.458769999999994, - "lngdeg": -3.164298, - "truebearingdeg": 297, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423154000, - "latdeg": 51.458802, - "lngdeg": -3.1644579999999998, - "truebearingdeg": 272, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423156000, - "latdeg": 51.458769999999994, - "lngdeg": -3.1645429999999997, - "truebearingdeg": 227, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423158000, - "latdeg": 51.458737, - "lngdeg": -3.164593, - "truebearingdeg": 218, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423160000, - "latdeg": 51.458687, - "lngdeg": -3.164653, - "truebearingdeg": 215, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423162000, - "latdeg": 51.458622, - "lngdeg": -3.1647219, - "truebearingdeg": 213, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423164000, - "latdeg": 51.458545, - "lngdeg": -3.164805, - "truebearingdeg": 212, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423166000, - "latdeg": 51.458453, - "lngdeg": -3.164897, - "truebearingdeg": 211, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423168000, - "latdeg": 51.458346999999996, - "lngdeg": -3.165003, - "truebearingdeg": 211, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423170000, - "latdeg": 51.458245, - "lngdeg": -3.1651029999999998, - "truebearingdeg": 210, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423172000, - "latdeg": 51.458132899999995, - "lngdeg": -3.1652069999999997, - "truebearingdeg": 211, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423174000, - "latdeg": 51.458026999999994, - "lngdeg": -3.165317, - "truebearingdeg": 212, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423176000, - "latdeg": 51.457916999999995, - "lngdeg": -3.1654229999999997, - "truebearingdeg": 210, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423178000, - "latdeg": 51.457805, - "lngdeg": -3.1655279999999997, - "truebearingdeg": 210, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423180000, - "latdeg": 51.457702999999995, - "lngdeg": -3.1656329999999997, - "truebearingdeg": 211, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423182000, - "latdeg": 51.457603, - "lngdeg": -3.1657249999999997, - "truebearingdeg": 209, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423184000, - "latdeg": 51.457499999999996, - "lngdeg": -3.1657919999999997, - "truebearingdeg": 195, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423186000, - "latdeg": 51.457378, - "lngdeg": -3.165828, - "truebearingdeg": 191, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423194000, - "latdeg": 51.457017, - "lngdeg": -3.166203, - "truebearingdeg": 236, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423196000, - "latdeg": 51.456981999999996, - "lngdeg": -3.16629, - "truebearingdeg": 237, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423198000, - "latdeg": 51.456947, - "lngdeg": -3.166353, - "truebearingdeg": 223, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423200000, - "latdeg": 51.45691, - "lngdeg": -3.166433, - "truebearingdeg": 242, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423202000, - "latdeg": 51.456902, - "lngdeg": -3.166518, - "truebearingdeg": 280, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346423204000, - "latdeg": 51.456917, - "lngdeg": -3.166572, - "truebearingdeg": 315, - "knotspeed": 2.699784017278618, - "tack": "PORT" - }, - { - "timepoint": 1346423207000, - "latdeg": 51.456936999999996, - "lngdeg": -3.1665799999999997, - "truebearingdeg": 353, - "knotspeed": 2.159827213822894, - "tack": "PORT" - }, - { - "timepoint": 1346423209000, - "latdeg": 51.456973, - "lngdeg": -3.1665929999999998, - "truebearingdeg": 353, - "knotspeed": 2.699784017278618, - "tack": "PORT" - }, - { - "timepoint": 1346423211000, - "latdeg": 51.45702, - "lngdeg": -3.166598, - "truebearingdeg": 0, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346423213000, - "latdeg": 51.457088, - "lngdeg": -3.1665829999999997, - "truebearingdeg": 11, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423215000, - "latdeg": 51.45718, - "lngdeg": -3.16655, - "truebearingdeg": 13, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423217000, - "latdeg": 51.457307, - "lngdeg": -3.1664999999999996, - "truebearingdeg": 14, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423219000, - "latdeg": 51.457443, - "lngdeg": -3.1664049999999997, - "truebearingdeg": 24, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423221000, - "latdeg": 51.457606999999996, - "lngdeg": -3.1662649999999997, - "truebearingdeg": 33, - "knotspeed": 19.43844492440605, - "tack": "PORT" - }, - { - "timepoint": 1346423223000, - "latdeg": 51.457763, - "lngdeg": -3.166105, - "truebearingdeg": 31, - "knotspeed": 18.898488120950326, - "tack": "PORT" - }, - { - "timepoint": 1346423225000, - "latdeg": 51.457916999999995, - "lngdeg": -3.165978, - "truebearingdeg": 24, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423227000, - "latdeg": 51.458079999999995, - "lngdeg": -3.1658749999999998, - "truebearingdeg": 20, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423229000, - "latdeg": 51.458239999999996, - "lngdeg": -3.1657819999999997, - "truebearingdeg": 19, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423231000, - "latdeg": 51.458397, - "lngdeg": -3.1656969999999998, - "truebearingdeg": 20, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423233000, - "latdeg": 51.458557, - "lngdeg": -3.1656079999999998, - "truebearingdeg": 17, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423235000, - "latdeg": 51.458725, - "lngdeg": -3.16552, - "truebearingdeg": 17, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423237000, - "latdeg": 51.458897, - "lngdeg": -3.1654399, - "truebearingdeg": 17, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423239000, - "latdeg": 51.459056999999994, - "lngdeg": -3.165348, - "truebearingdeg": 21, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423241000, - "latdeg": 51.459205, - "lngdeg": -3.1652429, - "truebearingdeg": 23, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423243000, - "latdeg": 51.459347, - "lngdeg": -3.165168, - "truebearingdeg": 12, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423245000, - "latdeg": 51.459468, - "lngdeg": -3.1651379, - "truebearingdeg": 7, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423247000, - "latdeg": 51.459587, - "lngdeg": -3.1651119999999997, - "truebearingdeg": 9, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423249000, - "latdeg": 51.459717, - "lngdeg": -3.1650769999999997, - "truebearingdeg": 10, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423251000, - "latdeg": 51.459857899999996, - "lngdeg": -3.1650299, - "truebearingdeg": 11, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423259000, - "latdeg": 51.460507, - "lngdeg": -3.1647469999999998, - "truebearingdeg": 17, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423261000, - "latdeg": 51.46064, - "lngdeg": -3.164673, - "truebearingdeg": 19, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423263000, - "latdeg": 51.460758, - "lngdeg": -3.164608, - "truebearingdeg": 19, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423265000, - "latdeg": 51.460865, - "lngdeg": -3.1645529999999997, - "truebearingdeg": 18, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423268000, - "latdeg": 51.46096, - "lngdeg": -3.1645049999999997, - "truebearingdeg": 19, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423270000, - "latdeg": 51.46109, - "lngdeg": -3.164437, - "truebearingdeg": 16, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423272000, - "latdeg": 51.461182, - "lngdeg": -3.164405, - "truebearingdeg": 10, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423274000, - "latdeg": 51.461273, - "lngdeg": -3.1643879999999998, - "truebearingdeg": 3, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423276000, - "latdeg": 51.461368, - "lngdeg": -3.16438, - "truebearingdeg": 1, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423278000, - "latdeg": 51.461459999999995, - "lngdeg": -3.164373, - "truebearingdeg": 4, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423280000, - "latdeg": 51.46154, - "lngdeg": -3.1643619999999997, - "truebearingdeg": 6, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423282000, - "latdeg": 51.461631999999994, - "lngdeg": -3.1643369999999997, - "truebearingdeg": 11, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423284000, - "latdeg": 51.461732, - "lngdeg": -3.164288, - "truebearingdeg": 22, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423286000, - "latdeg": 51.461827, - "lngdeg": -3.1641879, - "truebearingdeg": 41, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423288000, - "latdeg": 51.461883, - "lngdeg": -3.164037, - "truebearingdeg": 73, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423290000, - "latdeg": 51.46188, - "lngdeg": -3.163885, - "truebearingdeg": 107, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423292000, - "latdeg": 51.461842, - "lngdeg": -3.163793, - "truebearingdeg": 141, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423294000, - "latdeg": 51.461788, - "lngdeg": -3.163748, - "truebearingdeg": 158, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423296000, - "latdeg": 51.461723, - "lngdeg": -3.1637329999999997, - "truebearingdeg": 176, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423298000, - "latdeg": 51.461655, - "lngdeg": -3.16375, - "truebearingdeg": 189, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423300000, - "latdeg": 51.461569999999995, - "lngdeg": -3.1637779999999998, - "truebearingdeg": 189, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423302000, - "latdeg": 51.461479999999995, - "lngdeg": -3.163808, - "truebearingdeg": 189, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423304000, - "latdeg": 51.461368, - "lngdeg": -3.163843, - "truebearingdeg": 189, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423306000, - "latdeg": 51.46125, - "lngdeg": -3.1638699999999997, - "truebearingdeg": 184, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423308000, - "latdeg": 51.461137, - "lngdeg": -3.163878, - "truebearingdeg": 178, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423310000, - "latdeg": 51.461028, - "lngdeg": -3.1638669999999998, - "truebearingdeg": 176, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423312000, - "latdeg": 51.460933, - "lngdeg": -3.163853, - "truebearingdeg": 175, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423314000, - "latdeg": 51.460853, - "lngdeg": -3.1638479999999998, - "truebearingdeg": 181, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423316000, - "latdeg": 51.460784999999994, - "lngdeg": -3.163858, - "truebearingdeg": 186, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423335000, - "latdeg": 51.459846999999996, - "lngdeg": -3.163528, - "truebearingdeg": 179, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423337000, - "latdeg": 51.459731999999995, - "lngdeg": -3.1634849999999997, - "truebearingdeg": 153, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423339000, - "latdeg": 51.45966, - "lngdeg": -3.1633649999999998, - "truebearingdeg": 121, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423341000, - "latdeg": 51.459637, - "lngdeg": -3.16322, - "truebearingdeg": 91, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423343000, - "latdeg": 51.459652, - "lngdeg": -3.163125, - "truebearingdeg": 58, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346423345000, - "latdeg": 51.459682, - "lngdeg": -3.16307, - "truebearingdeg": 44, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346423347000, - "latdeg": 51.459728, - "lngdeg": -3.1630219, - "truebearingdeg": 25, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423349000, - "latdeg": 51.459793, - "lngdeg": -3.162992, - "truebearingdeg": 10, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423351000, - "latdeg": 51.459885, - "lngdeg": -3.162985, - "truebearingdeg": 359, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423353000, - "latdeg": 51.460003, - "lngdeg": -3.1629829999999997, - "truebearingdeg": 1, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423355000, - "latdeg": 51.460113, - "lngdeg": -3.1629679, - "truebearingdeg": 6, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423357000, - "latdeg": 51.460243, - "lngdeg": -3.1629519999999998, - "truebearingdeg": 4, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423359000, - "latdeg": 51.460367999999995, - "lngdeg": -3.16294, - "truebearingdeg": 3, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423361000, - "latdeg": 51.460494999999995, - "lngdeg": -3.162925, - "truebearingdeg": 2, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423363000, - "latdeg": 51.460601999999994, - "lngdeg": -3.1629519999999998, - "truebearingdeg": 345, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423365000, - "latdeg": 51.460712, - "lngdeg": -3.163005, - "truebearingdeg": 342, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423367000, - "latdeg": 51.46082, - "lngdeg": -3.1630599999999998, - "truebearingdeg": 343, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423369000, - "latdeg": 51.460921899999995, - "lngdeg": -3.1631069999999997, - "truebearingdeg": 346, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423371000, - "latdeg": 51.461009999999995, - "lngdeg": -3.163128, - "truebearingdeg": 353, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423373000, - "latdeg": 51.46109, - "lngdeg": -3.163153, - "truebearingdeg": 354, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423375000, - "latdeg": 51.461163, - "lngdeg": -3.1631679999999998, - "truebearingdeg": 356, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423377000, - "latdeg": 51.461234999999995, - "lngdeg": -3.16317, - "truebearingdeg": 359, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423379000, - "latdeg": 51.461315, - "lngdeg": -3.16317, - "truebearingdeg": 0, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423381000, - "latdeg": 51.461406999999994, - "lngdeg": -3.1631679999999998, - "truebearingdeg": 356, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423389000, - "latdeg": 51.461765, - "lngdeg": -3.163338, - "truebearingdeg": 334, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423392000, - "latdeg": 51.461833, - "lngdeg": -3.163398, - "truebearingdeg": 330, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423394000, - "latdeg": 51.461945, - "lngdeg": -3.163528, - "truebearingdeg": 319, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423396000, - "latdeg": 51.462013, - "lngdeg": -3.16363, - "truebearingdeg": 313, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423398000, - "latdeg": 51.46207, - "lngdeg": -3.1637369, - "truebearingdeg": 307, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423400000, - "latdeg": 51.462112, - "lngdeg": -3.16384, - "truebearingdeg": 297, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423402000, - "latdeg": 51.462131899999996, - "lngdeg": -3.1639269999999997, - "truebearingdeg": 282, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346423404000, - "latdeg": 51.462123, - "lngdeg": -3.163993, - "truebearingdeg": 259, - "knotspeed": 3.7796976241900646, - "tack": "PORT" - }, - { - "timepoint": 1346423406000, - "latdeg": 51.46212, - "lngdeg": -3.1640379999999997, - "truebearingdeg": 262, - "knotspeed": 2.699784017278618, - "tack": "PORT" - }, - { - "timepoint": 1346423408000, - "latdeg": 51.462112, - "lngdeg": -3.1640669999999997, - "truebearingdeg": 253, - "knotspeed": 1.6198704103671706, - "tack": "PORT" - }, - { - "timepoint": 1346423410000, - "latdeg": 51.46212, - "lngdeg": -3.1640769, - "truebearingdeg": 251, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423412000, - "latdeg": 51.462112, - "lngdeg": -3.1640829999999998, - "truebearingdeg": 248, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423414000, - "latdeg": 51.462105, - "lngdeg": -3.164087, - "truebearingdeg": 241, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423418000, - "latdeg": 51.462081999999995, - "lngdeg": -3.1640799, - "truebearingdeg": 185, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423420000, - "latdeg": 51.462055, - "lngdeg": -3.1640919999999997, - "truebearingdeg": 206, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423422000, - "latdeg": 51.462016999999996, - "lngdeg": -3.164128, - "truebearingdeg": 215, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423424000, - "latdeg": 51.461971999999996, - "lngdeg": -3.164182, - "truebearingdeg": 207, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423426000, - "latdeg": 51.461895, - "lngdeg": -3.1642229, - "truebearingdeg": 192, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423428000, - "latdeg": 51.461788, - "lngdeg": -3.16425, - "truebearingdeg": 186, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423430000, - "latdeg": 51.46167, - "lngdeg": -3.164275, - "truebearingdeg": 188, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423432000, - "latdeg": 51.461566999999995, - "lngdeg": -3.1643049999999997, - "truebearingdeg": 188, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423434000, - "latdeg": 51.461475, - "lngdeg": -3.164325, - "truebearingdeg": 185, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423436000, - "latdeg": 51.461394999999996, - "lngdeg": -3.164333, - "truebearingdeg": 183, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423438000, - "latdeg": 51.461312, - "lngdeg": -3.164348, - "truebearingdeg": 188, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423440000, - "latdeg": 51.461227, - "lngdeg": -3.164367, - "truebearingdeg": 187, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423442000, - "latdeg": 51.461155, - "lngdeg": -3.164398, - "truebearingdeg": 203, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423444000, - "latdeg": 51.461098, - "lngdeg": -3.164447, - "truebearingdeg": 216, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423446000, - "latdeg": 51.461059999999996, - "lngdeg": -3.1645019999999997, - "truebearingdeg": 221, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423449000, - "latdeg": 51.461059999999996, - "lngdeg": -3.1645019999999997, - "truebearingdeg": 221, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423453000, - "latdeg": 51.460957, - "lngdeg": -3.164647, - "truebearingdeg": 221, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423455000, - "latdeg": 51.460907, - "lngdeg": -3.164717, - "truebearingdeg": 221, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423457000, - "latdeg": 51.460876999999996, - "lngdeg": -3.164757, - "truebearingdeg": 221, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423459000, - "latdeg": 51.46085, - "lngdeg": -3.1647979, - "truebearingdeg": 225, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423461000, - "latdeg": 51.460826999999995, - "lngdeg": -3.16484, - "truebearingdeg": 229, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423463000, - "latdeg": 51.460803, - "lngdeg": -3.164885, - "truebearingdeg": 232, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423465000, - "latdeg": 51.460784999999994, - "lngdeg": -3.164923, - "truebearingdeg": 224, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423467000, - "latdeg": 51.460761999999995, - "lngdeg": -3.164958, - "truebearingdeg": 224, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423469000, - "latdeg": 51.460735, - "lngdeg": -3.1649919, - "truebearingdeg": 228, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423471000, - "latdeg": 51.460716999999995, - "lngdeg": -3.165032, - "truebearingdeg": 246, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423473000, - "latdeg": 51.460702, - "lngdeg": -3.165085, - "truebearingdeg": 249, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423475000, - "latdeg": 51.46069, - "lngdeg": -3.16513, - "truebearingdeg": 249, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423477000, - "latdeg": 51.460685, - "lngdeg": -3.165155, - "truebearingdeg": 253, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423479000, - "latdeg": 51.46069, - "lngdeg": -3.165178, - "truebearingdeg": 262, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423481000, - "latdeg": 51.460677999999994, - "lngdeg": -3.165213, - "truebearingdeg": 245, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423485000, - "latdeg": 51.460654999999996, - "lngdeg": -3.1652519999999997, - "truebearingdeg": 239, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423489000, - "latdeg": 51.460609999999996, - "lngdeg": -3.165295, - "truebearingdeg": 229, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423491000, - "latdeg": 51.460598, - "lngdeg": -3.1653029999999998, - "truebearingdeg": 225, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423493000, - "latdeg": 51.460583, - "lngdeg": -3.165305, - "truebearingdeg": 220, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423495000, - "latdeg": 51.46056, - "lngdeg": -3.165307, - "truebearingdeg": 172, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423497000, - "latdeg": 51.460539999999995, - "lngdeg": -3.165302, - "truebearingdeg": 166, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423499000, - "latdeg": 51.460518, - "lngdeg": -3.1652869999999997, - "truebearingdeg": 149, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423501000, - "latdeg": 51.460487, - "lngdeg": -3.16525, - "truebearingdeg": 138, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423503000, - "latdeg": 51.46046, - "lngdeg": -3.16519, - "truebearingdeg": 128, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423505000, - "latdeg": 51.460432999999995, - "lngdeg": -3.165155, - "truebearingdeg": 130, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423507000, - "latdeg": 51.460403, - "lngdeg": -3.1651119999999997, - "truebearingdeg": 128, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423509000, - "latdeg": 51.46038, - "lngdeg": -3.16506, - "truebearingdeg": 126, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423511000, - "latdeg": 51.460358, - "lngdeg": -3.1649979999999998, - "truebearingdeg": 126, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423537000, - "latdeg": 51.459817, - "lngdeg": -3.1640379999999997, - "truebearingdeg": 144, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423539000, - "latdeg": 51.459725, - "lngdeg": -3.163955, - "truebearingdeg": 151, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423541000, - "latdeg": 51.459613, - "lngdeg": -3.163858, - "truebearingdeg": 152, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423543000, - "latdeg": 51.459494899999996, - "lngdeg": -3.163747, - "truebearingdeg": 152, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423545000, - "latdeg": 51.459367, - "lngdeg": -3.1636569999999997, - "truebearingdeg": 155, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423547000, - "latdeg": 51.45924, - "lngdeg": -3.1635899999999997, - "truebearingdeg": 168, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423549000, - "latdeg": 51.459125, - "lngdeg": -3.1635899999999997, - "truebearingdeg": 185, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423551000, - "latdeg": 51.459041899999995, - "lngdeg": -3.16362, - "truebearingdeg": 195, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423553000, - "latdeg": 51.458977, - "lngdeg": -3.1636629999999997, - "truebearingdeg": 202, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423555000, - "latdeg": 51.458923, - "lngdeg": -3.163707, - "truebearingdeg": 205, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423557000, - "latdeg": 51.458872899999996, - "lngdeg": -3.163757, - "truebearingdeg": 210, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423559000, - "latdeg": 51.458822999999995, - "lngdeg": -3.163808, - "truebearingdeg": 212, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423561000, - "latdeg": 51.458777999999995, - "lngdeg": -3.163858, - "truebearingdeg": 214, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423563000, - "latdeg": 51.45874, - "lngdeg": -3.1639049999999997, - "truebearingdeg": 220, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423565000, - "latdeg": 51.458709999999996, - "lngdeg": -3.163947, - "truebearingdeg": 214, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423567000, - "latdeg": 51.458675, - "lngdeg": -3.163983, - "truebearingdeg": 219, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423569000, - "latdeg": 51.458647899999995, - "lngdeg": -3.16402, - "truebearingdeg": 216, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423571000, - "latdeg": 51.458625, - "lngdeg": -3.164052, - "truebearingdeg": 215, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - } - ] - }, - { - "name": "Oman Air", - "track": [ - { - "timepoint": 1346422554000, - "latdeg": 51.458301999999996, - "lngdeg": -3.16436, - "truebearingdeg": 294, - "knotspeed": 1.6198704103671706, - "tack": "PORT" - }, - { - "timepoint": 1346422556000, - "latdeg": 51.45831, - "lngdeg": -3.164377, - "truebearingdeg": 297, - "knotspeed": 1.079913606911447, - "tack": "PORT" - }, - { - "timepoint": 1346422558000, - "latdeg": 51.45832, - "lngdeg": -3.164387, - "truebearingdeg": 300, - "knotspeed": 1.079913606911447, - "tack": "PORT" - }, - { - "timepoint": 1346422560000, - "latdeg": 51.458327999999995, - "lngdeg": -3.164393, - "truebearingdeg": 305, - "knotspeed": 1.079913606911447, - "tack": "PORT" - }, - { - "timepoint": 1346422562000, - "latdeg": 51.458343, - "lngdeg": -3.1643999999999997, - "truebearingdeg": 336, - "knotspeed": 2.159827213822894, - "tack": "PORT" - }, - { - "timepoint": 1346422568000, - "latdeg": 51.458487999999996, - "lngdeg": -3.164488, - "truebearingdeg": 353, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422570000, - "latdeg": 51.45858, - "lngdeg": -3.1644699999999997, - "truebearingdeg": 15, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422572000, - "latdeg": 51.458683, - "lngdeg": -3.164373, - "truebearingdeg": 37, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346422574000, - "latdeg": 51.458754899999995, - "lngdeg": -3.1642229, - "truebearingdeg": 60, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346422576000, - "latdeg": 51.458805, - "lngdeg": -3.1640599999999997, - "truebearingdeg": 67, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422578000, - "latdeg": 51.458839999999995, - "lngdeg": -3.16391, - "truebearingdeg": 70, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422580000, - "latdeg": 51.458872899999996, - "lngdeg": -3.163777, - "truebearingdeg": 66, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422582000, - "latdeg": 51.458908, - "lngdeg": -3.1636629999999997, - "truebearingdeg": 59, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422584000, - "latdeg": 51.458949999999994, - "lngdeg": -3.1635579999999996, - "truebearingdeg": 51, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422586000, - "latdeg": 51.459002999999996, - "lngdeg": -3.163465, - "truebearingdeg": 44, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422589000, - "latdeg": 51.459064999999995, - "lngdeg": -3.1633769999999997, - "truebearingdeg": 41, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422591000, - "latdeg": 51.459167, - "lngdeg": -3.163243, - "truebearingdeg": 41, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422593000, - "latdeg": 51.459236999999995, - "lngdeg": -3.163148, - "truebearingdeg": 40, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422595000, - "latdeg": 51.459305, - "lngdeg": -3.1630529999999997, - "truebearingdeg": 40, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422597000, - "latdeg": 51.459376999999996, - "lngdeg": -3.162962, - "truebearingdeg": 36, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422599000, - "latdeg": 51.459449899999996, - "lngdeg": -3.162877, - "truebearingdeg": 32, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422601000, - "latdeg": 51.45953, - "lngdeg": -3.162798, - "truebearingdeg": 34, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422603000, - "latdeg": 51.459607, - "lngdeg": -3.162722, - "truebearingdeg": 36, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422605000, - "latdeg": 51.459677899999996, - "lngdeg": -3.1626399999999997, - "truebearingdeg": 36, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422607000, - "latdeg": 51.459751999999995, - "lngdeg": -3.1625549, - "truebearingdeg": 36, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422609000, - "latdeg": 51.45982, - "lngdeg": -3.162473, - "truebearingdeg": 35, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422611000, - "latdeg": 51.459888, - "lngdeg": -3.162395, - "truebearingdeg": 35, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422615000, - "latdeg": 51.460018, - "lngdeg": -3.162232, - "truebearingdeg": 36, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422617000, - "latdeg": 51.460083, - "lngdeg": -3.1621569999999997, - "truebearingdeg": 36, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422619000, - "latdeg": 51.460142999999995, - "lngdeg": -3.162083, - "truebearingdeg": 35, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422621000, - "latdeg": 51.460204999999995, - "lngdeg": -3.16202, - "truebearingdeg": 31, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346422623000, - "latdeg": 51.460269999999994, - "lngdeg": -3.161965, - "truebearingdeg": 29, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346422625000, - "latdeg": 51.460335, - "lngdeg": -3.1619069, - "truebearingdeg": 30, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422627000, - "latdeg": 51.460403, - "lngdeg": -3.1618429999999997, - "truebearingdeg": 30, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422636000, - "latdeg": 51.460808, - "lngdeg": -3.1617599999999997, - "truebearingdeg": 305, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346422638000, - "latdeg": 51.460823, - "lngdeg": -3.161822, - "truebearingdeg": 265, - "knotspeed": 3.239740820734341, - "tack": "PORT" - }, - { - "timepoint": 1346422640000, - "latdeg": 51.460814899999995, - "lngdeg": -3.161857, - "truebearingdeg": 241, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422642000, - "latdeg": 51.460808, - "lngdeg": -3.16187, - "truebearingdeg": 237, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422644000, - "latdeg": 51.460797, - "lngdeg": -3.161882, - "truebearingdeg": 235, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422646000, - "latdeg": 51.460784999999994, - "lngdeg": -3.161892, - "truebearingdeg": 232, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422648000, - "latdeg": 51.460772999999996, - "lngdeg": -3.161908, - "truebearingdeg": 209, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422651000, - "latdeg": 51.46075, - "lngdeg": -3.1619419, - "truebearingdeg": 228, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422655000, - "latdeg": 51.460719999999995, - "lngdeg": -3.1620299999999997, - "truebearingdeg": 231, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422657000, - "latdeg": 51.460707899999996, - "lngdeg": -3.162048, - "truebearingdeg": 232, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422659000, - "latdeg": 51.460705, - "lngdeg": -3.16206, - "truebearingdeg": 232, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422661000, - "latdeg": 51.460696999999996, - "lngdeg": -3.162067, - "truebearingdeg": 231, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422663000, - "latdeg": 51.46069, - "lngdeg": -3.1620749999999997, - "truebearingdeg": 228, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422667000, - "latdeg": 51.46067, - "lngdeg": -3.1620749999999997, - "truebearingdeg": 215, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422669000, - "latdeg": 51.460654999999996, - "lngdeg": -3.16208, - "truebearingdeg": 198, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422671000, - "latdeg": 51.460637, - "lngdeg": -3.162102, - "truebearingdeg": 217, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422673000, - "latdeg": 51.460612999999995, - "lngdeg": -3.1621479999999997, - "truebearingdeg": 240, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422675000, - "latdeg": 51.460595, - "lngdeg": -3.1621949999999996, - "truebearingdeg": 232, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422677000, - "latdeg": 51.460575, - "lngdeg": -3.162235, - "truebearingdeg": 222, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422679000, - "latdeg": 51.460547999999996, - "lngdeg": -3.16227, - "truebearingdeg": 213, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422681000, - "latdeg": 51.46051, - "lngdeg": -3.162302, - "truebearingdeg": 207, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422683000, - "latdeg": 51.460453, - "lngdeg": -3.162347, - "truebearingdeg": 203, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422685000, - "latdeg": 51.460373, - "lngdeg": -3.162398, - "truebearingdeg": 201, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422687000, - "latdeg": 51.460257999999996, - "lngdeg": -3.162468, - "truebearingdeg": 203, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422689000, - "latdeg": 51.460136999999996, - "lngdeg": -3.1625479999999997, - "truebearingdeg": 202, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422691000, - "latdeg": 51.460003, - "lngdeg": -3.162632, - "truebearingdeg": 201, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422700000, - "latdeg": 51.459415, - "lngdeg": -3.162953, - "truebearingdeg": 198, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422702000, - "latdeg": 51.459302, - "lngdeg": -3.163032, - "truebearingdeg": 202, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422704000, - "latdeg": 51.459171999999995, - "lngdeg": -3.163125, - "truebearingdeg": 204, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422706000, - "latdeg": 51.45905, - "lngdeg": -3.1632469999999997, - "truebearingdeg": 212, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422708000, - "latdeg": 51.458926999999996, - "lngdeg": -3.163372, - "truebearingdeg": 216, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422710000, - "latdeg": 51.458805, - "lngdeg": -3.1634979999999997, - "truebearingdeg": 211, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422713000, - "latdeg": 51.458675, - "lngdeg": -3.16363, - "truebearingdeg": 214, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422715000, - "latdeg": 51.458458, - "lngdeg": -3.1637749, - "truebearingdeg": 205, - "knotspeed": 16.73866090712743, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422717000, - "latdeg": 51.45832, - "lngdeg": -3.1638799, - "truebearingdeg": 207, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422719000, - "latdeg": 51.458183, - "lngdeg": -3.164002, - "truebearingdeg": 207, - "knotspeed": 17.278617710583152, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422721000, - "latdeg": 51.458042, - "lngdeg": -3.1641299999999997, - "truebearingdeg": 208, - "knotspeed": 17.278617710583152, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422723000, - "latdeg": 51.457907999999996, - "lngdeg": -3.164263, - "truebearingdeg": 208, - "knotspeed": 17.278617710583152, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422725000, - "latdeg": 51.457763, - "lngdeg": -3.164387, - "truebearingdeg": 206, - "knotspeed": 17.818574514038875, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422727000, - "latdeg": 51.457614899999996, - "lngdeg": -3.164498, - "truebearingdeg": 200, - "knotspeed": 17.278617710583152, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422729000, - "latdeg": 51.457467, - "lngdeg": -3.1645969999999997, - "truebearingdeg": 202, - "knotspeed": 16.73866090712743, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422731000, - "latdeg": 51.457336999999995, - "lngdeg": -3.164672, - "truebearingdeg": 200, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422733000, - "latdeg": 51.457221999999994, - "lngdeg": -3.164727, - "truebearingdeg": 196, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422735000, - "latdeg": 51.457122999999996, - "lngdeg": -3.16477, - "truebearingdeg": 198, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422737000, - "latdeg": 51.457017, - "lngdeg": -3.1648329, - "truebearingdeg": 200, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422739000, - "latdeg": 51.456897999999995, - "lngdeg": -3.164908, - "truebearingdeg": 202, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422741000, - "latdeg": 51.456772, - "lngdeg": -3.164993, - "truebearingdeg": 202, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422743000, - "latdeg": 51.456649999999996, - "lngdeg": -3.16507, - "truebearingdeg": 203, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422745000, - "latdeg": 51.456537999999995, - "lngdeg": -3.165148, - "truebearingdeg": 204, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422747000, - "latdeg": 51.456432, - "lngdeg": -3.165248, - "truebearingdeg": 211, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422749000, - "latdeg": 51.456317999999996, - "lngdeg": -3.165355, - "truebearingdeg": 208, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422751000, - "latdeg": 51.456212, - "lngdeg": -3.1654679999999997, - "truebearingdeg": 211, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422753000, - "latdeg": 51.456092999999996, - "lngdeg": -3.1655819999999997, - "truebearingdeg": 210, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422755000, - "latdeg": 51.455977999999995, - "lngdeg": -3.165688, - "truebearingdeg": 210, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422776000, - "latdeg": 51.455022, - "lngdeg": -3.166778, - "truebearingdeg": 220, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422778000, - "latdeg": 51.454947999999995, - "lngdeg": -3.1668779999999996, - "truebearingdeg": 220, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422780000, - "latdeg": 51.454879999999996, - "lngdeg": -3.1669769999999997, - "truebearingdeg": 218, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422782000, - "latdeg": 51.454806999999995, - "lngdeg": -3.167068, - "truebearingdeg": 216, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422784000, - "latdeg": 51.454735, - "lngdeg": -3.16716, - "truebearingdeg": 218, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422786000, - "latdeg": 51.454662, - "lngdeg": -3.1672599999999997, - "truebearingdeg": 220, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422788000, - "latdeg": 51.454586899999995, - "lngdeg": -3.167348, - "truebearingdeg": 214, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422790000, - "latdeg": 51.454507, - "lngdeg": -3.167423, - "truebearingdeg": 207, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422792000, - "latdeg": 51.454425, - "lngdeg": -3.167507, - "truebearingdeg": 216, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422794000, - "latdeg": 51.454353, - "lngdeg": -3.167605, - "truebearingdeg": 226, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422796000, - "latdeg": 51.454308, - "lngdeg": -3.1677179, - "truebearingdeg": 248, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422798000, - "latdeg": 51.454311999999994, - "lngdeg": -3.167815, - "truebearingdeg": 285, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346422800000, - "latdeg": 51.454335, - "lngdeg": -3.167868, - "truebearingdeg": 305, - "knotspeed": 4.319654427645788, - "tack": "PORT" - }, - { - "timepoint": 1346422802000, - "latdeg": 51.454361999999996, - "lngdeg": -3.1679169999999996, - "truebearingdeg": 316, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346422804000, - "latdeg": 51.454406999999996, - "lngdeg": -3.167993, - "truebearingdeg": 315, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422806000, - "latdeg": 51.454471999999996, - "lngdeg": -3.168107, - "truebearingdeg": 307, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422808000, - "latdeg": 51.454513, - "lngdeg": -3.168247, - "truebearingdeg": 272, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422810000, - "latdeg": 51.454494999999994, - "lngdeg": -3.1683269999999997, - "truebearingdeg": 232, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422812000, - "latdeg": 51.454468, - "lngdeg": -3.168363, - "truebearingdeg": 215, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422814000, - "latdeg": 51.454432999999995, - "lngdeg": -3.168392, - "truebearingdeg": 210, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422816000, - "latdeg": 51.454383, - "lngdeg": -3.1684349999999997, - "truebearingdeg": 209, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422818000, - "latdeg": 51.454327, - "lngdeg": -3.168487, - "truebearingdeg": 207, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422820000, - "latdeg": 51.454246999999995, - "lngdeg": -3.168552, - "truebearingdeg": 212, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422828000, - "latdeg": 51.453956999999996, - "lngdeg": -3.168727, - "truebearingdeg": 171, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422830000, - "latdeg": 51.453894999999996, - "lngdeg": -3.168678, - "truebearingdeg": 147, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422832000, - "latdeg": 51.453835, - "lngdeg": -3.1685819, - "truebearingdeg": 128, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422835000, - "latdeg": 51.45377, - "lngdeg": -3.1684069999999998, - "truebearingdeg": 118, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422837000, - "latdeg": 51.453685, - "lngdeg": -3.1680979999999996, - "truebearingdeg": 110, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422839000, - "latdeg": 51.45364, - "lngdeg": -3.167865, - "truebearingdeg": 106, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422841000, - "latdeg": 51.453604999999996, - "lngdeg": -3.16763, - "truebearingdeg": 101, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422843000, - "latdeg": 51.453578, - "lngdeg": -3.1674029999999997, - "truebearingdeg": 99, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422845000, - "latdeg": 51.453562999999995, - "lngdeg": -3.1671869999999998, - "truebearingdeg": 95, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422847000, - "latdeg": 51.453551999999995, - "lngdeg": -3.166985, - "truebearingdeg": 97, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422849000, - "latdeg": 51.453533, - "lngdeg": -3.166797, - "truebearingdeg": 99, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422851000, - "latdeg": 51.453517999999995, - "lngdeg": -3.1666149999999997, - "truebearingdeg": 100, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422853000, - "latdeg": 51.453494899999995, - "lngdeg": -3.1664469, - "truebearingdeg": 105, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422855000, - "latdeg": 51.453464999999994, - "lngdeg": -3.166283, - "truebearingdeg": 105, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422857000, - "latdeg": 51.453452999999996, - "lngdeg": -3.166137, - "truebearingdeg": 88, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422859000, - "latdeg": 51.453472, - "lngdeg": -3.166013, - "truebearingdeg": 68, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346422861000, - "latdeg": 51.453503, - "lngdeg": -3.165928, - "truebearingdeg": 50, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346422863000, - "latdeg": 51.453545, - "lngdeg": -3.165858, - "truebearingdeg": 39, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346422865000, - "latdeg": 51.45361, - "lngdeg": -3.165797, - "truebearingdeg": 30, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422867000, - "latdeg": 51.453677899999995, - "lngdeg": -3.165733, - "truebearingdeg": 28, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422869000, - "latdeg": 51.453758, - "lngdeg": -3.165663, - "truebearingdeg": 28, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422871000, - "latdeg": 51.453849999999996, - "lngdeg": -3.165587, - "truebearingdeg": 29, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422873000, - "latdeg": 51.453927, - "lngdeg": -3.16551, - "truebearingdeg": 31, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422875000, - "latdeg": 51.454007, - "lngdeg": -3.165418, - "truebearingdeg": 35, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422877000, - "latdeg": 51.454082, - "lngdeg": -3.1653149999999997, - "truebearingdeg": 41, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422879000, - "latdeg": 51.454163, - "lngdeg": -3.1651879999999997, - "truebearingdeg": 48, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422881000, - "latdeg": 51.45422, - "lngdeg": -3.1650549999999997, - "truebearingdeg": 59, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422883000, - "latdeg": 51.454246999999995, - "lngdeg": -3.164932, - "truebearingdeg": 85, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422885000, - "latdeg": 51.454246999999995, - "lngdeg": -3.1648099999999997, - "truebearingdeg": 89, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422900000, - "latdeg": 51.454569899999996, - "lngdeg": -3.164522, - "truebearingdeg": 334, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422902000, - "latdeg": 51.454646999999994, - "lngdeg": -3.1646099999999997, - "truebearingdeg": 320, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422904000, - "latdeg": 51.454712, - "lngdeg": -3.1647019999999997, - "truebearingdeg": 317, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422906000, - "latdeg": 51.454772999999996, - "lngdeg": -3.1647949, - "truebearingdeg": 315, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422908000, - "latdeg": 51.45483, - "lngdeg": -3.1648899999999998, - "truebearingdeg": 313, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422910000, - "latdeg": 51.454895, - "lngdeg": -3.1649979999999998, - "truebearingdeg": 309, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422912000, - "latdeg": 51.454947999999995, - "lngdeg": -3.165097, - "truebearingdeg": 312, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422914000, - "latdeg": 51.455002, - "lngdeg": -3.1651979999999997, - "truebearingdeg": 309, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422916000, - "latdeg": 51.455054999999994, - "lngdeg": -3.165313, - "truebearingdeg": 300, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422918000, - "latdeg": 51.455104999999996, - "lngdeg": -3.165445, - "truebearingdeg": 305, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422920000, - "latdeg": 51.455161999999994, - "lngdeg": -3.165575, - "truebearingdeg": 307, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422922000, - "latdeg": 51.455222899999995, - "lngdeg": -3.165702, - "truebearingdeg": 308, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422924000, - "latdeg": 51.455279999999995, - "lngdeg": -3.165825, - "truebearingdeg": 310, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422926000, - "latdeg": 51.455345, - "lngdeg": -3.1659479999999998, - "truebearingdeg": 307, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422928000, - "latdeg": 51.455402, - "lngdeg": -3.1660679999999997, - "truebearingdeg": 312, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422930000, - "latdeg": 51.455462999999995, - "lngdeg": -3.166168, - "truebearingdeg": 312, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422932000, - "latdeg": 51.455524999999994, - "lngdeg": -3.166263, - "truebearingdeg": 315, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422934000, - "latdeg": 51.455593, - "lngdeg": -3.1663579, - "truebearingdeg": 318, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422936000, - "latdeg": 51.455661899999996, - "lngdeg": -3.16646, - "truebearingdeg": 315, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422938000, - "latdeg": 51.455726999999996, - "lngdeg": -3.1665699999999997, - "truebearingdeg": 312, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422940000, - "latdeg": 51.45578, - "lngdeg": -3.166702, - "truebearingdeg": 286, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422942000, - "latdeg": 51.455757, - "lngdeg": -3.166792, - "truebearingdeg": 238, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422944000, - "latdeg": 51.455737899999995, - "lngdeg": -3.16682, - "truebearingdeg": 219, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422946000, - "latdeg": 51.455712, - "lngdeg": -3.1668529999999997, - "truebearingdeg": 216, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422948000, - "latdeg": 51.455661899999996, - "lngdeg": -3.1669099999999997, - "truebearingdeg": 217, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422950000, - "latdeg": 51.455597, - "lngdeg": -3.1669829999999997, - "truebearingdeg": 210, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422964000, - "latdeg": 51.454997999999996, - "lngdeg": -3.1675619999999998, - "truebearingdeg": 214, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422966000, - "latdeg": 51.454918, - "lngdeg": -3.16765, - "truebearingdeg": 213, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422968000, - "latdeg": 51.454833, - "lngdeg": -3.167738, - "truebearingdeg": 213, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422970000, - "latdeg": 51.45475, - "lngdeg": -3.167837, - "truebearingdeg": 216, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422972000, - "latdeg": 51.454677999999994, - "lngdeg": -3.167942, - "truebearingdeg": 219, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422974000, - "latdeg": 51.454592999999996, - "lngdeg": -3.1680449999999998, - "truebearingdeg": 220, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422976000, - "latdeg": 51.454513, - "lngdeg": -3.168148, - "truebearingdeg": 216, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422978000, - "latdeg": 51.454425, - "lngdeg": -3.168247, - "truebearingdeg": 217, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422980000, - "latdeg": 51.45435, - "lngdeg": -3.1683399999999997, - "truebearingdeg": 215, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422982000, - "latdeg": 51.454288, - "lngdeg": -3.168412, - "truebearingdeg": 215, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422984000, - "latdeg": 51.45422, - "lngdeg": -3.168488, - "truebearingdeg": 216, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422986000, - "latdeg": 51.454158, - "lngdeg": -3.16856, - "truebearingdeg": 214, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422988000, - "latdeg": 51.454093, - "lngdeg": -3.1686329, - "truebearingdeg": 219, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422990000, - "latdeg": 51.454028, - "lngdeg": -3.1687019999999997, - "truebearingdeg": 205, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422992000, - "latdeg": 51.453945, - "lngdeg": -3.1687279999999998, - "truebearingdeg": 179, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422994000, - "latdeg": 51.453865, - "lngdeg": -3.168707, - "truebearingdeg": 161, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422996000, - "latdeg": 51.453767, - "lngdeg": -3.168628, - "truebearingdeg": 146, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422998000, - "latdeg": 51.453682, - "lngdeg": -3.1685, - "truebearingdeg": 132, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423000000, - "latdeg": 51.453613, - "lngdeg": -3.1683149999999998, - "truebearingdeg": 115, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423002000, - "latdeg": 51.453568, - "lngdeg": -3.168107, - "truebearingdeg": 109, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423004000, - "latdeg": 51.453522, - "lngdeg": -3.16789, - "truebearingdeg": 110, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423006000, - "latdeg": 51.453468, - "lngdeg": -3.167697, - "truebearingdeg": 114, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423008000, - "latdeg": 51.453418, - "lngdeg": -3.1674949999999997, - "truebearingdeg": 108, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423010000, - "latdeg": 51.453392, - "lngdeg": -3.167303, - "truebearingdeg": 99, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423012000, - "latdeg": 51.453376999999996, - "lngdeg": -3.167143, - "truebearingdeg": 85, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423015000, - "latdeg": 51.453402999999994, - "lngdeg": -3.167017, - "truebearingdeg": 60, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423021000, - "latdeg": 51.453601899999995, - "lngdeg": -3.1667899999999998, - "truebearingdeg": 34, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423023000, - "latdeg": 51.45375, - "lngdeg": -3.1666079999999996, - "truebearingdeg": 41, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423025000, - "latdeg": 51.453849999999996, - "lngdeg": -3.1664469, - "truebearingdeg": 47, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423027000, - "latdeg": 51.453945, - "lngdeg": -3.1662679999999996, - "truebearingdeg": 50, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423029000, - "latdeg": 51.454037, - "lngdeg": -3.1660779999999997, - "truebearingdeg": 53, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423031000, - "latdeg": 51.454125, - "lngdeg": -3.1658899999999996, - "truebearingdeg": 51, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423033000, - "latdeg": 51.454197, - "lngdeg": -3.165708, - "truebearingdeg": 60, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423035000, - "latdeg": 51.454252999999994, - "lngdeg": -3.1655319, - "truebearingdeg": 66, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423037000, - "latdeg": 51.454299999999996, - "lngdeg": -3.165348, - "truebearingdeg": 66, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423039000, - "latdeg": 51.45435, - "lngdeg": -3.165183, - "truebearingdeg": 68, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423041000, - "latdeg": 51.454392, - "lngdeg": -3.165035, - "truebearingdeg": 70, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423043000, - "latdeg": 51.454417899999996, - "lngdeg": -3.1649, - "truebearingdeg": 72, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423045000, - "latdeg": 51.454445, - "lngdeg": -3.164765, - "truebearingdeg": 72, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423047000, - "latdeg": 51.454474999999995, - "lngdeg": -3.1646449999999997, - "truebearingdeg": 62, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423049000, - "latdeg": 51.454527999999996, - "lngdeg": -3.16457, - "truebearingdeg": 35, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423051000, - "latdeg": 51.454586899999995, - "lngdeg": -3.164533, - "truebearingdeg": 7, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423053000, - "latdeg": 51.454657999999995, - "lngdeg": -3.1645469999999998, - "truebearingdeg": 345, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423055000, - "latdeg": 51.454738, - "lngdeg": -3.164608, - "truebearingdeg": 332, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423057000, - "latdeg": 51.454837999999995, - "lngdeg": -3.164703, - "truebearingdeg": 329, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423059000, - "latdeg": 51.454952, - "lngdeg": -3.1648199999999997, - "truebearingdeg": 327, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423061000, - "latdeg": 51.455067, - "lngdeg": -3.164962, - "truebearingdeg": 321, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423063000, - "latdeg": 51.455185, - "lngdeg": -3.165108, - "truebearingdeg": 326, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423065000, - "latdeg": 51.455326899999996, - "lngdeg": -3.165232, - "truebearingdeg": 332, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423067000, - "latdeg": 51.455472, - "lngdeg": -3.165355, - "truebearingdeg": 331, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423069000, - "latdeg": 51.455619999999996, - "lngdeg": -3.1654869999999997, - "truebearingdeg": 331, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423071000, - "latdeg": 51.455768, - "lngdeg": -3.165623, - "truebearingdeg": 333, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423073000, - "latdeg": 51.455909999999996, - "lngdeg": -3.1657379999999997, - "truebearingdeg": 334, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423075000, - "latdeg": 51.456043, - "lngdeg": -3.1658399999999998, - "truebearingdeg": 334, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423077000, - "latdeg": 51.456168, - "lngdeg": -3.1659379999999997, - "truebearingdeg": 333, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423080000, - "latdeg": 51.456292, - "lngdeg": -3.166022, - "truebearingdeg": 337, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423097000, - "latdeg": 51.457363, - "lngdeg": -3.165758, - "truebearingdeg": 70, - "knotspeed": 19.43844492440605, - "tack": "PORT" - }, - { - "timepoint": 1346423099000, - "latdeg": 51.457432, - "lngdeg": -3.1654869999999997, - "truebearingdeg": 66, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423101000, - "latdeg": 51.457499999999996, - "lngdeg": -3.1652679999999997, - "truebearingdeg": 63, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423103000, - "latdeg": 51.45758, - "lngdeg": -3.165043, - "truebearingdeg": 56, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423105000, - "latdeg": 51.457679999999996, - "lngdeg": -3.1648319999999996, - "truebearingdeg": 52, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423107000, - "latdeg": 51.457763, - "lngdeg": -3.164653, - "truebearingdeg": 50, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423109000, - "latdeg": 51.457842899999996, - "lngdeg": -3.164498, - "truebearingdeg": 51, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423111000, - "latdeg": 51.457923, - "lngdeg": -3.1643369999999997, - "truebearingdeg": 57, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423113000, - "latdeg": 51.457985, - "lngdeg": -3.1641499, - "truebearingdeg": 62, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423115000, - "latdeg": 51.458034999999995, - "lngdeg": -3.163963, - "truebearingdeg": 65, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423117000, - "latdeg": 51.458065, - "lngdeg": -3.1637999999999997, - "truebearingdeg": 74, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423119000, - "latdeg": 51.458099999999995, - "lngdeg": -3.1636479, - "truebearingdeg": 70, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423121000, - "latdeg": 51.45813, - "lngdeg": -3.1635199999999997, - "truebearingdeg": 64, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423123000, - "latdeg": 51.458183, - "lngdeg": -3.163418, - "truebearingdeg": 38, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423125000, - "latdeg": 51.458239999999996, - "lngdeg": -3.1633769999999997, - "truebearingdeg": 15, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423127000, - "latdeg": 51.458301999999996, - "lngdeg": -3.1633799999999996, - "truebearingdeg": 346, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423129000, - "latdeg": 51.458369999999995, - "lngdeg": -3.163427, - "truebearingdeg": 323, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423131000, - "latdeg": 51.458431999999995, - "lngdeg": -3.1635, - "truebearingdeg": 318, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423133000, - "latdeg": 51.458481899999995, - "lngdeg": -3.1635999999999997, - "truebearingdeg": 306, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423135000, - "latdeg": 51.458529999999996, - "lngdeg": -3.163713, - "truebearingdeg": 307, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423137000, - "latdeg": 51.458594999999995, - "lngdeg": -3.1638249999999997, - "truebearingdeg": 314, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423139000, - "latdeg": 51.458662999999994, - "lngdeg": -3.163948, - "truebearingdeg": 310, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423141000, - "latdeg": 51.458717, - "lngdeg": -3.164088, - "truebearingdeg": 294, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423143000, - "latdeg": 51.458706899999996, - "lngdeg": -3.1642099999999997, - "truebearingdeg": 254, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423145000, - "latdeg": 51.458683, - "lngdeg": -3.164278, - "truebearingdeg": 227, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423151000, - "latdeg": 51.458594999999995, - "lngdeg": -3.164348, - "truebearingdeg": 206, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423154000, - "latdeg": 51.458526899999995, - "lngdeg": -3.1644129999999997, - "truebearingdeg": 206, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423156000, - "latdeg": 51.458389999999994, - "lngdeg": -3.1645369999999997, - "truebearingdeg": 209, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423158000, - "latdeg": 51.458293, - "lngdeg": -3.164627, - "truebearingdeg": 211, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423160000, - "latdeg": 51.458202, - "lngdeg": -3.1647179999999997, - "truebearingdeg": 210, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423162000, - "latdeg": 51.458102999999994, - "lngdeg": -3.1648069999999997, - "truebearingdeg": 211, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423164000, - "latdeg": 51.458008, - "lngdeg": -3.1649049999999996, - "truebearingdeg": 211, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423166000, - "latdeg": 51.457919999999994, - "lngdeg": -3.165015, - "truebearingdeg": 217, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423168000, - "latdeg": 51.457817, - "lngdeg": -3.165143, - "truebearingdeg": 217, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423170000, - "latdeg": 51.457724999999996, - "lngdeg": -3.1652649999999998, - "truebearingdeg": 220, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423172000, - "latdeg": 51.457632999999994, - "lngdeg": -3.1653879999999996, - "truebearingdeg": 219, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423174000, - "latdeg": 51.457558, - "lngdeg": -3.1654929999999997, - "truebearingdeg": 219, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423176000, - "latdeg": 51.457485, - "lngdeg": -3.165587, - "truebearingdeg": 218, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423178000, - "latdeg": 51.457412999999995, - "lngdeg": -3.165673, - "truebearingdeg": 219, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423180000, - "latdeg": 51.457347999999996, - "lngdeg": -3.165762, - "truebearingdeg": 220, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423182000, - "latdeg": 51.45728, - "lngdeg": -3.1658549999999996, - "truebearingdeg": 219, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423184000, - "latdeg": 51.457206899999996, - "lngdeg": -3.165953, - "truebearingdeg": 218, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423186000, - "latdeg": 51.457135, - "lngdeg": -3.166047, - "truebearingdeg": 217, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423188000, - "latdeg": 51.457046999999996, - "lngdeg": -3.16615, - "truebearingdeg": 221, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423190000, - "latdeg": 51.456981999999996, - "lngdeg": -3.166298, - "truebearingdeg": 240, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423192000, - "latdeg": 51.45697, - "lngdeg": -3.166432, - "truebearingdeg": 279, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423194000, - "latdeg": 51.457004999999995, - "lngdeg": -3.166493, - "truebearingdeg": 307, - "knotspeed": 4.319654427645788, - "tack": "PORT" - }, - { - "timepoint": 1346423196000, - "latdeg": 51.457032, - "lngdeg": -3.1665229999999998, - "truebearingdeg": 328, - "knotspeed": 3.7796976241900646, - "tack": "PORT" - }, - { - "timepoint": 1346423198000, - "latdeg": 51.457073, - "lngdeg": -3.1665419999999997, - "truebearingdeg": 342, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346423200000, - "latdeg": 51.457127, - "lngdeg": -3.166557, - "truebearingdeg": 350, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423202000, - "latdeg": 51.457218, - "lngdeg": -3.16656, - "truebearingdeg": 3, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423204000, - "latdeg": 51.457325, - "lngdeg": -3.166522, - "truebearingdeg": 19, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423206000, - "latdeg": 51.457451999999996, - "lngdeg": -3.16643, - "truebearingdeg": 26, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423208000, - "latdeg": 51.457572899999995, - "lngdeg": -3.166337, - "truebearingdeg": 25, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423210000, - "latdeg": 51.457698, - "lngdeg": -3.166238, - "truebearingdeg": 26, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423220000, - "latdeg": 51.458473, - "lngdeg": -3.1657129999999998, - "truebearingdeg": 19, - "knotspeed": 20.518358531317496, - "tack": "PORT" - }, - { - "timepoint": 1346423222000, - "latdeg": 51.458659999999995, - "lngdeg": -3.165623, - "truebearingdeg": 16, - "knotspeed": 19.978401727861772, - "tack": "PORT" - }, - { - "timepoint": 1346423224000, - "latdeg": 51.458835, - "lngdeg": -3.165545, - "truebearingdeg": 15, - "knotspeed": 19.43844492440605, - "tack": "PORT" - }, - { - "timepoint": 1346423226000, - "latdeg": 51.459007, - "lngdeg": -3.1654679999999997, - "truebearingdeg": 15, - "knotspeed": 18.898488120950326, - "tack": "PORT" - }, - { - "timepoint": 1346423228000, - "latdeg": 51.459162899999995, - "lngdeg": -3.1654169999999997, - "truebearingdeg": 11, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423230000, - "latdeg": 51.459312, - "lngdeg": -3.165365, - "truebearingdeg": 13, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423232000, - "latdeg": 51.459449899999996, - "lngdeg": -3.165307, - "truebearingdeg": 15, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423234000, - "latdeg": 51.459582999999995, - "lngdeg": -3.1652429, - "truebearingdeg": 16, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423236000, - "latdeg": 51.459742999999996, - "lngdeg": -3.165175, - "truebearingdeg": 13, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423238000, - "latdeg": 51.459908, - "lngdeg": -3.1651119999999997, - "truebearingdeg": 13, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423240000, - "latdeg": 51.46008, - "lngdeg": -3.1650449999999997, - "truebearingdeg": 14, - "knotspeed": 19.43844492440605, - "tack": "PORT" - }, - { - "timepoint": 1346423242000, - "latdeg": 51.460257999999996, - "lngdeg": -3.164973, - "truebearingdeg": 12, - "knotspeed": 20.518358531317496, - "tack": "PORT" - }, - { - "timepoint": 1346423244000, - "latdeg": 51.460449999999994, - "lngdeg": -3.1649, - "truebearingdeg": 12, - "knotspeed": 21.05831533477322, - "tack": "PORT" - }, - { - "timepoint": 1346423246000, - "latdeg": 51.460625, - "lngdeg": -3.164843, - "truebearingdeg": 11, - "knotspeed": 19.43844492440605, - "tack": "PORT" - }, - { - "timepoint": 1346423248000, - "latdeg": 51.460772999999996, - "lngdeg": -3.1647779999999996, - "truebearingdeg": 16, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423250000, - "latdeg": 51.460888, - "lngdeg": -3.16472, - "truebearingdeg": 18, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423252000, - "latdeg": 51.460983, - "lngdeg": -3.164665, - "truebearingdeg": 17, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423254000, - "latdeg": 51.461075, - "lngdeg": -3.1646199999999998, - "truebearingdeg": 17, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423256000, - "latdeg": 51.461166999999996, - "lngdeg": -3.16458, - "truebearingdeg": 13, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423258000, - "latdeg": 51.46125, - "lngdeg": -3.164548, - "truebearingdeg": 13, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423260000, - "latdeg": 51.461335, - "lngdeg": -3.1645149999999997, - "truebearingdeg": 15, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423262000, - "latdeg": 51.461417999999995, - "lngdeg": -3.164478, - "truebearingdeg": 14, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423264000, - "latdeg": 51.461498, - "lngdeg": -3.16444, - "truebearingdeg": 18, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423266000, - "latdeg": 51.461586999999994, - "lngdeg": -3.164392, - "truebearingdeg": 18, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423268000, - "latdeg": 51.461687999999995, - "lngdeg": -3.1643369999999997, - "truebearingdeg": 17, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423270000, - "latdeg": 51.461799899999995, - "lngdeg": -3.1642669999999997, - "truebearingdeg": 29, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423272000, - "latdeg": 51.461883, - "lngdeg": -3.1641369999999998, - "truebearingdeg": 55, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423275000, - "latdeg": 51.461906899999995, - "lngdeg": -3.163967, - "truebearingdeg": 92, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423283000, - "latdeg": 51.461652, - "lngdeg": -3.163747, - "truebearingdeg": 179, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423285000, - "latdeg": 51.461555, - "lngdeg": -3.163753, - "truebearingdeg": 180, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423287000, - "latdeg": 51.461445, - "lngdeg": -3.163758, - "truebearingdeg": 182, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423289000, - "latdeg": 51.461338, - "lngdeg": -3.1637649999999997, - "truebearingdeg": 182, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423291000, - "latdeg": 51.461237999999994, - "lngdeg": -3.163777, - "truebearingdeg": 184, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423293000, - "latdeg": 51.461155, - "lngdeg": -3.1637869999999997, - "truebearingdeg": 183, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423295000, - "latdeg": 51.461078, - "lngdeg": -3.163795, - "truebearingdeg": 183, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423297000, - "latdeg": 51.461017999999996, - "lngdeg": -3.1637999999999997, - "truebearingdeg": 180, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423299000, - "latdeg": 51.460957, - "lngdeg": -3.1637999999999997, - "truebearingdeg": 177, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423301000, - "latdeg": 51.460892, - "lngdeg": -3.163792, - "truebearingdeg": 176, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423303000, - "latdeg": 51.460823, - "lngdeg": -3.163782, - "truebearingdeg": 176, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423305000, - "latdeg": 51.460758, - "lngdeg": -3.163767, - "truebearingdeg": 177, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423307000, - "latdeg": 51.460696999999996, - "lngdeg": -3.16375, - "truebearingdeg": 167, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423309000, - "latdeg": 51.46064, - "lngdeg": -3.1637429999999997, - "truebearingdeg": 170, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423311000, - "latdeg": 51.460583, - "lngdeg": -3.1637299999999997, - "truebearingdeg": 171, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423313000, - "latdeg": 51.46051, - "lngdeg": -3.163735, - "truebearingdeg": 175, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423315000, - "latdeg": 51.460418, - "lngdeg": -3.1637169999999997, - "truebearingdeg": 170, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423317000, - "latdeg": 51.460311999999995, - "lngdeg": -3.163678, - "truebearingdeg": 166, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423319000, - "latdeg": 51.460159999999995, - "lngdeg": -3.163627, - "truebearingdeg": 173, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423321000, - "latdeg": 51.460007, - "lngdeg": -3.163605, - "truebearingdeg": 177, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423323000, - "latdeg": 51.459877, - "lngdeg": -3.1635769999999996, - "truebearingdeg": 164, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423325000, - "latdeg": 51.459782, - "lngdeg": -3.1634849999999997, - "truebearingdeg": 140, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423327000, - "latdeg": 51.459742999999996, - "lngdeg": -3.163353, - "truebearingdeg": 107, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423329000, - "latdeg": 51.459742999999996, - "lngdeg": -3.1632249999999997, - "truebearingdeg": 75, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423331000, - "latdeg": 51.459773, - "lngdeg": -3.1631299999999998, - "truebearingdeg": 56, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346423333000, - "latdeg": 51.459796999999995, - "lngdeg": -3.1630819999999997, - "truebearingdeg": 36, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346423335000, - "latdeg": 51.459846999999996, - "lngdeg": -3.163058, - "truebearingdeg": 0, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346423337000, - "latdeg": 51.459903, - "lngdeg": -3.1630719999999997, - "truebearingdeg": 338, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346423340000, - "latdeg": 51.45995, - "lngdeg": -3.1631, - "truebearingdeg": 343, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346423348000, - "latdeg": 51.460387999999995, - "lngdeg": -3.1631999, - "truebearingdeg": 356, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423350000, - "latdeg": 51.460513, - "lngdeg": -3.163217, - "truebearingdeg": 356, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423352000, - "latdeg": 51.460631899999996, - "lngdeg": -3.163227, - "truebearingdeg": 358, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423354000, - "latdeg": 51.46075, - "lngdeg": -3.163233, - "truebearingdeg": 359, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423356000, - "latdeg": 51.460873, - "lngdeg": -3.1632349, - "truebearingdeg": 0, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423358000, - "latdeg": 51.460972, - "lngdeg": -3.163237, - "truebearingdeg": 359, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423360000, - "latdeg": 51.461062999999996, - "lngdeg": -3.16324, - "truebearingdeg": 358, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423362000, - "latdeg": 51.461152, - "lngdeg": -3.163248, - "truebearingdeg": 355, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423364000, - "latdeg": 51.461231999999995, - "lngdeg": -3.1632569999999998, - "truebearingdeg": 355, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423366000, - "latdeg": 51.461299999999994, - "lngdeg": -3.163265, - "truebearingdeg": 355, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423368000, - "latdeg": 51.461377, - "lngdeg": -3.1632879999999997, - "truebearingdeg": 341, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423370000, - "latdeg": 51.461475, - "lngdeg": -3.163335, - "truebearingdeg": 338, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423372000, - "latdeg": 51.461563, - "lngdeg": -3.1633929999999997, - "truebearingdeg": 332, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423374000, - "latdeg": 51.461642999999995, - "lngdeg": -3.1634599999999997, - "truebearingdeg": 333, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423376000, - "latdeg": 51.461712, - "lngdeg": -3.163518, - "truebearingdeg": 332, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423378000, - "latdeg": 51.461777, - "lngdeg": -3.16357, - "truebearingdeg": 330, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423380000, - "latdeg": 51.461838, - "lngdeg": -3.1636219999999997, - "truebearingdeg": 330, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423382000, - "latdeg": 51.461898, - "lngdeg": -3.163687, - "truebearingdeg": 322, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423384000, - "latdeg": 51.46196, - "lngdeg": -3.163757, - "truebearingdeg": 321, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423386000, - "latdeg": 51.46201, - "lngdeg": -3.163837, - "truebearingdeg": 311, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423388000, - "latdeg": 51.462052, - "lngdeg": -3.163935, - "truebearingdeg": 302, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423390000, - "latdeg": 51.462081999999995, - "lngdeg": -3.1640319999999997, - "truebearingdeg": 301, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423392000, - "latdeg": 51.462112, - "lngdeg": -3.164128, - "truebearingdeg": 295, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423394000, - "latdeg": 51.462123, - "lngdeg": -3.1642199, - "truebearingdeg": 288, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346423396000, - "latdeg": 51.462131899999996, - "lngdeg": -3.164298, - "truebearingdeg": 279, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346423398000, - "latdeg": 51.462134999999996, - "lngdeg": -3.164363, - "truebearingdeg": 277, - "knotspeed": 3.7796976241900646, - "tack": "PORT" - }, - { - "timepoint": 1346423400000, - "latdeg": 51.462137999999996, - "lngdeg": -3.164412, - "truebearingdeg": 265, - "knotspeed": 2.159827213822894, - "tack": "PORT" - }, - { - "timepoint": 1346423405000, - "latdeg": 51.462128, - "lngdeg": -3.16446, - "truebearingdeg": 249, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423409000, - "latdeg": 51.462128, - "lngdeg": -3.16446, - "truebearingdeg": 249, - "knotspeed": 0.5399568034557235, - "tack": "PORT" - }, - { - "timepoint": 1346423411000, - "latdeg": 51.462134999999996, - "lngdeg": -3.164453, - "truebearingdeg": 219, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423413000, - "latdeg": 51.462123, - "lngdeg": -3.16445, - "truebearingdeg": 213, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423415000, - "latdeg": 51.462117, - "lngdeg": -3.16445, - "truebearingdeg": 209, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423417000, - "latdeg": 51.462117, - "lngdeg": -3.1644479999999997, - "truebearingdeg": 208, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423419000, - "latdeg": 51.462108, - "lngdeg": -3.16445, - "truebearingdeg": 200, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423421000, - "latdeg": 51.462105, - "lngdeg": -3.164452, - "truebearingdeg": 200, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423423000, - "latdeg": 51.462089999999996, - "lngdeg": -3.164463, - "truebearingdeg": 199, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423425000, - "latdeg": 51.462078, - "lngdeg": -3.1644669999999997, - "truebearingdeg": 197, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423427000, - "latdeg": 51.462055, - "lngdeg": -3.1644829999999997, - "truebearingdeg": 206, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423429000, - "latdeg": 51.462027899999995, - "lngdeg": -3.1645079999999997, - "truebearingdeg": 216, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423431000, - "latdeg": 51.461987, - "lngdeg": -3.164552, - "truebearingdeg": 206, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423433000, - "latdeg": 51.46194, - "lngdeg": -3.164573, - "truebearingdeg": 188, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423435000, - "latdeg": 51.461883, - "lngdeg": -3.1645749999999997, - "truebearingdeg": 170, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423437000, - "latdeg": 51.461818, - "lngdeg": -3.1645399999999997, - "truebearingdeg": 150, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423439000, - "latdeg": 51.461752999999995, - "lngdeg": -3.164468, - "truebearingdeg": 138, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423441000, - "latdeg": 51.461692899999996, - "lngdeg": -3.164363, - "truebearingdeg": 131, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423443000, - "latdeg": 51.461647, - "lngdeg": -3.164263, - "truebearingdeg": 126, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423445000, - "latdeg": 51.461602, - "lngdeg": -3.164158, - "truebearingdeg": 125, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423447000, - "latdeg": 51.461552, - "lngdeg": -3.1640599999999997, - "truebearingdeg": 124, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423449000, - "latdeg": 51.461517, - "lngdeg": -3.16396, - "truebearingdeg": 123, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423451000, - "latdeg": 51.461475, - "lngdeg": -3.163862, - "truebearingdeg": 125, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423453000, - "latdeg": 51.461425, - "lngdeg": -3.163767, - "truebearingdeg": 135, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423455000, - "latdeg": 51.461344999999994, - "lngdeg": -3.1637049999999998, - "truebearingdeg": 161, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423457000, - "latdeg": 51.461265, - "lngdeg": -3.163703, - "truebearingdeg": 189, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423459000, - "latdeg": 51.461197, - "lngdeg": -3.16375, - "truebearingdeg": 215, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423461000, - "latdeg": 51.461163, - "lngdeg": -3.163817, - "truebearingdeg": 225, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423463000, - "latdeg": 51.461124999999996, - "lngdeg": -3.163877, - "truebearingdeg": 223, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423465000, - "latdeg": 51.461082, - "lngdeg": -3.1639429999999997, - "truebearingdeg": 229, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423467000, - "latdeg": 51.461048, - "lngdeg": -3.1640099999999998, - "truebearingdeg": 228, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423470000, - "latdeg": 51.461009999999995, - "lngdeg": -3.164088, - "truebearingdeg": 239, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423485000, - "latdeg": 51.460857, - "lngdeg": -3.1645079999999997, - "truebearingdeg": 236, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423487000, - "latdeg": 51.460847, - "lngdeg": -3.164533, - "truebearingdeg": 232, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423489000, - "latdeg": 51.460829999999994, - "lngdeg": -3.1645629, - "truebearingdeg": 237, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423491000, - "latdeg": 51.460823, - "lngdeg": -3.1645849999999998, - "truebearingdeg": 236, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423493000, - "latdeg": 51.460814899999995, - "lngdeg": -3.164605, - "truebearingdeg": 236, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423495000, - "latdeg": 51.460812, - "lngdeg": -3.1646229999999997, - "truebearingdeg": 237, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423497000, - "latdeg": 51.460808, - "lngdeg": -3.16464, - "truebearingdeg": 238, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423499000, - "latdeg": 51.4608, - "lngdeg": -3.16466, - "truebearingdeg": 238, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423501000, - "latdeg": 51.460792999999995, - "lngdeg": -3.1646699999999996, - "truebearingdeg": 236, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423503000, - "latdeg": 51.460777, - "lngdeg": -3.164688, - "truebearingdeg": 220, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423505000, - "latdeg": 51.460772999999996, - "lngdeg": -3.164695, - "truebearingdeg": 219, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423507000, - "latdeg": 51.460772999999996, - "lngdeg": -3.164697, - "truebearingdeg": 219, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423509000, - "latdeg": 51.460769899999995, - "lngdeg": -3.164707, - "truebearingdeg": 219, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423513000, - "latdeg": 51.460758, - "lngdeg": -3.1647369999999997, - "truebearingdeg": 221, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423515000, - "latdeg": 51.460743, - "lngdeg": -3.164765, - "truebearingdeg": 237, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423517000, - "latdeg": 51.460732, - "lngdeg": -3.164792, - "truebearingdeg": 236, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423519000, - "latdeg": 51.460722999999994, - "lngdeg": -3.1648199999999997, - "truebearingdeg": 238, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423521000, - "latdeg": 51.460705, - "lngdeg": -3.1648579999999997, - "truebearingdeg": 232, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423523000, - "latdeg": 51.460685, - "lngdeg": -3.164895, - "truebearingdeg": 231, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423525000, - "latdeg": 51.460667, - "lngdeg": -3.16493, - "truebearingdeg": 220, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423527000, - "latdeg": 51.46064, - "lngdeg": -3.164967, - "truebearingdeg": 218, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423529000, - "latdeg": 51.460617, - "lngdeg": -3.1649919, - "truebearingdeg": 210, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423531000, - "latdeg": 51.460595, - "lngdeg": -3.165015, - "truebearingdeg": 212, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423533000, - "latdeg": 51.460575, - "lngdeg": -3.1650269, - "truebearingdeg": 200, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423535000, - "latdeg": 51.46056, - "lngdeg": -3.165037, - "truebearingdeg": 200, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423543000, - "latdeg": 51.46051, - "lngdeg": -3.165067, - "truebearingdeg": 199, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423545000, - "latdeg": 51.460491999999995, - "lngdeg": -3.165073, - "truebearingdeg": 183, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423547000, - "latdeg": 51.460471999999996, - "lngdeg": -3.165075, - "truebearingdeg": 184, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423549000, - "latdeg": 51.460442, - "lngdeg": -3.165082, - "truebearingdeg": 189, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423551000, - "latdeg": 51.460412, - "lngdeg": -3.165092, - "truebearingdeg": 190, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423553000, - "latdeg": 51.46038, - "lngdeg": -3.1651029999999998, - "truebearingdeg": 189, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423555000, - "latdeg": 51.460347, - "lngdeg": -3.1651149999999997, - "truebearingdeg": 193, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423557000, - "latdeg": 51.460299899999995, - "lngdeg": -3.165132, - "truebearingdeg": 193, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423559000, - "latdeg": 51.46024, - "lngdeg": -3.165155, - "truebearingdeg": 193, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423561000, - "latdeg": 51.460162999999994, - "lngdeg": -3.16518, - "truebearingdeg": 186, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423563000, - "latdeg": 51.460083, - "lngdeg": -3.1651729, - "truebearingdeg": 168, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423565000, - "latdeg": 51.459992, - "lngdeg": -3.1651219999999998, - "truebearingdeg": 152, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423567000, - "latdeg": 51.459891999999996, - "lngdeg": -3.165012, - "truebearingdeg": 136, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423569000, - "latdeg": 51.459812, - "lngdeg": -3.1648549999999998, - "truebearingdeg": 122, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423571000, - "latdeg": 51.459754999999994, - "lngdeg": -3.164672, - "truebearingdeg": 112, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - } - ] - }, - { - "name": "GAC Pindar", - "track": [ - { - "timepoint": 1346422553000, - "latdeg": 51.460367999999995, - "lngdeg": -3.164535, - "truebearingdeg": 316, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346422555000, - "latdeg": 51.460418, - "lngdeg": -3.164605, - "truebearingdeg": 320, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346422557000, - "latdeg": 51.460482999999996, - "lngdeg": -3.164682, - "truebearingdeg": 328, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422559000, - "latdeg": 51.460575, - "lngdeg": -3.164727, - "truebearingdeg": 348, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422561000, - "latdeg": 51.460674999999995, - "lngdeg": -3.16472, - "truebearingdeg": 9, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422563000, - "latdeg": 51.460772999999996, - "lngdeg": -3.1646699999999996, - "truebearingdeg": 19, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422565000, - "latdeg": 51.460865, - "lngdeg": -3.1646169, - "truebearingdeg": 23, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422567000, - "latdeg": 51.460944999999995, - "lngdeg": -3.164548, - "truebearingdeg": 24, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422569000, - "latdeg": 51.461022, - "lngdeg": -3.164523, - "truebearingdeg": 4, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346422571000, - "latdeg": 51.461093, - "lngdeg": -3.164532, - "truebearingdeg": 350, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346422573000, - "latdeg": 51.461152, - "lngdeg": -3.164558, - "truebearingdeg": 341, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346422576000, - "latdeg": 51.461205, - "lngdeg": -3.1645779999999997, - "truebearingdeg": 353, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346422584000, - "latdeg": 51.461388, - "lngdeg": -3.164692, - "truebearingdeg": 338, - "knotspeed": 3.239740820734341, - "tack": "PORT" - }, - { - "timepoint": 1346422586000, - "latdeg": 51.461417999999995, - "lngdeg": -3.16471, - "truebearingdeg": 346, - "knotspeed": 3.239740820734341, - "tack": "PORT" - }, - { - "timepoint": 1346422588000, - "latdeg": 51.461445, - "lngdeg": -3.1647249, - "truebearingdeg": 347, - "knotspeed": 2.699784017278618, - "tack": "PORT" - }, - { - "timepoint": 1346422590000, - "latdeg": 51.461479999999995, - "lngdeg": -3.1647369999999997, - "truebearingdeg": 354, - "knotspeed": 3.7796976241900646, - "tack": "PORT" - }, - { - "timepoint": 1346422592000, - "latdeg": 51.461517, - "lngdeg": -3.1647429999999996, - "truebearingdeg": 359, - "knotspeed": 4.319654427645788, - "tack": "PORT" - }, - { - "timepoint": 1346422594000, - "latdeg": 51.461566999999995, - "lngdeg": -3.16473, - "truebearingdeg": 19, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346422596000, - "latdeg": 51.461622999999996, - "lngdeg": -3.1646929999999998, - "truebearingdeg": 13, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346422598000, - "latdeg": 51.461687999999995, - "lngdeg": -3.164668, - "truebearingdeg": 21, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346422600000, - "latdeg": 51.461749999999995, - "lngdeg": -3.1646229999999997, - "truebearingdeg": 20, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346422602000, - "latdeg": 51.461814999999994, - "lngdeg": -3.164592, - "truebearingdeg": 19, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346422604000, - "latdeg": 51.461883, - "lngdeg": -3.164542, - "truebearingdeg": 23, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422606000, - "latdeg": 51.461971999999996, - "lngdeg": -3.164468, - "truebearingdeg": 30, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422608000, - "latdeg": 51.462067, - "lngdeg": -3.164355, - "truebearingdeg": 41, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346422610000, - "latdeg": 51.462146999999995, - "lngdeg": -3.164202, - "truebearingdeg": 60, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346422612000, - "latdeg": 51.462185, - "lngdeg": -3.16403, - "truebearingdeg": 77, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422614000, - "latdeg": 51.462188, - "lngdeg": -3.1638819999999996, - "truebearingdeg": 94, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422616000, - "latdeg": 51.462173, - "lngdeg": -3.1637649999999997, - "truebearingdeg": 106, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422618000, - "latdeg": 51.462143, - "lngdeg": -3.163662, - "truebearingdeg": 119, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422620000, - "latdeg": 51.462101999999994, - "lngdeg": -3.163563, - "truebearingdeg": 121, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422622000, - "latdeg": 51.462055, - "lngdeg": -3.163455, - "truebearingdeg": 121, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422624000, - "latdeg": 51.462005, - "lngdeg": -3.163348, - "truebearingdeg": 137, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422626000, - "latdeg": 51.461932999999995, - "lngdeg": -3.1632729, - "truebearingdeg": 152, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422628000, - "latdeg": 51.461856999999995, - "lngdeg": -3.163205, - "truebearingdeg": 147, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422630000, - "latdeg": 51.461773, - "lngdeg": -3.163112, - "truebearingdeg": 140, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422632000, - "latdeg": 51.461692899999996, - "lngdeg": -3.1630469999999997, - "truebearingdeg": 164, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422634000, - "latdeg": 51.461616899999996, - "lngdeg": -3.1630469999999997, - "truebearingdeg": 181, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422636000, - "latdeg": 51.46154, - "lngdeg": -3.16301, - "truebearingdeg": 151, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422638000, - "latdeg": 51.461467999999996, - "lngdeg": -3.162937, - "truebearingdeg": 145, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422641000, - "latdeg": 51.461406999999994, - "lngdeg": -3.162867, - "truebearingdeg": 135, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422650000, - "latdeg": 51.461101899999996, - "lngdeg": -3.1625319999999997, - "truebearingdeg": 165, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422652000, - "latdeg": 51.461048, - "lngdeg": -3.162547, - "truebearingdeg": 207, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422654000, - "latdeg": 51.461006999999995, - "lngdeg": -3.1626, - "truebearingdeg": 247, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422656000, - "latdeg": 51.460986999999996, - "lngdeg": -3.1626619999999996, - "truebearingdeg": 239, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422658000, - "latdeg": 51.460972, - "lngdeg": -3.1626949, - "truebearingdeg": 223, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422660000, - "latdeg": 51.460957, - "lngdeg": -3.162737, - "truebearingdeg": 232, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422662000, - "latdeg": 51.460941999999996, - "lngdeg": -3.162785, - "truebearingdeg": 241, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422664000, - "latdeg": 51.460927, - "lngdeg": -3.1628149999999997, - "truebearingdeg": 221, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422666000, - "latdeg": 51.46091, - "lngdeg": -3.1628469999999997, - "truebearingdeg": 234, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422668000, - "latdeg": 51.460895, - "lngdeg": -3.1628949, - "truebearingdeg": 241, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422670000, - "latdeg": 51.460882999999995, - "lngdeg": -3.1629129999999996, - "truebearingdeg": 224, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422676000, - "latdeg": 51.460808, - "lngdeg": -3.1629579999999997, - "truebearingdeg": 202, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422678000, - "latdeg": 51.460772999999996, - "lngdeg": -3.162995, - "truebearingdeg": 218, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422680000, - "latdeg": 51.460735, - "lngdeg": -3.163043, - "truebearingdeg": 207, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422682000, - "latdeg": 51.460685, - "lngdeg": -3.163068, - "truebearingdeg": 197, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422684000, - "latdeg": 51.460625, - "lngdeg": -3.1630919, - "truebearingdeg": 194, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422686000, - "latdeg": 51.460544999999996, - "lngdeg": -3.163137, - "truebearingdeg": 202, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422688000, - "latdeg": 51.460453, - "lngdeg": -3.1631999, - "truebearingdeg": 201, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422690000, - "latdeg": 51.460347, - "lngdeg": -3.163275, - "truebearingdeg": 206, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422692000, - "latdeg": 51.46024, - "lngdeg": -3.163367, - "truebearingdeg": 208, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422694000, - "latdeg": 51.460133, - "lngdeg": -3.163477, - "truebearingdeg": 210, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422696000, - "latdeg": 51.460021999999995, - "lngdeg": -3.163592, - "truebearingdeg": 215, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422698000, - "latdeg": 51.459917999999995, - "lngdeg": -3.1637079999999997, - "truebearingdeg": 214, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422700000, - "latdeg": 51.459812, - "lngdeg": -3.1638099, - "truebearingdeg": 211, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422702000, - "latdeg": 51.459702, - "lngdeg": -3.1639179, - "truebearingdeg": 209, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422705000, - "latdeg": 51.459592, - "lngdeg": -3.16402, - "truebearingdeg": 210, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422713000, - "latdeg": 51.459106999999996, - "lngdeg": -3.16459, - "truebearingdeg": 220, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422715000, - "latdeg": 51.458991999999995, - "lngdeg": -3.1647179999999997, - "truebearingdeg": 209, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422717000, - "latdeg": 51.45887, - "lngdeg": -3.164843, - "truebearingdeg": 211, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422719000, - "latdeg": 51.458743, - "lngdeg": -3.1649819999999997, - "truebearingdeg": 216, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422721000, - "latdeg": 51.458614999999995, - "lngdeg": -3.165142, - "truebearingdeg": 216, - "knotspeed": 17.278617710583152, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422723000, - "latdeg": 51.458476999999995, - "lngdeg": -3.165292, - "truebearingdeg": 208, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422725000, - "latdeg": 51.458343, - "lngdeg": -3.165393, - "truebearingdeg": 204, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422727000, - "latdeg": 51.458202, - "lngdeg": -3.165495, - "truebearingdeg": 205, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422729000, - "latdeg": 51.458079999999995, - "lngdeg": -3.1655919999999997, - "truebearingdeg": 203, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422731000, - "latdeg": 51.457949899999996, - "lngdeg": -3.165677, - "truebearingdeg": 201, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422733000, - "latdeg": 51.457825, - "lngdeg": -3.165758, - "truebearingdeg": 202, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422735000, - "latdeg": 51.457695, - "lngdeg": -3.1658369, - "truebearingdeg": 202, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422737000, - "latdeg": 51.457572899999995, - "lngdeg": -3.16592, - "truebearingdeg": 202, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422739000, - "latdeg": 51.457446999999995, - "lngdeg": -3.165993, - "truebearingdeg": 199, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422741000, - "latdeg": 51.457325, - "lngdeg": -3.166058, - "truebearingdeg": 199, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422743000, - "latdeg": 51.457198, - "lngdeg": -3.16613, - "truebearingdeg": 199, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422745000, - "latdeg": 51.457077, - "lngdeg": -3.1662, - "truebearingdeg": 203, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422747000, - "latdeg": 51.456962999999995, - "lngdeg": -3.166282, - "truebearingdeg": 202, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422749000, - "latdeg": 51.456852, - "lngdeg": -3.1663729999999997, - "truebearingdeg": 211, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422751000, - "latdeg": 51.456748, - "lngdeg": -3.1664749999999997, - "truebearingdeg": 212, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422753000, - "latdeg": 51.456649999999996, - "lngdeg": -3.1665799999999997, - "truebearingdeg": 211, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422755000, - "latdeg": 51.456547, - "lngdeg": -3.166677, - "truebearingdeg": 208, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422757000, - "latdeg": 51.456437, - "lngdeg": -3.1667699999999996, - "truebearingdeg": 208, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422759000, - "latdeg": 51.456325, - "lngdeg": -3.166867, - "truebearingdeg": 210, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422761000, - "latdeg": 51.456215, - "lngdeg": -3.166963, - "truebearingdeg": 211, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422763000, - "latdeg": 51.456112, - "lngdeg": -3.167062, - "truebearingdeg": 210, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422765000, - "latdeg": 51.456002, - "lngdeg": -3.167153, - "truebearingdeg": 205, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422767000, - "latdeg": 51.455895, - "lngdeg": -3.167245, - "truebearingdeg": 210, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422770000, - "latdeg": 51.455788, - "lngdeg": -3.167337, - "truebearingdeg": 207, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422778000, - "latdeg": 51.455338, - "lngdeg": -3.167767, - "truebearingdeg": 210, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422780000, - "latdeg": 51.45523, - "lngdeg": -3.167868, - "truebearingdeg": 210, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422782000, - "latdeg": 51.455116999999994, - "lngdeg": -3.16797, - "truebearingdeg": 206, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422784000, - "latdeg": 51.455005, - "lngdeg": -3.168052, - "truebearingdeg": 205, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422786000, - "latdeg": 51.454907, - "lngdeg": -3.168147, - "truebearingdeg": 212, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422788000, - "latdeg": 51.454792, - "lngdeg": -3.168263, - "truebearingdeg": 213, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422790000, - "latdeg": 51.454677999999994, - "lngdeg": -3.16837, - "truebearingdeg": 207, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422792000, - "latdeg": 51.454578, - "lngdeg": -3.1684479999999997, - "truebearingdeg": 204, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422794000, - "latdeg": 51.454479899999995, - "lngdeg": -3.168525, - "truebearingdeg": 208, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422796000, - "latdeg": 51.454377, - "lngdeg": -3.168615, - "truebearingdeg": 207, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422798000, - "latdeg": 51.454265, - "lngdeg": -3.168695, - "truebearingdeg": 205, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422800000, - "latdeg": 51.454152, - "lngdeg": -3.168762, - "truebearingdeg": 192, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422802000, - "latdeg": 51.454024999999994, - "lngdeg": -3.1687719999999997, - "truebearingdeg": 172, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422804000, - "latdeg": 51.453922, - "lngdeg": -3.168692, - "truebearingdeg": 144, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422806000, - "latdeg": 51.453846999999996, - "lngdeg": -3.168555, - "truebearingdeg": 122, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422808000, - "latdeg": 51.453787999999996, - "lngdeg": -3.168377, - "truebearingdeg": 117, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422810000, - "latdeg": 51.453734999999995, - "lngdeg": -3.1681969999999997, - "truebearingdeg": 115, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422812000, - "latdeg": 51.453669999999995, - "lngdeg": -3.1680229, - "truebearingdeg": 127, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422814000, - "latdeg": 51.453575, - "lngdeg": -3.1678729999999997, - "truebearingdeg": 132, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422816000, - "latdeg": 51.45348, - "lngdeg": -3.1677049999999998, - "truebearingdeg": 131, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422818000, - "latdeg": 51.453379999999996, - "lngdeg": -3.167525, - "truebearingdeg": 131, - "knotspeed": 16.73866090712743, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422820000, - "latdeg": 51.4533, - "lngdeg": -3.1673079, - "truebearingdeg": 117, - "knotspeed": 17.278617710583152, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422822000, - "latdeg": 51.453235, - "lngdeg": -3.167065, - "truebearingdeg": 111, - "knotspeed": 17.818574514038875, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422824000, - "latdeg": 51.453187, - "lngdeg": -3.166808, - "truebearingdeg": 103, - "knotspeed": 17.818574514038875, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422826000, - "latdeg": 51.453154999999995, - "lngdeg": -3.1665549, - "truebearingdeg": 100, - "knotspeed": 17.278617710583152, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422828000, - "latdeg": 51.453143, - "lngdeg": -3.1663099999999997, - "truebearingdeg": 89, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422830000, - "latdeg": 51.45317, - "lngdeg": -3.1661069, - "truebearingdeg": 66, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422832000, - "latdeg": 51.453235, - "lngdeg": -3.165978, - "truebearingdeg": 41, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422835000, - "latdeg": 51.453311899999996, - "lngdeg": -3.165902, - "truebearingdeg": 26, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422843000, - "latdeg": 51.453737999999994, - "lngdeg": -3.1656199999999997, - "truebearingdeg": 34, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346422845000, - "latdeg": 51.453841999999995, - "lngdeg": -3.1654999999999998, - "truebearingdeg": 37, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422847000, - "latdeg": 51.453942, - "lngdeg": -3.165377, - "truebearingdeg": 40, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422849000, - "latdeg": 51.454037, - "lngdeg": -3.1652419999999997, - "truebearingdeg": 42, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422851000, - "latdeg": 51.454128, - "lngdeg": -3.165088, - "truebearingdeg": 46, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422853000, - "latdeg": 51.454212, - "lngdeg": -3.1649529999999997, - "truebearingdeg": 47, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346422855000, - "latdeg": 51.454285, - "lngdeg": -3.1648169999999998, - "truebearingdeg": 50, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422857000, - "latdeg": 51.454353, - "lngdeg": -3.164695, - "truebearingdeg": 44, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422859000, - "latdeg": 51.454432999999995, - "lngdeg": -3.1645979, - "truebearingdeg": 30, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422861000, - "latdeg": 51.454513, - "lngdeg": -3.16453, - "truebearingdeg": 14, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422863000, - "latdeg": 51.454601999999994, - "lngdeg": -3.164533, - "truebearingdeg": 355, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422865000, - "latdeg": 51.454685, - "lngdeg": -3.16458, - "truebearingdeg": 339, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422867000, - "latdeg": 51.454767999999994, - "lngdeg": -3.1646579999999997, - "truebearingdeg": 324, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422869000, - "latdeg": 51.454859899999995, - "lngdeg": -3.1647749999999997, - "truebearingdeg": 319, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346422871000, - "latdeg": 51.454944999999995, - "lngdeg": -3.164907, - "truebearingdeg": 317, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346422873000, - "latdeg": 51.455028, - "lngdeg": -3.165043, - "truebearingdeg": 313, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346422875000, - "latdeg": 51.455107999999996, - "lngdeg": -3.1651729, - "truebearingdeg": 311, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346422877000, - "latdeg": 51.455192999999994, - "lngdeg": -3.1653349, - "truebearingdeg": 311, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346422879000, - "latdeg": 51.455276999999995, - "lngdeg": -3.165483, - "truebearingdeg": 312, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422881000, - "latdeg": 51.455365, - "lngdeg": -3.165645, - "truebearingdeg": 308, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346422883000, - "latdeg": 51.455436999999996, - "lngdeg": -3.16581, - "truebearingdeg": 306, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346422885000, - "latdeg": 51.455524999999994, - "lngdeg": -3.1659419, - "truebearingdeg": 322, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422887000, - "latdeg": 51.455611999999995, - "lngdeg": -3.166048, - "truebearingdeg": 323, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422889000, - "latdeg": 51.455703, - "lngdeg": -3.1661319999999997, - "truebearingdeg": 326, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422891000, - "latdeg": 51.455777, - "lngdeg": -3.1662499, - "truebearingdeg": 298, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422893000, - "latdeg": 51.455782899999996, - "lngdeg": -3.1663479999999997, - "truebearingdeg": 252, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422895000, - "latdeg": 51.455768, - "lngdeg": -3.1663929, - "truebearingdeg": 234, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422897000, - "latdeg": 51.455749999999995, - "lngdeg": -3.1664269999999997, - "truebearingdeg": 216, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422900000, - "latdeg": 51.455718, - "lngdeg": -3.166477, - "truebearingdeg": 222, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422908000, - "latdeg": 51.455425, - "lngdeg": -3.16688, - "truebearingdeg": 214, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422910000, - "latdeg": 51.455341999999995, - "lngdeg": -3.16697, - "truebearingdeg": 216, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422912000, - "latdeg": 51.45525, - "lngdeg": -3.1670819999999997, - "truebearingdeg": 219, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422914000, - "latdeg": 51.455161999999994, - "lngdeg": -3.1671929999999997, - "truebearingdeg": 222, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422916000, - "latdeg": 51.45509, - "lngdeg": -3.167332, - "truebearingdeg": 225, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422918000, - "latdeg": 51.455013, - "lngdeg": -3.167453, - "truebearingdeg": 221, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422920000, - "latdeg": 51.454944999999995, - "lngdeg": -3.1675429999999998, - "truebearingdeg": 217, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422922000, - "latdeg": 51.454879999999996, - "lngdeg": -3.167608, - "truebearingdeg": 211, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422924000, - "latdeg": 51.454814999999996, - "lngdeg": -3.1676729999999997, - "truebearingdeg": 211, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422926000, - "latdeg": 51.454738, - "lngdeg": -3.167748, - "truebearingdeg": 212, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422928000, - "latdeg": 51.454662, - "lngdeg": -3.1678279999999996, - "truebearingdeg": 212, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422930000, - "latdeg": 51.454578, - "lngdeg": -3.167915, - "truebearingdeg": 214, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422932000, - "latdeg": 51.454507, - "lngdeg": -3.16802, - "truebearingdeg": 230, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422934000, - "latdeg": 51.454445, - "lngdeg": -3.168117, - "truebearingdeg": 237, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422936000, - "latdeg": 51.45438, - "lngdeg": -3.1682449999999998, - "truebearingdeg": 228, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422938000, - "latdeg": 51.454315, - "lngdeg": -3.1683619999999997, - "truebearingdeg": 226, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422940000, - "latdeg": 51.454249999999995, - "lngdeg": -3.168463, - "truebearingdeg": 219, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422942000, - "latdeg": 51.454181999999996, - "lngdeg": -3.1685629, - "truebearingdeg": 222, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422944000, - "latdeg": 51.45411, - "lngdeg": -3.1686699999999997, - "truebearingdeg": 222, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422946000, - "latdeg": 51.454032999999995, - "lngdeg": -3.168757, - "truebearingdeg": 201, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422948000, - "latdeg": 51.453942, - "lngdeg": -3.1687719999999997, - "truebearingdeg": 172, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422950000, - "latdeg": 51.453838, - "lngdeg": -3.168723, - "truebearingdeg": 156, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422952000, - "latdeg": 51.453731999999995, - "lngdeg": -3.168618, - "truebearingdeg": 142, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422954000, - "latdeg": 51.453627999999995, - "lngdeg": -3.168462, - "truebearingdeg": 132, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422956000, - "latdeg": 51.453551999999995, - "lngdeg": -3.1682669999999997, - "truebearingdeg": 116, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422958000, - "latdeg": 51.453497999999996, - "lngdeg": -3.1680669999999997, - "truebearingdeg": 114, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422960000, - "latdeg": 51.453444999999995, - "lngdeg": -3.1678829, - "truebearingdeg": 118, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422962000, - "latdeg": 51.453385, - "lngdeg": -3.1677199999999996, - "truebearingdeg": 131, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422965000, - "latdeg": 51.4533, - "lngdeg": -3.167598, - "truebearingdeg": 137, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422973000, - "latdeg": 51.453117, - "lngdeg": -3.1668179999999997, - "truebearingdeg": 71, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422975000, - "latdeg": 51.453154999999995, - "lngdeg": -3.166673, - "truebearingdeg": 64, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422977000, - "latdeg": 51.453207899999995, - "lngdeg": -3.166547, - "truebearingdeg": 51, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422979000, - "latdeg": 51.453281999999994, - "lngdeg": -3.166422, - "truebearingdeg": 44, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422981000, - "latdeg": 51.453376999999996, - "lngdeg": -3.166277, - "truebearingdeg": 44, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346422983000, - "latdeg": 51.453494899999995, - "lngdeg": -3.166092, - "truebearingdeg": 44, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346422985000, - "latdeg": 51.453621999999996, - "lngdeg": -3.16588, - "truebearingdeg": 47, - "knotspeed": 18.898488120950326, - "tack": "PORT" - }, - { - "timepoint": 1346422987000, - "latdeg": 51.453747, - "lngdeg": -3.1656579999999996, - "truebearingdeg": 48, - "knotspeed": 19.43844492440605, - "tack": "PORT" - }, - { - "timepoint": 1346422989000, - "latdeg": 51.453877, - "lngdeg": -3.1654419999999996, - "truebearingdeg": 46, - "knotspeed": 18.898488120950326, - "tack": "PORT" - }, - { - "timepoint": 1346422991000, - "latdeg": 51.454007, - "lngdeg": -3.1652299999999998, - "truebearingdeg": 45, - "knotspeed": 19.43844492440605, - "tack": "PORT" - }, - { - "timepoint": 1346422993000, - "latdeg": 51.454131999999994, - "lngdeg": -3.165035, - "truebearingdeg": 43, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346422995000, - "latdeg": 51.454249999999995, - "lngdeg": -3.164863, - "truebearingdeg": 46, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346422997000, - "latdeg": 51.45435, - "lngdeg": -3.164685, - "truebearingdeg": 48, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346422999000, - "latdeg": 51.454453, - "lngdeg": -3.1645399999999997, - "truebearingdeg": 32, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423001000, - "latdeg": 51.454581999999995, - "lngdeg": -3.164482, - "truebearingdeg": 0, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423003000, - "latdeg": 51.454685, - "lngdeg": -3.16456, - "truebearingdeg": 323, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423005000, - "latdeg": 51.454758, - "lngdeg": -3.164665, - "truebearingdeg": 326, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423007000, - "latdeg": 51.454842, - "lngdeg": -3.1647599, - "truebearingdeg": 326, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423009000, - "latdeg": 51.45493, - "lngdeg": -3.1648669999999997, - "truebearingdeg": 322, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423011000, - "latdeg": 51.455017, - "lngdeg": -3.1649719999999997, - "truebearingdeg": 326, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423013000, - "latdeg": 51.455112, - "lngdeg": -3.16507, - "truebearingdeg": 330, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423015000, - "latdeg": 51.45522, - "lngdeg": -3.165167, - "truebearingdeg": 332, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423017000, - "latdeg": 51.455332999999996, - "lngdeg": -3.16526, - "truebearingdeg": 331, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423019000, - "latdeg": 51.455462999999995, - "lngdeg": -3.1653699, - "truebearingdeg": 331, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423021000, - "latdeg": 51.455593, - "lngdeg": -3.1654929999999997, - "truebearingdeg": 329, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423023000, - "latdeg": 51.455729999999996, - "lngdeg": -3.1656269999999997, - "truebearingdeg": 328, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423025000, - "latdeg": 51.455859999999994, - "lngdeg": -3.1657449, - "truebearingdeg": 331, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423027000, - "latdeg": 51.455987, - "lngdeg": -3.165845, - "truebearingdeg": 333, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423030000, - "latdeg": 51.456105, - "lngdeg": -3.1659479999999998, - "truebearingdeg": 332, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423038000, - "latdeg": 51.456665, - "lngdeg": -3.1664019999999997, - "truebearingdeg": 336, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423040000, - "latdeg": 51.456798, - "lngdeg": -3.1664779999999997, - "truebearingdeg": 344, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423042000, - "latdeg": 51.456928, - "lngdeg": -3.166502, - "truebearingdeg": 1, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423044000, - "latdeg": 51.457046999999996, - "lngdeg": -3.1664429999999997, - "truebearingdeg": 26, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423046000, - "latdeg": 51.457153, - "lngdeg": -3.1663069999999998, - "truebearingdeg": 48, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423048000, - "latdeg": 51.457229999999996, - "lngdeg": -3.166108, - "truebearingdeg": 63, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423050000, - "latdeg": 51.457274999999996, - "lngdeg": -3.1658999999999997, - "truebearingdeg": 75, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423052000, - "latdeg": 51.45731, - "lngdeg": -3.1657099, - "truebearingdeg": 67, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423054000, - "latdeg": 51.45737, - "lngdeg": -3.165545, - "truebearingdeg": 54, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423056000, - "latdeg": 51.457446999999995, - "lngdeg": -3.1653879999999996, - "truebearingdeg": 49, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423058000, - "latdeg": 51.457535, - "lngdeg": -3.1652269, - "truebearingdeg": 50, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423060000, - "latdeg": 51.457626999999995, - "lngdeg": -3.1650649, - "truebearingdeg": 45, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423062000, - "latdeg": 51.457721899999996, - "lngdeg": -3.16491, - "truebearingdeg": 42, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423064000, - "latdeg": 51.457825, - "lngdeg": -3.164745, - "truebearingdeg": 46, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423066000, - "latdeg": 51.457916999999995, - "lngdeg": -3.16457, - "truebearingdeg": 51, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423068000, - "latdeg": 51.458, - "lngdeg": -3.164395, - "truebearingdeg": 52, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423070000, - "latdeg": 51.458068, - "lngdeg": -3.1642349999999997, - "truebearingdeg": 56, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423072000, - "latdeg": 51.458118, - "lngdeg": -3.164075, - "truebearingdeg": 71, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423074000, - "latdeg": 51.45813, - "lngdeg": -3.1639269999999997, - "truebearingdeg": 91, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423076000, - "latdeg": 51.458118, - "lngdeg": -3.1638029999999997, - "truebearingdeg": 104, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423078000, - "latdeg": 51.458092, - "lngdeg": -3.163687, - "truebearingdeg": 110, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423080000, - "latdeg": 51.458065, - "lngdeg": -3.16356, - "truebearingdeg": 100, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423082000, - "latdeg": 51.458073, - "lngdeg": -3.163452, - "truebearingdeg": 70, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423084000, - "latdeg": 51.458102999999994, - "lngdeg": -3.163372, - "truebearingdeg": 48, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423086000, - "latdeg": 51.458152999999996, - "lngdeg": -3.163315, - "truebearingdeg": 26, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423088000, - "latdeg": 51.458228, - "lngdeg": -3.163303, - "truebearingdeg": 356, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423090000, - "latdeg": 51.458301999999996, - "lngdeg": -3.16334, - "truebearingdeg": 334, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423092000, - "latdeg": 51.458357899999996, - "lngdeg": -3.163407, - "truebearingdeg": 315, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423095000, - "latdeg": 51.458405, - "lngdeg": -3.1635, - "truebearingdeg": 312, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423106000, - "latdeg": 51.458822999999995, - "lngdeg": -3.1641149, - "truebearingdeg": 296, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423108000, - "latdeg": 51.458842999999995, - "lngdeg": -3.1642229, - "truebearingdeg": 271, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423110000, - "latdeg": 51.458816999999996, - "lngdeg": -3.1642729999999997, - "truebearingdeg": 224, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423112000, - "latdeg": 51.458787, - "lngdeg": -3.1643, - "truebearingdeg": 210, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423114000, - "latdeg": 51.458748, - "lngdeg": -3.164333, - "truebearingdeg": 208, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423116000, - "latdeg": 51.458683, - "lngdeg": -3.164377, - "truebearingdeg": 205, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423118000, - "latdeg": 51.458602899999995, - "lngdeg": -3.164443, - "truebearingdeg": 209, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423120000, - "latdeg": 51.458507999999995, - "lngdeg": -3.164535, - "truebearingdeg": 212, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423122000, - "latdeg": 51.458405, - "lngdeg": -3.164647, - "truebearingdeg": 211, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423124000, - "latdeg": 51.458301999999996, - "lngdeg": -3.164748, - "truebearingdeg": 214, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423126000, - "latdeg": 51.458197999999996, - "lngdeg": -3.164857, - "truebearingdeg": 218, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423128000, - "latdeg": 51.458115, - "lngdeg": -3.1649379, - "truebearingdeg": 213, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423130000, - "latdeg": 51.458003, - "lngdeg": -3.165053, - "truebearingdeg": 213, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423132000, - "latdeg": 51.457893, - "lngdeg": -3.1651819999999997, - "truebearingdeg": 214, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423134000, - "latdeg": 51.457786999999996, - "lngdeg": -3.165302, - "truebearingdeg": 214, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423136000, - "latdeg": 51.457676899999996, - "lngdeg": -3.165413, - "truebearingdeg": 212, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423138000, - "latdeg": 51.457561999999996, - "lngdeg": -3.1655119999999997, - "truebearingdeg": 210, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423140000, - "latdeg": 51.457451999999996, - "lngdeg": -3.165625, - "truebearingdeg": 213, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423142000, - "latdeg": 51.457342999999995, - "lngdeg": -3.165737, - "truebearingdeg": 213, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423144000, - "latdeg": 51.457237, - "lngdeg": -3.165848, - "truebearingdeg": 215, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423146000, - "latdeg": 51.457138, - "lngdeg": -3.1659729999999997, - "truebearingdeg": 217, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423148000, - "latdeg": 51.457035, - "lngdeg": -3.166105, - "truebearingdeg": 220, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423150000, - "latdeg": 51.456962999999995, - "lngdeg": -3.166245, - "truebearingdeg": 235, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423152000, - "latdeg": 51.456942999999995, - "lngdeg": -3.1663919999999997, - "truebearingdeg": 267, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423154000, - "latdeg": 51.456955, - "lngdeg": -3.166495, - "truebearingdeg": 303, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346423156000, - "latdeg": 51.456984999999996, - "lngdeg": -3.166562, - "truebearingdeg": 314, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346423159000, - "latdeg": 51.457035, - "lngdeg": -3.1666079999999996, - "truebearingdeg": 342, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423168000, - "latdeg": 51.457538, - "lngdeg": -3.166308, - "truebearingdeg": 34, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423170000, - "latdeg": 51.45765, - "lngdeg": -3.166187, - "truebearingdeg": 34, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423172000, - "latdeg": 51.45776, - "lngdeg": -3.16607, - "truebearingdeg": 35, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423174000, - "latdeg": 51.457867, - "lngdeg": -3.1659479999999998, - "truebearingdeg": 33, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423176000, - "latdeg": 51.457982, - "lngdeg": -3.1658549999999996, - "truebearingdeg": 21, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423178000, - "latdeg": 51.458102999999994, - "lngdeg": -3.165788, - "truebearingdeg": 20, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423180000, - "latdeg": 51.458236899999996, - "lngdeg": -3.165715, - "truebearingdeg": 19, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423182000, - "latdeg": 51.458382, - "lngdeg": -3.165642, - "truebearingdeg": 19, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423184000, - "latdeg": 51.458529999999996, - "lngdeg": -3.1655569999999997, - "truebearingdeg": 18, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423186000, - "latdeg": 51.458695, - "lngdeg": -3.165472, - "truebearingdeg": 17, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423188000, - "latdeg": 51.45887, - "lngdeg": -3.165375, - "truebearingdeg": 19, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423190000, - "latdeg": 51.459033, - "lngdeg": -3.165292, - "truebearingdeg": 15, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423192000, - "latdeg": 51.459187, - "lngdeg": -3.1652229999999997, - "truebearingdeg": 16, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423194000, - "latdeg": 51.459328, - "lngdeg": -3.16517, - "truebearingdeg": 15, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423196000, - "latdeg": 51.459472999999996, - "lngdeg": -3.16511, - "truebearingdeg": 13, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423198000, - "latdeg": 51.459621999999996, - "lngdeg": -3.1650479999999996, - "truebearingdeg": 14, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423200000, - "latdeg": 51.459784899999995, - "lngdeg": -3.164983, - "truebearingdeg": 14, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423202000, - "latdeg": 51.459953, - "lngdeg": -3.164923, - "truebearingdeg": 12, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423204000, - "latdeg": 51.460125, - "lngdeg": -3.164865, - "truebearingdeg": 13, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423206000, - "latdeg": 51.460282, - "lngdeg": -3.1648069999999997, - "truebearingdeg": 13, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423208000, - "latdeg": 51.460418, - "lngdeg": -3.164755, - "truebearingdeg": 13, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423210000, - "latdeg": 51.460539999999995, - "lngdeg": -3.1647049999999997, - "truebearingdeg": 14, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423212000, - "latdeg": 51.46064, - "lngdeg": -3.1646549999999998, - "truebearingdeg": 19, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423214000, - "latdeg": 51.460727999999996, - "lngdeg": -3.164605, - "truebearingdeg": 18, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423216000, - "latdeg": 51.460812, - "lngdeg": -3.1645499999999998, - "truebearingdeg": 20, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423218000, - "latdeg": 51.460882999999995, - "lngdeg": -3.1645019999999997, - "truebearingdeg": 23, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423220000, - "latdeg": 51.460952999999996, - "lngdeg": -3.164453, - "truebearingdeg": 24, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423223000, - "latdeg": 51.461022, - "lngdeg": -3.164398, - "truebearingdeg": 25, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423232000, - "latdeg": 51.461537, - "lngdeg": -3.164225, - "truebearingdeg": 3, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423234000, - "latdeg": 51.461658, - "lngdeg": -3.1641879, - "truebearingdeg": 19, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423236000, - "latdeg": 51.461752999999995, - "lngdeg": -3.164098, - "truebearingdeg": 38, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423238000, - "latdeg": 51.461818, - "lngdeg": -3.1639679999999997, - "truebearingdeg": 60, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423240000, - "latdeg": 51.461853, - "lngdeg": -3.16382, - "truebearingdeg": 78, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423242000, - "latdeg": 51.461853, - "lngdeg": -3.1636819999999997, - "truebearingdeg": 102, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423244000, - "latdeg": 51.461822999999995, - "lngdeg": -3.163582, - "truebearingdeg": 123, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423246000, - "latdeg": 51.461768, - "lngdeg": -3.1635169999999997, - "truebearingdeg": 156, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423248000, - "latdeg": 51.461712, - "lngdeg": -3.163483, - "truebearingdeg": 161, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423250000, - "latdeg": 51.461642999999995, - "lngdeg": -3.1634669, - "truebearingdeg": 175, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423252000, - "latdeg": 51.461569999999995, - "lngdeg": -3.163477, - "truebearingdeg": 183, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423254000, - "latdeg": 51.461498, - "lngdeg": -3.163483, - "truebearingdeg": 181, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423256000, - "latdeg": 51.461422, - "lngdeg": -3.163487, - "truebearingdeg": 179, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423258000, - "latdeg": 51.461341999999995, - "lngdeg": -3.163487, - "truebearingdeg": 179, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423260000, - "latdeg": 51.461258, - "lngdeg": -3.1634849999999997, - "truebearingdeg": 178, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423262000, - "latdeg": 51.461166999999996, - "lngdeg": -3.1634849999999997, - "truebearingdeg": 177, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423264000, - "latdeg": 51.461078, - "lngdeg": -3.1634699, - "truebearingdeg": 177, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423266000, - "latdeg": 51.46098, - "lngdeg": -3.163465, - "truebearingdeg": 178, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423268000, - "latdeg": 51.460876999999996, - "lngdeg": -3.1634629999999997, - "truebearingdeg": 178, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423270000, - "latdeg": 51.460777, - "lngdeg": -3.1634629999999997, - "truebearingdeg": 180, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423272000, - "latdeg": 51.460682, - "lngdeg": -3.1634629999999997, - "truebearingdeg": 175, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423274000, - "latdeg": 51.460583, - "lngdeg": -3.1634569999999997, - "truebearingdeg": 178, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423276000, - "latdeg": 51.460468, - "lngdeg": -3.1634499999999997, - "truebearingdeg": 179, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423278000, - "latdeg": 51.460342, - "lngdeg": -3.1634629999999997, - "truebearingdeg": 183, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423280000, - "latdeg": 51.460204999999995, - "lngdeg": -3.1634819999999997, - "truebearingdeg": 185, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423282000, - "latdeg": 51.46006, - "lngdeg": -3.163507, - "truebearingdeg": 187, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423284000, - "latdeg": 51.45993, - "lngdeg": -3.1634849999999997, - "truebearingdeg": 170, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423287000, - "latdeg": 51.459835, - "lngdeg": -3.1634119999999997, - "truebearingdeg": 144, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423296000, - "latdeg": 51.459812, - "lngdeg": -3.1629299, - "truebearingdeg": 29, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346423298000, - "latdeg": 51.459865, - "lngdeg": -3.162908, - "truebearingdeg": 12, - "knotspeed": 5.399568034557236, - "tack": "PORT" - }, - { - "timepoint": 1346423300000, - "latdeg": 51.459917999999995, - "lngdeg": -3.162905, - "truebearingdeg": 4, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423302000, - "latdeg": 51.459983, - "lngdeg": -3.162897, - "truebearingdeg": 6, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423304000, - "latdeg": 51.460052, - "lngdeg": -3.16289, - "truebearingdeg": 5, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423306000, - "latdeg": 51.460128, - "lngdeg": -3.162873, - "truebearingdeg": 10, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423308000, - "latdeg": 51.460207999999994, - "lngdeg": -3.1628499999999997, - "truebearingdeg": 9, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423310000, - "latdeg": 51.460297, - "lngdeg": -3.162823, - "truebearingdeg": 12, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423312000, - "latdeg": 51.4604, - "lngdeg": -3.162788, - "truebearingdeg": 9, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423314000, - "latdeg": 51.460513, - "lngdeg": -3.1627669999999997, - "truebearingdeg": 3, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423316000, - "latdeg": 51.460628, - "lngdeg": -3.1627699999999996, - "truebearingdeg": 355, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423318000, - "latdeg": 51.460743, - "lngdeg": -3.162785, - "truebearingdeg": 354, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423320000, - "latdeg": 51.460857, - "lngdeg": -3.162792, - "truebearingdeg": 358, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423322000, - "latdeg": 51.460968, - "lngdeg": -3.1628, - "truebearingdeg": 354, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423324000, - "latdeg": 51.461054999999995, - "lngdeg": -3.1628119999999997, - "truebearingdeg": 353, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423326000, - "latdeg": 51.461132, - "lngdeg": -3.162823, - "truebearingdeg": 352, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423328000, - "latdeg": 51.46122, - "lngdeg": -3.162827, - "truebearingdeg": 358, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423330000, - "latdeg": 51.461284899999995, - "lngdeg": -3.162843, - "truebearingdeg": 347, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423332000, - "latdeg": 51.461344999999994, - "lngdeg": -3.1628719999999997, - "truebearingdeg": 345, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423334000, - "latdeg": 51.461417999999995, - "lngdeg": -3.1628979999999998, - "truebearingdeg": 344, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423336000, - "latdeg": 51.461498, - "lngdeg": -3.162962, - "truebearingdeg": 332, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423338000, - "latdeg": 51.46159, - "lngdeg": -3.1630149999999997, - "truebearingdeg": 339, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423340000, - "latdeg": 51.461704999999995, - "lngdeg": -3.163135, - "truebearingdeg": 329, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423342000, - "latdeg": 51.461822999999995, - "lngdeg": -3.163227, - "truebearingdeg": 334, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423344000, - "latdeg": 51.461932999999995, - "lngdeg": -3.163313, - "truebearingdeg": 330, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423346000, - "latdeg": 51.462027899999995, - "lngdeg": -3.1634029999999997, - "truebearingdeg": 331, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423348000, - "latdeg": 51.462112, - "lngdeg": -3.163468, - "truebearingdeg": 333, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423351000, - "latdeg": 51.462185, - "lngdeg": -3.163535, - "truebearingdeg": 331, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423360000, - "latdeg": 51.462257, - "lngdeg": -3.1638569999999997, - "truebearingdeg": 228, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423362000, - "latdeg": 51.462247, - "lngdeg": -3.1638729999999997, - "truebearingdeg": 222, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423364000, - "latdeg": 51.462227, - "lngdeg": -3.1638949999999997, - "truebearingdeg": 211, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423366000, - "latdeg": 51.462207899999996, - "lngdeg": -3.1639149, - "truebearingdeg": 208, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423368000, - "latdeg": 51.462176899999996, - "lngdeg": -3.163945, - "truebearingdeg": 211, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423370000, - "latdeg": 51.462146999999995, - "lngdeg": -3.1639779999999997, - "truebearingdeg": 224, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423372000, - "latdeg": 51.46212, - "lngdeg": -3.164018, - "truebearingdeg": 217, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423374000, - "latdeg": 51.462089999999996, - "lngdeg": -3.164055, - "truebearingdeg": 213, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423376000, - "latdeg": 51.462055, - "lngdeg": -3.164097, - "truebearingdeg": 213, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423378000, - "latdeg": 51.462013, - "lngdeg": -3.1641429999999997, - "truebearingdeg": 219, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423380000, - "latdeg": 51.461982899999995, - "lngdeg": -3.1641969999999997, - "truebearingdeg": 233, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423382000, - "latdeg": 51.46196, - "lngdeg": -3.1642579, - "truebearingdeg": 238, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423384000, - "latdeg": 51.461932999999995, - "lngdeg": -3.1643149999999998, - "truebearingdeg": 229, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423386000, - "latdeg": 51.461906899999995, - "lngdeg": -3.164357, - "truebearingdeg": 228, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423388000, - "latdeg": 51.461898, - "lngdeg": -3.164377, - "truebearingdeg": 213, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423390000, - "latdeg": 51.46188, - "lngdeg": -3.164405, - "truebearingdeg": 221, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423392000, - "latdeg": 51.461842, - "lngdeg": -3.164463, - "truebearingdeg": 222, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423394000, - "latdeg": 51.461794999999995, - "lngdeg": -3.164533, - "truebearingdeg": 223, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423396000, - "latdeg": 51.461737899999996, - "lngdeg": -3.1646199999999998, - "truebearingdeg": 223, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423398000, - "latdeg": 51.461681999999996, - "lngdeg": -3.16471, - "truebearingdeg": 227, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423400000, - "latdeg": 51.461622999999996, - "lngdeg": -3.1648, - "truebearingdeg": 220, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423402000, - "latdeg": 51.461552, - "lngdeg": -3.164888, - "truebearingdeg": 216, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423404000, - "latdeg": 51.461475, - "lngdeg": -3.1649719999999997, - "truebearingdeg": 210, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423406000, - "latdeg": 51.461397999999996, - "lngdeg": -3.165038, - "truebearingdeg": 208, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423408000, - "latdeg": 51.461318, - "lngdeg": -3.165098, - "truebearingdeg": 202, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423410000, - "latdeg": 51.46125, - "lngdeg": -3.1651379, - "truebearingdeg": 196, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423412000, - "latdeg": 51.461177899999996, - "lngdeg": -3.165152, - "truebearingdeg": 185, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423415000, - "latdeg": 51.461104999999996, - "lngdeg": -3.165167, - "truebearingdeg": 177, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423423000, - "latdeg": 51.460767, - "lngdeg": -3.165123, - "truebearingdeg": 169, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423425000, - "latdeg": 51.460702, - "lngdeg": -3.165092, - "truebearingdeg": 169, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423427000, - "latdeg": 51.46064, - "lngdeg": -3.165067, - "truebearingdeg": 165, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423429000, - "latdeg": 51.460578, - "lngdeg": -3.165043, - "truebearingdeg": 167, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423431000, - "latdeg": 51.460522, - "lngdeg": -3.1650199999999997, - "truebearingdeg": 165, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423433000, - "latdeg": 51.460471999999996, - "lngdeg": -3.1649979999999998, - "truebearingdeg": 171, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423435000, - "latdeg": 51.460432999999995, - "lngdeg": -3.164962, - "truebearingdeg": 162, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423437000, - "latdeg": 51.460392, - "lngdeg": -3.164932, - "truebearingdeg": 159, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423439000, - "latdeg": 51.460347, - "lngdeg": -3.16491, - "truebearingdeg": 165, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423441000, - "latdeg": 51.460305, - "lngdeg": -3.164872, - "truebearingdeg": 160, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423443000, - "latdeg": 51.460246999999995, - "lngdeg": -3.164843, - "truebearingdeg": 166, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423445000, - "latdeg": 51.460178, - "lngdeg": -3.164812, - "truebearingdeg": 160, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423447000, - "latdeg": 51.460102, - "lngdeg": -3.164757, - "truebearingdeg": 160, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423449000, - "latdeg": 51.460018, - "lngdeg": -3.1647019999999997, - "truebearingdeg": 155, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423451000, - "latdeg": 51.459938, - "lngdeg": -3.1646519, - "truebearingdeg": 158, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423453000, - "latdeg": 51.459862, - "lngdeg": -3.1645999999999996, - "truebearingdeg": 150, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423455000, - "latdeg": 51.45979, - "lngdeg": -3.164535, - "truebearingdeg": 144, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423457000, - "latdeg": 51.459717, - "lngdeg": -3.164443, - "truebearingdeg": 136, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423459000, - "latdeg": 51.459647999999994, - "lngdeg": -3.1643429999999997, - "truebearingdeg": 141, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423461000, - "latdeg": 51.459568, - "lngdeg": -3.1642419999999998, - "truebearingdeg": 138, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423463000, - "latdeg": 51.45948, - "lngdeg": -3.164138, - "truebearingdeg": 150, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423465000, - "latdeg": 51.459396999999996, - "lngdeg": -3.164107, - "truebearingdeg": 184, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423467000, - "latdeg": 51.459337999999995, - "lngdeg": -3.164155, - "truebearingdeg": 219, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423469000, - "latdeg": 51.459305, - "lngdeg": -3.164212, - "truebearingdeg": 218, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423471000, - "latdeg": 51.459281999999995, - "lngdeg": -3.164247, - "truebearingdeg": 210, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423473000, - "latdeg": 51.459258, - "lngdeg": -3.1642829999999997, - "truebearingdeg": 216, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423475000, - "latdeg": 51.459236999999995, - "lngdeg": -3.16432, - "truebearingdeg": 230, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423477000, - "latdeg": 51.459213, - "lngdeg": -3.164358, - "truebearingdeg": 221, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423480000, - "latdeg": 51.45919, - "lngdeg": -3.164395, - "truebearingdeg": 232, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423488000, - "latdeg": 51.459092, - "lngdeg": -3.1645629, - "truebearingdeg": 229, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423490000, - "latdeg": 51.459077, - "lngdeg": -3.16459, - "truebearingdeg": 228, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423492000, - "latdeg": 51.45906, - "lngdeg": -3.164622, - "truebearingdeg": 231, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423494000, - "latdeg": 51.459044999999996, - "lngdeg": -3.16465, - "truebearingdeg": 229, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423496000, - "latdeg": 51.459033, - "lngdeg": -3.164675, - "truebearingdeg": 232, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423498000, - "latdeg": 51.459027, - "lngdeg": -3.1646929999999998, - "truebearingdeg": 232, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423500000, - "latdeg": 51.459018, - "lngdeg": -3.164707, - "truebearingdeg": 231, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423502000, - "latdeg": 51.459011999999994, - "lngdeg": -3.1647119999999997, - "truebearingdeg": 229, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423504000, - "latdeg": 51.459007, - "lngdeg": -3.1647179999999997, - "truebearingdeg": 227, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423506000, - "latdeg": 51.459002999999996, - "lngdeg": -3.164717, - "truebearingdeg": 231, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423508000, - "latdeg": 51.459002999999996, - "lngdeg": -3.1647119999999997, - "truebearingdeg": 220, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423514000, - "latdeg": 51.458979899999996, - "lngdeg": -3.1647019999999997, - "truebearingdeg": 206, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423520000, - "latdeg": 51.458953, - "lngdeg": -3.164697, - "truebearingdeg": 194, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423522000, - "latdeg": 51.458946999999995, - "lngdeg": -3.164697, - "truebearingdeg": 190, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423524000, - "latdeg": 51.458937999999996, - "lngdeg": -3.164698, - "truebearingdeg": 187, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423526000, - "latdeg": 51.458932, - "lngdeg": -3.164697, - "truebearingdeg": 185, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423528000, - "latdeg": 51.458923, - "lngdeg": -3.164695, - "truebearingdeg": 183, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423530000, - "latdeg": 51.45892, - "lngdeg": -3.1646899, - "truebearingdeg": 176, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423532000, - "latdeg": 51.458915, - "lngdeg": -3.164678, - "truebearingdeg": 168, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423538000, - "latdeg": 51.458908, - "lngdeg": -3.164637, - "truebearingdeg": 151, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423542000, - "latdeg": 51.458892999999996, - "lngdeg": -3.16463, - "truebearingdeg": 153, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423545000, - "latdeg": 51.458881999999996, - "lngdeg": -3.1646349999999996, - "truebearingdeg": 157, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423553000, - "latdeg": 51.458832, - "lngdeg": -3.1646519, - "truebearingdeg": 169, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423559000, - "latdeg": 51.458813, - "lngdeg": -3.164627, - "truebearingdeg": 159, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423561000, - "latdeg": 51.458805, - "lngdeg": -3.164625, - "truebearingdeg": 161, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423563000, - "latdeg": 51.45879, - "lngdeg": -3.164628, - "truebearingdeg": 164, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423569000, - "latdeg": 51.458769999999994, - "lngdeg": -3.1646449999999997, - "truebearingdeg": 179, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423571000, - "latdeg": 51.458763, - "lngdeg": -3.1646419999999997, - "truebearingdeg": 174, - "knotspeed": 0, - "tack": "STARBOARD" - } - ] - }, - { - "name": "Red Bull Sailing Team", - "track": [ - { - "timepoint": 1346422554000, - "latdeg": 51.461566999999995, - "lngdeg": -3.1646549999999998, - "truebearingdeg": 1, - "knotspeed": 5.939524838012959, - "tack": "PORT" - }, - { - "timepoint": 1346422556000, - "latdeg": 51.461628, - "lngdeg": -3.16465, - "truebearingdeg": 4, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346422558000, - "latdeg": 51.461704999999995, - "lngdeg": -3.16464, - "truebearingdeg": 4, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422560000, - "latdeg": 51.461788, - "lngdeg": -3.164603, - "truebearingdeg": 21, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422562000, - "latdeg": 51.461883, - "lngdeg": -3.164513, - "truebearingdeg": 35, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346422564000, - "latdeg": 51.461971999999996, - "lngdeg": -3.164393, - "truebearingdeg": 44, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422566000, - "latdeg": 51.462042999999994, - "lngdeg": -3.164272, - "truebearingdeg": 45, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422568000, - "latdeg": 51.462108, - "lngdeg": -3.164167, - "truebearingdeg": 40, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422570000, - "latdeg": 51.462182, - "lngdeg": -3.164075, - "truebearingdeg": 36, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422572000, - "latdeg": 51.46225, - "lngdeg": -3.163992, - "truebearingdeg": 40, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422574000, - "latdeg": 51.462303, - "lngdeg": -3.163888, - "truebearingdeg": 55, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422576000, - "latdeg": 51.462329999999994, - "lngdeg": -3.163773, - "truebearingdeg": 83, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346422579000, - "latdeg": 51.462329999999994, - "lngdeg": -3.163668, - "truebearingdeg": 94, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422587000, - "latdeg": 51.462257, - "lngdeg": -3.163353, - "truebearingdeg": 128, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422589000, - "latdeg": 51.462199999999996, - "lngdeg": -3.1633, - "truebearingdeg": 168, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422591000, - "latdeg": 51.462165, - "lngdeg": -3.1633069999999996, - "truebearingdeg": 206, - "knotspeed": 3.239740820734341, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422593000, - "latdeg": 51.462146999999995, - "lngdeg": -3.16334, - "truebearingdeg": 241, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422595000, - "latdeg": 51.462134999999996, - "lngdeg": -3.1633769999999997, - "truebearingdeg": 260, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422597000, - "latdeg": 51.462123, - "lngdeg": -3.1634149999999996, - "truebearingdeg": 250, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422599000, - "latdeg": 51.462108, - "lngdeg": -3.163443, - "truebearingdeg": 224, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422601000, - "latdeg": 51.462089999999996, - "lngdeg": -3.163465, - "truebearingdeg": 220, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422603000, - "latdeg": 51.46207, - "lngdeg": -3.1634919999999997, - "truebearingdeg": 215, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422605000, - "latdeg": 51.462047999999996, - "lngdeg": -3.163513, - "truebearingdeg": 202, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422607000, - "latdeg": 51.462005, - "lngdeg": -3.1635329999999997, - "truebearingdeg": 193, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422609000, - "latdeg": 51.461951899999995, - "lngdeg": -3.163538, - "truebearingdeg": 163, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422611000, - "latdeg": 51.461892, - "lngdeg": -3.163535, - "truebearingdeg": 172, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422615000, - "latdeg": 51.461746999999995, - "lngdeg": -3.1634129, - "truebearingdeg": 144, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422617000, - "latdeg": 51.461723, - "lngdeg": -3.16328, - "truebearingdeg": 109, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422619000, - "latdeg": 51.461687999999995, - "lngdeg": -3.163188, - "truebearingdeg": 123, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422621000, - "latdeg": 51.461652, - "lngdeg": -3.1631099999999996, - "truebearingdeg": 129, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422623000, - "latdeg": 51.461616899999996, - "lngdeg": -3.163027, - "truebearingdeg": 125, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422625000, - "latdeg": 51.461582, - "lngdeg": -3.162953, - "truebearingdeg": 133, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422627000, - "latdeg": 51.461543, - "lngdeg": -3.1628849999999997, - "truebearingdeg": 129, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422629000, - "latdeg": 51.461527999999994, - "lngdeg": -3.16281, - "truebearingdeg": 139, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422631000, - "latdeg": 51.461495, - "lngdeg": -3.1627579999999997, - "truebearingdeg": 150, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422633000, - "latdeg": 51.461436899999995, - "lngdeg": -3.16273, - "truebearingdeg": 162, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422635000, - "latdeg": 51.46138, - "lngdeg": -3.162708, - "truebearingdeg": 164, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422637000, - "latdeg": 51.461287999999996, - "lngdeg": -3.1627199999999998, - "truebearingdeg": 181, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422639000, - "latdeg": 51.461234999999995, - "lngdeg": -3.1627329, - "truebearingdeg": 207, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422641000, - "latdeg": 51.461208, - "lngdeg": -3.162747, - "truebearingdeg": 194, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422644000, - "latdeg": 51.461152, - "lngdeg": -3.162762, - "truebearingdeg": 204, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422652000, - "latdeg": 51.461124999999996, - "lngdeg": -3.162978, - "truebearingdeg": 252, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422654000, - "latdeg": 51.461116999999994, - "lngdeg": -3.163013, - "truebearingdeg": 251, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422656000, - "latdeg": 51.461104999999996, - "lngdeg": -3.1630469999999997, - "truebearingdeg": 237, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422658000, - "latdeg": 51.461075, - "lngdeg": -3.16307, - "truebearingdeg": 214, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422660000, - "latdeg": 51.461067, - "lngdeg": -3.1630819999999997, - "truebearingdeg": 219, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422662000, - "latdeg": 51.461062999999996, - "lngdeg": -3.163102, - "truebearingdeg": 235, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422664000, - "latdeg": 51.461054999999995, - "lngdeg": -3.163122, - "truebearingdeg": 234, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422666000, - "latdeg": 51.461045, - "lngdeg": -3.163137, - "truebearingdeg": 232, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422668000, - "latdeg": 51.461037, - "lngdeg": -3.163143, - "truebearingdeg": 231, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422672000, - "latdeg": 51.461009999999995, - "lngdeg": -3.1631619, - "truebearingdeg": 229, - "knotspeed": 0, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422676000, - "latdeg": 51.46098, - "lngdeg": -3.1631649, - "truebearingdeg": 220, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422678000, - "latdeg": 51.46098, - "lngdeg": -3.163172, - "truebearingdeg": 215, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422680000, - "latdeg": 51.460957, - "lngdeg": -3.163175, - "truebearingdeg": 175, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422682000, - "latdeg": 51.46093, - "lngdeg": -3.163172, - "truebearingdeg": 181, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422684000, - "latdeg": 51.460895, - "lngdeg": -3.163173, - "truebearingdeg": 182, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422686000, - "latdeg": 51.460842, - "lngdeg": -3.1631769999999997, - "truebearingdeg": 188, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422688000, - "latdeg": 51.460797, - "lngdeg": -3.1632029999999998, - "truebearingdeg": 194, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422690000, - "latdeg": 51.460738, - "lngdeg": -3.163217, - "truebearingdeg": 186, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422692000, - "latdeg": 51.460682, - "lngdeg": -3.1632379999999998, - "truebearingdeg": 193, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422694000, - "latdeg": 51.460628, - "lngdeg": -3.1632849999999997, - "truebearingdeg": 199, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422696000, - "latdeg": 51.460552, - "lngdeg": -3.1633299999999998, - "truebearingdeg": 198, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422698000, - "latdeg": 51.460468, - "lngdeg": -3.163372, - "truebearingdeg": 196, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422700000, - "latdeg": 51.460373, - "lngdeg": -3.163402, - "truebearingdeg": 195, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422702000, - "latdeg": 51.460288, - "lngdeg": -3.163445, - "truebearingdeg": 203, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422704000, - "latdeg": 51.460204999999995, - "lngdeg": -3.163502, - "truebearingdeg": 207, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422706000, - "latdeg": 51.460125, - "lngdeg": -3.163572, - "truebearingdeg": 207, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422709000, - "latdeg": 51.460032999999996, - "lngdeg": -3.16364, - "truebearingdeg": 206, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422717000, - "latdeg": 51.459595, - "lngdeg": -3.163963, - "truebearingdeg": 199, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422719000, - "latdeg": 51.459468, - "lngdeg": -3.164043, - "truebearingdeg": 202, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422721000, - "latdeg": 51.459347, - "lngdeg": -3.164138, - "truebearingdeg": 207, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422723000, - "latdeg": 51.459224999999996, - "lngdeg": -3.16425, - "truebearingdeg": 210, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422725000, - "latdeg": 51.459117899999995, - "lngdeg": -3.1643649999999997, - "truebearingdeg": 206, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422727000, - "latdeg": 51.459011999999994, - "lngdeg": -3.1644669999999997, - "truebearingdeg": 208, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422729000, - "latdeg": 51.458887999999995, - "lngdeg": -3.1645529999999997, - "truebearingdeg": 206, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422731000, - "latdeg": 51.458751899999996, - "lngdeg": -3.164678, - "truebearingdeg": 213, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422733000, - "latdeg": 51.458633, - "lngdeg": -3.164822, - "truebearingdeg": 215, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422735000, - "latdeg": 51.458515, - "lngdeg": -3.164945, - "truebearingdeg": 213, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422737000, - "latdeg": 51.458405, - "lngdeg": -3.1650579999999997, - "truebearingdeg": 215, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422739000, - "latdeg": 51.45831, - "lngdeg": -3.165165, - "truebearingdeg": 213, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422741000, - "latdeg": 51.458225, - "lngdeg": -3.165248, - "truebearingdeg": 211, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422743000, - "latdeg": 51.458144999999995, - "lngdeg": -3.1653279999999997, - "truebearingdeg": 212, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422745000, - "latdeg": 51.458073, - "lngdeg": -3.16541, - "truebearingdeg": 213, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422747000, - "latdeg": 51.458003, - "lngdeg": -3.165482, - "truebearingdeg": 222, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422749000, - "latdeg": 51.457949899999996, - "lngdeg": -3.1655599999999997, - "truebearingdeg": 212, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422751000, - "latdeg": 51.457882, - "lngdeg": -3.165612, - "truebearingdeg": 204, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422753000, - "latdeg": 51.457812999999994, - "lngdeg": -3.1656679999999997, - "truebearingdeg": 206, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422755000, - "latdeg": 51.457747999999995, - "lngdeg": -3.165708, - "truebearingdeg": 204, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422757000, - "latdeg": 51.457679999999996, - "lngdeg": -3.1657479, - "truebearingdeg": 202, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422759000, - "latdeg": 51.457603, - "lngdeg": -3.1657919999999997, - "truebearingdeg": 201, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422761000, - "latdeg": 51.457505, - "lngdeg": -3.165848, - "truebearingdeg": 202, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422763000, - "latdeg": 51.457417, - "lngdeg": -3.165905, - "truebearingdeg": 204, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422765000, - "latdeg": 51.45737, - "lngdeg": -3.165977, - "truebearingdeg": 207, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422767000, - "latdeg": 51.457218, - "lngdeg": -3.166067, - "truebearingdeg": 206, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422769000, - "latdeg": 51.457122999999996, - "lngdeg": -3.1661419, - "truebearingdeg": 202, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422771000, - "latdeg": 51.457035, - "lngdeg": -3.1662299999999997, - "truebearingdeg": 212, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422774000, - "latdeg": 51.456942999999995, - "lngdeg": -3.166327, - "truebearingdeg": 212, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422788000, - "latdeg": 51.456443, - "lngdeg": -3.166772, - "truebearingdeg": 202, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422790000, - "latdeg": 51.456359899999995, - "lngdeg": -3.1668279999999998, - "truebearingdeg": 203, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422792000, - "latdeg": 51.456277, - "lngdeg": -3.1668819999999998, - "truebearingdeg": 203, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422794000, - "latdeg": 51.456185, - "lngdeg": -3.166957, - "truebearingdeg": 208, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422796000, - "latdeg": 51.456081999999995, - "lngdeg": -3.1670279999999997, - "truebearingdeg": 204, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422798000, - "latdeg": 51.455987, - "lngdeg": -3.167113, - "truebearingdeg": 209, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422800000, - "latdeg": 51.45588, - "lngdeg": -3.1672119999999997, - "truebearingdeg": 212, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422802000, - "latdeg": 51.455782899999996, - "lngdeg": -3.167315, - "truebearingdeg": 218, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422804000, - "latdeg": 51.455697, - "lngdeg": -3.167427, - "truebearingdeg": 217, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422806000, - "latdeg": 51.455608, - "lngdeg": -3.1675329999999997, - "truebearingdeg": 216, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422808000, - "latdeg": 51.455528, - "lngdeg": -3.167662, - "truebearingdeg": 219, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422810000, - "latdeg": 51.455439999999996, - "lngdeg": -3.167758, - "truebearingdeg": 210, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422812000, - "latdeg": 51.455357, - "lngdeg": -3.167837, - "truebearingdeg": 211, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422814000, - "latdeg": 51.455276999999995, - "lngdeg": -3.1679, - "truebearingdeg": 206, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422816000, - "latdeg": 51.4552, - "lngdeg": -3.167957, - "truebearingdeg": 206, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422818000, - "latdeg": 51.455132, - "lngdeg": -3.168017, - "truebearingdeg": 207, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422820000, - "latdeg": 51.455051999999995, - "lngdeg": -3.1680979999999996, - "truebearingdeg": 214, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422822000, - "latdeg": 51.454963, - "lngdeg": -3.16819, - "truebearingdeg": 214, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422824000, - "latdeg": 51.454868, - "lngdeg": -3.168282, - "truebearingdeg": 209, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422826000, - "latdeg": 51.454764999999995, - "lngdeg": -3.1683819, - "truebearingdeg": 213, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422828000, - "latdeg": 51.454673, - "lngdeg": -3.168485, - "truebearingdeg": 206, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422830000, - "latdeg": 51.454578, - "lngdeg": -3.168555, - "truebearingdeg": 205, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422832000, - "latdeg": 51.45449, - "lngdeg": -3.168618, - "truebearingdeg": 203, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422834000, - "latdeg": 51.454392, - "lngdeg": -3.1686829999999997, - "truebearingdeg": 201, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422836000, - "latdeg": 51.454291999999995, - "lngdeg": -3.1687369999999997, - "truebearingdeg": 200, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422839000, - "latdeg": 51.454197, - "lngdeg": -3.168793, - "truebearingdeg": 200, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422847000, - "latdeg": 51.453841999999995, - "lngdeg": -3.1684479999999997, - "truebearingdeg": 110, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422849000, - "latdeg": 51.453803, - "lngdeg": -3.168128, - "truebearingdeg": 105, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422851000, - "latdeg": 51.453773, - "lngdeg": -3.167957, - "truebearingdeg": 105, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422853000, - "latdeg": 51.453747, - "lngdeg": -3.167792, - "truebearingdeg": 101, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422855000, - "latdeg": 51.453722899999995, - "lngdeg": -3.167642, - "truebearingdeg": 107, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422857000, - "latdeg": 51.453692999999994, - "lngdeg": -3.1674979999999997, - "truebearingdeg": 117, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422859000, - "latdeg": 51.453648, - "lngdeg": -3.167363, - "truebearingdeg": 116, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422861000, - "latdeg": 51.453601899999995, - "lngdeg": -3.167202, - "truebearingdeg": 116, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422863000, - "latdeg": 51.453545, - "lngdeg": -3.1670569, - "truebearingdeg": 124, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422865000, - "latdeg": 51.453468, - "lngdeg": -3.166922, - "truebearingdeg": 125, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422867000, - "latdeg": 51.453396999999995, - "lngdeg": -3.166775, - "truebearingdeg": 127, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422869000, - "latdeg": 51.453323, - "lngdeg": -3.166613, - "truebearingdeg": 123, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422871000, - "latdeg": 51.453266899999996, - "lngdeg": -3.166435, - "truebearingdeg": 115, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422873000, - "latdeg": 51.453235, - "lngdeg": -3.166258, - "truebearingdeg": 97, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422875000, - "latdeg": 51.45324, - "lngdeg": -3.1660999999999997, - "truebearingdeg": 76, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422877000, - "latdeg": 51.453266899999996, - "lngdeg": -3.166, - "truebearingdeg": 56, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346422879000, - "latdeg": 51.453308, - "lngdeg": -3.1659219999999997, - "truebearingdeg": 44, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346422881000, - "latdeg": 51.453365, - "lngdeg": -3.165848, - "truebearingdeg": 36, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422883000, - "latdeg": 51.453441999999995, - "lngdeg": -3.1657599999999997, - "truebearingdeg": 37, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422885000, - "latdeg": 51.45353, - "lngdeg": -3.1656329999999997, - "truebearingdeg": 44, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346422887000, - "latdeg": 51.453616999999994, - "lngdeg": -3.165475, - "truebearingdeg": 47, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346422889000, - "latdeg": 51.453711999999996, - "lngdeg": -3.1653029999999998, - "truebearingdeg": 52, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346422891000, - "latdeg": 51.4538, - "lngdeg": -3.1651119999999997, - "truebearingdeg": 53, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346422893000, - "latdeg": 51.453891999999996, - "lngdeg": -3.1649399999999996, - "truebearingdeg": 48, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346422895000, - "latdeg": 51.453992, - "lngdeg": -3.1647819999999998, - "truebearingdeg": 40, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346422897000, - "latdeg": 51.454102, - "lngdeg": -3.1646699999999996, - "truebearingdeg": 32, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346422899000, - "latdeg": 51.454212, - "lngdeg": -3.16457, - "truebearingdeg": 24, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346422901000, - "latdeg": 51.454341899999996, - "lngdeg": -3.1644919999999996, - "truebearingdeg": 17, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346422904000, - "latdeg": 51.454457, - "lngdeg": -3.164465, - "truebearingdeg": 359, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346422912000, - "latdeg": 51.454764999999995, - "lngdeg": -3.164812, - "truebearingdeg": 314, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346422914000, - "latdeg": 51.454817999999996, - "lngdeg": -3.1649029, - "truebearingdeg": 309, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346422916000, - "latdeg": 51.454865, - "lngdeg": -3.165002, - "truebearingdeg": 308, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346422918000, - "latdeg": 51.454913, - "lngdeg": -3.165108, - "truebearingdeg": 306, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422920000, - "latdeg": 51.454963, - "lngdeg": -3.165215, - "truebearingdeg": 303, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422922000, - "latdeg": 51.455013, - "lngdeg": -3.1653379999999998, - "truebearingdeg": 304, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422924000, - "latdeg": 51.455067, - "lngdeg": -3.16546, - "truebearingdeg": 305, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422926000, - "latdeg": 51.455116999999994, - "lngdeg": -3.1655729999999997, - "truebearingdeg": 307, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422928000, - "latdeg": 51.455169999999995, - "lngdeg": -3.165682, - "truebearingdeg": 308, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422930000, - "latdeg": 51.455222899999995, - "lngdeg": -3.165793, - "truebearingdeg": 307, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422932000, - "latdeg": 51.455276999999995, - "lngdeg": -3.165908, - "truebearingdeg": 308, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346422934000, - "latdeg": 51.455338, - "lngdeg": -3.166007, - "truebearingdeg": 316, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346422936000, - "latdeg": 51.455397999999995, - "lngdeg": -3.1661129999999997, - "truebearingdeg": 313, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422938000, - "latdeg": 51.455462999999995, - "lngdeg": -3.1662179999999998, - "truebearingdeg": 314, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422940000, - "latdeg": 51.455528, - "lngdeg": -3.1663349999999997, - "truebearingdeg": 308, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422942000, - "latdeg": 51.455597, - "lngdeg": -3.1664499999999998, - "truebearingdeg": 309, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346422944000, - "latdeg": 51.455664999999996, - "lngdeg": -3.166575, - "truebearingdeg": 312, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422946000, - "latdeg": 51.455749999999995, - "lngdeg": -3.166673, - "truebearingdeg": 324, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346422948000, - "latdeg": 51.45581, - "lngdeg": -3.166782, - "truebearingdeg": 302, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422950000, - "latdeg": 51.45583, - "lngdeg": -3.1669229999999997, - "truebearingdeg": 283, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346422952000, - "latdeg": 51.45583, - "lngdeg": -3.167027, - "truebearingdeg": 257, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422954000, - "latdeg": 51.455818, - "lngdeg": -3.16707, - "truebearingdeg": 217, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422956000, - "latdeg": 51.455791999999995, - "lngdeg": -3.1670979999999997, - "truebearingdeg": 216, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422958000, - "latdeg": 51.455742, - "lngdeg": -3.167153, - "truebearingdeg": 214, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422960000, - "latdeg": 51.455673, - "lngdeg": -3.167223, - "truebearingdeg": 217, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422962000, - "latdeg": 51.455577999999996, - "lngdeg": -3.1673299999999998, - "truebearingdeg": 219, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422964000, - "latdeg": 51.455498, - "lngdeg": -3.1674249999999997, - "truebearingdeg": 213, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422966000, - "latdeg": 51.455413, - "lngdeg": -3.16751, - "truebearingdeg": 211, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422969000, - "latdeg": 51.455329899999995, - "lngdeg": -3.167588, - "truebearingdeg": 209, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422977000, - "latdeg": 51.45494, - "lngdeg": -3.1679649999999997, - "truebearingdeg": 212, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422979000, - "latdeg": 51.454853, - "lngdeg": -3.1680479999999998, - "truebearingdeg": 208, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422981000, - "latdeg": 51.454777, - "lngdeg": -3.168138, - "truebearingdeg": 209, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422983000, - "latdeg": 51.454677999999994, - "lngdeg": -3.16822, - "truebearingdeg": 208, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422985000, - "latdeg": 51.454581999999995, - "lngdeg": -3.1683049999999997, - "truebearingdeg": 207, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422987000, - "latdeg": 51.454479899999995, - "lngdeg": -3.1683879999999998, - "truebearingdeg": 207, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422989000, - "latdeg": 51.454364999999996, - "lngdeg": -3.168482, - "truebearingdeg": 207, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422991000, - "latdeg": 51.454262, - "lngdeg": -3.168565, - "truebearingdeg": 208, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422993000, - "latdeg": 51.454173, - "lngdeg": -3.1686479999999997, - "truebearingdeg": 208, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422995000, - "latdeg": 51.45409, - "lngdeg": -3.1687, - "truebearingdeg": 188, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422997000, - "latdeg": 51.453987, - "lngdeg": -3.1687, - "truebearingdeg": 173, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346422999000, - "latdeg": 51.453888, - "lngdeg": -3.168638, - "truebearingdeg": 151, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423001000, - "latdeg": 51.453773, - "lngdeg": -3.168535, - "truebearingdeg": 137, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423003000, - "latdeg": 51.453674899999996, - "lngdeg": -3.168357, - "truebearingdeg": 129, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423005000, - "latdeg": 51.453595, - "lngdeg": -3.1681499, - "truebearingdeg": 124, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423007000, - "latdeg": 51.453513, - "lngdeg": -3.1679329999999997, - "truebearingdeg": 120, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423009000, - "latdeg": 51.453441999999995, - "lngdeg": -3.1677019, - "truebearingdeg": 113, - "knotspeed": 16.198704103671705, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423011000, - "latdeg": 51.453387899999996, - "lngdeg": -3.1674849999999997, - "truebearingdeg": 105, - "knotspeed": 15.658747300215984, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423013000, - "latdeg": 51.453353, - "lngdeg": -3.167278, - "truebearingdeg": 107, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423015000, - "latdeg": 51.453305, - "lngdeg": -3.1671169999999997, - "truebearingdeg": 115, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423017000, - "latdeg": 51.453258, - "lngdeg": -3.166947, - "truebearingdeg": 110, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423019000, - "latdeg": 51.453232, - "lngdeg": -3.1667669999999997, - "truebearingdeg": 95, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423021000, - "latdeg": 51.453235, - "lngdeg": -3.166617, - "truebearingdeg": 80, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423023000, - "latdeg": 51.453272999999996, - "lngdeg": -3.1664999999999996, - "truebearingdeg": 61, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423025000, - "latdeg": 51.453331999999996, - "lngdeg": -3.166395, - "truebearingdeg": 46, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423027000, - "latdeg": 51.453407, - "lngdeg": -3.16628, - "truebearingdeg": 43, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423029000, - "latdeg": 51.453503, - "lngdeg": -3.166137, - "truebearingdeg": 41, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423031000, - "latdeg": 51.453613, - "lngdeg": -3.1659729999999997, - "truebearingdeg": 42, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423034000, - "latdeg": 51.453717, - "lngdeg": -3.1657979999999997, - "truebearingdeg": 45, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423042000, - "latdeg": 51.454155, - "lngdeg": -3.165062, - "truebearingdeg": 51, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423044000, - "latdeg": 51.45422, - "lngdeg": -3.1649029, - "truebearingdeg": 60, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423046000, - "latdeg": 51.454262, - "lngdeg": -3.164757, - "truebearingdeg": 68, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423048000, - "latdeg": 51.454311999999994, - "lngdeg": -3.164638, - "truebearingdeg": 47, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423050000, - "latdeg": 51.454361999999996, - "lngdeg": -3.164565, - "truebearingdeg": 33, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423052000, - "latdeg": 51.454417899999996, - "lngdeg": -3.1644769999999998, - "truebearingdeg": 42, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423054000, - "latdeg": 51.454494999999994, - "lngdeg": -3.164472, - "truebearingdeg": 352, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423056000, - "latdeg": 51.454575, - "lngdeg": -3.164503, - "truebearingdeg": 338, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423058000, - "latdeg": 51.454657999999995, - "lngdeg": -3.1645969999999997, - "truebearingdeg": 323, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423060000, - "latdeg": 51.454761999999995, - "lngdeg": -3.164723, - "truebearingdeg": 325, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423062000, - "latdeg": 51.454879999999996, - "lngdeg": -3.16485, - "truebearingdeg": 321, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423064000, - "latdeg": 51.455002, - "lngdeg": -3.164993, - "truebearingdeg": 324, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423066000, - "latdeg": 51.455123, - "lngdeg": -3.16512, - "truebearingdeg": 328, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423068000, - "latdeg": 51.455256999999996, - "lngdeg": -3.1652429, - "truebearingdeg": 325, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423070000, - "latdeg": 51.455407, - "lngdeg": -3.165375, - "truebearingdeg": 331, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423072000, - "latdeg": 51.455554899999996, - "lngdeg": -3.1654999999999998, - "truebearingdeg": 333, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423074000, - "latdeg": 51.455697, - "lngdeg": -3.165625, - "truebearingdeg": 331, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423076000, - "latdeg": 51.455836999999995, - "lngdeg": -3.16575, - "truebearingdeg": 329, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423078000, - "latdeg": 51.455962899999996, - "lngdeg": -3.165858, - "truebearingdeg": 333, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423080000, - "latdeg": 51.456072999999996, - "lngdeg": -3.16595, - "truebearingdeg": 334, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423082000, - "latdeg": 51.456188, - "lngdeg": -3.166028, - "truebearingdeg": 333, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423084000, - "latdeg": 51.456303, - "lngdeg": -3.16612, - "truebearingdeg": 335, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423086000, - "latdeg": 51.456421999999996, - "lngdeg": -3.1662119, - "truebearingdeg": 333, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423088000, - "latdeg": 51.456537999999995, - "lngdeg": -3.1663069999999998, - "truebearingdeg": 332, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423090000, - "latdeg": 51.456661999999994, - "lngdeg": -3.166403, - "truebearingdeg": 334, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423092000, - "latdeg": 51.456782999999994, - "lngdeg": -3.166467, - "truebearingdeg": 346, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423094000, - "latdeg": 51.456917, - "lngdeg": -3.1664819, - "truebearingdeg": 0, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423096000, - "latdeg": 51.457023, - "lngdeg": -3.1664469, - "truebearingdeg": 18, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423099000, - "latdeg": 51.457127, - "lngdeg": -3.16635, - "truebearingdeg": 37, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423107000, - "latdeg": 51.457375, - "lngdeg": -3.1651569999999998, - "truebearingdeg": 76, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423109000, - "latdeg": 51.45742, - "lngdeg": -3.164888, - "truebearingdeg": 74, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423111000, - "latdeg": 51.45747, - "lngdeg": -3.1646579999999997, - "truebearingdeg": 69, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423113000, - "latdeg": 51.457519999999995, - "lngdeg": -3.164425, - "truebearingdeg": 69, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423115000, - "latdeg": 51.457577, - "lngdeg": -3.164208, - "truebearingdeg": 65, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423117000, - "latdeg": 51.457637999999996, - "lngdeg": -3.164033, - "truebearingdeg": 60, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423119000, - "latdeg": 51.457695, - "lngdeg": -3.163872, - "truebearingdeg": 63, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423121000, - "latdeg": 51.45774, - "lngdeg": -3.163713, - "truebearingdeg": 66, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423123000, - "latdeg": 51.457775, - "lngdeg": -3.163567, - "truebearingdeg": 72, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423125000, - "latdeg": 51.457778, - "lngdeg": -3.163427, - "truebearingdeg": 94, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423127000, - "latdeg": 51.457775, - "lngdeg": -3.1633199999999997, - "truebearingdeg": 123, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423129000, - "latdeg": 51.457724999999996, - "lngdeg": -3.16324, - "truebearingdeg": 129, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423131000, - "latdeg": 51.457671999999995, - "lngdeg": -3.1631619, - "truebearingdeg": 134, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423133000, - "latdeg": 51.457623, - "lngdeg": -3.1630749999999996, - "truebearingdeg": 132, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423135000, - "latdeg": 51.457561999999996, - "lngdeg": -3.162998, - "truebearingdeg": 153, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423137000, - "latdeg": 51.457505, - "lngdeg": -3.162997, - "truebearingdeg": 198, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423139000, - "latdeg": 51.457454999999996, - "lngdeg": -3.163048, - "truebearingdeg": 212, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423141000, - "latdeg": 51.457404999999994, - "lngdeg": -3.163135, - "truebearingdeg": 233, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423143000, - "latdeg": 51.457359999999994, - "lngdeg": -3.1632379999999998, - "truebearingdeg": 234, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423145000, - "latdeg": 51.45731, - "lngdeg": -3.163338, - "truebearingdeg": 233, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423147000, - "latdeg": 51.457263, - "lngdeg": -3.1634319, - "truebearingdeg": 238, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423149000, - "latdeg": 51.457218, - "lngdeg": -3.163528, - "truebearingdeg": 230, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423151000, - "latdeg": 51.457167999999996, - "lngdeg": -3.1636279999999997, - "truebearingdeg": 232, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423153000, - "latdeg": 51.457117999999994, - "lngdeg": -3.163732, - "truebearingdeg": 229, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423155000, - "latdeg": 51.457069999999995, - "lngdeg": -3.163815, - "truebearingdeg": 222, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423157000, - "latdeg": 51.457007999999995, - "lngdeg": -3.163885, - "truebearingdeg": 209, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423159000, - "latdeg": 51.456942999999995, - "lngdeg": -3.163942, - "truebearingdeg": 204, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423161000, - "latdeg": 51.456874899999995, - "lngdeg": -3.163982, - "truebearingdeg": 198, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423164000, - "latdeg": 51.45681, - "lngdeg": -3.164015, - "truebearingdeg": 201, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423172000, - "latdeg": 51.456382999999995, - "lngdeg": -3.16431, - "truebearingdeg": 211, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423174000, - "latdeg": 51.456292, - "lngdeg": -3.164405, - "truebearingdeg": 215, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423176000, - "latdeg": 51.456202999999995, - "lngdeg": -3.164517, - "truebearingdeg": 220, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423178000, - "latdeg": 51.456126999999995, - "lngdeg": -3.164622, - "truebearingdeg": 221, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423180000, - "latdeg": 51.456055, - "lngdeg": -3.164745, - "truebearingdeg": 227, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423182000, - "latdeg": 51.45597, - "lngdeg": -3.1648769999999997, - "truebearingdeg": 221, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423184000, - "latdeg": 51.455887, - "lngdeg": -3.164993, - "truebearingdeg": 223, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423186000, - "latdeg": 51.455822, - "lngdeg": -3.1651219999999998, - "truebearingdeg": 233, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423188000, - "latdeg": 51.455788, - "lngdeg": -3.165272, - "truebearingdeg": 264, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423190000, - "latdeg": 51.455802999999996, - "lngdeg": -3.165372, - "truebearingdeg": 300, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346423192000, - "latdeg": 51.455833, - "lngdeg": -3.165413, - "truebearingdeg": 319, - "knotspeed": 3.7796976241900646, - "tack": "PORT" - }, - { - "timepoint": 1346423194000, - "latdeg": 51.455872, - "lngdeg": -3.1654429, - "truebearingdeg": 324, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346423196000, - "latdeg": 51.455925, - "lngdeg": -3.1654929999999997, - "truebearingdeg": 324, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423198000, - "latdeg": 51.455993, - "lngdeg": -3.165572, - "truebearingdeg": 322, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423200000, - "latdeg": 51.456072999999996, - "lngdeg": -3.165682, - "truebearingdeg": 316, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423202000, - "latdeg": 51.45615, - "lngdeg": -3.1658169999999997, - "truebearingdeg": 307, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423204000, - "latdeg": 51.456199999999995, - "lngdeg": -3.165953, - "truebearingdeg": 298, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423206000, - "latdeg": 51.456244999999996, - "lngdeg": -3.166085, - "truebearingdeg": 304, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423208000, - "latdeg": 51.456292, - "lngdeg": -3.1661919999999997, - "truebearingdeg": 308, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423210000, - "latdeg": 51.456348, - "lngdeg": -3.166277, - "truebearingdeg": 325, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423212000, - "latdeg": 51.456427999999995, - "lngdeg": -3.1663319999999997, - "truebearingdeg": 342, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423214000, - "latdeg": 51.456528, - "lngdeg": -3.166388, - "truebearingdeg": 339, - "knotspeed": 11.339092872570195, - "tack": "PORT" - }, - { - "timepoint": 1346423216000, - "latdeg": 51.456638, - "lngdeg": -3.166457, - "truebearingdeg": 339, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423218000, - "latdeg": 51.456756999999996, - "lngdeg": -3.166533, - "truebearingdeg": 335, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423220000, - "latdeg": 51.456872, - "lngdeg": -3.1666079999999996, - "truebearingdeg": 340, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423222000, - "latdeg": 51.456984999999996, - "lngdeg": -3.166642, - "truebearingdeg": 350, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423224000, - "latdeg": 51.457111999999995, - "lngdeg": -3.166638, - "truebearingdeg": 0, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423226000, - "latdeg": 51.457245, - "lngdeg": -3.1666, - "truebearingdeg": 13, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423229000, - "latdeg": 51.457397, - "lngdeg": -3.1665069999999997, - "truebearingdeg": 22, - "knotspeed": 17.818574514038875, - "tack": "PORT" - }, - { - "timepoint": 1346423236000, - "latdeg": 51.458102999999994, - "lngdeg": -3.1659219999999997, - "truebearingdeg": 22, - "knotspeed": 21.05831533477322, - "tack": "PORT" - }, - { - "timepoint": 1346423238000, - "latdeg": 51.458293, - "lngdeg": -3.16581, - "truebearingdeg": 19, - "knotspeed": 21.05831533477322, - "tack": "PORT" - }, - { - "timepoint": 1346423240000, - "latdeg": 51.458481899999995, - "lngdeg": -3.1656999999999997, - "truebearingdeg": 19, - "knotspeed": 19.978401727861772, - "tack": "PORT" - }, - { - "timepoint": 1346423242000, - "latdeg": 51.458656999999995, - "lngdeg": -3.165597, - "truebearingdeg": 18, - "knotspeed": 20.518358531317496, - "tack": "PORT" - }, - { - "timepoint": 1346423244000, - "latdeg": 51.458852, - "lngdeg": -3.165488, - "truebearingdeg": 19, - "knotspeed": 20.518358531317496, - "tack": "PORT" - }, - { - "timepoint": 1346423246000, - "latdeg": 51.459022999999995, - "lngdeg": -3.1654079999999998, - "truebearingdeg": 16, - "knotspeed": 19.43844492440605, - "tack": "PORT" - }, - { - "timepoint": 1346423248000, - "latdeg": 51.459195, - "lngdeg": -3.1653319, - "truebearingdeg": 12, - "knotspeed": 18.3585313174946, - "tack": "PORT" - }, - { - "timepoint": 1346423250000, - "latdeg": 51.459342899999996, - "lngdeg": -3.1652769999999997, - "truebearingdeg": 10, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423252000, - "latdeg": 51.459494899999996, - "lngdeg": -3.165272, - "truebearingdeg": 7, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423254000, - "latdeg": 51.459607, - "lngdeg": -3.1652679999999997, - "truebearingdeg": 8, - "knotspeed": 12.958963282937365, - "tack": "PORT" - }, - { - "timepoint": 1346423256000, - "latdeg": 51.459717, - "lngdeg": -3.165212, - "truebearingdeg": 16, - "knotspeed": 14.038876889848812, - "tack": "PORT" - }, - { - "timepoint": 1346423258000, - "latdeg": 51.459849999999996, - "lngdeg": -3.165152, - "truebearingdeg": 14, - "knotspeed": 14.578833693304535, - "tack": "PORT" - }, - { - "timepoint": 1346423260000, - "latdeg": 51.459983, - "lngdeg": -3.1650869999999998, - "truebearingdeg": 16, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423262000, - "latdeg": 51.460125, - "lngdeg": -3.1650169999999997, - "truebearingdeg": 16, - "knotspeed": 15.658747300215984, - "tack": "PORT" - }, - { - "timepoint": 1346423264000, - "latdeg": 51.460277999999995, - "lngdeg": -3.1649369999999997, - "truebearingdeg": 20, - "knotspeed": 16.73866090712743, - "tack": "PORT" - }, - { - "timepoint": 1346423266000, - "latdeg": 51.460423, - "lngdeg": -3.1648449999999997, - "truebearingdeg": 20, - "knotspeed": 17.278617710583152, - "tack": "PORT" - }, - { - "timepoint": 1346423268000, - "latdeg": 51.46056, - "lngdeg": -3.164758, - "truebearingdeg": 21, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423270000, - "latdeg": 51.460685, - "lngdeg": -3.164672, - "truebearingdeg": 22, - "knotspeed": 13.498920086393088, - "tack": "PORT" - }, - { - "timepoint": 1346423272000, - "latdeg": 51.4608, - "lngdeg": -3.1645979, - "truebearingdeg": 19, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423274000, - "latdeg": 51.460902999999995, - "lngdeg": -3.164552, - "truebearingdeg": 15, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423276000, - "latdeg": 51.46098, - "lngdeg": -3.164523, - "truebearingdeg": 15, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423278000, - "latdeg": 51.461062999999996, - "lngdeg": -3.1644919999999996, - "truebearingdeg": 12, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423280000, - "latdeg": 51.461143, - "lngdeg": -3.164463, - "truebearingdeg": 11, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423282000, - "latdeg": 51.461227, - "lngdeg": -3.1644349999999997, - "truebearingdeg": 11, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423284000, - "latdeg": 51.461303, - "lngdeg": -3.164408, - "truebearingdeg": 13, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423286000, - "latdeg": 51.461377, - "lngdeg": -3.16438, - "truebearingdeg": 15, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423288000, - "latdeg": 51.461445, - "lngdeg": -3.1643529999999997, - "truebearingdeg": 14, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423290000, - "latdeg": 51.461512899999995, - "lngdeg": -3.164325, - "truebearingdeg": 15, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423293000, - "latdeg": 51.46159, - "lngdeg": -3.1642919999999997, - "truebearingdeg": 14, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423301000, - "latdeg": 51.461842, - "lngdeg": -3.16376, - "truebearingdeg": 128, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423303000, - "latdeg": 51.461788, - "lngdeg": -3.163703, - "truebearingdeg": 157, - "knotspeed": 6.479481641468682, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423305000, - "latdeg": 51.461723, - "lngdeg": -3.1636949999999997, - "truebearingdeg": 179, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423307000, - "latdeg": 51.461631999999994, - "lngdeg": -3.1637079999999997, - "truebearingdeg": 188, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423309000, - "latdeg": 51.461527999999994, - "lngdeg": -3.163738, - "truebearingdeg": 195, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423311000, - "latdeg": 51.46141, - "lngdeg": -3.1637679999999997, - "truebearingdeg": 183, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423313000, - "latdeg": 51.461284899999995, - "lngdeg": -3.1637679999999997, - "truebearingdeg": 184, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423315000, - "latdeg": 51.461163, - "lngdeg": -3.16378, - "truebearingdeg": 185, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423317000, - "latdeg": 51.461051999999995, - "lngdeg": -3.1637999999999997, - "truebearingdeg": 187, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423319000, - "latdeg": 51.460952999999996, - "lngdeg": -3.1638219999999997, - "truebearingdeg": 185, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423321000, - "latdeg": 51.460865, - "lngdeg": -3.1638349999999997, - "truebearingdeg": 185, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423323000, - "latdeg": 51.460788, - "lngdeg": -3.163837, - "truebearingdeg": 180, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423325000, - "latdeg": 51.460716999999995, - "lngdeg": -3.1638479999999998, - "truebearingdeg": 186, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423327000, - "latdeg": 51.460637, - "lngdeg": -3.1638629999999996, - "truebearingdeg": 186, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423329000, - "latdeg": 51.460547999999996, - "lngdeg": -3.1638699999999997, - "truebearingdeg": 181, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423331000, - "latdeg": 51.460445, - "lngdeg": -3.16385, - "truebearingdeg": 175, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423333000, - "latdeg": 51.460327, - "lngdeg": -3.16383, - "truebearingdeg": 171, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423335000, - "latdeg": 51.460204999999995, - "lngdeg": -3.1638099, - "truebearingdeg": 174, - "knotspeed": 13.498920086393088, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423337000, - "latdeg": 51.460074999999996, - "lngdeg": -3.1637869999999997, - "truebearingdeg": 172, - "knotspeed": 14.038876889848812, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423339000, - "latdeg": 51.459934999999994, - "lngdeg": -3.1637519999999997, - "truebearingdeg": 166, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423341000, - "latdeg": 51.459796999999995, - "lngdeg": -3.1636729999999997, - "truebearingdeg": 154, - "knotspeed": 15.118790496760258, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423343000, - "latdeg": 51.459677899999996, - "lngdeg": -3.163537, - "truebearingdeg": 135, - "knotspeed": 16.73866090712743, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423345000, - "latdeg": 51.459598, - "lngdeg": -3.1633329999999997, - "truebearingdeg": 115, - "knotspeed": 14.578833693304535, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423347000, - "latdeg": 51.459582999999995, - "lngdeg": -3.1631449999999997, - "truebearingdeg": 82, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423349000, - "latdeg": 51.45961, - "lngdeg": -3.163033, - "truebearingdeg": 62, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423351000, - "latdeg": 51.45966, - "lngdeg": -3.1629649, - "truebearingdeg": 30, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423353000, - "latdeg": 51.459725, - "lngdeg": -3.162932, - "truebearingdeg": 18, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423355000, - "latdeg": 51.459812, - "lngdeg": -3.1629099999999997, - "truebearingdeg": 4, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423358000, - "latdeg": 51.459938, - "lngdeg": -3.1629229999999997, - "truebearingdeg": 353, - "knotspeed": 12.419006479481641, - "tack": "PORT" - }, - { - "timepoint": 1346423366000, - "latdeg": 51.460583, - "lngdeg": -3.1630819999999997, - "truebearingdeg": 344, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423368000, - "latdeg": 51.460722999999994, - "lngdeg": -3.163153, - "truebearingdeg": 341, - "knotspeed": 16.198704103671705, - "tack": "PORT" - }, - { - "timepoint": 1346423370000, - "latdeg": 51.460862, - "lngdeg": -3.16323, - "truebearingdeg": 339, - "knotspeed": 15.118790496760258, - "tack": "PORT" - }, - { - "timepoint": 1346423372000, - "latdeg": 51.460983, - "lngdeg": -3.163297, - "truebearingdeg": 344, - "knotspeed": 11.879049676025918, - "tack": "PORT" - }, - { - "timepoint": 1346423374000, - "latdeg": 51.461082, - "lngdeg": -3.163328, - "truebearingdeg": 344, - "knotspeed": 9.719222462203025, - "tack": "PORT" - }, - { - "timepoint": 1346423376000, - "latdeg": 51.461155, - "lngdeg": -3.163363, - "truebearingdeg": 345, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423378000, - "latdeg": 51.46122, - "lngdeg": -3.163385, - "truebearingdeg": 348, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423380000, - "latdeg": 51.461284899999995, - "lngdeg": -3.16341, - "truebearingdeg": 347, - "knotspeed": 7.019438444924406, - "tack": "PORT" - }, - { - "timepoint": 1346423382000, - "latdeg": 51.461344999999994, - "lngdeg": -3.1634569999999997, - "truebearingdeg": 341, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423384000, - "latdeg": 51.461422, - "lngdeg": -3.163487, - "truebearingdeg": 349, - "knotspeed": 7.559395248380129, - "tack": "PORT" - }, - { - "timepoint": 1346423386000, - "latdeg": 51.461498, - "lngdeg": -3.163512, - "truebearingdeg": 346, - "knotspeed": 8.099352051835853, - "tack": "PORT" - }, - { - "timepoint": 1346423388000, - "latdeg": 51.461574999999996, - "lngdeg": -3.163542, - "truebearingdeg": 349, - "knotspeed": 8.639308855291576, - "tack": "PORT" - }, - { - "timepoint": 1346423390000, - "latdeg": 51.461658, - "lngdeg": -3.1635679999999997, - "truebearingdeg": 341, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423392000, - "latdeg": 51.461746999999995, - "lngdeg": -3.163627, - "truebearingdeg": 333, - "knotspeed": 10.799136069114471, - "tack": "PORT" - }, - { - "timepoint": 1346423394000, - "latdeg": 51.461822999999995, - "lngdeg": -3.163722, - "truebearingdeg": 321, - "knotspeed": 10.259179265658748, - "tack": "PORT" - }, - { - "timepoint": 1346423396000, - "latdeg": 51.461883, - "lngdeg": -3.1638219999999997, - "truebearingdeg": 315, - "knotspeed": 9.1792656587473, - "tack": "PORT" - }, - { - "timepoint": 1346423398000, - "latdeg": 51.461906899999995, - "lngdeg": -3.1639169999999996, - "truebearingdeg": 301, - "knotspeed": 6.479481641468682, - "tack": "PORT" - }, - { - "timepoint": 1346423400000, - "latdeg": 51.461912999999996, - "lngdeg": -3.163993, - "truebearingdeg": 277, - "knotspeed": 4.859611231101512, - "tack": "PORT" - }, - { - "timepoint": 1346423402000, - "latdeg": 51.461912999999996, - "lngdeg": -3.164058, - "truebearingdeg": 263, - "knotspeed": 3.7796976241900646, - "tack": "PORT" - }, - { - "timepoint": 1346423404000, - "latdeg": 51.461898, - "lngdeg": -3.1640949999999997, - "truebearingdeg": 241, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423406000, - "latdeg": 51.461883, - "lngdeg": -3.16412, - "truebearingdeg": 211, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423408000, - "latdeg": 51.461877, - "lngdeg": -3.1641399999999997, - "truebearingdeg": 221, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423410000, - "latdeg": 51.461856999999995, - "lngdeg": -3.164155, - "truebearingdeg": 205, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423412000, - "latdeg": 51.461844899999996, - "lngdeg": -3.1641679999999996, - "truebearingdeg": 203, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423414000, - "latdeg": 51.461853, - "lngdeg": -3.164192, - "truebearingdeg": 217, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423416000, - "latdeg": 51.46183, - "lngdeg": -3.16423, - "truebearingdeg": 227, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423418000, - "latdeg": 51.461791999999996, - "lngdeg": -3.1643019999999997, - "truebearingdeg": 239, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423420000, - "latdeg": 51.461765, - "lngdeg": -3.164368, - "truebearingdeg": 238, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423423000, - "latdeg": 51.461737899999996, - "lngdeg": -3.1644379999999996, - "truebearingdeg": 237, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423431000, - "latdeg": 51.46159, - "lngdeg": -3.1647719999999997, - "truebearingdeg": 234, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423433000, - "latdeg": 51.461577999999996, - "lngdeg": -3.1648129999999997, - "truebearingdeg": 233, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423435000, - "latdeg": 51.46156, - "lngdeg": -3.164847, - "truebearingdeg": 213, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423437000, - "latdeg": 51.46154, - "lngdeg": -3.164872, - "truebearingdeg": 225, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423439000, - "latdeg": 51.461521999999995, - "lngdeg": -3.164892, - "truebearingdeg": 212, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423441000, - "latdeg": 51.461501999999996, - "lngdeg": -3.16491, - "truebearingdeg": 209, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423443000, - "latdeg": 51.461472, - "lngdeg": -3.16493, - "truebearingdeg": 206, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423445000, - "latdeg": 51.461445, - "lngdeg": -3.164955, - "truebearingdeg": 211, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423447000, - "latdeg": 51.46141, - "lngdeg": -3.1649919, - "truebearingdeg": 219, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423449000, - "latdeg": 51.461372, - "lngdeg": -3.165032, - "truebearingdeg": 209, - "knotspeed": 4.319654427645788, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423451000, - "latdeg": 51.461329899999996, - "lngdeg": -3.1650649, - "truebearingdeg": 208, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423453000, - "latdeg": 51.461276999999995, - "lngdeg": -3.165108, - "truebearingdeg": 205, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423455000, - "latdeg": 51.461208, - "lngdeg": -3.165175, - "truebearingdeg": 211, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423457000, - "latdeg": 51.461132, - "lngdeg": -3.165247, - "truebearingdeg": 208, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423459000, - "latdeg": 51.461051999999995, - "lngdeg": -3.16531, - "truebearingdeg": 200, - "knotspeed": 9.1792656587473, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423461000, - "latdeg": 51.460975, - "lngdeg": -3.1653599999999997, - "truebearingdeg": 199, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423463000, - "latdeg": 51.460915, - "lngdeg": -3.165387, - "truebearingdeg": 196, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423465000, - "latdeg": 51.46085, - "lngdeg": -3.165415, - "truebearingdeg": 192, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423467000, - "latdeg": 51.460781999999995, - "lngdeg": -3.1654329999999997, - "truebearingdeg": 186, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423469000, - "latdeg": 51.460702, - "lngdeg": -3.1654419999999996, - "truebearingdeg": 181, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423471000, - "latdeg": 51.460612999999995, - "lngdeg": -3.165428, - "truebearingdeg": 163, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423473000, - "latdeg": 51.460539999999995, - "lngdeg": -3.1653569999999998, - "truebearingdeg": 143, - "knotspeed": 7.559395248380129, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423475000, - "latdeg": 51.460494999999995, - "lngdeg": -3.165292, - "truebearingdeg": 127, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423477000, - "latdeg": 51.460457, - "lngdeg": -3.165235, - "truebearingdeg": 145, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423479000, - "latdeg": 51.460387999999995, - "lngdeg": -3.165247, - "truebearingdeg": 146, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423481000, - "latdeg": 51.460342, - "lngdeg": -3.1651949999999998, - "truebearingdeg": 134, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423483000, - "latdeg": 51.460293, - "lngdeg": -3.165145, - "truebearingdeg": 144, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423485000, - "latdeg": 51.460243, - "lngdeg": -3.1650899999999997, - "truebearingdeg": 145, - "knotspeed": 5.399568034557236, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423488000, - "latdeg": 51.460192899999996, - "lngdeg": -3.1650229999999997, - "truebearingdeg": 146, - "knotspeed": 7.019438444924406, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423496000, - "latdeg": 51.459846999999996, - "lngdeg": -3.164573, - "truebearingdeg": 135, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423498000, - "latdeg": 51.459754999999994, - "lngdeg": -3.164457, - "truebearingdeg": 133, - "knotspeed": 11.879049676025918, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423500000, - "latdeg": 51.459692999999994, - "lngdeg": -3.1643179999999997, - "truebearingdeg": 128, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423502000, - "latdeg": 51.459621999999996, - "lngdeg": -3.1641999999999997, - "truebearingdeg": 136, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423504000, - "latdeg": 51.459553, - "lngdeg": -3.1640769, - "truebearingdeg": 135, - "knotspeed": 10.799136069114471, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423506000, - "latdeg": 51.459492, - "lngdeg": -3.163957, - "truebearingdeg": 128, - "knotspeed": 9.719222462203025, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423508000, - "latdeg": 51.459438, - "lngdeg": -3.1638349999999997, - "truebearingdeg": 126, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423510000, - "latdeg": 51.459382, - "lngdeg": -3.1637, - "truebearingdeg": 122, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423512000, - "latdeg": 51.459317, - "lngdeg": -3.163532, - "truebearingdeg": 116, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423514000, - "latdeg": 51.459266899999996, - "lngdeg": -3.163353, - "truebearingdeg": 111, - "knotspeed": 12.958963282937365, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423516000, - "latdeg": 51.459224999999996, - "lngdeg": -3.163178, - "truebearingdeg": 109, - "knotspeed": 12.419006479481641, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423518000, - "latdeg": 51.459187, - "lngdeg": -3.16302, - "truebearingdeg": 109, - "knotspeed": 11.339092872570195, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423520000, - "latdeg": 51.459145, - "lngdeg": -3.162865, - "truebearingdeg": 119, - "knotspeed": 10.259179265658748, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423522000, - "latdeg": 51.459083, - "lngdeg": -3.1627479999999997, - "truebearingdeg": 146, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423524000, - "latdeg": 51.459007, - "lngdeg": -3.162715, - "truebearingdeg": 177, - "knotspeed": 8.639308855291576, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423526000, - "latdeg": 51.458932, - "lngdeg": -3.162762, - "truebearingdeg": 210, - "knotspeed": 8.099352051835853, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423528000, - "latdeg": 51.458887999999995, - "lngdeg": -3.162845, - "truebearingdeg": 240, - "knotspeed": 5.939524838012959, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423530000, - "latdeg": 51.458867, - "lngdeg": -3.162918, - "truebearingdeg": 233, - "knotspeed": 4.859611231101512, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423532000, - "latdeg": 51.458842999999995, - "lngdeg": -3.162975, - "truebearingdeg": 237, - "knotspeed": 3.7796976241900646, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423534000, - "latdeg": 51.458832, - "lngdeg": -3.1630249999999998, - "truebearingdeg": 243, - "knotspeed": 2.699784017278618, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423536000, - "latdeg": 51.458813, - "lngdeg": -3.1630599999999998, - "truebearingdeg": 227, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423542000, - "latdeg": 51.458769999999994, - "lngdeg": -3.163097, - "truebearingdeg": 213, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423544000, - "latdeg": 51.458763, - "lngdeg": -3.1630949999999998, - "truebearingdeg": 211, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423546000, - "latdeg": 51.458743, - "lngdeg": -3.163103, - "truebearingdeg": 195, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423548000, - "latdeg": 51.458728, - "lngdeg": -3.163113, - "truebearingdeg": 194, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423550000, - "latdeg": 51.458709999999996, - "lngdeg": -3.163125, - "truebearingdeg": 198, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423553000, - "latdeg": 51.45869, - "lngdeg": -3.163135, - "truebearingdeg": 196, - "knotspeed": 2.159827213822894, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423561000, - "latdeg": 51.458607, - "lngdeg": -3.1632029999999998, - "truebearingdeg": 217, - "knotspeed": 1.6198704103671706, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423563000, - "latdeg": 51.458594999999995, - "lngdeg": -3.16322, - "truebearingdeg": 218, - "knotspeed": 1.079913606911447, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423567000, - "latdeg": 51.458577, - "lngdeg": -3.163233, - "truebearingdeg": 215, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423569000, - "latdeg": 51.458571899999995, - "lngdeg": -3.1632219999999998, - "truebearingdeg": 206, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - }, - { - "timepoint": 1346423571000, - "latdeg": 51.458568, - "lngdeg": -3.163208, - "truebearingdeg": 197, - "knotspeed": 0.5399568034557235, - "tack": "STARBOARD" - } - ] - } - ] - -} \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/showraceDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/showraceDoc.html deleted file mode 100755 index c0a87ac3289..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/showraceDoc.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - Deprecated Web Services API - - -

    SAP Sailing Analytics - Web Services

    -

    Name: showrace

    - -Description: -

    This service exports race and competitor data, either for a specific time point or for the end of the race. -Time points in the document are Unix millisecond time stamps.

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Webservice Type:REST
    Output format:Json
    Base-Url:http://www.sapsailing.com/sailingserver/moderator?action=showrace
    Mandatory parameters: -
    regattaname
    -
    racename
    -
    Optional parameters: -
    time
    -
    timeasmillis
    -
    sinceupdate
    -
    Request method:GET
    Example:http://www.sapsailing.com/sailingserver/moderator?action=showrace&regattaname=ESS%202012%20Cardiff%20%28Extreme40%29&racename=Cardiff%20Race12
    - -Back to Web Service Overview - - diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/showraceSample.json b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/showraceSample.json deleted file mode 100755 index 25b60ceff12..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/showraceSample.json +++ /dev/null @@ -1,1949 +0,0 @@ -{ - - "name": "Cardiff Race12", - "startoftracking": 1346422553000, - "endoftracking": 1346423572000, - "start": 1346422684000, - "end": 1346423392000, - "timeofnewestevent": 1346423572000, - "timeoflastevent": 1346423548000, - "updatecount": 4235, - "windaveragingintervalmillis": 30000, - "speedaveragingintervalmillis": 8000, - "wind": { - "truebearingdeg": 107.92486112148626, - "knotspeed": 9.994357892258785, - "meterspersecondspeed": 5.1423747566197076 - }, - "legs": [ - { - "from": "Start", - "fromwaypointid": 1, - "to": "Red", - "towaypointid": 2, - "upordownwindleg": false, - "competitors": [ - { - "name": "Alinghi", - "currentSpeedOverGroundInKnots": 10.272349838961413, - "currentSpeedOverGroundInMetersPerSecond": 5.285409335196729, - "averageSpeedOverGroundInKnots": 13.082006984716463, - "averageSpeedOverGroundInMetersPerSecond": 6.729965815470803, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 13.082006984716463, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.729965815470803, - "distanceTraveledOverGroundInMeters": 847.9756927493212, - "velocityMadeGoodInKnots": 0.046225615305889874, - "velocityMadeGoodInMetersPerSecond": 0.02378436311974995, - "averageVelocityMadeGoodInKnots": 11.147985698149608, - "averageVelocityMadeGoodInMetersPerSecond": 5.7350193091591875, - "rank": 4, - "gapToLeaderInSeconds": 13, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "GAC Pindar", - "currentSpeedOverGroundInKnots": 12.711053514686297, - "currentSpeedOverGroundInMetersPerSecond": 6.5401901181259525, - "averageSpeedOverGroundInKnots": 13.775329288114673, - "averageSpeedOverGroundInMetersPerSecond": 7.086641622663437, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 13.775329288114673, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.086641622663437, - "distanceTraveledOverGroundInMeters": 843.310353096949, - "velocityMadeGoodInKnots": 0.5274743558353315, - "velocityMadeGoodInMetersPerSecond": 0.2714002081427179, - "averageVelocityMadeGoodInKnots": 10.259629671751073, - "averageVelocityMadeGoodInMetersPerSecond": 5.278009486689718, - "rank": 1, - "gapToLeaderInSeconds": 0, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Groupe Edmond de Rothschild", - "currentSpeedOverGroundInKnots": 10.839714814886195, - "currentSpeedOverGroundInMetersPerSecond": 5.57733437544825, - "averageSpeedOverGroundInKnots": 12.86105355491837, - "averageSpeedOverGroundInMetersPerSecond": 6.616297551030228, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 12.86105355491837, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.616297551030228, - "distanceTraveledOverGroundInMeters": 846.8860865318693, - "velocityMadeGoodInKnots": 3.0797703490135584, - "velocityMadeGoodInMetersPerSecond": 1.5846273937438373, - "averageVelocityMadeGoodInKnots": 8.59819464436454, - "averageVelocityMadeGoodInMetersPerSecond": 4.423293467045314, - "rank": 7, - "gapToLeaderInSeconds": 32, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Oman Air", - "currentSpeedOverGroundInKnots": 7.907086482812614, - "currentSpeedOverGroundInMetersPerSecond": 4.06841563669828, - "averageSpeedOverGroundInKnots": 12.142163685507393, - "averageSpeedOverGroundInMetersPerSecond": 6.246468651544358, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 12.142163685507393, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.246468651544358, - "distanceTraveledOverGroundInMeters": 886.998548519299, - "velocityMadeGoodInKnots": 2.8784643535966454, - "velocityMadeGoodInMetersPerSecond": 1.4810498672686294, - "averageVelocityMadeGoodInKnots": 9.990110721578992, - "averageVelocityMadeGoodInMetersPerSecond": 5.139356960101192, - "rank": 6, - "gapToLeaderInSeconds": 24, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Red Bull Sailing Team", - "currentSpeedOverGroundInKnots": 11.9479605487459, - "currentSpeedOverGroundInMetersPerSecond": 6.147557590122787, - "averageSpeedOverGroundInKnots": 10.904165221135408, - "averageSpeedOverGroundInMetersPerSecond": 5.609587219317437, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 10.904165221135408, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.609587219317437, - "distanceTraveledOverGroundInMeters": 841.4380828976156, - "velocityMadeGoodInKnots": 6.978722866790283, - "velocityMadeGoodInMetersPerSecond": 3.5907467683765675, - "averageVelocityMadeGoodInKnots": 10.696146630272573, - "averageVelocityMadeGoodInMetersPerSecond": 5.502573210906889, - "rank": 8, - "gapToLeaderInSeconds": 40, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "SAP Extreme Sailing Team", - "currentSpeedOverGroundInKnots": 11.024778080258729, - "currentSpeedOverGroundInMetersPerSecond": 5.672554566128678, - "averageSpeedOverGroundInKnots": 12.631593875586763, - "averageSpeedOverGroundInMetersPerSecond": 6.498253293774079, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 12.631593875586763, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.498253293774079, - "distanceTraveledOverGroundInMeters": 844.7729281906302, - "velocityMadeGoodInKnots": 1.2685982386617098, - "velocityMadeGoodInMetersPerSecond": 0.6527290326314125, - "averageVelocityMadeGoodInKnots": 10.593088893471844, - "averageVelocityMadeGoodInMetersPerSecond": 5.449555730752737, - "rank": 5, - "gapToLeaderInSeconds": 20, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "The Wave Muscat", - "currentSpeedOverGroundInKnots": 10.371547417159197, - "currentSpeedOverGroundInMetersPerSecond": 5.336449244667772, - "averageSpeedOverGroundInKnots": 13.218405968488108, - "averageSpeedOverGroundInMetersPerSecond": 6.800135514899994, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 13.218405968488108, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.800135514899994, - "distanceTraveledOverGroundInMeters": 850.0169393624993, - "velocityMadeGoodInKnots": 0.507382995007697, - "velocityMadeGoodInMetersPerSecond": 0.26106264490354364, - "averageVelocityMadeGoodInKnots": 11.490599011168458, - "averageVelocityMadeGoodInMetersPerSecond": 5.9112748246344395, - "rank": 3, - "gapToLeaderInSeconds": 9, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "ZouLou", - "currentSpeedOverGroundInKnots": 12.401069344655951, - "currentSpeedOverGroundInMetersPerSecond": 6.38069465197395, - "averageSpeedOverGroundInKnots": 12.991510624794627, - "averageSpeedOverGroundInMetersPerSecond": 6.683410465866569, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 12.991510624794627, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.683410465866569, - "distanceTraveledOverGroundInMeters": 855.4765396309208, - "velocityMadeGoodInKnots": 5.097766400838335, - "velocityMadeGoodInMetersPerSecond": 2.6229424178535687, - "averageVelocityMadeGoodInKnots": 12.80977124114262, - "averageVelocityMadeGoodInMetersPerSecond": 6.589915649610036, - "rank": 9, - "gapToLeaderInSeconds": 48, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Team Wales", - "currentSpeedOverGroundInKnots": 10.446174275359837, - "currentSpeedOverGroundInMetersPerSecond": 5.374846836180285, - "averageSpeedOverGroundInKnots": 13.233975432560737, - "averageSpeedOverGroundInMetersPerSecond": 6.808145139195134, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 13.233975432560737, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.808145139195134, - "distanceTraveledOverGroundInMeters": 803.361126425026, - "velocityMadeGoodInKnots": 2.2709159017109437, - "velocityMadeGoodInMetersPerSecond": 1.1684493124275503, - "averageVelocityMadeGoodInKnots": 10.744253450003114, - "averageVelocityMadeGoodInMetersPerSecond": 5.527321497057158, - "rank": 2, - "gapToLeaderInSeconds": 5, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - } - ] - }, - { - "from": "Red", - "fromwaypointid": 2, - "to": "White", - "towaypointid": 3, - "upordownwindleg": true, - "competitors": [ - { - "name": "Alinghi", - "currentSpeedOverGroundInKnots": 5.854938649520188, - "currentSpeedOverGroundInMetersPerSecond": 3.0125285723628457, - "averageSpeedOverGroundInKnots": 12.994817576981513, - "averageSpeedOverGroundInMetersPerSecond": 6.685111709047156, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 12.994817576981513, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.685111709047156, - "distanceTraveledOverGroundInMeters": 347.62580887045215, - "velocityMadeGoodInKnots": 5.666739980530651, - "velocityMadeGoodInMetersPerSecond": 2.9156951294269233, - "averageVelocityMadeGoodInKnots": 10.37864078563717, - "averageVelocityMadeGoodInMetersPerSecond": 5.339234093055566, - "rank": 2, - "gapToLeaderInSeconds": 10, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "GAC Pindar", - "currentSpeedOverGroundInKnots": 10.526111572851, - "currentSpeedOverGroundInMetersPerSecond": 5.415976796219975, - "averageSpeedOverGroundInKnots": 13.570637176602942, - "averageSpeedOverGroundInMetersPerSecond": 6.981338903074624, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 13.570637176602942, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.981338903074624, - "distanceTraveledOverGroundInMeters": 383.97363966910433, - "velocityMadeGoodInKnots": 6.818645144097885, - "velocityMadeGoodInMetersPerSecond": 3.50838233344792, - "averageVelocityMadeGoodInKnots": 10.092849458777172, - "averageVelocityMadeGoodInMetersPerSecond": 5.192210332682034, - "rank": 1, - "gapToLeaderInSeconds": 0, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Groupe Edmond de Rothschild", - "currentSpeedOverGroundInKnots": 5.833310986897551, - "currentSpeedOverGroundInMetersPerSecond": 3.001400539175093, - "averageSpeedOverGroundInKnots": 11.858092250755524, - "averageSpeedOverGroundInMetersPerSecond": 6.100329680110897, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 11.858092250755524, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.100329680110897, - "distanceTraveledOverGroundInMeters": 329.41780272598845, - "velocityMadeGoodInKnots": 5.598525514104891, - "velocityMadeGoodInMetersPerSecond": 2.88059689160458, - "averageVelocityMadeGoodInKnots": 9.952011960012356, - "averageVelocityMadeGoodInMetersPerSecond": 5.119757263873023, - "rank": 6, - "gapToLeaderInSeconds": 31, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Oman Air", - "currentSpeedOverGroundInKnots": 7.282423375726804, - "currentSpeedOverGroundInMetersPerSecond": 3.747009116349655, - "averageSpeedOverGroundInKnots": 10.197073145874905, - "averageSpeedOverGroundInMetersPerSecond": 5.2458276294889785, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 10.197073145874905, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.2458276294889785, - "distanceTraveledOverGroundInMeters": 361.96210643473955, - "velocityMadeGoodInKnots": 0.8535643643455652, - "velocityMadeGoodInMetersPerSecond": 0.43918257557702506, - "averageVelocityMadeGoodInKnots": 7.843425125737789, - "averageVelocityMadeGoodInMetersPerSecond": 4.035006481351774, - "rank": 8, - "gapToLeaderInSeconds": 38, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Red Bull Sailing Team", - "currentSpeedOverGroundInKnots": 14.204139591076778, - "currentSpeedOverGroundInMetersPerSecond": 7.308424379042088, - "averageSpeedOverGroundInKnots": 12.272958726583632, - "averageSpeedOverGroundInMetersPerSecond": 6.313755433786912, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 12.272958726583632, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.313755433786912, - "distanceTraveledOverGroundInMeters": 334.62903799070637, - "velocityMadeGoodInKnots": 7.209715329140886, - "velocityMadeGoodInMetersPerSecond": 3.70959880671324, - "averageVelocityMadeGoodInKnots": 9.842750572088883, - "averageVelocityMadeGoodInMetersPerSecond": 5.0635483498635026, - "rank": 7, - "gapToLeaderInSeconds": 38, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "SAP Extreme Sailing Team", - "currentSpeedOverGroundInKnots": 3.1675002538278694, - "currentSpeedOverGroundInMetersPerSecond": 1.6297668667126006, - "averageSpeedOverGroundInKnots": 11.295453746284354, - "averageSpeedOverGroundInMetersPerSecond": 5.810883427255173, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 11.295453746284354, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.810883427255173, - "distanceTraveledOverGroundInMeters": 354.46388906256556, - "velocityMadeGoodInKnots": 2.0614630971579535, - "velocityMadeGoodInMetersPerSecond": 1.060680026351577, - "averageVelocityMadeGoodInKnots": 8.781972008430497, - "averageVelocityMadeGoodInMetersPerSecond": 4.517836711003689, - "rank": 4, - "gapToLeaderInSeconds": 26, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "The Wave Muscat", - "currentSpeedOverGroundInKnots": 6.682614828914118, - "currentSpeedOverGroundInMetersPerSecond": 3.4383909576660057, - "averageSpeedOverGroundInKnots": 11.388419473254784, - "averageSpeedOverGroundInMetersPerSecond": 5.858709129018849, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 11.388419473254784, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.858709129018849, - "distanceTraveledOverGroundInMeters": 421.8270572893572, - "velocityMadeGoodInKnots": 6.361455620332581, - "velocityMadeGoodInMetersPerSecond": 3.273145623761678, - "averageVelocityMadeGoodInKnots": 7.414622747732481, - "averageVelocityMadeGoodInMetersPerSecond": 3.8144114802223763, - "rank": 5, - "gapToLeaderInSeconds": 26, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "ZouLou", - "currentSpeedOverGroundInKnots": 13.969655417218608, - "currentSpeedOverGroundInMetersPerSecond": 7.187775758142784, - "averageSpeedOverGroundInKnots": 13.057144344337889, - "averageSpeedOverGroundInMetersPerSecond": 6.717175368253825, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 13.057144344337889, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.717175368253825, - "distanceTraveledOverGroundInMeters": 369.44464525396035, - "velocityMadeGoodInKnots": 8.118085000922571, - "velocityMadeGoodInMetersPerSecond": 4.176980235335799, - "averageVelocityMadeGoodInKnots": 9.495296726485726, - "averageVelocityMadeGoodInMetersPerSecond": 4.8848026492921015, - "rank": 9, - "gapToLeaderInSeconds": 48, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Team Wales", - "currentSpeedOverGroundInKnots": 4.866687450803765, - "currentSpeedOverGroundInMetersPerSecond": 2.504045879201059, - "averageSpeedOverGroundInKnots": 10.422152683125246, - "averageSpeedOverGroundInMetersPerSecond": 5.361618546985543, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 10.422152683125246, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.361618546985543, - "distanceTraveledOverGroundInMeters": 402.1213910239157, - "velocityMadeGoodInKnots": 1.4911415913922177, - "velocityMadeGoodInMetersPerSecond": 0.7672337693710569, - "averageVelocityMadeGoodInKnots": 7.162969417956694, - "averageVelocityMadeGoodInMetersPerSecond": 3.6849498227932767, - "rank": 3, - "gapToLeaderInSeconds": 25, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - } - ] - }, - { - "from": "White", - "fromwaypointid": 3, - "to": "Red", - "towaypointid": 4, - "upordownwindleg": true, - "competitors": [ - { - "name": "Alinghi", - "currentSpeedOverGroundInKnots": 11.23774208724483, - "currentSpeedOverGroundInMetersPerSecond": 5.782130463389889, - "averageSpeedOverGroundInKnots": 8.798881144639969, - "averageSpeedOverGroundInMetersPerSecond": 4.5265355221870065, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 8.798881144639969, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 4.5265355221870065, - "distanceTraveledOverGroundInMeters": 452.65355221870067, - "velocityMadeGoodInKnots": 0.15508113770384271, - "velocityMadeGoodInMetersPerSecond": 0.07979355315800774, - "averageVelocityMadeGoodInKnots": 5.0922509530105815, - "averageVelocityMadeGoodInMetersPerSecond": 2.6196802124932215, - "rank": 2, - "gapToLeaderInSeconds": 22, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "GAC Pindar", - "currentSpeedOverGroundInKnots": 9.899895992671796, - "currentSpeedOverGroundInMetersPerSecond": 5.093771485340547, - "averageSpeedOverGroundInKnots": 10.353403103785347, - "averageSpeedOverGroundInMetersPerSecond": 5.326250707836239, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 10.353403103785347, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.326250707836239, - "distanceTraveledOverGroundInMeters": 468.7100622895891, - "velocityMadeGoodInKnots": 5.116697228919332, - "velocityMadeGoodInMetersPerSecond": 2.6326828547575776, - "averageVelocityMadeGoodInKnots": 6.33696301652862, - "averageVelocityMadeGoodInMetersPerSecond": 3.2600154185030568, - "rank": 1, - "gapToLeaderInSeconds": 0, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Groupe Edmond de Rothschild", - "currentSpeedOverGroundInKnots": 18.520725944294817, - "currentSpeedOverGroundInMetersPerSecond": 9.529427962949246, - "averageSpeedOverGroundInKnots": 10.28990664299978, - "averageSpeedOverGroundInMetersPerSecond": 5.29358530634322, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 10.28990664299978, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.29358530634322, - "distanceTraveledOverGroundInMeters": 418.1932392011144, - "velocityMadeGoodInKnots": 1.020051527460395, - "velocityMadeGoodInMetersPerSecond": 0.5248448456430249, - "averageVelocityMadeGoodInKnots": 6.087442432620199, - "averageVelocityMadeGoodInMetersPerSecond": 3.1316509403368356, - "rank": 3, - "gapToLeaderInSeconds": 22, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Oman Air", - "currentSpeedOverGroundInKnots": 9.044499236417664, - "currentSpeedOverGroundInMetersPerSecond": 4.653646093226788, - "averageSpeedOverGroundInKnots": 9.394740780756548, - "averageSpeedOverGroundInMetersPerSecond": 4.8330722016558685, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 9.394740780756548, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 4.8330722016558685, - "distanceTraveledOverGroundInMeters": 463.9749313589634, - "velocityMadeGoodInKnots": 0.33531733055358404, - "velocityMadeGoodInMetersPerSecond": 0.17253008094011216, - "averageVelocityMadeGoodInKnots": 5.810709950964479, - "averageVelocityMadeGoodInMetersPerSecond": 2.989287452551727, - "rank": 7, - "gapToLeaderInSeconds": 46, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Red Bull Sailing Team", - "currentSpeedOverGroundInKnots": 12.395572308765887, - "currentSpeedOverGroundInMetersPerSecond": 6.37786627431307, - "averageSpeedOverGroundInKnots": 10.14445604676253, - "averageSpeedOverGroundInMetersPerSecond": 5.218759055167834, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 10.14445604676253, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.218759055167834, - "distanceTraveledOverGroundInMeters": 532.3134236271192, - "velocityMadeGoodInKnots": 2.7082048021837166, - "velocityMadeGoodInMetersPerSecond": 1.3934465986346938, - "averageVelocityMadeGoodInKnots": 5.204178991047287, - "averageVelocityMadeGoodInMetersPerSecond": 2.677260969838771, - "rank": 8, - "gapToLeaderInSeconds": 52, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "SAP Extreme Sailing Team", - "currentSpeedOverGroundInKnots": 12.926010253434026, - "currentSpeedOverGroundInMetersPerSecond": 6.650791331232179, - "averageSpeedOverGroundInKnots": 9.195114913331702, - "averageSpeedOverGroundInMetersPerSecond": 4.730375783191754, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 9.195114913331702, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 4.730375783191754, - "distanceTraveledOverGroundInMeters": 482.4983298855589, - "velocityMadeGoodInKnots": 5.507364038474394, - "velocityMadeGoodInMetersPerSecond": 2.833691780129478, - "averageVelocityMadeGoodInKnots": 5.269634303875374, - "averageVelocityMadeGoodInMetersPerSecond": 2.710934091882553, - "rank": 6, - "gapToLeaderInSeconds": 40, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "The Wave Muscat", - "currentSpeedOverGroundInKnots": 8.84772618278622, - "currentSpeedOverGroundInMetersPerSecond": 4.552400891215254, - "averageSpeedOverGroundInKnots": 9.960891163352581, - "averageSpeedOverGroundInMetersPerSecond": 5.124325120702495, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 9.960891163352581, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.124325120702495, - "distanceTraveledOverGroundInMeters": 502.1838618288445, - "velocityMadeGoodInKnots": 0.38617563174530095, - "velocityMadeGoodInMetersPerSecond": 0.19869808963383914, - "averageVelocityMadeGoodInKnots": 5.580730373182078, - "averageVelocityMadeGoodInMetersPerSecond": 2.8709757364258914, - "rank": 5, - "gapToLeaderInSeconds": 36, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "ZouLou", - "currentSpeedOverGroundInKnots": 13.517096632521662, - "currentSpeedOverGroundInMetersPerSecond": 6.9549216923388535, - "averageSpeedOverGroundInKnots": 10.60823365597394, - "averageSpeedOverGroundInMetersPerSecond": 5.457346869684371, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 10.60823365597394, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.457346869684371, - "distanceTraveledOverGroundInMeters": 551.1920338381215, - "velocityMadeGoodInKnots": 4.63934423977241, - "velocityMadeGoodInMetersPerSecond": 2.387071482036232, - "averageVelocityMadeGoodInKnots": 5.306986550644109, - "averageVelocityMadeGoodInMetersPerSecond": 2.7301497477202474, - "rank": 9, - "gapToLeaderInSeconds": 61, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Team Wales", - "currentSpeedOverGroundInKnots": 9.48971557965058, - "currentSpeedOverGroundInMetersPerSecond": 4.8827222689407686, - "averageSpeedOverGroundInKnots": 9.77527060847059, - "averageSpeedOverGroundInMetersPerSecond": 5.028833657468759, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 9.77527060847059, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.028833657468759, - "distanceTraveledOverGroundInMeters": 457.62386282965707, - "velocityMadeGoodInKnots": 0.5591986522083943, - "velocityMadeGoodInMetersPerSecond": 0.2877232398571135, - "averageVelocityMadeGoodInKnots": 6.229236759810703, - "averageVelocityMadeGoodInMetersPerSecond": 3.2045962442137284, - "rank": 4, - "gapToLeaderInSeconds": 28, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - } - ] - }, - { - "from": "Red", - "fromwaypointid": 4, - "to": "White", - "towaypointid": 5, - "upordownwindleg": true, - "competitors": [ - { - "name": "Alinghi", - "currentSpeedOverGroundInKnots": 11.86799632351759, - "currentSpeedOverGroundInMetersPerSecond": 6.1064137750143415, - "averageSpeedOverGroundInKnots": 14.231274147700328, - "averageSpeedOverGroundInMetersPerSecond": 7.321199922650279, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 14.231274147700328, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.321199922650279, - "distanceTraveledOverGroundInMeters": 358.7387962098637, - "velocityMadeGoodInKnots": 9.636762851506216, - "velocityMadeGoodInMetersPerSecond": 4.958382174956935, - "averageVelocityMadeGoodInKnots": 11.104653325556523, - "averageVelocityMadeGoodInMetersPerSecond": 5.712727210814078, - "rank": 2, - "gapToLeaderInSeconds": 20, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "GAC Pindar", - "currentSpeedOverGroundInKnots": 15.447411479605046, - "currentSpeedOverGroundInMetersPerSecond": 7.948122301020118, - "averageSpeedOverGroundInKnots": 14.593069438704216, - "averageSpeedOverGroundInMetersPerSecond": 7.50732350013339, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 14.593069438704216, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.50732350013339, - "distanceTraveledOverGroundInMeters": 382.8734985068029, - "velocityMadeGoodInKnots": 11.415751727221283, - "velocityMadeGoodInMetersPerSecond": 5.873721367869995, - "averageVelocityMadeGoodInKnots": 10.816893048878756, - "averageVelocityMadeGoodInMetersPerSecond": 5.564690535145404, - "rank": 1, - "gapToLeaderInSeconds": 0, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Groupe Edmond de Rothschild", - "currentSpeedOverGroundInKnots": 13.196514698319344, - "currentSpeedOverGroundInMetersPerSecond": 6.789973382138033, - "averageSpeedOverGroundInKnots": 13.390498861636958, - "averageSpeedOverGroundInMetersPerSecond": 6.888667747708791, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 13.390498861636958, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.888667747708791, - "distanceTraveledOverGroundInMeters": 564.8707553121209, - "velocityMadeGoodInKnots": 12.293027782378221, - "velocityMadeGoodInMetersPerSecond": 6.32510426702755, - "averageVelocityMadeGoodInKnots": 6.052422784163775, - "averageVelocityMadeGoodInMetersPerSecond": 3.113635276742031, - "rank": 7, - "gapToLeaderInSeconds": 53, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Oman Air", - "currentSpeedOverGroundInKnots": 8.127343284192822, - "currentSpeedOverGroundInMetersPerSecond": 4.181743879252879, - "averageSpeedOverGroundInKnots": 12.428187262342417, - "averageSpeedOverGroundInMetersPerSecond": 6.393611891627265, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 12.428187262342417, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.393611891627265, - "distanceTraveledOverGroundInMeters": 351.6486540394996, - "velocityMadeGoodInKnots": 7.441072975552053, - "velocityMadeGoodInMetersPerSecond": 3.8286387423930743, - "averageVelocityMadeGoodInKnots": 10.203625681453541, - "averageVelocityMadeGoodInMetersPerSecond": 5.249198545014433, - "rank": 6, - "gapToLeaderInSeconds": 50, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Red Bull Sailing Team", - "currentSpeedOverGroundInKnots": 9.481153590415133, - "currentSpeedOverGroundInMetersPerSecond": 4.878316887646097, - "averageSpeedOverGroundInKnots": 13.76312921416595, - "averageSpeedOverGroundInMetersPerSecond": 7.080365362398705, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 13.76312921416595, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.080365362398705, - "distanceTraveledOverGroundInMeters": 346.93790275753656, - "velocityMadeGoodInKnots": 8.513981457684947, - "velocityMadeGoodInMetersPerSecond": 4.380679959463841, - "averageVelocityMadeGoodInKnots": 11.01158742096657, - "averageVelocityMadeGoodInMetersPerSecond": 5.66484997323058, - "rank": 5, - "gapToLeaderInSeconds": 50, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "SAP Extreme Sailing Team", - "currentSpeedOverGroundInKnots": 12.116786756903402, - "currentSpeedOverGroundInMetersPerSecond": 6.234423363836714, - "averageSpeedOverGroundInKnots": 9.541467128528124, - "averageSpeedOverGroundInMetersPerSecond": 4.908554756120579, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 9.541467128528124, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 4.908554756120579, - "distanceTraveledOverGroundInMeters": 422.1357090263698, - "velocityMadeGoodInKnots": 6.714853561498059, - "velocityMadeGoodInMetersPerSecond": 3.454978681100793, - "averageVelocityMadeGoodInKnots": 6.4743178214289, - "averageVelocityMadeGoodInMetersPerSecond": 3.330676834801756, - "rank": 9, - "gapToLeaderInSeconds": 75, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "The Wave Muscat", - "currentSpeedOverGroundInKnots": 11.782513996954147, - "currentSpeedOverGroundInMetersPerSecond": 6.06243074348838, - "averageSpeedOverGroundInKnots": 15.043881259778573, - "averageSpeedOverGroundInMetersPerSecond": 7.739241136974977, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 15.043881259778573, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.739241136974977, - "distanceTraveledOverGroundInMeters": 394.7012979857238, - "velocityMadeGoodInKnots": 10.430011729981558, - "velocityMadeGoodInMetersPerSecond": 5.366530757623567, - "averageVelocityMadeGoodInKnots": 10.74709509030487, - "averageVelocityMadeGoodInMetersPerSecond": 5.528783363123504, - "rank": 4, - "gapToLeaderInSeconds": 36, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "ZouLou", - "currentSpeedOverGroundInKnots": 9.331628247944975, - "currentSpeedOverGroundInMetersPerSecond": 4.801381945463466, - "averageSpeedOverGroundInKnots": 14.681129539479056, - "averageSpeedOverGroundInMetersPerSecond": 7.552625529754224, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 14.681129539479056, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.552625529754224, - "distanceTraveledOverGroundInMeters": 347.42077436869437, - "velocityMadeGoodInKnots": 8.833609288323569, - "velocityMadeGoodInMetersPerSecond": 4.5451373568782625, - "averageVelocityMadeGoodInKnots": 12.079467831237654, - "averageVelocityMadeGoodInMetersPerSecond": 6.214215117625593, - "rank": 8, - "gapToLeaderInSeconds": 56, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Team Wales", - "currentSpeedOverGroundInKnots": 8.84834192086242, - "currentSpeedOverGroundInMetersPerSecond": 4.552717705559294, - "averageSpeedOverGroundInKnots": 14.082515691438376, - "averageSpeedOverGroundInMetersPerSecond": 7.244671961262187, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 14.082515691438376, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.244671961262187, - "distanceTraveledOverGroundInMeters": 383.9676139468959, - "velocityMadeGoodInKnots": 7.313822127820419, - "velocityMadeGoodInMetersPerSecond": 3.7631646464893787, - "averageVelocityMadeGoodInKnots": 10.523033723700884, - "averageVelocityMadeGoodInMetersPerSecond": 5.413516237859454, - "rank": 3, - "gapToLeaderInSeconds": 30, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - } - ] - }, - { - "from": "White", - "fromwaypointid": 5, - "to": "Green", - "towaypointid": 6, - "upordownwindleg": false, - "competitors": [ - { - "name": "Alinghi", - "currentSpeedOverGroundInKnots": 17.22835698437698, - "currentSpeedOverGroundInMetersPerSecond": 8.864468233933744, - "averageSpeedOverGroundInKnots": 15.051168362926546, - "averageSpeedOverGroundInMetersPerSecond": 7.742989946705546, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 15.051168362926546, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.742989946705546, - "distanceTraveledOverGroundInMeters": 332.94856770833843, - "velocityMadeGoodInKnots": 14.152079841482292, - "velocityMadeGoodInMetersPerSecond": 7.281638191771569, - "averageVelocityMadeGoodInKnots": 14.356181594852186, - "averageVelocityMadeGoodInMetersPerSecond": 7.385457864907291, - "rank": 2, - "gapToLeaderInSeconds": 17, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "GAC Pindar", - "currentSpeedOverGroundInKnots": 14.059249858455908, - "currentSpeedOverGroundInMetersPerSecond": 7.233874586893855, - "averageSpeedOverGroundInKnots": 14.153398121501775, - "averageSpeedOverGroundInMetersPerSecond": 7.281137033617023, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 14.153398121501775, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.281137033617023, - "distanceTraveledOverGroundInMeters": 334.93230354638314, - "velocityMadeGoodInKnots": 11.537245527947272, - "velocityMadeGoodInMetersPerSecond": 5.936233303171314, - "averageVelocityMadeGoodInKnots": 13.420987576091076, - "averageVelocityMadeGoodInMetersPerSecond": 6.904352497477965, - "rank": 1, - "gapToLeaderInSeconds": 0, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Groupe Edmond de Rothschild", - "currentSpeedOverGroundInKnots": 14.927862562116124, - "currentSpeedOverGroundInMetersPerSecond": 7.680799951057693, - "averageSpeedOverGroundInKnots": 14.087435699594504, - "averageSpeedOverGroundInMetersPerSecond": 7.2472030321247285, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 14.087435699594504, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.2472030321247285, - "distanceTraveledOverGroundInMeters": 362.36015160623646, - "velocityMadeGoodInKnots": 13.063275504239515, - "velocityMadeGoodInMetersPerSecond": 6.721418115695236, - "averageVelocityMadeGoodInKnots": 12.953269061016485, - "averageVelocityMadeGoodInMetersPerSecond": 6.663737305834037, - "rank": 7, - "gapToLeaderInSeconds": 57, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Oman Air", - "currentSpeedOverGroundInKnots": 16.990897303954814, - "currentSpeedOverGroundInMetersPerSecond": 8.742288632254306, - "averageSpeedOverGroundInKnots": 13.686862100040702, - "averageSpeedOverGroundInMetersPerSecond": 7.041130169243162, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 13.686862100040702, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.041130169243162, - "distanceTraveledOverGroundInMeters": 330.9331179544286, - "velocityMadeGoodInKnots": 8.333887900365855, - "velocityMadeGoodInMetersPerSecond": 4.288016821624354, - "averageVelocityMadeGoodInKnots": 12.80360805792799, - "averageVelocityMadeGoodInMetersPerSecond": 6.586745034245177, - "rank": 5, - "gapToLeaderInSeconds": 51, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Red Bull Sailing Team", - "currentSpeedOverGroundInKnots": 11.635533265512876, - "currentSpeedOverGroundInMetersPerSecond": 5.98680507436375, - "averageSpeedOverGroundInKnots": 13.67369518173987, - "averageSpeedOverGroundInMetersPerSecond": 7.034356521272844, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 13.67369518173987, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.034356521272844, - "distanceTraveledOverGroundInMeters": 351.71782606364224, - "velocityMadeGoodInKnots": 8.92108265419473, - "velocityMadeGoodInMetersPerSecond": 4.590144833434694, - "averageVelocityMadeGoodInKnots": 12.826794928201231, - "averageVelocityMadeGoodInMetersPerSecond": 6.5986733908413004, - "rank": 6, - "gapToLeaderInSeconds": 54, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "SAP Extreme Sailing Team", - "currentSpeedOverGroundInKnots": 15.641110569914915, - "currentSpeedOverGroundInMetersPerSecond": 8.047785863514832, - "averageSpeedOverGroundInKnots": 14.709654780350046, - "averageSpeedOverGroundInMetersPerSecond": 7.567300181446746, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 14.709654780350046, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.567300181446746, - "distanceTraveledOverGroundInMeters": 332.96120798365683, - "velocityMadeGoodInKnots": 12.000395869590914, - "velocityMadeGoodInMetersPerSecond": 6.1745370192342355, - "averageVelocityMadeGoodInKnots": 14.083986105252123, - "averageVelocityMadeGoodInMetersPerSecond": 7.245428407479704, - "rank": 9, - "gapToLeaderInSeconds": 73, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "The Wave Muscat", - "currentSpeedOverGroundInKnots": 13.744073626114067, - "currentSpeedOverGroundInMetersPerSecond": 7.071707660458634, - "averageSpeedOverGroundInKnots": 14.695465063060793, - "averageSpeedOverGroundInMetersPerSecond": 7.5600003602190515, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 14.695465063060793, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.5600003602190515, - "distanceTraveledOverGroundInMeters": 347.76001657007635, - "velocityMadeGoodInKnots": 9.997888955926374, - "velocityMadeGoodInMetersPerSecond": 5.144191586961783, - "averageVelocityMadeGoodInKnots": 13.708926634996399, - "averageVelocityMadeGoodInMetersPerSecond": 7.052481146670369, - "rank": 3, - "gapToLeaderInSeconds": 36, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "ZouLou", - "currentSpeedOverGroundInKnots": 15.057178344948353, - "currentSpeedOverGroundInMetersPerSecond": 7.747336513429953, - "averageSpeedOverGroundInKnots": 12.834123357548114, - "averageSpeedOverGroundInMetersPerSecond": 6.602443460605308, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 12.834123357548114, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.602443460605308, - "distanceTraveledOverGroundInMeters": 336.7246164908707, - "velocityMadeGoodInKnots": 12.379330245499787, - "velocityMadeGoodInMetersPerSecond": 6.369509281594237, - "averageVelocityMadeGoodInKnots": 12.078526467793816, - "averageVelocityMadeGoodInMetersPerSecond": 6.213730838431708, - "rank": 8, - "gapToLeaderInSeconds": 61, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Team Wales", - "currentSpeedOverGroundInKnots": 12.88906972937111, - "currentSpeedOverGroundInMetersPerSecond": 6.631784405476141, - "averageSpeedOverGroundInKnots": 14.532074503732792, - "averageSpeedOverGroundInMetersPerSecond": 7.475944994698092, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 14.532074503732792, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.475944994698092, - "distanceTraveledOverGroundInMeters": 485.936424655376, - "velocityMadeGoodInKnots": 2.5645722307368284, - "velocityMadeGoodInMetersPerSecond": 1.3195436508316187, - "averageVelocityMadeGoodInKnots": 10.881816880965237, - "averageVelocityMadeGoodInMetersPerSecond": 5.598090239874338, - "rank": 4, - "gapToLeaderInSeconds": 49, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - } - ] - }, - { - "from": "Green", - "fromwaypointid": 6, - "to": "Yellow", - "towaypointid": 7, - "upordownwindleg": false, - "competitors": [ - { - "name": "Alinghi", - "currentSpeedOverGroundInKnots": 12.73719449815627, - "currentSpeedOverGroundInMetersPerSecond": 6.553640380259683, - "averageSpeedOverGroundInKnots": 12.472878443166246, - "averageSpeedOverGroundInMetersPerSecond": 6.416603021317747, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 12.472878443166246, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.416603021317747, - "distanceTraveledOverGroundInMeters": 288.7471359592986, - "velocityMadeGoodInKnots": 2.0442681311051336, - "velocityMadeGoodInMetersPerSecond": 1.0518327386794553, - "averageVelocityMadeGoodInKnots": 10.273193883049416, - "averageVelocityMadeGoodInMetersPerSecond": 5.284987519835422, - "rank": 2, - "gapToLeaderInSeconds": 21, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "GAC Pindar", - "currentSpeedOverGroundInKnots": 6.655542412617342, - "currentSpeedOverGroundInMetersPerSecond": 3.42446144746975, - "averageSpeedOverGroundInKnots": 12.480081269880763, - "averageSpeedOverGroundInMetersPerSecond": 6.420308475505325, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 12.480081269880763, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.420308475505325, - "distanceTraveledOverGroundInMeters": 263.23264749571837, - "velocityMadeGoodInKnots": 5.044269633364627, - "velocityMadeGoodInMetersPerSecond": 2.5954168449670276, - "averageVelocityMadeGoodInKnots": 10.341655784993499, - "averageVelocityMadeGoodInMetersPerSecond": 5.320207364946656, - "rank": 1, - "gapToLeaderInSeconds": 0, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Groupe Edmond de Rothschild", - "currentSpeedOverGroundInKnots": 8.958311719998157, - "currentSpeedOverGroundInMetersPerSecond": 4.609300221931274, - "averageSpeedOverGroundInKnots": 15.337137552272265, - "averageSpeedOverGroundInMetersPerSecond": 7.890105207446733, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 15.337137552272265, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.890105207446733, - "distanceTraveledOverGroundInMeters": 252.48336663829545, - "velocityMadeGoodInKnots": 8.563199085552663, - "velocityMadeGoodInMetersPerSecond": 4.40600379615811, - "averageVelocityMadeGoodInKnots": 14.796952243729065, - "averageVelocityMadeGoodInMetersPerSecond": 7.612209876496173, - "rank": 7, - "gapToLeaderInSeconds": 48, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Oman Air", - "currentSpeedOverGroundInKnots": 8.032563710213566, - "currentSpeedOverGroundInMetersPerSecond": 4.132977155674608, - "averageSpeedOverGroundInKnots": 13.997340928481876, - "averageSpeedOverGroundInMetersPerSecond": 7.2008542776523425, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 13.997340928481876, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.2008542776523425, - "distanceTraveledOverGroundInMeters": 208.82477405191793, - "velocityMadeGoodInKnots": 7.726441885968059, - "velocityMadeGoodInMetersPerSecond": 3.9754689737162874, - "averageVelocityMadeGoodInKnots": 13.37980354893481, - "averageVelocityMadeGoodInMetersPerSecond": 6.883165603507575, - "rank": 5, - "gapToLeaderInSeconds": 39, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Red Bull Sailing Team", - "currentSpeedOverGroundInKnots": 7.577206036296706, - "currentSpeedOverGroundInMetersPerSecond": 3.898682983620108, - "averageSpeedOverGroundInKnots": 14.185337120251894, - "averageSpeedOverGroundInMetersPerSecond": 7.297567874085141, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 14.185337120251894, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.297567874085141, - "distanceTraveledOverGroundInMeters": 248.11730771889484, - "velocityMadeGoodInKnots": 1.4178291051362368, - "velocityMadeGoodInMetersPerSecond": 0.7295124587344032, - "averageVelocityMadeGoodInKnots": 12.704792713729393, - "averageVelocityMadeGoodInMetersPerSecond": 6.5359100293963435, - "rank": 6, - "gapToLeaderInSeconds": 47, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "SAP Extreme Sailing Team", - "currentSpeedOverGroundInKnots": 7.9920660030704225, - "currentSpeedOverGroundInMetersPerSecond": 4.112139960413151, - "averageSpeedOverGroundInKnots": 13.373250911189261, - "averageSpeedOverGroundInMetersPerSecond": 6.87979463542292, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 13.373250911189261, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.87979463542292, - "distanceTraveledOverGroundInMeters": 247.6726068752251, - "velocityMadeGoodInKnots": 6.753004056777937, - "velocityMadeGoodInMetersPerSecond": 3.4746081706582697, - "averageVelocityMadeGoodInKnots": 12.910407255784124, - "averageVelocityMadeGoodInMetersPerSecond": 6.641687288253388, - "rank": 9, - "gapToLeaderInSeconds": 68, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "The Wave Muscat", - "currentSpeedOverGroundInKnots": 8.371847540001024, - "currentSpeedOverGroundInMetersPerSecond": 4.307548110651083, - "averageSpeedOverGroundInKnots": 13.028618602335065, - "averageSpeedOverGroundInMetersPerSecond": 6.702500458756816, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 13.028618602335065, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.702500458756816, - "distanceTraveledOverGroundInMeters": 281.5050192677863, - "velocityMadeGoodInKnots": 3.708713610199787, - "velocityMadeGoodInMetersPerSecond": 1.908236172270296, - "averageVelocityMadeGoodInKnots": 10.094146939742838, - "averageVelocityMadeGoodInMetersPerSecond": 5.192877814556594, - "rank": 4, - "gapToLeaderInSeconds": 37, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "ZouLou", - "currentSpeedOverGroundInKnots": 7.9010119519333974, - "currentSpeedOverGroundInMetersPerSecond": 4.065290121823954, - "averageSpeedOverGroundInKnots": 12.884422646239871, - "averageSpeedOverGroundInMetersPerSecond": 6.62831965023229, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 12.884422646239871, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.62831965023229, - "distanceTraveledOverGroundInMeters": 271.7611056595239, - "velocityMadeGoodInKnots": 0.5492390307889552, - "velocityMadeGoodInMetersPerSecond": 0.2825987379806616, - "averageVelocityMadeGoodInKnots": 11.18077022074396, - "averageVelocityMadeGoodInMetersPerSecond": 5.751885124671616, - "rank": 8, - "gapToLeaderInSeconds": 61, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Team Wales", - "currentSpeedOverGroundInKnots": 6.380120680211533, - "currentSpeedOverGroundInMetersPerSecond": 3.282749315543284, - "averageSpeedOverGroundInKnots": 9.539527361618829, - "averageSpeedOverGroundInMetersPerSecond": 4.907556853810576, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 9.539527361618829, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 4.907556853810576, - "distanceTraveledOverGroundInMeters": 98.1511370762115, - "velocityMadeGoodInKnots": 6.338965131536592, - "velocityMadeGoodInMetersPerSecond": 3.261573642540341, - "averageVelocityMadeGoodInKnots": 9.313715170145406, - "averageVelocityMadeGoodInMetersPerSecond": 4.791389026419248, - "rank": 3, - "gapToLeaderInSeconds": 28, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - } - ] - }, - { - "from": "Yellow", - "fromwaypointid": 7, - "to": "Green", - "towaypointid": 8, - "upordownwindleg": false, - "competitors": [ - { - "name": "Alinghi", - "currentSpeedOverGroundInKnots": 17.07843015862714, - "currentSpeedOverGroundInMetersPerSecond": 8.787326717451403, - "averageSpeedOverGroundInKnots": 10.834909976429024, - "averageSpeedOverGroundInMetersPerSecond": 5.573959243429599, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 10.834909976429024, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.573959243429599, - "distanceTraveledOverGroundInMeters": 395.7511062835015, - "velocityMadeGoodInKnots": 1.5557672723540206, - "velocityMadeGoodInMetersPerSecond": 0.800485477383709, - "averageVelocityMadeGoodInKnots": 5.865040904963357, - "averageVelocityMadeGoodInMetersPerSecond": 3.017237709997816, - "rank": 2, - "gapToLeaderInSeconds": 26, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "GAC Pindar", - "currentSpeedOverGroundInKnots": 11.17726711879387, - "currentSpeedOverGroundInMetersPerSecond": 5.751014412261634, - "averageSpeedOverGroundInKnots": 10.702265928035905, - "averageSpeedOverGroundInMetersPerSecond": 5.505721249645137, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 10.702265928035905, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.505721249645137, - "distanceTraveledOverGroundInMeters": 363.3776024765791, - "velocityMadeGoodInKnots": 11.074346440903149, - "velocityMadeGoodInMetersPerSecond": 5.6980588645791395, - "averageVelocityMadeGoodInKnots": 6.798513750457726, - "averageVelocityMadeGoodInMetersPerSecond": 3.497457629402141, - "rank": 1, - "gapToLeaderInSeconds": 0, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Groupe Edmond de Rothschild", - "currentSpeedOverGroundInKnots": 6.494143786941852, - "currentSpeedOverGroundInMetersPerSecond": 3.3414173712645536, - "averageSpeedOverGroundInKnots": 11.022622423301229, - "averageSpeedOverGroundInMetersPerSecond": 5.670526868876077, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 11.022622423301229, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.670526868876077, - "distanceTraveledOverGroundInMeters": 357.2431927391928, - "velocityMadeGoodInKnots": 5.446201375439294, - "velocityMadeGoodInMetersPerSecond": 2.8022218910350567, - "averageVelocityMadeGoodInKnots": 6.354185063045867, - "averageVelocityMadeGoodInMetersPerSecond": 3.2688752046558185, - "rank": 4, - "gapToLeaderInSeconds": 45, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Oman Air", - "currentSpeedOverGroundInKnots": 10.85821744671481, - "currentSpeedOverGroundInMetersPerSecond": 5.586854493486068, - "averageSpeedOverGroundInKnots": 9.617885411042968, - "averageSpeedOverGroundInMetersPerSecond": 4.947867717014327, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 9.617885411042968, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 4.947867717014327, - "distanceTraveledOverGroundInMeters": 326.55926932294557, - "velocityMadeGoodInKnots": 9.375954391509739, - "velocityMadeGoodInMetersPerSecond": 4.824188977609302, - "averageVelocityMadeGoodInKnots": 5.887530262653277, - "averageVelocityMadeGoodInMetersPerSecond": 3.0288072351205186, - "rank": 3, - "gapToLeaderInSeconds": 39, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Red Bull Sailing Team", - "currentSpeedOverGroundInKnots": 12.64736244353747, - "currentSpeedOverGroundInMetersPerSecond": 6.507419292823459, - "averageSpeedOverGroundInKnots": 9.579640075736258, - "averageSpeedOverGroundInMetersPerSecond": 4.928192616739874, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 9.579640075736258, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 4.928192616739874, - "distanceTraveledOverGroundInMeters": 448.4655281233286, - "velocityMadeGoodInKnots": 1.4087531345299598, - "velocityMadeGoodInMetersPerSecond": 0.724842619747179, - "averageVelocityMadeGoodInKnots": 5.390647920172599, - "averageVelocityMadeGoodInMetersPerSecond": 2.7731888744887923, - "rank": 9, - "gapToLeaderInSeconds": 72, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "SAP Extreme Sailing Team", - "currentSpeedOverGroundInKnots": 13.467623154225157, - "currentSpeedOverGroundInMetersPerSecond": 6.929466213492016, - "averageSpeedOverGroundInKnots": 9.657121629588039, - "averageSpeedOverGroundInMetersPerSecond": 4.968052571665846, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 9.657121629588039, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 4.968052571665846, - "distanceTraveledOverGroundInMeters": 332.8595223016117, - "velocityMadeGoodInKnots": 10.754085783386241, - "velocityMadeGoodInMetersPerSecond": 5.533275860157315, - "averageVelocityMadeGoodInKnots": 6.5321767434956355, - "averageVelocityMadeGoodInMetersPerSecond": 3.3604420358205322, - "rank": 7, - "gapToLeaderInSeconds": 69, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "The Wave Muscat", - "currentSpeedOverGroundInKnots": 14.794869124413447, - "currentSpeedOverGroundInMetersPerSecond": 7.612371133097508, - "averageSpeedOverGroundInKnots": 10.419592977212291, - "averageSpeedOverGroundInMetersPerSecond": 5.360301720499212, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 10.419592977212291, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.360301720499212, - "distanceTraveledOverGroundInMeters": 439.5447410809354, - "velocityMadeGoodInKnots": 5.760927490191929, - "velocityMadeGoodInMetersPerSecond": 2.964157219467364, - "averageVelocityMadeGoodInKnots": 5.999755143057533, - "averageVelocityMadeGoodInMetersPerSecond": 3.086540701372931, - "rank": 6, - "gapToLeaderInSeconds": 53, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "ZouLou", - "currentSpeedOverGroundInKnots": 6.8926865721567925, - "currentSpeedOverGroundInMetersPerSecond": 3.5464787048905633, - "averageSpeedOverGroundInKnots": 9.10755190494045, - "averageSpeedOverGroundInMetersPerSecond": 4.685329479986032, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 9.10755190494045, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 4.685329479986032, - "distanceTraveledOverGroundInMeters": 356.0850404789384, - "velocityMadeGoodInKnots": 1.6140886589350134, - "velocityMadeGoodInMetersPerSecond": 0.8304934508181459, - "averageVelocityMadeGoodInKnots": 6.20431621337708, - "averageVelocityMadeGoodInMetersPerSecond": 3.1917760075484307, - "rank": 8, - "gapToLeaderInSeconds": 71, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Team Wales", - "currentSpeedOverGroundInKnots": 19.21295581965983, - "currentSpeedOverGroundInMetersPerSecond": 9.885599462432195, - "averageSpeedOverGroundInKnots": 4.311903509256569, - "averageSpeedOverGroundInMetersPerSecond": 2.2182348053175462, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 4.311903509256569, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 2.2182348053175462, - "distanceTraveledOverGroundInMeters": 195.20466286794405, - "velocityMadeGoodInKnots": 8.130254228210296, - "velocityMadeGoodInMetersPerSecond": 4.183241640809425, - "averageVelocityMadeGoodInKnots": 3.3899459250906796, - "averageVelocityMadeGoodInMetersPerSecond": 1.743938848129983, - "rank": 5, - "gapToLeaderInSeconds": 50, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - } - ] - }, - { - "from": "Green", - "fromwaypointid": 8, - "to": "Wales", - "towaypointid": 9, - "upordownwindleg": false, - "competitors": [ - { - "name": "Alinghi", - "currentSpeedOverGroundInKnots": 6.152083313576186, - "currentSpeedOverGroundInMetersPerSecond": 3.1654177560381025, - "averageSpeedOverGroundInKnots": 14.033990582000166, - "averageSpeedOverGroundInMetersPerSecond": 7.219708488295641, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 14.033990582000166, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.219708488295641, - "distanceTraveledOverGroundInMeters": 599.2358045285382, - "velocityMadeGoodInKnots": 4.141690293786237, - "velocityMadeGoodInMetersPerSecond": 2.131014703105624, - "averageVelocityMadeGoodInKnots": 13.005906224277357, - "averageVelocityMadeGoodInMetersPerSecond": 6.690816202044907, - "rank": 2, - "gapToLeaderInSeconds": 21, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "GAC Pindar", - "currentSpeedOverGroundInKnots": 10.761800172253496, - "currentSpeedOverGroundInMetersPerSecond": 5.537245127518097, - "averageSpeedOverGroundInKnots": 13.191265228936834, - "averageSpeedOverGroundInMetersPerSecond": 6.786173112219727, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 13.191265228936834, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.786173112219727, - "distanceTraveledOverGroundInMeters": 597.183233875336, - "velocityMadeGoodInKnots": 8.078760784397836, - "velocityMadeGoodInMetersPerSecond": 4.156746833594475, - "averageVelocityMadeGoodInKnots": 12.410613269426761, - "averageVelocityMadeGoodInMetersPerSecond": 6.3845710486051015, - "rank": 1, - "gapToLeaderInSeconds": 0, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Groupe Edmond de Rothschild", - "currentSpeedOverGroundInKnots": 7.344582030904409, - "currentSpeedOverGroundInMetersPerSecond": 3.778991471067843, - "averageSpeedOverGroundInKnots": 12.4370199075807, - "averageSpeedOverGroundInMetersPerSecond": 6.398155796899848, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 12.4370199075807, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.398155796899848, - "distanceTraveledOverGroundInMeters": 646.2137354868847, - "velocityMadeGoodInKnots": 6.836962551691641, - "velocityMadeGoodInMetersPerSecond": 3.517807148471785, - "averageVelocityMadeGoodInKnots": 10.643770225436901, - "averageVelocityMadeGoodInMetersPerSecond": 5.475628460419205, - "rank": 6, - "gapToLeaderInSeconds": 58, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Oman Air", - "currentSpeedOverGroundInKnots": 8.525636581265598, - "currentSpeedOverGroundInMetersPerSecond": 4.386676844299519, - "averageSpeedOverGroundInKnots": 13.310962905878991, - "averageSpeedOverGroundInMetersPerSecond": 6.847750917135525, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 13.310962905878991, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.847750917135525, - "distanceTraveledOverGroundInMeters": 629.9930843764683, - "velocityMadeGoodInKnots": 5.936408208475738, - "velocityMadeGoodInMetersPerSecond": 3.0544469234887806, - "averageVelocityMadeGoodInKnots": 11.593303972304643, - "averageVelocityMadeGoodInMetersPerSecond": 5.964110821307832, - "rank": 4, - "gapToLeaderInSeconds": 43, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Red Bull Sailing Team", - "currentSpeedOverGroundInKnots": 7.617398605697863, - "currentSpeedOverGroundInMetersPerSecond": 3.919363177037264, - "averageSpeedOverGroundInKnots": 14.172932810056666, - "averageSpeedOverGroundInMetersPerSecond": 7.29118654561804, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 14.172932810056666, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.29118654561804, - "distanceTraveledOverGroundInMeters": 597.8772967406793, - "velocityMadeGoodInKnots": 6.509258009899878, - "velocityMadeGoodInMetersPerSecond": 3.3491940588159848, - "averageVelocityMadeGoodInKnots": 13.447728524531403, - "averageVelocityMadeGoodInMetersPerSecond": 6.9181092298422655, - "rank": 7, - "gapToLeaderInSeconds": 66, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "SAP Extreme Sailing Team", - "currentSpeedOverGroundInKnots": 14.46881399406978, - "currentSpeedOverGroundInMetersPerSecond": 7.4446067114487375, - "averageSpeedOverGroundInKnots": 12.810349654677076, - "averageSpeedOverGroundInMetersPerSecond": 6.590213211239428, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 12.810349654677076, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.590213211239428, - "distanceTraveledOverGroundInMeters": 612.8898286452668, - "velocityMadeGoodInKnots": 13.727490507282292, - "velocityMadeGoodInMetersPerSecond": 7.063175185177497, - "averageVelocityMadeGoodInKnots": 11.53253964012975, - "averageVelocityMadeGoodInMetersPerSecond": 5.932850948200083, - "rank": 9, - "gapToLeaderInSeconds": 74, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "The Wave Muscat", - "currentSpeedOverGroundInKnots": 9.335752838096798, - "currentSpeedOverGroundInMetersPerSecond": 4.803504161668528, - "averageSpeedOverGroundInKnots": 13.953991455957851, - "averageSpeedOverGroundInMetersPerSecond": 7.178553382342762, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 13.953991455957851, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.178553382342762, - "distanceTraveledOverGroundInMeters": 610.1770374991347, - "velocityMadeGoodInKnots": 8.100002812915106, - "velocityMadeGoodInMetersPerSecond": 4.167676447322958, - "averageVelocityMadeGoodInKnots": 12.603266373791095, - "averageVelocityMadeGoodInMetersPerSecond": 6.483680367850308, - "rank": 5, - "gapToLeaderInSeconds": 50, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "ZouLou", - "currentSpeedOverGroundInKnots": 8.423111081128361, - "currentSpeedOverGroundInMetersPerSecond": 4.333924626548351, - "averageSpeedOverGroundInKnots": 13.411711508404423, - "averageSpeedOverGroundInMetersPerSecond": 6.899580475990276, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 13.411711508404423, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.899580475990276, - "distanceTraveledOverGroundInMeters": 614.0626623631346, - "velocityMadeGoodInKnots": 7.622599839796101, - "velocityMadeGoodInMetersPerSecond": 3.9220393564595324, - "averageVelocityMadeGoodInKnots": 12.292961428218868, - "averageVelocityMadeGoodInMetersPerSecond": 6.32404571251704, - "rank": 8, - "gapToLeaderInSeconds": 72, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Team Wales", - "currentSpeedOverGroundInKnots": 10.241715945568181, - "currentSpeedOverGroundInMetersPerSecond": 5.269647346104428, - "averageSpeedOverGroundInKnots": 12.485798127784347, - "averageSpeedOverGroundInMetersPerSecond": 6.423249481293504, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 12.485798127784347, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.423249481293504, - "distanceTraveledOverGroundInMeters": 507.43670902218685, - "velocityMadeGoodInKnots": 9.06482941320338, - "velocityMadeGoodInMetersPerSecond": 4.664106533910172, - "averageVelocityMadeGoodInKnots": 9.453973309990772, - "averageVelocityMadeGoodInMetersPerSecond": 4.863544047250808, - "rank": 3, - "gapToLeaderInSeconds": 41, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - } - ] - }, - { - "from": "Wales", - "fromwaypointid": 9, - "to": "Black Conical", - "towaypointid": 10, - "upordownwindleg": false, - "competitors": [ - { - "name": "Alinghi", - "currentSpeedOverGroundInKnots": 10.211064742660167, - "currentSpeedOverGroundInMetersPerSecond": 5.253876450785952, - "averageSpeedOverGroundInKnots": 10.893042686100026, - "averageSpeedOverGroundInMetersPerSecond": 5.603865292960347, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 10.893042686100026, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.603865292960347, - "distanceTraveledOverGroundInMeters": 224.15461171841392, - "velocityMadeGoodInKnots": 6.630746044890362, - "velocityMadeGoodInMetersPerSecond": 3.4117030274862272, - "averageVelocityMadeGoodInKnots": 10.579389812053588, - "averageVelocityMadeGoodInMetersPerSecond": 5.442508314423123, - "rank": 2, - "gapToLeaderInSeconds": 7, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "GAC Pindar", - "currentSpeedOverGroundInKnots": 7.696065073648478, - "currentSpeedOverGroundInMetersPerSecond": 3.959839259977521, - "averageSpeedOverGroundInKnots": 9.76435668692884, - "averageSpeedOverGroundInMetersPerSecond": 5.023219051164504, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 9.76435668692884, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.023219051164504, - "distanceTraveledOverGroundInMeters": 271.2538287628832, - "velocityMadeGoodInKnots": 1.0957180432769993, - "velocityMadeGoodInMetersPerSecond": 0.5637773698783294, - "averageVelocityMadeGoodInKnots": 8.21087035933301, - "averageVelocityMadeGoodInMetersPerSecond": 4.224036640412426, - "rank": 1, - "gapToLeaderInSeconds": 0, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Groupe Edmond de Rothschild", - "currentSpeedOverGroundInKnots": 12.400987733440756, - "currentSpeedOverGroundInMetersPerSecond": 6.380652660736754, - "averageSpeedOverGroundInKnots": 10.563936929222566, - "averageSpeedOverGroundInMetersPerSecond": 5.434558664700052, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 10.563936929222566, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.434558664700052, - "distanceTraveledOverGroundInMeters": 211.94778792330206, - "velocityMadeGoodInKnots": 12.390199580609572, - "velocityMadeGoodInMetersPerSecond": 6.375101856434197, - "averageVelocityMadeGoodInKnots": 10.364332339638475, - "averageVelocityMadeGoodInMetersPerSecond": 5.331873192502903, - "rank": 5, - "gapToLeaderInSeconds": 43, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Oman Air", - "currentSpeedOverGroundInKnots": 9.608674440322943, - "currentSpeedOverGroundInMetersPerSecond": 4.943929907169496, - "averageSpeedOverGroundInKnots": 9.681921641164157, - "averageSpeedOverGroundInMetersPerSecond": 4.980810799843339, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 9.681921641164157, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 4.980810799843339, - "distanceTraveledOverGroundInMeters": 229.11729679279358, - "velocityMadeGoodInKnots": 4.11903270315679, - "velocityMadeGoodInMetersPerSecond": 2.119356743349256, - "averageVelocityMadeGoodInKnots": 9.351310957582971, - "averageVelocityMadeGoodInMetersPerSecond": 4.810729970401017, - "rank": 4, - "gapToLeaderInSeconds": 35, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Red Bull Sailing Team", - "currentSpeedOverGroundInKnots": 15.683791842644203, - "currentSpeedOverGroundInMetersPerSecond": 8.06974656392496, - "averageSpeedOverGroundInKnots": 11.662232782270358, - "averageSpeedOverGroundInMetersPerSecond": 5.999570864656862, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 11.662232782270358, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.999570864656862, - "distanceTraveledOverGroundInMeters": 227.98369285696077, - "velocityMadeGoodInKnots": 13.938587502686984, - "velocityMadeGoodInMetersPerSecond": 7.171790453118639, - "averageVelocityMadeGoodInKnots": 11.358787565078293, - "averageVelocityMadeGoodInMetersPerSecond": 5.843465158479166, - "rank": 7, - "gapToLeaderInSeconds": 50, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "SAP Extreme Sailing Team", - "currentSpeedOverGroundInKnots": 12.929897614247041, - "currentSpeedOverGroundInMetersPerSecond": 6.65279148635272, - "averageSpeedOverGroundInKnots": 11.346661867273507, - "averageSpeedOverGroundInMetersPerSecond": 5.837227160608482, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 11.346661867273507, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.837227160608482, - "distanceTraveledOverGroundInMeters": 274.34967654859867, - "velocityMadeGoodInKnots": 12.202893549449591, - "velocityMadeGoodInMetersPerSecond": 6.278727700457077, - "averageVelocityMadeGoodInKnots": 8.916748541973202, - "averageVelocityMadeGoodInMetersPerSecond": 4.587171749926214, - "rank": 8, - "gapToLeaderInSeconds": 67, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "The Wave Muscat", - "currentSpeedOverGroundInKnots": 6.621293153932253, - "currentSpeedOverGroundInMetersPerSecond": 3.4068392525079756, - "averageSpeedOverGroundInKnots": 10.37367149724784, - "averageSpeedOverGroundInMetersPerSecond": 5.3366776702508325, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 10.37367149724784, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.3366776702508325, - "distanceTraveledOverGroundInMeters": 250.8238505017891, - "velocityMadeGoodInKnots": 1.8082570774048763, - "velocityMadeGoodInMetersPerSecond": 0.9303984956880701, - "averageVelocityMadeGoodInKnots": 9.278213069971503, - "averageVelocityMadeGoodInMetersPerSecond": 4.773125168218674, - "rank": 6, - "gapToLeaderInSeconds": 43, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "ZouLou", - "currentSpeedOverGroundInKnots": 11.626372962971606, - "currentSpeedOverGroundInMetersPerSecond": 5.982091844253419, - "averageSpeedOverGroundInKnots": 11.46669241999873, - "averageSpeedOverGroundInMetersPerSecond": 5.8989762116215685, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 11.46669241999873, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.8989762116215685, - "distanceTraveledOverGroundInMeters": 306.74676300432156, - "velocityMadeGoodInKnots": 11.57198718471946, - "velocityMadeGoodInMetersPerSecond": 5.954108850626626, - "averageVelocityMadeGoodInKnots": 7.85633768115588, - "averageVelocityMadeGoodInMetersPerSecond": 4.041649273750192, - "rank": 9, - "gapToLeaderInSeconds": 70, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Team Wales", - "currentSpeedOverGroundInKnots": 10.725272730278864, - "currentSpeedOverGroundInMetersPerSecond": 5.518450743970982, - "averageSpeedOverGroundInKnots": 9.922180622915766, - "averageSpeedOverGroundInMetersPerSecond": 5.104410698233333, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 9.922180622915766, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.104410698233333, - "distanceTraveledOverGroundInMeters": 199.0720172311, - "velocityMadeGoodInKnots": 7.005725218873651, - "velocityMadeGoodInMetersPerSecond": 3.6046402285887953, - "averageVelocityMadeGoodInKnots": 9.683899213459808, - "averageVelocityMadeGoodInMetersPerSecond": 4.981828150924324, - "rank": 3, - "gapToLeaderInSeconds": 26, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - } - ] - }, - { - "from": "Black Conical", - "fromwaypointid": 10, - "to": "Finish Old", - "towaypointid": 11, - "upordownwindleg": false, - "competitors": [ - { - "name": "Alinghi", - "currentSpeedOverGroundInKnots": 8.212089226200899, - "currentSpeedOverGroundInMetersPerSecond": 4.225348020469979, - "averageSpeedOverGroundInKnots": 8.567162227779416, - "averageSpeedOverGroundInMetersPerSecond": 4.407329012735411, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 8.567162227779416, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 4.407329012735411, - "distanceTraveledOverGroundInMeters": 180.70048952215183, - "velocityMadeGoodInKnots": 8.181672027772013, - "velocityMadeGoodInMetersPerSecond": 4.2096975269561385, - "averageVelocityMadeGoodInKnots": 7.432787530756306, - "averageVelocityMadeGoodInMetersPerSecond": 3.8237562519335215, - "rank": 2, - "gapToLeaderInSeconds": 12, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "GAC Pindar", - "currentSpeedOverGroundInKnots": 7.95694449235812, - "currentSpeedOverGroundInMetersPerSecond": 4.0940689675541515, - "averageSpeedOverGroundInKnots": 9.31224223835934, - "averageSpeedOverGroundInMetersPerSecond": 4.790631284844861, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 9.31224223835934, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 4.790631284844861, - "distanceTraveledOverGroundInMeters": 172.46272625441497, - "velocityMadeGoodInKnots": 7.953459168584739, - "velocityMadeGoodInMetersPerSecond": 4.092275671658197, - "averageVelocityMadeGoodInKnots": 8.267413866365645, - "averageVelocityMadeGoodInMetersPerSecond": 4.253125133474771, - "rank": 1, - "gapToLeaderInSeconds": 0, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Groupe Edmond de Rothschild", - "currentSpeedOverGroundInKnots": 7.870769252370632, - "currentSpeedOverGroundInMetersPerSecond": 4.049729412823923, - "averageSpeedOverGroundInKnots": 10.446712375180269, - "averageSpeedOverGroundInMetersPerSecond": 5.374253144120516, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 10.446712375180269, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.374253144120516, - "distanceTraveledOverGroundInMeters": 214.97012576482064, - "velocityMadeGoodInKnots": 7.867900951802834, - "velocityMadeGoodInMetersPerSecond": 4.048253592506775, - "averageVelocityMadeGoodInKnots": 6.960630484458762, - "averageVelocityMadeGoodInMetersPerSecond": 3.5808576825604517, - "rank": 6, - "gapToLeaderInSeconds": 47, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Oman Air", - "currentSpeedOverGroundInKnots": 8.6661549283905, - "currentSpeedOverGroundInMetersPerSecond": 4.458977437182701, - "averageSpeedOverGroundInKnots": 9.545957474021172, - "averageSpeedOverGroundInMetersPerSecond": 4.9108647894131146, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 9.545957474021172, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 4.9108647894131146, - "distanceTraveledOverGroundInMeters": 176.7911324188721, - "velocityMadeGoodInKnots": 8.664779550317213, - "velocityMadeGoodInMetersPerSecond": 4.458269766959049, - "averageVelocityMadeGoodInKnots": 8.626260292619865, - "averageVelocityMadeGoodInMetersPerSecond": 4.4377316838699965, - "rank": 4, - "gapToLeaderInSeconds": 35, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Red Bull Sailing Team", - "currentSpeedOverGroundInKnots": 7.625795828445893, - "currentSpeedOverGroundInMetersPerSecond": 3.9236837813973136, - "averageSpeedOverGroundInKnots": 12.646156867909426, - "averageSpeedOverGroundInMetersPerSecond": 6.50574514426896, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 12.646156867909426, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 6.50574514426896, - "distanceTraveledOverGroundInMeters": 227.70108004941358, - "velocityMadeGoodInKnots": 7.54415971886457, - "velocityMadeGoodInMetersPerSecond": 3.881679735348012, - "averageVelocityMadeGoodInKnots": 8.825099007800123, - "averageVelocityMadeGoodInMetersPerSecond": 4.540023156234952, - "rank": 7, - "gapToLeaderInSeconds": 49, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "SAP Extreme Sailing Team", - "currentSpeedOverGroundInKnots": 6.770439061616234, - "currentSpeedOverGroundInMetersPerSecond": 3.483578964953264, - "averageSpeedOverGroundInKnots": 11.452490202295168, - "averageSpeedOverGroundInMetersPerSecond": 5.891669959625181, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 11.452490202295168, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.891669959625181, - "distanceTraveledOverGroundInMeters": 194.425108667631, - "velocityMadeGoodInKnots": 6.688635877631399, - "velocityMadeGoodInMetersPerSecond": 3.4414889544824003, - "averageVelocityMadeGoodInKnots": 8.251441758626202, - "averageVelocityMadeGoodInMetersPerSecond": 4.244908371382146, - "rank": 9, - "gapToLeaderInSeconds": 64, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "The Wave Muscat", - "currentSpeedOverGroundInKnots": 8.332641054983721, - "currentSpeedOverGroundInMetersPerSecond": 4.2873752850406515, - "averageSpeedOverGroundInKnots": 10.446553851178393, - "averageSpeedOverGroundInMetersPerSecond": 5.37417159232844, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 10.446553851178393, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 5.37417159232844, - "distanceTraveledOverGroundInMeters": 171.97349095451008, - "velocityMadeGoodInKnots": 8.299902841697637, - "velocityMadeGoodInMetersPerSecond": 4.270530564910148, - "averageVelocityMadeGoodInKnots": 9.848802603934429, - "averageVelocityMadeGoodInMetersPerSecond": 5.066661784024045, - "rank": 5, - "gapToLeaderInSeconds": 39, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "ZouLou", - "currentSpeedOverGroundInKnots": 7.373853007582403, - "currentSpeedOverGroundInMetersPerSecond": 3.7940522016513567, - "averageSpeedOverGroundInKnots": 14.20081700722379, - "averageSpeedOverGroundInMetersPerSecond": 7.305531415938461, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 14.20081700722379, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 7.305531415938461, - "distanceTraveledOverGroundInMeters": 146.11062831876922, - "velocityMadeGoodInKnots": 7.188573471403532, - "velocityMadeGoodInMetersPerSecond": 3.698720733633545, - "averageVelocityMadeGoodInKnots": 13.903142581202443, - "averageVelocityMadeGoodInMetersPerSecond": 7.152394461218591, - "rank": 8, - "gapToLeaderInSeconds": 54, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - }, - { - "name": "Team Wales", - "currentSpeedOverGroundInKnots": 7.382405409678406, - "currentSpeedOverGroundInMetersPerSecond": 3.7984526500964755, - "averageSpeedOverGroundInKnots": 8.952699421064992, - "averageSpeedOverGroundInMetersPerSecond": 4.605666479947878, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInKnots": 8.952699421064992, - "currentSpeedOverGroundOrAverageSpeedOverGroundIfLegFinishedInMetersPerSecond": 4.605666479947878, - "distanceTraveledOverGroundInMeters": 184.22665919791513, - "velocityMadeGoodInKnots": 7.3657226260100925, - "velocityMadeGoodInMetersPerSecond": 3.7898688944884706, - "averageVelocityMadeGoodInKnots": 7.699387573616958, - "averageVelocityMadeGoodInMetersPerSecond": 3.960907162871835, - "rank": 3, - "gapToLeaderInSeconds": 30, - "estimatedTimeToNextMarkInSeconds": 0, - "windwardDistanceToGoInMeters": 0, - "started": true, - "finished": true - } - ] - } - ], - "ranks": [ - { - "competitor": "Alinghi", - "rank": 2 - }, - { - "competitor": "GAC Pindar", - "rank": 1 - }, - { - "competitor": "Groupe Edmond de Rothschild", - "rank": 6 - }, - { - "competitor": "Oman Air", - "rank": 4 - }, - { - "competitor": "Red Bull Sailing Team", - "rank": 7 - }, - { - "competitor": "SAP Extreme Sailing Team", - "rank": 9 - }, - { - "competitor": "The Wave Muscat", - "rank": 5 - }, - { - "competitor": "ZouLou", - "rank": 8 - }, - { - "competitor": "Team Wales", - "rank": 3 - } - ] - -} \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/showwaypointsDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/showwaypointsDoc.html deleted file mode 100755 index b74b7381097..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/showwaypointsDoc.html +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - Deprecated Web Services API - - -

    SAP Sailing Analytics - Web Services

    -

    Name: wind

    - -Description: -

    This service lists the waypoints of a race's course and the mark positions at the time requested -or at the end of the race if no time point is provided.

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Webservice Type:REST
    Output format:Json
    Base-Url:http://www.sapsailing.com/sailingserver/moderator?action=showwaypoints
    Mandatory parameters: -
    regattaname
    -
    racename
    -
    Optional parameters: -
    time
    -
    timeasmillis
    -
    Request method:GET
    Example:http://www.sapsailing.com/sailingserver/moderator?action=showwaypoints&regattaname=ESS%202012%20Cardiff%20%28Extreme40%29&racename=Cardiff%20Race12
    - -Back to Web Service Overview - - diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/showwaypointsSample.json b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/showwaypointsSample.json deleted file mode 100755 index 8c152fee7e2..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/showwaypointsSample.json +++ /dev/null @@ -1,134 +0,0 @@ -[ - - { - "name": "Start", - "marks": [ - { - "name": "Start (1)", - "lat": 51.461163, - "lng": -3.164802 - }, - { - "name": "Start (2)", - "lat": 51.460262, - "lng": -3.1621099999999998 - } - ] - }, - { - "name": "Red", - "marks": [ - { - "name": "Red", - "lat": 51.454012999999996, - "lng": -3.168643 - } - ] - }, - { - "name": "White", - "marks": [ - { - "name": "White (1)", - "lat": 51.453719899999996, - "lng": -3.164758 - }, - { - "name": "White (2)", - "lat": 51.454581999999995, - "lng": -3.164605 - } - ] - }, - { - "name": "Red", - "marks": [ - { - "name": "Red", - "lat": 51.454012999999996, - "lng": -3.168643 - } - ] - }, - { - "name": "White", - "marks": [ - { - "name": "White (1)", - "lat": 51.453719899999996, - "lng": -3.164758 - }, - { - "name": "White (2)", - "lat": 51.454581999999995, - "lng": -3.164605 - } - ] - }, - { - "name": "Green", - "marks": [ - { - "name": "Green", - "lat": 51.456981999999996, - "lng": -3.166352 - } - ] - }, - { - "name": "Yellow", - "marks": [ - { - "name": "Yellow", - "lat": 51.458275, - "lng": -3.163478 - } - ] - }, - { - "name": "Green", - "marks": [ - { - "name": "Green", - "lat": 51.456981999999996, - "lng": -3.166352 - } - ] - }, - { - "name": "Wales", - "marks": [ - { - "name": "Wales", - "lat": 51.461697, - "lng": -3.163815 - } - ] - }, - { - "name": "Black Conical", - "marks": [ - { - "name": "Black Conical", - "lat": 51.459857899999996, - "lng": -3.1632919999999998 - } - ] - }, - { - "name": "Finish Old", - "marks": [ - { - "name": "Finish Old (1)", - "lat": 51.461166999999996, - "lng": -3.164787 - }, - { - "name": "Finish Old (2)", - "lat": 51.461231999999995, - "lng": -3.162228 - } - ] - } - -] \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/windExportDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/windExportDoc.html deleted file mode 100644 index 52464184bf6..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/windExportDoc.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - Deprecated Web Services API - - -

    SAP Sailing Analytics - Web Services

    -

    Name: wind

    - -Description: -

    This service exports the wind data of tracked race.

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Webservice Type:REST
    Output format:Json
    Base-Url:http://www.sapsailing.com/sailingserver/wind
    Mandatory parameters: -
    regattaname
    -
    racename
    -
    Optional parameters: -
    windsource
    -
    fromtime
    -
    fromtimeasmillis
    -
    totime
    -
    totimeasmillis
    -
    Request method:GET
    Example:http://www.sapsailing.com/sailingserver/wind?regattaname=ESS 2012 Cardiff (Extreme40)&racename=Cardiff Race1&windsource=COMBINED
    - -Back to Web Service Overview - - diff --git a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/windExportSample.json b/java/com.sap.sailing.server.gateway/webservices/api/deprecated/windExportSample.json deleted file mode 100644 index 7063052ae38..00000000000 --- a/java/com.sap.sailing.server.gateway/webservices/api/deprecated/windExportSample.json +++ /dev/null @@ -1 +0,0 @@ -{"TRACK_BASED_ESTIMATION":[{"truebearingdeg":170.78599841392423,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346332941000,"dampenedtruebearingdeg":170.78599841392423,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":170.8140124923136,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346332942000,"dampenedtruebearingdeg":170.8140124923136,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":129.0119197913391,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346332981000,"dampenedtruebearingdeg":129.0119197913391,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":127.1567369575464,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346332982000,"dampenedtruebearingdeg":127.1567369575464,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":126.59604019858851,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346332983000,"dampenedtruebearingdeg":126.59604019858851,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":126.86130632373475,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346332984000,"dampenedtruebearingdeg":126.86130632373475,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":126.88426358656358,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346332985000,"dampenedtruebearingdeg":126.88426358656358,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":127.7555171599174,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346332986000,"dampenedtruebearingdeg":127.7555171599174,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":128.9215528311404,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346332987000,"dampenedtruebearingdeg":128.9215528311404,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":129.57616029753297,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346332988000,"dampenedtruebearingdeg":129.57616029753297,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":129.83263121624248,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346332989000,"dampenedtruebearingdeg":129.83263121624248,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":129.72205816367344,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346332990000,"dampenedtruebearingdeg":129.72205816367344,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":129.18301638991062,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346332991000,"dampenedtruebearingdeg":129.18301638991062,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":129.43720960654457,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346332992000,"dampenedtruebearingdeg":129.43720960654457,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":130.45551136404822,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346332993000,"dampenedtruebearingdeg":130.45551136404822,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":130.93119209963652,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346332994000,"dampenedtruebearingdeg":130.93119209963652,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":130.86160290324221,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346332995000,"dampenedtruebearingdeg":130.86160290324221,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":131.3200664523681,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346332996000,"dampenedtruebearingdeg":131.3200664523681,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":131.5053701340775,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346332997000,"dampenedtruebearingdeg":131.5053701340775,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":131.79682140398995,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346332998000,"dampenedtruebearingdeg":131.79682140398995,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":131.8744277259226,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346332999000,"dampenedtruebearingdeg":131.8744277259226,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":132.20952909528233,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333000000,"dampenedtruebearingdeg":132.20952909528233,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":132.27441868951576,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333001000,"dampenedtruebearingdeg":132.27441868951576,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":132.02529736484382,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333002000,"dampenedtruebearingdeg":132.02529736484382,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":132.1901248844116,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333003000,"dampenedtruebearingdeg":132.1901248844116,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":132.74660731220843,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333004000,"dampenedtruebearingdeg":132.74660731220843,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":133.18418518017063,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333005000,"dampenedtruebearingdeg":133.18418518017063,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":134.65185488282123,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333006000,"dampenedtruebearingdeg":134.65185488282123,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":135.0571606990481,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333007000,"dampenedtruebearingdeg":135.0571606990481,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":134.34723276568658,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333008000,"dampenedtruebearingdeg":134.34723276568658,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":134.52090594171494,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333009000,"dampenedtruebearingdeg":134.52090594171494,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":134.11171568887198,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333010000,"dampenedtruebearingdeg":134.11171568887198,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":133.85888540546364,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333011000,"dampenedtruebearingdeg":133.85888540546364,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":139.55428063097986,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333012000,"dampenedtruebearingdeg":139.55428063097986,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":139.64930136985183,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333013000,"dampenedtruebearingdeg":139.64930136985183,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":137.47789665940593,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333015000,"dampenedtruebearingdeg":137.47789665940593,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":132.06382935505908,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333016000,"dampenedtruebearingdeg":132.06382935505908,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":132.59531711482794,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333017000,"dampenedtruebearingdeg":132.59531711482794,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":139.36195304905152,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333018000,"dampenedtruebearingdeg":139.36195304905152,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":140.38123940175464,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333019000,"dampenedtruebearingdeg":140.38123940175464,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":140.80107709253184,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333020000,"dampenedtruebearingdeg":140.80107709253184,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":140.90275377465508,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333021000,"dampenedtruebearingdeg":140.90275377465508,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":140.85095346624814,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333022000,"dampenedtruebearingdeg":140.85095346624814,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":141.39760378173122,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333023000,"dampenedtruebearingdeg":141.39760378173122,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":142.0782881050826,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333024000,"dampenedtruebearingdeg":142.0782881050826,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":142.3153634475574,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333025000,"dampenedtruebearingdeg":142.3153634475574,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":141.97667565027012,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333026000,"dampenedtruebearingdeg":141.97667565027012,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":142.08558064807698,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333027000,"dampenedtruebearingdeg":142.08558064807698,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":142.3782922328686,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333028000,"dampenedtruebearingdeg":142.3782922328686,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":142.40369974219357,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333029000,"dampenedtruebearingdeg":142.40369974219357,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":136.40271441145273,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333036000,"dampenedtruebearingdeg":136.40271441145273,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":136.11428545446387,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333037000,"dampenedtruebearingdeg":136.11428545446387,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":135.976525698464,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333038000,"dampenedtruebearingdeg":135.976525698464,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":136.38155672812792,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333039000,"dampenedtruebearingdeg":136.38155672812792,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":135.67719188041752,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333040000,"dampenedtruebearingdeg":135.67719188041752,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":137.000437594061,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333041000,"dampenedtruebearingdeg":137.000437594061,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":138.0157963408773,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333042000,"dampenedtruebearingdeg":138.0157963408773,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":138.33376149216386,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333043000,"dampenedtruebearingdeg":138.33376149216386,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":138.79555608531157,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333044000,"dampenedtruebearingdeg":138.79555608531157,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":139.18737337998675,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333045000,"dampenedtruebearingdeg":139.18737337998675,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":164.44333479994617,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333082000,"dampenedtruebearingdeg":164.44333479994617,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":163.15727379154873,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333083000,"dampenedtruebearingdeg":163.15727379154873,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":166.09434543857276,"knotspeed":3.0,"meterspersecondspeed":1.5435833333333333,"timepoint":1346333084000,"dampenedtruebearingdeg":166.09434543857276,"dampenedknotspeed":3.0,"dampenedmeterspersecondspeed":1.5435833333333333},{"truebearingdeg":165.3730631973565,"knotspeed":3.0,"meterspersecondspeed":1.5435833333333333,"timepoint":1346333085000,"dampenedtruebearingdeg":165.3730631973565,"dampenedknotspeed":3.0,"dampenedmeterspersecondspeed":1.5435833333333333},{"truebearingdeg":165.12675886412927,"knotspeed":3.0,"meterspersecondspeed":1.5435833333333333,"timepoint":1346333086000,"dampenedtruebearingdeg":165.12675886412927,"dampenedknotspeed":3.0,"dampenedmeterspersecondspeed":1.5435833333333333},{"truebearingdeg":126.97293846108835,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333109000,"dampenedtruebearingdeg":126.97293846108835,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":127.63314793507148,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333110000,"dampenedtruebearingdeg":127.63314793507148,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":127.92225810628767,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333111000,"dampenedtruebearingdeg":127.92225810628767,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":128.29214686332864,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333112000,"dampenedtruebearingdeg":128.29214686332864,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":128.4917039143308,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333113000,"dampenedtruebearingdeg":128.4917039143308,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":128.12992347839693,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333114000,"dampenedtruebearingdeg":128.12992347839693,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":129.6216907270532,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333133000,"dampenedtruebearingdeg":129.6216907270532,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":130.1292739898787,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333134000,"dampenedtruebearingdeg":130.1292739898787,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":131.0566934871553,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333135000,"dampenedtruebearingdeg":131.0566934871553,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":131.78317477290364,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333136000,"dampenedtruebearingdeg":131.78317477290364,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":132.32621142024834,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333137000,"dampenedtruebearingdeg":132.32621142024834,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":133.06557200256066,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333138000,"dampenedtruebearingdeg":133.06557200256066,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":132.5921025859417,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333139000,"dampenedtruebearingdeg":132.5921025859417,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":132.54312805541053,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333140000,"dampenedtruebearingdeg":132.54312805541053,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":130.96007591953526,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333141000,"dampenedtruebearingdeg":130.96007591953526,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":130.74116322927316,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333142000,"dampenedtruebearingdeg":130.74116322927316,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":130.4594929520376,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333143000,"dampenedtruebearingdeg":130.4594929520376,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":131.1063572835708,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333144000,"dampenedtruebearingdeg":131.1063572835708,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":131.7165188448432,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333145000,"dampenedtruebearingdeg":131.7165188448432,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":132.34239657803627,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333146000,"dampenedtruebearingdeg":132.34239657803627,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":132.0557867905627,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333147000,"dampenedtruebearingdeg":132.0557867905627,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":131.58219474790857,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333148000,"dampenedtruebearingdeg":131.58219474790857,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":131.18830993132963,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333149000,"dampenedtruebearingdeg":131.18830993132963,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":130.29554120183468,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333150000,"dampenedtruebearingdeg":130.29554120183468,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":128.58002103404817,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333151000,"dampenedtruebearingdeg":128.58002103404817,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":128.40813400692286,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333152000,"dampenedtruebearingdeg":128.40813400692286,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":128.41595729781727,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333153000,"dampenedtruebearingdeg":128.41595729781727,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":129.30838071841788,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333154000,"dampenedtruebearingdeg":129.30838071841788,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":132.03912558116542,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333155000,"dampenedtruebearingdeg":132.03912558116542,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":134.38643005411535,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333167000,"dampenedtruebearingdeg":134.38643005411535,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":134.87704714102182,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333168000,"dampenedtruebearingdeg":134.87704714102182,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":135.98333475691464,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333169000,"dampenedtruebearingdeg":135.98333475691464,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":137.2414997908883,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333170000,"dampenedtruebearingdeg":137.2414997908883,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":135.65633492816016,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333171000,"dampenedtruebearingdeg":135.65633492816016,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":136.36848773314364,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333172000,"dampenedtruebearingdeg":136.36848773314364,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":136.56601709023892,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333173000,"dampenedtruebearingdeg":136.56601709023892,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":135.87333085809178,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333174000,"dampenedtruebearingdeg":135.87333085809178,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":135.5427537225676,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333175000,"dampenedtruebearingdeg":135.5427537225676,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":135.54431809087208,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333176000,"dampenedtruebearingdeg":135.54431809087208,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":135.66803868294107,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333177000,"dampenedtruebearingdeg":135.66803868294107,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":136.0330028801843,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333178000,"dampenedtruebearingdeg":136.0330028801843,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":136.18645518670724,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333179000,"dampenedtruebearingdeg":136.18645518670724,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":134.70757601689968,"knotspeed":3.0,"meterspersecondspeed":1.5435833333333333,"timepoint":1346333180000,"dampenedtruebearingdeg":134.70757601689968,"dampenedknotspeed":3.0,"dampenedmeterspersecondspeed":1.5435833333333333},{"truebearingdeg":135.10827289217048,"knotspeed":3.0,"meterspersecondspeed":1.5435833333333333,"timepoint":1346333181000,"dampenedtruebearingdeg":135.10827289217048,"dampenedknotspeed":3.0,"dampenedmeterspersecondspeed":1.5435833333333333},{"truebearingdeg":135.35545992410664,"knotspeed":3.0,"meterspersecondspeed":1.5435833333333333,"timepoint":1346333182000,"dampenedtruebearingdeg":135.35545992410664,"dampenedknotspeed":3.0,"dampenedmeterspersecondspeed":1.5435833333333333},{"truebearingdeg":135.5519555980694,"knotspeed":3.0,"meterspersecondspeed":1.5435833333333333,"timepoint":1346333183000,"dampenedtruebearingdeg":135.5519555980694,"dampenedknotspeed":3.0,"dampenedmeterspersecondspeed":1.5435833333333333},{"truebearingdeg":136.61539467880345,"knotspeed":3.0,"meterspersecondspeed":1.5435833333333333,"timepoint":1346333184000,"dampenedtruebearingdeg":136.61539467880345,"dampenedknotspeed":3.0,"dampenedmeterspersecondspeed":1.5435833333333333},{"truebearingdeg":136.64049996273735,"knotspeed":3.0,"meterspersecondspeed":1.5435833333333333,"timepoint":1346333185000,"dampenedtruebearingdeg":136.64049996273735,"dampenedknotspeed":3.0,"dampenedmeterspersecondspeed":1.5435833333333333},{"truebearingdeg":135.9868169186443,"knotspeed":3.0,"meterspersecondspeed":1.5435833333333333,"timepoint":1346333186000,"dampenedtruebearingdeg":135.9868169186443,"dampenedknotspeed":3.0,"dampenedmeterspersecondspeed":1.5435833333333333},{"truebearingdeg":135.0309507836098,"knotspeed":3.0,"meterspersecondspeed":1.5435833333333333,"timepoint":1346333187000,"dampenedtruebearingdeg":135.0309507836098,"dampenedknotspeed":3.0,"dampenedmeterspersecondspeed":1.5435833333333333},{"truebearingdeg":132.50537064762932,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333188000,"dampenedtruebearingdeg":132.50537064762932,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":130.7150930406542,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333189000,"dampenedtruebearingdeg":130.7150930406542,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":130.2714372616539,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333190000,"dampenedtruebearingdeg":130.2714372616539,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":129.5016015178668,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333191000,"dampenedtruebearingdeg":129.5016015178668,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":127.39908925011844,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333192000,"dampenedtruebearingdeg":127.39908925011844,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":126.0253752333814,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333193000,"dampenedtruebearingdeg":126.0253752333814,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":126.30674849087684,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333194000,"dampenedtruebearingdeg":126.30674849087684,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":126.4010626331599,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333195000,"dampenedtruebearingdeg":126.4010626331599,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":126.63493485158835,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333196000,"dampenedtruebearingdeg":126.63493485158835,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":127.50737709251399,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333197000,"dampenedtruebearingdeg":127.50737709251399,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":128.70643822881914,"knotspeed":2.0,"meterspersecondspeed":1.0290555555555556,"timepoint":1346333198000,"dampenedtruebearingdeg":128.70643822881914,"dampenedknotspeed":2.0,"dampenedmeterspersecondspeed":1.0290555555555556},{"truebearingdeg":128.80695814118587,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333199000,"dampenedtruebearingdeg":128.80695814118587,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":130.67098387517626,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333200000,"dampenedtruebearingdeg":130.67098387517626,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":128.40001633777112,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333201000,"dampenedtruebearingdeg":128.40001633777112,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":128.77640702046813,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333202000,"dampenedtruebearingdeg":128.77640702046813,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":125.48005399318629,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333203000,"dampenedtruebearingdeg":125.48005399318629,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":123.30450592067211,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333204000,"dampenedtruebearingdeg":123.30450592067211,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":121.32768156267947,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333205000,"dampenedtruebearingdeg":121.32768156267947,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":120.84112041416924,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333206000,"dampenedtruebearingdeg":120.84112041416924,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":122.02814739273398,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333207000,"dampenedtruebearingdeg":122.02814739273398,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":124.05559684583942,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333208000,"dampenedtruebearingdeg":124.05559684583942,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":126.81938579358031,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333209000,"dampenedtruebearingdeg":126.81938579358031,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":129.3219173718087,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333210000,"dampenedtruebearingdeg":129.3219173718087,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":125.09216018244828,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333215000,"dampenedtruebearingdeg":125.09216018244828,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":125.90321197065462,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333216000,"dampenedtruebearingdeg":125.90321197065462,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":126.98299605281602,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333217000,"dampenedtruebearingdeg":126.98299605281602,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":128.59954480884306,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333218000,"dampenedtruebearingdeg":128.59954480884306,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":130.22978552633356,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333219000,"dampenedtruebearingdeg":130.22978552633356,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":132.0957983344275,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333220000,"dampenedtruebearingdeg":132.0957983344275,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":134.21581062202029,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333221000,"dampenedtruebearingdeg":134.21581062202029,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778},{"truebearingdeg":135.89484210968612,"knotspeed":1.0,"meterspersecondspeed":0.5145277777777778,"timepoint":1346333222000,"dampenedtruebearingdeg":135.89484210968612,"dampenedknotspeed":1.0,"dampenedmeterspersecondspeed":0.5145277777777778}]} \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html index cc613e5f33c..53e849021ed 100644 --- a/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html @@ -5,10 +5,10 @@ - SAP Sailing Analytics Webservices API Version 1.0 Beta + SAP Sailing Analytics Webservices API Version 1.0 -

    SAP Sailing Analytics Webservices API Version 1.0 Beta - Overview

    +

    SAP Sailing Analytics Webservices API Version 1.0 - Overview

    A number of web services are available to query the state of a SAP Sailing Analytics server instance. diff --git a/java/com.sap.sailing.server.gateway/webservices/index.html b/java/com.sap.sailing.server.gateway/webservices/index.html index 301b7a686d3..618b197825e 100644 --- a/java/com.sap.sailing.server.gateway/webservices/index.html +++ b/java/com.sap.sailing.server.gateway/webservices/index.html @@ -24,15 +24,10 @@ In all subsequent explanations, you may replace the "www.sapsailing.com" hostnam name to make sure you get to the event you're interested in.

    -

    -Attention: -We are going to introduce a new and more managed WebServices API. The old API is marked as deprecated but will continue to work for some time. -


    Webservices API-Documention:
    \ No newline at end of file diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index c371bf053ec..790dcb1a1c5 100644 --- a/java/com.sap.sailing.server/SailingServer (No Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy).launch @@ -34,5 +34,5 @@ - + diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SimulatorUtils.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SimulatorUtils.java index 0b1d16ee6ae..a2324cbcbd8 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SimulatorUtils.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/SimulatorUtils.java @@ -67,103 +67,6 @@ public class SimulatorUtils { return prependedBundlePath; } - /*public static RaceHandle loadRace(RacingEventServiceImpl service, TracTracAdapterFactory tracTracAdapterFactory, - URL paramURL, URI liveURI, URI storedURI, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, - long timeoutInMilliseconds) throws Exception { - // TODO: TracTrac Username / Password - String tractracUsername = ""; - String tractracPassword = ""; - RaceHandle raceHandle = tracTracAdapterFactory.getOrCreateTracTracAdapter(service.getBaseDomainFactory()) - .addTracTracRace(service, paramURL, liveURI, storedURI, null, raceLogStore, regattaLogStore, - timeoutInMilliseconds, tractracUsername, tractracPassword, TracTracConnectionConstants.ONLINE_STATUS, - TracTracConnectionConstants.ONLINE_VISIBILITY); - return raceHandle; - }*/ - - public static boolean savePathsToFiles(Map paths, Path raceCourse) { - if (paths == null) { - return false; - } - - if (paths.isEmpty()) { - return true; - } - - if (pathPrefix == null) { - pathPrefix = getPathPrefix(); - } - - String filePath = ""; - boolean result = true; - - for (String name : PATH_NAMES) { - filePath = pathPrefix + "\\resources\\" + name + ".dat"; - result &= saveToFile(paths.get(name), filePath); - } - - filePath = pathPrefix + "\\resources\\" + RACECOURSE_DAT; - result &= saveToFile(raceCourse, filePath); - - return result; - } - - public static boolean saveLegPathsToFiles(Map paths, Path raceCourse, int selectedRaceIndex, - int selectedCompetitorIndex, int selectedLegIndex) { - if (paths == null) { - return false; - } - - if (paths.isEmpty()) { - return true; - } - - if (pathPrefix == null) { - pathPrefix = getPathPrefix(); - } - - String filePath = ""; - String fileName = ""; - boolean result = true; - - for (String name : PATH_NAMES) { - - fileName = SimulatorUtils.getFileName(selectedRaceIndex, selectedCompetitorIndex, selectedLegIndex, - name); - filePath = pathPrefix + "\\resources\\" + fileName; - result &= saveToFile(paths.get(name), filePath); - } - - fileName = SimulatorUtils.getFileName(selectedRaceIndex, selectedCompetitorIndex, selectedLegIndex, - "racecourse"); - filePath = pathPrefix + "\\resources\\" + fileName; - result &= saveToFile(raceCourse, filePath); - - return result; - } - - public static boolean saveToFile(Path path, String fileName) { - boolean result = true; - try { - OutputStream file = new FileOutputStream(fileName); - OutputStream buffer = new BufferedOutputStream(file); - ObjectOutput output = new ObjectOutputStream(buffer); - - try { - output.writeObject(path); - } finally { - output.close(); - buffer.close(); - file.close(); - } - } catch (IOException ex) { - System.err.println("[ERROR][SerializationUtils][saveToFile][IOException] " + ex.getMessage()); - LOGGER.severe("[ERROR][SerializationUtils][saveToFile][IOException] " + ex.getMessage()); - result = false; - } - - return result; - } - public static Util.Pair, Path> readLegPathsFromResources(int selectedRaceIndex, int selectedCompetitorIndex, int selectedLegIndex) { HashMap paths = new HashMap(); diff --git a/java/com.sap.sse.common/META-INF/MANIFEST.MF b/java/com.sap.sse.common/META-INF/MANIFEST.MF index 81dcd9a9a05..d8f1e10697a 100644 --- a/java/com.sap.sse.common/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.common/META-INF/MANIFEST.MF @@ -15,5 +15,5 @@ Export-Package: com.sap.sse.common, com.sap.sse.common.settings, com.sap.sse.common.util Require-Bundle: org.json.simple;bundle-version="1.1.0", - com.sap.sse.datamining.annotations;bundle-version="1.0.0" + com.sap.sse.datamining.annotations Bundle-ActivationPolicy: lazy diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java index 2b820bc9e63..d22e33221a6 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/URLFieldWithFileUpload.java @@ -11,6 +11,7 @@ import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.json.client.JSONArray; import com.google.gwt.json.client.JSONObject; import com.google.gwt.json.client.JSONParser; +import com.google.gwt.json.client.JSONValue; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; @@ -54,7 +55,7 @@ public class URLFieldWithFileUpload extends Composite implements HasValue]*>(.*)
    ", "$1")).isObject(); + final JSONObject resultJson = parseAfterReplacingSurroundingPreElement(event.getResults()).isObject(); Window.alert(stringMessages.removeResult(resultJson.get("status").isString().stringValue(), resultJson.get("message") == null ? "" : resultJson.get("message").isString().stringValue())); setURL(""); @@ -110,7 +111,7 @@ public class URLFieldWithFileUpload extends Composite implements HasValue]*>(.*)", "$1")); + JSONArray resultJson = parseAfterReplacingSurroundingPreElement(result).isArray(); if (resultJson != null) { if (resultJson.get(0).isObject().get("file_uri") != null) { uri = resultJson.get(0).isObject().get("file_uri").isString().stringValue(); @@ -185,4 +186,14 @@ public class URLFieldWithFileUpload extends Composite implements HasValue]*>(.*)", "$1")); + } } diff --git a/java/com.sap.sse.gwt/META-INF/MANIFEST.MF b/java/com.sap.sse.gwt/META-INF/MANIFEST.MF index 1b53b52f991..0d827bce8f4 100644 --- a/java/com.sap.sse.gwt/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.gwt/META-INF/MANIFEST.MF @@ -20,6 +20,7 @@ Export-Package: com.google.gwt.user.client.rpc.core.java.util, com.sap.sse.common.impl, com.sap.sse.gwt.client, com.sap.sse.gwt.client.async, + com.sap.sse.gwt.client.celltable, com.sap.sse.gwt.client.controls, com.sap.sse.gwt.client.controls.busyindicator, com.sap.sse.gwt.client.controls.carousel, diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/AsyncActionsExecutor.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/AsyncActionsExecutor.java index ff024bfda05..6ac2bde5be8 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/AsyncActionsExecutor.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/AsyncActionsExecutor.java @@ -19,9 +19,9 @@ import com.sap.sse.common.impl.MillisecondsTimePoint; */ public class AsyncActionsExecutor { private class ExecutionJob implements AsyncCallback { - private AsyncAction action; - private String category; - private AsyncCallback callback; + private final AsyncAction action; + private final String category; + private final AsyncCallback callback; public ExecutionJob(AsyncAction action, String category, AsyncCallback callback) { this.action = action; diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/MarkedAsyncCallback.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/MarkedAsyncCallback.java index 9313bef647f..c2d04861efd 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/MarkedAsyncCallback.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/MarkedAsyncCallback.java @@ -51,8 +51,7 @@ public final class MarkedAsyncCallback implements AsyncCallback { public MarkedAsyncCallback(AsyncCallback callback, String category) { this.callback = callback; this.category = category; - - if(DebugInfo.isDebugIdEnabled()) { + if (DebugInfo.isDebugIdEnabled()) { PendingAjaxCallMarker.incrementPendingAjaxCalls(this.category); } } @@ -70,7 +69,7 @@ public final class MarkedAsyncCallback implements AsyncCallback { try { this.callback.onFailure(cause); } finally { - if(DebugInfo.isDebugIdEnabled()) { + if (DebugInfo.isDebugIdEnabled()) { PendingAjaxCallMarker.decrementPendingAjaxCalls(this.category); } } @@ -89,7 +88,7 @@ public final class MarkedAsyncCallback implements AsyncCallback { try { this.callback.onSuccess(result); } finally { - if(DebugInfo.isDebugIdEnabled()) { + if (DebugInfo.isDebugIdEnabled()) { PendingAjaxCallMarker.decrementPendingAjaxCalls(this.category); } } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/celltable/EntityIdentityComparator.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/celltable/EntityIdentityComparator.java new file mode 100644 index 00000000000..54a26665325 --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/celltable/EntityIdentityComparator.java @@ -0,0 +1,35 @@ +package com.sap.sse.gwt.client.celltable; + +/** + * An entity can change its state over time while remaining the same. Objects such as DTOs (data transfer objects) + * represent the state of an entity at a given point in time, so two such DTOs may represent different states of the + * same entity. These copied may not be equal due to their different state. So, e.g., a UI that needs to decide whether + * a refresh is needed would rely on this regular definition of state-based equality. + *

    + * + * However, different, non-equal objects may still represent the same entity. This equivalence can become important, e.g., for + * selection management where in a UI widget a new and different copy of the same entity is being displayed and an old copy + * was in the selection.

    + * + * Implementors of this interface are expected to define under which conditions two objects represent the same + * entity. This can, e.g., be decided based on an ID field if the object type offers one. + * + * @author Lukas Furmanek + * + * @param type of the entity representation, such as a DTO class + */ +public interface EntityIdentityComparator { + /** + * Compares the objects and returns true if the objects represent the same entity. + * Note that this does not imply that they are also {@link #equals(Object) equal}. + */ + boolean representSameEntity(T dto1, T dto2); + + /** + * A hash code that is "in line" wiht the equality definition implemented by + * {@link #representSameEntity(Object, Object)}. More specifically, two objects that represent the same entity + * according to {@link #representSameEntity(Object, Object)} must have equal hash codes. Conversely, two objects + * with equal hash codes may still represent different entities. + */ + int hashCode(T t); +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/celltable/EntityIdentityWrapper.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/celltable/EntityIdentityWrapper.java new file mode 100755 index 00000000000..a4553cf4e8b --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/celltable/EntityIdentityWrapper.java @@ -0,0 +1,42 @@ +package com.sap.sse.gwt.client.celltable; + + +/** + * Wraps an object such that its {@link #equals(Object)} and {@link #hashCode()} implementations are based on the + * {@link RefreshableMultiSelectionModel#comp} comparator's + * {@link EntityIdentityComparator#representSameEntity(Object, Object)} and + * {@link EntityIdentityComparator#hashCode(Object)} methods, respectively. + * + * @author Axel Uhl (D043530) + * + * @param + */ +class EntityIdentityWrapper { + private final T t; + private final EntityIdentityComparator comp; + + public EntityIdentityWrapper(T t, EntityIdentityComparator comp) { + super(); + this.t = t; + this.comp = comp; + } + + @Override + public int hashCode() { + return comp.hashCode(t); + } + + private T getT() { + return t; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof EntityIdentityWrapper)) { + throw new ClassCastException("Can only compare EntityIdentityWrapper with other objects of same class"); + } + @SuppressWarnings("unchecked") // need to cast to generic type argument T + EntityIdentityWrapper objAsT = ((EntityIdentityWrapper) obj); + return comp.representSameEntity(t, objAsT.getT()); + } +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/celltable/HasDataAdapter.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/celltable/HasDataAdapter.java new file mode 100755 index 00000000000..5007ae68773 --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/celltable/HasDataAdapter.java @@ -0,0 +1,125 @@ +package com.sap.sse.gwt.client.celltable; + +import java.util.List; + +import com.google.gwt.event.shared.GwtEvent; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.view.client.HasData; +import com.google.gwt.view.client.ListDataProvider; +import com.google.gwt.view.client.Range; +import com.google.gwt.view.client.SelectionModel; +import com.google.gwt.view.client.RangeChangeEvent.Handler; + +/** + * Adapts a {@link RefreshableSelectionModel} to the {@link HasData} interface and forwards calls to + * {@link HasData#setRowData(int, java.util.List)} to {@link RefreshableSelectionModel#refreshSelectionModel(Iterable)}. + * + * @author Axel Uhl (D043530) + * + */ +public class HasDataAdapter implements HasData { + private final RefreshableSelectionModel selectionModel; + private final ListDataProvider listDataProvider; + + public HasDataAdapter(RefreshableSelectionModel selectionModel, ListDataProvider listDataProvider) { + super(); + this.selectionModel = selectionModel; + this.listDataProvider = listDataProvider; + } + + /** + * This method is called when the {@link ListDataProvider} has new elements. It takes the new elements and refreshes + * the {@link RefreshableSingleSelectionModel selectionmodel} with them. + */ + @Override + public void setRowData(int start, List values) { + selectionModel.refreshSelectionModel(listDataProvider.getList()); + } + + @Override + public int getRowCount() { + return Integer.MAX_VALUE; + } + + @Override + public Range getVisibleRange() { + return new Range(0, Integer.MAX_VALUE); + } + + @Override + public int getVisibleItemCount() { + return Integer.MAX_VALUE; + } + + @Override + public HandlerRegistration addRangeChangeHandler(Handler handler) { + return null; + } + + @Override + public HandlerRegistration addRowCountChangeHandler( + com.google.gwt.view.client.RowCountChangeEvent.Handler handler) { + return null; + } + + @Override + public boolean isRowCountExact() { + return false; + } + + @Override + public void setRowCount(int count) { + return; + } + + @Override + public void setRowCount(int count, boolean isExact) { + return; + } + + @Override + public void setVisibleRange(int start, int length) { + return; + } + + @Override + public void setVisibleRange(Range range) { + return; + } + + @Override + public HandlerRegistration addCellPreviewHandler(com.google.gwt.view.client.CellPreviewEvent.Handler handler) { + return null; + } + + @Override + public SelectionModel getSelectionModel() { + return null; + } + + @Override + public T getVisibleItem(int indexOnPage) { + return null; + } + + @Override + public Iterable getVisibleItems() { + return null; + } + + @Override + public void setSelectionModel(SelectionModel selectionModel) { + return; + } + + @Override + public void setVisibleRangeAndClearData(Range range, boolean forceRangeChangeEvent) { + selectionModel.clear(); + } + + @Override + public void fireEvent(GwtEvent event) { + selectionModel.fireEvent(event); + } + +} diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/celltable/RefreshableMultiSelectionModel.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/celltable/RefreshableMultiSelectionModel.java new file mode 100644 index 00000000000..8073b625899 --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/celltable/RefreshableMultiSelectionModel.java @@ -0,0 +1,148 @@ +package com.sap.sse.gwt.client.celltable; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.gwt.view.client.ListDataProvider; +import com.google.gwt.view.client.MultiSelectionModel; +import com.google.gwt.view.client.ProvidesKey; +import com.google.gwt.view.client.SelectionChangeEvent; + +/** + * This {@link RefreshableMultiSelectionModel} implements the {@link RefreshableSelectionModel} interface. So it + * register it self as a display on the {@link ListDataProvider} and reacts on the changes of {@link ListDataProvider}. + * When the {@link ListDataProvider} is changed this {@link RefreshableMultiSelectionModel selection model} will refresh + * the selection according to the {@link ListDataProvider} changes. To make this class work correct it is very important + * to set the {@link ListDataProvider}, otherwise it won´t work. + *

    + * For more details on the update process read the {@link RefreshableSelectionModel} Javadoc and see the methods + * {@link RefreshableMultiSelectionModel#refreshSelectionModel(Iterable)} and + * {@link RefreshableMultiSelectionModel#setRowData(int, List)}.

    + * + * TODO try to factor out the commonalities with RefreshableSingleSelectionModel into a delegate + * + * @author Lukas Furmanek + * @param + * the type of entries + */ +public class RefreshableMultiSelectionModel extends MultiSelectionModel + implements RefreshableSelectionModel { + final EntityIdentityComparator comp; + private boolean dontcheckSelectionState = false; + private final ListDataProvider listDataProvider; + + /** + * @param comp + * {@link EntityIdentityComparator} to compare the identity of the objects; if not null, this will + * also be used to determine the selection "keys" using a {@link ProvidesKey} implementation based on this + * comparator. + * @param listDataProvider + * {@link ListDataProvider} to add this {@link RefreshableSingleSelectionModel selectionmodel} as an + * display on {@link ListDataProvider} + */ + public RefreshableMultiSelectionModel(final EntityIdentityComparator comp, ListDataProvider listDataProvider) { + super(/* keyProvider */ comp == null ? null : new ProvidesKey() { + @Override + public Object getKey(T item) { + return new EntityIdentityWrapper(item, comp); + } + }); + this.comp = comp; + this.listDataProvider = listDataProvider; + this.listDataProvider.addDataDisplay(new HasDataAdapter(this, listDataProvider)); + } + + /** + * @return the {@link EntityIdentityComparator} for the {@link RefreshableSingleSelectionModel}. If the + * {@link EntityIdentityComparator} is not set this method will return null. + */ + @Override + public EntityIdentityComparator getEntityIdentityComparator() { + return comp; + } + + /** + * Checks the old selection state of the object. If it was selected before, the old version will be replaced with + * the new one. In all other cases this method behave same as + * super.setSelected(T item, boolean selected). + *

    + * When the {@link EntityIdentityComparator} is null this method also behaves like the super method + */ + @Override + public void setSelected(T item, boolean selected) { + if (comp == null || dontcheckSelectionState || item == null || getSelectedSet().isEmpty()) { + super.setSelected(item, selected); + } else { + T wasSelectedBefore = null; + Set selectedSet = getSelectedSet(); + for (T it : selectedSet) { + if (comp.representSameEntity(it, item)) { + wasSelectedBefore = it; + break; + } + } + if (wasSelectedBefore != null) { + super.setSelected(wasSelectedBefore, false); + isSelected(item); // triggers the deleting of the wasSelectedBefrore element in super class + super.setSelected(item, selected); + } else { + super.setSelected(item, selected); + } + } + } + + /** + * Refreshes the {@link RefreshableMultiSelectionModel} with the newObjects.All objects from the + * current selection that {@link EntityIdentityComparator#representSameEntity(Object, Object) represent the same + * entity} as an object from newObjects will be reselected. All others are de-selected. That means a + * selected object is not contained in newObjects the object wouldn't be selected anymore. If this + * selection model has no {@link EntityIdentityComparator} set, this method will use the {@link #equals(Object)} + * method to compare. If an object is reselected it will be replaced with the new version of it. + *

    + * + * When the selection is refreshed this method triggers a + * {@link SelectionChangeEvent.Handler#onSelectionChange(SelectionChangeEvent) onSelectionChangedEvent} using + * {@link AbstractSelectionModel#fireEvent(com.google.gwt.event.shared.GwtEvent)}. + * + * @param newObjects + * the new objects to refresh the {@link RefreshableMultiSelectionModel selection model} + */ + @Override + public void refreshSelectionModel(Iterable newObjects) { + if (!dontcheckSelectionState) { // avoid endless recursions + dontcheckSelectionState = true; + try { + final Set selectedSet = getSelectedSet(); // gets the selected set as a non-live copy, so later + // changes to the selection won't change this set anymore + final boolean isEmpty = selectedSet.isEmpty(); + if (!isEmpty) { + clear(); + if (comp == null) { + for (T it : newObjects) { + final boolean selected = selectedSet.contains(it); + if (selected) { + setSelected(it, true); + } + } + } else { + final Map, T> wrappedNewObjects = new HashMap<>(); + for (T it : newObjects) { + wrappedNewObjects.put(new EntityIdentityWrapper(it, comp), it); + } + for (final T selected : selectedSet) { + T newSelectedElement = wrappedNewObjects.remove(new EntityIdentityWrapper(selected, comp)); + if (newSelectedElement != null) { + setSelected(newSelectedElement, true); + } + } + } + SelectionChangeEvent.fire(this); + } + } finally { + dontcheckSelectionState = false; + } + } + } +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/celltable/RefreshableSelectionModel.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/celltable/RefreshableSelectionModel.java new file mode 100644 index 00000000000..92e8c704f9a --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/celltable/RefreshableSelectionModel.java @@ -0,0 +1,42 @@ +package com.sap.sse.gwt.client.celltable; + +import com.google.gwt.view.client.HasData; +import com.google.gwt.view.client.ListDataProvider; +import com.google.gwt.view.client.SelectionChangeEvent.Handler; +import com.google.gwt.view.client.SetSelectionModel; +import com.google.gwt.view.client.SingleSelectionModel; + +/** + * The {@link RefreshableSelectionModel} unifies the refresh behavior of SelectionModels e.g. of a + * {@link SingleSelectionModel}. This {@link RefreshableSelectionModel selection model} will automatically refresh the + * selection when there are changes on the {@link ListDataProvider}. Therefore the {@link RefreshableSelectionModel + * selection model} is a listener of the {@link ListDataProvider} and implements the {@link HasData}-interface. + *

    + * All objects from the current selection that {@link EntityIdentityComparator#representSameEntity(Object, Object) + * represent the same entity} as a new object from {@link ListDataProvider} will be reselected. All others are + * de-selected. That means a selected object that is not contained in {@link ListDataProvider#getList()} wouldn't be + * selected anymore. If an element is reselected the element will be replaced with new version of it. + *

    + * If this selection model has no {@link EntityIdentityComparator} set, this method will use the {@link #equals(Object)} + * method to compare. + *

    + * When the selection is refreshed this {@link RefreshableSelectionModel selection model} triggers a + * {@link SelectionChangeEvent.Handler#onSelectionChange(SelectionChangeEvent) onSelectionChangedEvent} using + * {@link AbstractSelectionModel#fireEvent(com.google.gwt.event.shared.GwtEvent)}. + *

    + * When you use this {@link RefreshableSelectionModel selection model} make sure that you don't modify + * {@link ListDataProvider} in {@link Handler#onSelectionChange(com.google.gwt.view.client.SelectionChangeEvent) + * onSelectionChange()} (it causes a stack overflow). + * + * @author D064976 + */ + +public interface RefreshableSelectionModel extends SetSelectionModel { + /** + * @return Returns an instance of {@link EntityIdentityComparator}, to compare objects from the type + * <T>. + */ + EntityIdentityComparator getEntityIdentityComparator(); + + void refreshSelectionModel(Iterable newObjects); +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/celltable/RefreshableSingleSelectionModel.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/celltable/RefreshableSingleSelectionModel.java new file mode 100644 index 00000000000..dc0c7a5da15 --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/celltable/RefreshableSingleSelectionModel.java @@ -0,0 +1,117 @@ +package com.sap.sse.gwt.client.celltable; + +import java.util.List; + +import com.google.gwt.view.client.ListDataProvider; +import com.google.gwt.view.client.ProvidesKey; +import com.google.gwt.view.client.SelectionChangeEvent; +import com.google.gwt.view.client.SingleSelectionModel; + +/** + * This {@link RefreshableSingleSelectionModel} implements the {@link RefreshableSelectionModel} interface. So it + * register it self as a display on the {@link ListDataProvider} and reacts on the changes of {@link ListDataProvider}. + * When the {@link ListDataProvider} is changed this {@link RefreshableSingleSelectionModel selectionmodel} will refresh + * the selection according to the {@link ListDataProvider} changes. To make this class work correct it is very important + * to set the {@link ListDataProvider}, otherwise it won´t work. + *

    + * For more details on the update process read the {@link RefreshableSelectionModel} Javadoc and see the methods + * {@link RefreshableSingleSelectionModel#refreshSelectionModel(Iterable)} and + * {@link RefreshableSingleSelectionModel#setRowData(int, List)}.

    + * + * TODO try to factor out the commonalities with RefreshableMultiSelectionModel into a delegate + * + * @author Lukas Furmanek + * @param + * the type of entries + */ +public class RefreshableSingleSelectionModel extends SingleSelectionModel implements RefreshableSelectionModel { + private final EntityIdentityComparator comp; + private boolean dontcheckSelectionState = false; + private final ListDataProvider listDataProvider; + + /** + * @param comp + * {@link EntityIdentityComparator} to compare the identity of the objects + * @param listDataProvider + * {@link ListDataProvider} to add this {@link RefreshableSingleSelectionModel selectionmodel} as an + * display on {@link ListDataProvider} + */ + public RefreshableSingleSelectionModel(final EntityIdentityComparator comp, ListDataProvider listDataProvider) { + super(comp == null ? null : new ProvidesKey() { + @Override + public Object getKey(T item) { + return new EntityIdentityWrapper(item, comp); + } + }); + this.comp = comp; + this.listDataProvider = listDataProvider; + this.listDataProvider.addDataDisplay(new HasDataAdapter(this, listDataProvider)); + } + + /** + * @return the {@link EntityIdentityComparator} for the {@link RefreshableSingleSelectionModel}. If the + * {@link EntityIdentityComparator} is not set this method will return null. + */ + @Override + public EntityIdentityComparator getEntityIdentityComparator() { + return comp; + } + + /** + * Checks the old selection state of the object. If it was selected before, the old version will be replaced with the + * new one. In all other cases this method behave same as super.setSelected(T item, boolean selected). + *

    + * When the {@link EntityIdentityComparator} is null this method also behaves like the super method + */ + @Override + public void setSelected(T item, boolean selected) { + if (comp == null || dontcheckSelectionState || item == null || getSelectedObject() == null) { + super.setSelected(item, selected); + } else { + if (comp.representSameEntity(getSelectedObject(), item)) { + super.setSelected(getSelectedObject(), false); // This old version of item will be deleted with the next clear() + } + super.setSelected(item, selected); + } + } + + /** + * Refreshes the {@link RefreshableSingleSelectionModel} with the newObjects. If the current selected + * object {@link EntityIdentityComparator#representSameEntity(Object, Object) represent the same entity} as an + * object from newObjects it will be reselected. All others are de-selected. That means a selected + * object is not contained in newObjects the object wouldn't be selected anymore. If this selection + * model has no {@link EntityIdentityComparator} set, this method will use the {@link #equals(Object)} method to + * compare. If an object is reselected it will be replaced with the new version of it. + *

    + * + * When the selection is refreshed this method triggers a + * {@link SelectionChangeEvent.Handler#onSelectionChange(SelectionChangeEvent) onSelectionChangedEvent} using + * {@link AbstractSelectionModel#fireEvent(com.google.gwt.event.shared.GwtEvent)}. + * + * @param newObjects + * the new objects to refresh the {@link RefreshableSingleSelectionModel selectionmodel} + */ + @Override + public void refreshSelectionModel(Iterable newObjects) { + if (!dontcheckSelectionState) { // avoid endless recursion + try { + // avoid a new selection state check in setSelected + dontcheckSelectionState = true; + final T selected = getSelectedObject(); + if (selected != null) { + clear(); + for (final T it : newObjects) { + boolean isEqual = comp == null ? selected.equals(it) : comp.representSameEntity(selected, it); + if (isEqual) { + setSelected(it, true); + break; + } + } + } + SelectionChangeEvent.fire(this); + } finally { + dontcheckSelectionState = false; + } + } + } +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/BusyIndicatorResources.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/BusyIndicatorResources.java index 1add987b8c5..b3978b24713 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/BusyIndicatorResources.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/BusyIndicatorResources.java @@ -7,4 +7,7 @@ public interface BusyIndicatorResources extends ClientBundle { @Source("busy_indicator_circle.gif") ImageResource busyIndicatorCircle(); + + @Source("busy_indicator_circle_inverted.gif") + ImageResource busyIndicatorCircleInverted(); } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/BusyStateChangeListener.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/BusyStateChangeListener.java new file mode 100644 index 00000000000..f302065808d --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/BusyStateChangeListener.java @@ -0,0 +1,14 @@ +package com.sap.sse.gwt.client.controls.busyindicator; + + +/** + * Allows UI components to observe the busy state of an operation. + */ +public interface BusyStateChangeListener { + + /** + * @param busyState the new busy state + */ + void onBusyStateChange(boolean busyState); + +} diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/BusyStateProvider.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/BusyStateProvider.java new file mode 100644 index 00000000000..d886b844853 --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/BusyStateProvider.java @@ -0,0 +1,13 @@ +package com.sap.sse.gwt.client.controls.busyindicator; + + +public interface BusyStateProvider { + + void addBusyStateChangeListener(BusyStateChangeListener listener); + + void removeBusyStateChangeListener(BusyStateChangeListener listener); + + void setBusyState(boolean isBusy); + + boolean isBusy(); +} diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/SimpleBusyIndicator.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/SimpleBusyIndicator.java index 5a607e233d6..1c4254c971d 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/SimpleBusyIndicator.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/SimpleBusyIndicator.java @@ -1,5 +1,6 @@ package com.sap.sse.gwt.client.controls.busyindicator; +import com.google.gwt.dom.client.Style.Visibility; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.user.client.ui.Image; @@ -12,9 +13,13 @@ public class SimpleBusyIndicator extends BusyIndicator { * The busy indicator component is a circling GIF. */ public SimpleBusyIndicator() { - this(false, 1.0f); + this(false, 1.0f, RESOURCES.busyIndicatorCircle()); } - + + public SimpleBusyIndicator(ImageResource imageResource) { + this(false, 1.0f, imageResource); + } + /** * Creates a new SimpleBusyIndicator with a custom busy state.
    * The busy indicator component is a circling GIF. @@ -23,22 +28,29 @@ public class SimpleBusyIndicator extends BusyIndicator { * @param scale Scales the displayed image. 1.0 is 100%, 0.50 is 50%, ... */ public SimpleBusyIndicator(boolean busy, float scale) { - this(busy, scale, STYLE_NAME_PREFIX + "Simple", STYLE_NAME_PREFIX + "Circle"); + this(busy, scale, RESOURCES.busyIndicatorCircle()); } - - public SimpleBusyIndicator(boolean busy, float scale, String panelCssClass, String imageCssClass) { - this.setStyleName(panelCssClass); - ImageResource resource = RESOURCES.busyIndicatorCircle(); - busyIndicator = new Image(resource.getSafeUri()); - busyIndicator.setStyleName(imageCssClass); - busyIndicator.setPixelSize((int) (resource.getWidth() * scale), (int) (resource.getHeight() * scale)); + + public SimpleBusyIndicator(boolean busy, float scale, ImageResource imageResource) { + this.setStyleName(STYLE_NAME_PREFIX + "Simple"); + busyIndicator = new Image(imageResource.getSafeUri()); + busyIndicator.setStyleName(STYLE_NAME_PREFIX + "Circle"); + busyIndicator.setPixelSize((int) (imageResource.getWidth() * scale), (int) (imageResource.getHeight() * scale)); add(busyIndicator); setBusy(busy); } + public void setPanelStyleClass(String panelCssClass) { + this.setStyleName(panelCssClass); + } + + public void setImageStyleClass(String imageCssClass) { + busyIndicator.setStyleName(imageCssClass); + } + @Override public void setBusy(boolean busy) { super.setBusy(busy); - busyIndicator.setVisible(busy); + busyIndicator.getElement().getStyle().setVisibility(busy ? Visibility.VISIBLE : Visibility.HIDDEN); } } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/busy_indicator_circle_inverted.gif b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/busy_indicator_circle_inverted.gif new file mode 100644 index 00000000000..d31fd9ec8c0 Binary files /dev/null and b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/busyindicator/busy_indicator_circle_inverted.gif differ diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/SliderBar.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/SliderBar.java index 93ffbc08988..3f31188524e 100755 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/SliderBar.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/SliderBar.java @@ -1,1256 +1,1256 @@ -package com.sap.sse.gwt.client.controls.slider; - -/* - * Copyright 2008 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.dom.client.Style.Position; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.dom.client.Style.Visibility; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.logical.shared.HasValueChangeHandlers; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.resources.client.ClientBundle; -import com.google.gwt.resources.client.CssResource; -import com.google.gwt.resources.client.CssResource.NotStrict; -import com.google.gwt.resources.client.ImageResource; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.FocusPanel; -import com.google.gwt.user.client.ui.HasValue; -import com.google.gwt.user.client.ui.Image; -import com.google.gwt.user.client.ui.RequiresResize; -import com.sap.sse.common.Util; - -/** - * A widget that allows the user to select a value within a range of possible values using a sliding bar that responds - * to mouse events. - * - *

    Keyboard Events

    - *

    - * SliderBar listens for the following key events. Holding down a key will repeat the action until the key is released. - *

      - *
    • left arrow - shift left one step
    • - *
    • right arrow - shift right one step
    • - *
    • ctrl+left arrow - jump left 10% of the distance
    • - *
    • ctrl+right arrow - jump right 10% of the distance
    • - *
    • home - jump to min value
    • - *
    • end - jump to max value
    • - *
    • space - jump to middle value
    • - *
    - *

    - * - *

    CSS Style Rules

    - *
      - *
    • .gwt-SliderBar-shell { primary style }
    • - *
    • .gwt-SliderBar-shell-focused { primary style when focused }
    • - *
    • .gwt-SliderBar-shell gwt-SliderBar-line { the line that the knob moves along }
    • - *
    • .gwt-SliderBar-shell gwt-SliderBar-line-sliding { the line that the knob moves along when sliding }
    • - *
    • .gwt-SliderBar-shell .gwt-SliderBar-knob { the sliding knob }
    • - *
    • .gwt-SliderBar-shell .gwt-SliderBar-knob-sliding { the sliding knob when sliding }
    • - *
    • - * .gwt-SliderBar-shell .gwt-SliderBar-tick { the ticks along the line }
    • - *
    • .gwt-SliderBar-shell .gwt-SliderBar-ticklabel { the text labels along the line }
    • - *
    - */ -public class SliderBar extends FocusPanel implements RequiresResize, HasValue, HasValueChangeHandlers { - /** - * A formatter used to format the labels displayed in the widget. - */ - public static interface LabelFormatter { - /** - * Generate the text to display in each label based on the label's value. - * - * Override this method to change the text displayed within the SliderBar. - * - * @param slider - * the Slider bar - * @param value - * the value the label displays - * @return the text to display for the label - */ - String formatLabel(SliderBar slider, Double value, Double previousValue); - } - - /** - * A {@link ClientBundle} that provides images for {@link SliderBar}. - */ - public static interface SliderBarImages extends ClientBundle { - public static final SliderBarImages INSTANCE = GWT.create(SliderBarImages.class); - - /** - * An image used for the sliding knob. - * - * @return a prototype of this image - */ - @Source("slider_blue.png") - ImageResource slider(); - - /** - * An image used for the disabled sliding knob. - * - * @return a prototype of this image - */ - @Source("slider_blue.png") - ImageResource sliderDisabled(); - - /** - * An image used for the sliding knob while sliding. - * - * @return a prototype of this image - */ - @Source("slider_blue.png") - ImageResource sliderSliding(); - - @NotStrict - @Source("SliderBar.css") - CssResource sliderBarCss(); - } - - /** - * The timer used to continue to shift the knob as the user holds down one of the left/right arrow keys. Only IE - * auto-repeats, so we just keep catching the events. - */ - private class KeyTimer extends Timer { - /** - * A bit indicating that this is the first run. - */ - private boolean firstRun = true; - - /** - * The delay between shifts, which shortens as the user holds down the button. - */ - private int repeatDelay = 30; - - /** - * A bit indicating whether we are shifting to a higher or lower value. - */ - private boolean shiftRight = false; - - /** - * The number of steps to shift with each press. - */ - private int multiplier = 1; - - /** - * This method will be called when a timer fires. Override it to implement the timer's logic. - */ - @Override - public void run() { - // Highlight the knob on first run - if (firstRun) { - firstRun = false; - startSliding(true, false); - } - - // Slide the slider bar - if (shiftRight) { - setCurrentValue(curValue + multiplier * stepSize); - } else { - setCurrentValue(curValue - multiplier * stepSize); - } - - // Repeat this timer until cancelled by keyup event - schedule(repeatDelay); - } - - /** - * Schedules a timer to elapse in the future. - * - * @param delayMillis - * how long to wait before the timer elapses, in milliseconds - * @param shiftRight - * whether to shift up or not - * @param multiplier - * the number of steps to shift - */ - public void schedule(int delayMillis, boolean shiftRight, int multiplier) { - firstRun = true; - this.shiftRight = shiftRight; - this.multiplier = multiplier; - super.schedule(delayMillis); - } - } - - /** - * The current value. - */ - protected Double curValue; - - /** - * The knob that slides across the line. - */ - protected Image knobImage = new Image(); - - /** - * The timer used to continue to shift the knob if the user holds down a key. - */ - private KeyTimer keyTimer = new KeyTimer(); - - /** - * The elements used to display labels above the ticks. - */ - protected List tickLabelElements = new ArrayList(); - - /** - * The elements used to display the marker labels. - */ - protected List markerLabelElements = new ArrayList(); - - /** - * The formatter used to generate label text. - */ - protected LabelFormatter tickLabelFormatter; - - /** - * The line that the knob moves over. - */ - protected Element lineElement; - - /** - * The offset between the edge of the shell and the line. - */ - protected int lineLeftOffset = 0; - - /** - * The maximum slider value. - */ - protected Double maxValue; - - /** - * The minimum slider value. - */ - protected Double minValue; - - /** - * The number of labels to show. - */ - private int numTickLabels = 0; - - /** - * The number of tick marks to show. - */ - protected int numTicks = 0; - - /** - * A bit indicating whether or not we are currently sliding the slider bar due to keyboard events. - */ - private boolean slidingKeyboard = false; - - /** - * A bit indicating whether or not we are currently sliding the slider bar due to mouse events. - */ - private boolean slidingMouse = false; - - /** - * A bit indicating whether or not the slider is enabled - */ - protected boolean enabled = true; - - /** - * The images used with the sliding bar. - */ - private SliderBarImages images; - - /** - * The size of the increments between knob positions. - */ - protected double stepSize; - - /** - * The elements used to display tick marks, which are the vertical lines along the slider bar. - */ - protected List tickElements = new ArrayList(); - - /** - * The elements used to display additional markers on the slider bar. - */ - protected List markerElements = new ArrayList(); - - private List markers = new ArrayList(); - - private class Marker { - String name; - Double position; - - public Marker(String name, Double position) { - super(); - this.name = name; - this.position = position; - } - } - - /** - * Create a slider bar. - */ - public SliderBar() { - this(null, null, null); - } - - /** - * Create a slider bar. - * - * @param minValue - * the minimum value in the range - * @param maxValue - * the maximum value in the range - */ - public SliderBar(double minValue, double maxValue) { - this(minValue, maxValue, null); - } - - /** - * Create a slider bar. - * - * @param minValue - * the minimum value in the range - * @param maxValue - * the maximum value in the range - * @param tickLabelFormatter - * the label formatter - */ - public SliderBar(Double minValue, Double maxValue, LabelFormatter tickLabelFormatter) { - this(minValue, maxValue, tickLabelFormatter, SliderBarImages.INSTANCE); - } - - /** - * Create a slider bar. - * - * @param minValue - * the minimum value in the range - * @param maxValue - * the maximum value in the range - * @param tickLabelFormatter - * the label formatter - * @param images - * the images to use for the slider - */ - public SliderBar(Double minValue, Double maxValue, LabelFormatter tickLabelFormatter, SliderBarImages images) { - super(); - images.sliderBarCss().ensureInjected(); - this.minValue = minValue; - this.maxValue = maxValue; - this.images = images; - setLabelFormatter(tickLabelFormatter); - - // Create the outer shell - getElement().getStyle().setPosition(Position.RELATIVE); - setStyleName("gwt-SliderBar-shell"); - - // Create the line - lineElement = DOM.createDiv(); - DOM.appendChild(getElement(), lineElement); - lineElement.getStyle().setPosition(Position.ABSOLUTE); - lineElement.setPropertyString("className", "gwt-SliderBar-line"); - - // Create the knob - knobImage.setResource(images.slider()); - Element knobElement = knobImage.getElement(); - DOM.appendChild(getElement(), knobElement); - knobElement.getStyle().setPosition(Position.ABSOLUTE); - knobElement.setPropertyString("className", "gwt-SliderBar-knob"); - - sinkEvents(Event.MOUSEEVENTS | Event.KEYEVENTS | Event.FOCUSEVENTS); - - // workaround to render properly when parent Widget does not - // implement ProvidesResize since DOM doesn't provide element - // height and width until onModuleLoad() finishes. - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - @Override - public void execute() { - onResize(); - } - }); - } - - public boolean isMinMaxInitialized() { - return minValue != null && maxValue != null; - } - - public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) { - return addHandler(handler, ValueChangeEvent.getType()); - } - - /** - * Return the current value. - * - * @return the current value - */ - public Double getCurrentValue() { - return curValue; - } - - /** - * Return the label formatter. - * - * @return the label formatter - */ - public LabelFormatter getLabelFormatter() { - return tickLabelFormatter; - } - - /** - * Return the max value. - * - * @return the max value - */ - public Double getMaxValue() { - return maxValue; - } - - /** - * Return the minimum value. - * - * @return the minimum value - */ - public Double getMinValue() { - return minValue; - } - - /** - * Return the number of labels. - * - * @return the number of labels - */ - public int getNumLabels() { - return numTickLabels; - } - - /** - * Return the number of ticks. - * - * @return the number of ticks - */ - public int getNumTicks() { - return numTicks; - } - - /** - * Return the step size. - * - * @return the step size - */ - public double getStepSize() { - return stepSize; - } - - /** - * Return the total range between the minimum and maximum values. - * - * @return the total range - */ - public double getTotalRange() { - if (minValue == null || maxValue == null || minValue > maxValue) { - return 0; - } else { - return maxValue - minValue; - } - } - - public Double getValue() { - return curValue; - } - - /** - * @return Gets whether this widget is enabled - */ - public boolean isEnabled() { - return enabled; - } - - /** - * Listen for events that will move the knob. - * - * @param event - * the event that occurred - */ - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (enabled) { - switch (DOM.eventGetType(event)) { - // Unhighlight and cancel keyboard events - case Event.ONBLUR: - keyTimer.cancel(); - if (slidingMouse) { - DOM.releaseCapture(getElement()); - slidingMouse = false; - slideKnob(event); - stopSliding(true, true); - } else if (slidingKeyboard) { - slidingKeyboard = false; - stopSliding(true, true); - } - unhighlight(); - break; - - // Highlight on focus - case Event.ONFOCUS: - highlight(); - break; - - // Mousewheel events - case Event.ONMOUSEWHEEL: - int velocityY = event.getMouseWheelVelocityY(); - event.preventDefault(); - if (velocityY > 0) { - shiftRight(1); - } else { - shiftLeft(1); - } - break; - - // Shift left or right on key press - case Event.ONKEYDOWN: - if (!slidingKeyboard) { - int multiplier = 1; - if (event.getCtrlKey()) { - multiplier = (int) (getTotalRange() / stepSize / 10); - } - - switch (event.getKeyCode()) { - case KeyCodes.KEY_HOME: - event.preventDefault(); - setCurrentValue(minValue); - break; - case KeyCodes.KEY_END: - event.preventDefault(); - setCurrentValue(maxValue); - break; - case KeyCodes.KEY_LEFT: - event.preventDefault(); - slidingKeyboard = true; - startSliding(false, true); - shiftLeft(multiplier); - keyTimer.schedule(400, false, multiplier); - break; - case KeyCodes.KEY_RIGHT: - event.preventDefault(); - slidingKeyboard = true; - startSliding(false, true); - shiftRight(multiplier); - keyTimer.schedule(400, true, multiplier); - break; - case 32: - event.preventDefault(); - setCurrentValue(minValue + getTotalRange() / 2); - break; - } - } - break; - // Stop shifting on key up - case Event.ONKEYUP: - keyTimer.cancel(); - if (slidingKeyboard) { - slidingKeyboard = false; - stopSliding(true, true); - } - break; - - // Mouse Events - case Event.ONMOUSEDOWN: - setFocus(true); - slidingMouse = true; - DOM.setCapture(getElement()); - startSliding(true, true); - event.preventDefault(); - slideKnob(event); - break; - case Event.ONMOUSEUP: - if (slidingMouse) { - DOM.releaseCapture(getElement()); - slidingMouse = false; - slideKnob(event); - stopSliding(true, true); - } - break; - case Event.ONMOUSEMOVE: - if (slidingMouse) { - slideKnob(event); - } - break; - } - } - } - - /** - * This method is called when the dimensions of the parent element change. Subclasses should override this method as - * needed. - * - * @param width - * the new client width of the element - * @param height - * the new client height of the element - */ - public void onResize(int width, int height) { - // Center the line in the shell - int lineWidth = lineElement.getOffsetWidth(); - lineLeftOffset = (width / 2) - (lineWidth / 2); - lineElement.getStyle().setLeft(lineLeftOffset, Unit.PX); - - // Draw the other components - drawTickLabels(); - drawTicks(); - drawMarkers(); - drawMarkerLabels(); - drawKnob(); - } - - /** - * Redraw the progress bar when something changes the layout. - */ - public void redraw() { - if (isAttached()) { - int width = getElement().getClientWidth(); - int height = getElement().getClientHeight(); - onResize(width, height); - } - } - - /** - * Set the current value and fire the onValueChange event. - * - * @param curValue - * the current value - */ - public synchronized void setCurrentValue(Double curValue) { - setCurrentValue(curValue, true); - } - - /** - * Set the current value and optionally fire the onValueChange event. - * - * @param curValue - * the current value - * @param fireEvent - * fire the onValue change event if true - */ - public synchronized void setCurrentValue(Double curValue, boolean fireEvent) { - // Confine the value to the range - if (!isMinMaxInitialized() || curValue == null) { - return; - } - this.curValue = Math.max(minValue, Math.min(maxValue, curValue)); - double remainder = (this.curValue - minValue) % stepSize; - this.curValue -= remainder; - // Go to next step if more than halfway there - if ((remainder > (stepSize / 2)) && ((this.curValue + stepSize) <= maxValue)) { - this.curValue += stepSize; - } - // Redraw the knob - drawKnob(); - // Fire the ValueChangeEvent if the value actually changed - if (fireEvent && !curValue.equals(this.curValue)) { - ValueChangeEvent.fire(this, this.curValue); - } - } - - /** - * Sets whether this widget is enabled. - * - * @param enabled - * true to enable the widget, false to disable it - */ - public void setEnabled(boolean enabled) { - this.enabled = enabled; - if (enabled) { - knobImage.setResource(images.slider()); - lineElement.setPropertyString("className", "gwt-SliderBar-line"); - } else { - knobImage.setResource(images.sliderDisabled()); - lineElement.setPropertyString("className", "gwt-SliderBar-line gwt-SliderBar-line-disabled"); - } - redraw(); - } - - /** - * Set the label formatter. - * - * @param labelFormatter - * the label formatter - */ - public void setLabelFormatter(LabelFormatter labelFormatter) { - this.tickLabelFormatter = labelFormatter; - } - - /** - * Set the max value. - * - * @param maxValue - * the current value - */ - public void setMaxValue(Double maxValue, boolean fireEvent) { - if (!Util.equalsWithNull(maxValue, this.maxValue)) { - this.maxValue = maxValue; - onMinMaxValueChanged(fireEvent); - } - } - - /** - * Set the minimum value. - * - * @param minValue - * the current value - */ - public void setMinValue(Double minValue, boolean fireEvent) { - if (!Util.equalsWithNull(minValue, this.minValue)) { - this.minValue = minValue; - onMinMaxValueChanged(fireEvent); - } - } - - /** - * Set the minimum and maximum value - * - * @param minValue - * the current value for min - * @param maxValue - * the current valuefor max - */ - public void setMinAndMaxValue(Double minValue, Double maxValue, boolean fireEvent) { - boolean changed = false; - if (!Util.equalsWithNull(minValue, this.minValue)) { - this.minValue = minValue; - changed = true; - } - if (!Util.equalsWithNull(maxValue, this.maxValue)) { - this.maxValue = maxValue; - changed = true; - } - if (changed) { - onMinMaxValueChanged(fireEvent); - } - } - - /** - * Handle value changes of min and/or max value - */ - protected void onMinMaxValueChanged(boolean fireEvent) { - redraw(); - resetCurrentValue(fireEvent); - } - - /** - * Set the number of tick labels to show on the line. Tick labels indicate the value of the slider at that point. - * Use this method to enable tick labels. - * - * If you set the number of tick labels equal to the total range divided by the step size, you will get a properly - * aligned "jumping" effect where the knob jumps between tick labels. - * - * Note that the number of tick labels displayed will be one more than the number you specify, so specify 1 labels - * to show labels on either end of the line. In other words, numTickLabels is really the number of slots between the - * labels. - * - * setNumTickLabels(0) will disable the labels. - * - * @param numTickLabels - * the number of tick labels to show - */ - public void setNumTickLabels(int numTickLabels) { - this.numTickLabels = numTickLabels; - drawTickLabels(); - } - - /** - * Set the number of ticks to show on the line. A tick is a vertical line that represents a division of the overall - * line. Use this method to enable ticks. - * - * If you set the number of ticks equal to the total range divided by the step size, you will get a properly aligned - * "jumping" effect where the knob jumps between ticks. - * - * Note that the number of ticks displayed will be one more than the number you specify, so specify 1 tick to show - * ticks on either end of the line. In other words, numTicks is really the number of slots between the ticks. - * - * setNumTicks(0) will disable ticks. - * - * @param numTicks - * the number of ticks to show - */ - public void setNumTicks(int numTicks) { - this.numTicks = numTicks; - drawTicks(); - } - - /** - * Set the step size. - * - * @param stepSize - * the current value - */ - public void setStepSize(double stepSize, boolean fireEvent) { - this.stepSize = stepSize; - resetCurrentValue(fireEvent); - } - - public void setValue(Double value) { - setCurrentValue(value, false); - } - - public void setValue(Double value, boolean fireEvent) { - setCurrentValue(value, fireEvent); - } - - /** - * Shift to the left (smaller value). - * - * @param numSteps - * the number of steps to shift - */ - public void shiftLeft(int numSteps) { - setCurrentValue(getCurrentValue() - numSteps * stepSize); - } - - /** - * Shift to the right (greater value). - * - * @param numSteps - * the number of steps to shift - */ - public void shiftRight(int numSteps) { - setCurrentValue(getCurrentValue() + numSteps * stepSize); - } - - /** - * Format the label to display above the ticks - * - * Override this method in a subclass to customize the format. By default, this method returns the integer portion - * of the value. - * - * @param value - * the value at the label - * @return the text to put in the label - */ - protected String formatTickLabel(Double value, Double previousValue) { - if (tickLabelFormatter != null) { - return tickLabelFormatter.formatLabel(this, value, previousValue); - } else { - return (int) (10 * value) / 10.0 + ""; - } - } - - /** - * Get the percentage of the knob's position relative to the size of the line. The return value will be between 0.0 - * and 1.0. - * - * @return the current percent complete - */ - protected double getKnobPercent() { - // If we have no range - if (maxValue <= minValue) { - return 0; - } - - // Calculate the relative progress - double percent = (curValue - minValue) / (maxValue - minValue); - return Math.max(0.0, Math.min(1.0, percent)); - } - - /** - * This method is called immediately after a widget becomes attached to the browser's document. - */ - @Override - protected void onLoad() { - // Reset the position attribute of the parent element - getElement().getStyle().setPosition(Position.RELATIVE); - } - - /** - * Draw the knob where it is supposed to be relative to the line. - */ - protected void drawKnob() { - if (isAttached() && isMinMaxInitialized()) { - // Move the knob to the correct position - Element knobElement = knobImage.getElement(); - int lineWidth = lineElement.getOffsetWidth(); - int knobWidth = knobElement.getOffsetWidth(); - int knobLeftOffset = (int) (lineLeftOffset + (getKnobPercent() * lineWidth) - (knobWidth / 2)); - knobLeftOffset = Math.min(knobLeftOffset, lineLeftOffset + lineWidth - (knobWidth / 2) - 1); - knobElement.getStyle().setLeft(knobLeftOffset, Unit.PX); - } - } - - /** - * Draw the labels along the line. - */ - protected void drawTickLabels() { - if (isAttached() && isMinMaxInitialized()) { - // Draw the tick labels - int lineWidth = lineElement.getOffsetWidth(); - if (numTickLabels > 0) { - // Create the labels or make them visible - Double previousValue = null; - for (int i = 0; i <= numTickLabels; i++) { - Element label = null; - if (i < tickLabelElements.size()) { - label = tickLabelElements.get(i); - } else { // Create the new label - label = DOM.createDiv(); - label.getStyle().setPosition(Position.ABSOLUTE); - label.getStyle().setDisplay(Display.NONE); - if (enabled) { - label.setPropertyString("className", "gwt-SliderBar-ticklabel"); - } else { - label.setPropertyString("className", "gwt-SliderBar-ticklabel-disabled"); - } - DOM.appendChild(getElement(), label); - tickLabelElements.add(label); - } - - // Set the label text - double value = minValue + (getTotalRange() * i / numTickLabels); - label.getStyle().setVisibility(Visibility.HIDDEN); - label.getStyle().setProperty("display", ""); - label.setPropertyString("innerHTML", formatTickLabel(value, previousValue)); - - // Move to the left so the label width is not clipped by the shell - label.getStyle().setLeft(0, Unit.PX); - - // Position the label and make it visible - int labelWidth = label.getOffsetWidth(); - int labelLeftOffset = lineLeftOffset + (lineWidth * i / numTickLabels) - (labelWidth / 2); - labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth); - labelLeftOffset = Math.max(labelLeftOffset, lineLeftOffset); - label.getStyle().setLeft(labelLeftOffset, Unit.PX); - label.getStyle().setVisibility(Visibility.VISIBLE); - - previousValue = value; - } - - // Hide unused labels - for (int i = (numTickLabels + 1); i < tickLabelElements.size(); i++) { - tickLabelElements.get(i).getStyle().setDisplay(Display.NONE); - } - } else { // Hide all labels - for (Element elem : tickLabelElements) { - elem.getStyle().setDisplay(Display.NONE); - } - } - } - } - - /** - * Draw the tick along the line. - */ - protected void drawTicks() { - if (isAttached() && isMinMaxInitialized()) { - // Draw the ticks - int lineWidth = lineElement.getOffsetWidth(); - if (numTicks > 0) { - // Create the ticks or make them visible - for (int i = 0; i <= numTicks; i++) { - Element tick = null; - if (i < tickElements.size()) { - tick = tickElements.get(i); - } else { // Create the new tick - tick = DOM.createDiv(); - tick.getStyle().setPosition(Position.ABSOLUTE); - tick.getStyle().setDisplay(Display.NONE); - DOM.appendChild(getElement(), tick); - tickElements.add(tick); - } - if (enabled) { - tick.setPropertyString("className", "gwt-SliderBar-tick"); - } else { - tick.setPropertyString("className", "gwt-SliderBar-tick gwt-SliderBar-tick-disabled"); - } - // Position the tick and make it visible - tick.getStyle().setVisibility(Visibility.HIDDEN); - tick.getStyle().setProperty("display", ""); - int tickWidth = tick.getOffsetWidth(); - int tickLeftOffset = lineLeftOffset + (lineWidth * i / numTicks) - (tickWidth / 2); - tickLeftOffset = Math.min(tickLeftOffset, lineLeftOffset + lineWidth - tickWidth); - tick.getStyle().setLeft(tickLeftOffset, Unit.PX); - tick.getStyle().setVisibility(Visibility.VISIBLE); - } - - // Hide unused ticks - for (int i = (numTicks + 1); i < tickElements.size(); i++) { - tickElements.get(i).getStyle().setDisplay(Display.NONE); - } - } else { // Hide all ticks - for (Element elem : tickElements) { - elem.getStyle().setDisplay(Display.NONE); - } - } - } - } - - /** - * Draw the markers. - */ - private void drawMarkers() { - if (isAttached() && isMinMaxInitialized()) { - int numMarkers = markers.size(); - // Draw the markers - int lineWidth = lineElement.getOffsetWidth(); - if (numMarkers > 0) { - // Create the markers or make them visible - Element lastMarkerElement = null; - int lastMarkerOffsetLeft = 0; - for (int i = 0; i < numMarkers; i++) { - Marker marker = markers.get(i); - Element markerElem = null; - if (i < markerElements.size()) { - markerElem = markerElements.get(i); - } else { // Create the new markes - markerElem = DOM.createDiv(); - markerElem.getStyle().setPosition(Position.ABSOLUTE); - markerElem.getStyle().setDisplay(Display.NONE); - DOM.appendChild(getElement(), markerElem); - markerElements.add(markerElem); - } - if (enabled) { - markerElem.setPropertyString("className", "gwt-SliderBar-mark"); - } else { - markerElem.setPropertyString("className", "gwt-SliderBar-mark gwt-SliderBar-mark-disabled"); - } - // Position the marker and make it visible - markerElem.getStyle().setVisibility(Visibility.HIDDEN); - markerElem.getStyle().setProperty("display", ""); - double markerLinePosition = (marker.position - minValue) * lineWidth / getTotalRange(); - int markerWidth = markerElem.getOffsetWidth(); - int markerLeftOffset = lineLeftOffset + (int) markerLinePosition - (markerWidth / 2); - markerLeftOffset = Math.min(markerLeftOffset, lineLeftOffset + lineWidth - markerWidth); - markerElem.getStyle().setLeft(markerLeftOffset, Unit.PX); - markerElem.getStyle().setVisibility(Visibility.VISIBLE); - // change class of last marker if it is too close to the one just created - if (lastMarkerOffsetLeft > 0 && (markerLeftOffset-lastMarkerOffsetLeft) <= markerWidth) { - if (lastMarkerElement != null) { - lastMarkerElement.setPropertyString("className", "gwt-SliderBar-mark-tooClose"); - lastMarkerElement.getStyle().setLeft(markerLeftOffset-10, Unit.PX); - } - } - lastMarkerElement = markerElem; - lastMarkerOffsetLeft = markerLeftOffset; - } - - // Hide unused markers - for (int i = numMarkers; i < markerElements.size(); i++) { - markerElements.get(i).getStyle().setDisplay(Display.NONE); - } - } else { // Hide all markers - for (Element elem : markerElements) { - elem.getStyle().setDisplay(Display.NONE); - } - } - } - } - - /** - * Draw the marker labels. - */ - private void drawMarkerLabels() { - if (isAttached() && isMinMaxInitialized()) { - int numMarkers = markers.size(); - // Draw the marker labels - int lineWidth = lineElement.getOffsetWidth(); - if (numMarkers > 0) { - Element lastMarkerLabel = null; - Marker lastMarker = null; - int lastMarkerLabelOffsetLeft = 0; - // Create the labels or make them visible - for (int i = 0; i < numMarkers; i++) { - Marker marker = markers.get(i); - Element label = null; - if (i < markerLabelElements.size()) { - label = markerLabelElements.get(i); - } else { // Create the new label - label = DOM.createDiv(); - label.getStyle().setPosition(Position.ABSOLUTE); - label.getStyle().setDisplay(Display.NONE); - if (enabled) { - label.setPropertyString("className", "gwt-SliderBar-markerlabel"); - } else { - label.setPropertyString("className", "gwt-SliderBar-markerlabel-disabled"); - } - DOM.appendChild(getElement(), label); - markerLabelElements.add(label); - } - - // Set the marker label text - label.getStyle().setVisibility(Visibility.HIDDEN); - label.getStyle().setProperty("display", ""); - label.setPropertyString("innerHTML", marker.name); - - // Move to the left so the label width is not clipped by the shell - label.getStyle().setLeft(0, Unit.PX); - - // Position the label and make it visible - double markerLinePosition = (marker.position - minValue) * lineWidth / getTotalRange(); - int labelWidth = label.getOffsetWidth(); - int labelLeftOffset = lineLeftOffset + (int) markerLinePosition - (labelWidth / 2); - labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth); - - label.getStyle().setLeft(labelLeftOffset, Unit.PX); - label.getStyle().setVisibility(Visibility.VISIBLE); - - // hide last marker label if it is too close to the one just created - if (lastMarkerLabelOffsetLeft > 0 && (labelLeftOffset-lastMarkerLabelOffsetLeft) <= 20) { - if (lastMarkerLabel != null && lastMarker != null) { - lastMarkerLabel.setPropertyString("className", "gwt-SliderBar-markerlabel-tooClose"); - lastMarkerLabel.setPropertyString("innerHTML", lastMarker.name); // no space for more - int currentMarkerOffsetLeft = labelLeftOffset; - try { - currentMarkerOffsetLeft = Integer.parseInt(markerElements.get(i).getStyle().getLeft().replace("px", "")); - } catch (NumberFormatException ex) { - // ignore, can't do anything - } - lastMarkerLabel.getStyle().setLeft(currentMarkerOffsetLeft-8, Unit.PX); - } - } - lastMarker = marker; - lastMarkerLabel = label; - lastMarkerLabelOffsetLeft = labelLeftOffset; - } - - // Hide unused labels - for (int i = (numMarkers + 1); i < markerLabelElements.size(); i++) { - markerLabelElements.get(i).getStyle().setDisplay(Display.NONE); - } - } else { // Hide all labels - for (Element elem : markerLabelElements) { - elem.getStyle().setDisplay(Display.NONE); - } - } - } - } - - /** - * Highlight this widget. - */ - private void highlight() { - String styleName = getStylePrimaryName(); - getElement().setPropertyString("className", styleName + " " + styleName + "-focused"); - } - - /** - * Reset the progress to constrain the progress to the current range and redraw the knob as needed. - */ - private synchronized void resetCurrentValue(boolean fireEvent) { - setCurrentValue(getCurrentValue(), fireEvent); - } - - /** - * Slide the knob to a new location. - * - * @param event - * the mouse event - */ - private void slideKnob(Event event) { - // Adding scrollLeft to adjust the position, if the user had scrolled with the lower scroll bar - int x = event.getClientX() + Window.getScrollLeft(); - if (x > 0) { - int lineWidth = lineElement.getOffsetWidth(); - int lineLeft = lineElement.getAbsoluteLeft(); - double percent = (double) (x - lineLeft) / lineWidth * 1.0; - setCurrentValue(getTotalRange() * percent + minValue, true); - } - } - - /** - * Start sliding the knob. - * - * @param highlight - * true to change the style - * @param fireEvent - * true to fire the event - */ - private void startSliding(boolean highlight, boolean fireEvent) { - if (highlight) { - lineElement.setPropertyString("className", "gwt-SliderBar-line gwt-SliderBar-line-sliding"); - knobImage.getElement().setPropertyString("className", "gwt-SliderBar-knob gwt-SliderBar-knob-sliding"); - knobImage.setResource(images.sliderSliding()); - } - } - - /** - * Stop sliding the knob. - * - * @param unhighlight - * true to change the style - * @param fireEvent - * true to fire the event - */ - private void stopSliding(boolean unhighlight, boolean fireEvent) { - if (unhighlight) { - lineElement.setPropertyString("className", "gwt-SliderBar-line"); - knobImage.getElement().setPropertyString("className", "gwt-SliderBar-knob"); - knobImage.setResource(images.slider()); - } - } - - /** - * Unhighlight this widget. - */ - private void unhighlight() { - getElement().setPropertyString("className", getStylePrimaryName()); - } - - @Override - public void onResize() { - redraw(); - } - - public void clearMarkers() { - markers.clear(); - } - - public boolean addMarker(String markerName, Double markerPosition) { - return markers.add(new Marker(markerName, markerPosition)); - } - - public boolean setMarker(String markerName, Double markerPosition) { - Marker marker = findMarkerByName(markerName); - if (marker != null) { - marker.position = markerPosition; - return true; - } - return false; - } - - public boolean removeMarker(String markerName) { - Marker marker = findMarkerByName(markerName); - if (marker != null) { - return markers.remove(marker); - } - return false; - } - - public Iterator getMarkers() { - return markers.iterator(); - } - - private Marker findMarkerByName(String markerName) { - for (Marker marker : markers) { - if (marker.name.equals(markerName)) { - return marker; - } - } - return null; - } -} +package com.sap.sse.gwt.client.controls.slider; + +/* + * Copyright 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.dom.client.Style.Visibility; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.logical.shared.HasValueChangeHandlers; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.CssResource.NotStrict; +import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.FocusPanel; +import com.google.gwt.user.client.ui.HasValue; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.RequiresResize; +import com.sap.sse.common.Util; + +/** + * A widget that allows the user to select a value within a range of possible values using a sliding bar that responds + * to mouse events. + * + *

    Keyboard Events

    + *

    + * SliderBar listens for the following key events. Holding down a key will repeat the action until the key is released. + *

      + *
    • left arrow - shift left one step
    • + *
    • right arrow - shift right one step
    • + *
    • ctrl+left arrow - jump left 10% of the distance
    • + *
    • ctrl+right arrow - jump right 10% of the distance
    • + *
    • home - jump to min value
    • + *
    • end - jump to max value
    • + *
    • space - jump to middle value
    • + *
    + *

    + * + *

    CSS Style Rules

    + *
      + *
    • .gwt-SliderBar-shell { primary style }
    • + *
    • .gwt-SliderBar-shell-focused { primary style when focused }
    • + *
    • .gwt-SliderBar-shell gwt-SliderBar-line { the line that the knob moves along }
    • + *
    • .gwt-SliderBar-shell gwt-SliderBar-line-sliding { the line that the knob moves along when sliding }
    • + *
    • .gwt-SliderBar-shell .gwt-SliderBar-knob { the sliding knob }
    • + *
    • .gwt-SliderBar-shell .gwt-SliderBar-knob-sliding { the sliding knob when sliding }
    • + *
    • + * .gwt-SliderBar-shell .gwt-SliderBar-tick { the ticks along the line }
    • + *
    • .gwt-SliderBar-shell .gwt-SliderBar-ticklabel { the text labels along the line }
    • + *
    + */ +public class SliderBar extends FocusPanel implements RequiresResize, HasValue, HasValueChangeHandlers { + /** + * A formatter used to format the labels displayed in the widget. + */ + public static interface LabelFormatter { + /** + * Generate the text to display in each label based on the label's value. + * + * Override this method to change the text displayed within the SliderBar. + * + * @param slider + * the Slider bar + * @param value + * the value the label displays + * @return the text to display for the label + */ + String formatLabel(SliderBar slider, Double value, Double previousValue); + } + + /** + * A {@link ClientBundle} that provides images for {@link SliderBar}. + */ + public static interface SliderBarImages extends ClientBundle { + public static final SliderBarImages INSTANCE = GWT.create(SliderBarImages.class); + + /** + * An image used for the sliding knob. + * + * @return a prototype of this image + */ + @Source("slider_blue.png") + ImageResource slider(); + + /** + * An image used for the disabled sliding knob. + * + * @return a prototype of this image + */ + @Source("slider_blue.png") + ImageResource sliderDisabled(); + + /** + * An image used for the sliding knob while sliding. + * + * @return a prototype of this image + */ + @Source("slider_blue.png") + ImageResource sliderSliding(); + + @NotStrict + @Source("SliderBar.css") + CssResource sliderBarCss(); + } + + /** + * The timer used to continue to shift the knob as the user holds down one of the left/right arrow keys. Only IE + * auto-repeats, so we just keep catching the events. + */ + private class KeyTimer extends Timer { + /** + * A bit indicating that this is the first run. + */ + private boolean firstRun = true; + + /** + * The delay between shifts, which shortens as the user holds down the button. + */ + private int repeatDelay = 30; + + /** + * A bit indicating whether we are shifting to a higher or lower value. + */ + private boolean shiftRight = false; + + /** + * The number of steps to shift with each press. + */ + private int multiplier = 1; + + /** + * This method will be called when a timer fires. Override it to implement the timer's logic. + */ + @Override + public void run() { + // Highlight the knob on first run + if (firstRun) { + firstRun = false; + startSliding(true, false); + } + + // Slide the slider bar + if (shiftRight) { + setCurrentValue(curValue + multiplier * stepSize); + } else { + setCurrentValue(curValue - multiplier * stepSize); + } + + // Repeat this timer until cancelled by keyup event + schedule(repeatDelay); + } + + /** + * Schedules a timer to elapse in the future. + * + * @param delayMillis + * how long to wait before the timer elapses, in milliseconds + * @param shiftRight + * whether to shift up or not + * @param multiplier + * the number of steps to shift + */ + public void schedule(int delayMillis, boolean shiftRight, int multiplier) { + firstRun = true; + this.shiftRight = shiftRight; + this.multiplier = multiplier; + super.schedule(delayMillis); + } + } + + /** + * The current value. + */ + protected Double curValue; + + /** + * The knob that slides across the line. + */ + protected Image knobImage = new Image(); + + /** + * The timer used to continue to shift the knob if the user holds down a key. + */ + private KeyTimer keyTimer = new KeyTimer(); + + /** + * The elements used to display labels above the ticks. + */ + protected List tickLabelElements = new ArrayList(); + + /** + * The elements used to display the marker labels. + */ + protected List markerLabelElements = new ArrayList(); + + /** + * The formatter used to generate label text. + */ + protected LabelFormatter tickLabelFormatter; + + /** + * The line that the knob moves over. + */ + protected Element lineElement; + + /** + * The offset between the edge of the shell and the line. + */ + protected int lineLeftOffset = 0; + + /** + * The maximum slider value. + */ + protected Double maxValue; + + /** + * The minimum slider value. + */ + protected Double minValue; + + /** + * The number of labels to show. + */ + private int numTickLabels = 0; + + /** + * The number of tick marks to show. + */ + protected int numTicks = 0; + + /** + * A bit indicating whether or not we are currently sliding the slider bar due to keyboard events. + */ + private boolean slidingKeyboard = false; + + /** + * A bit indicating whether or not we are currently sliding the slider bar due to mouse events. + */ + private boolean slidingMouse = false; + + /** + * A bit indicating whether or not the slider is enabled + */ + protected boolean enabled = true; + + /** + * The images used with the sliding bar. + */ + private SliderBarImages images; + + /** + * The size of the increments between knob positions. + */ + protected double stepSize; + + /** + * The elements used to display tick marks, which are the vertical lines along the slider bar. + */ + protected List tickElements = new ArrayList(); + + /** + * The elements used to display additional markers on the slider bar. + */ + protected List markerElements = new ArrayList(); + + private List markers = new ArrayList(); + + private class Marker { + String name; + Double position; + + public Marker(String name, Double position) { + super(); + this.name = name; + this.position = position; + } + } + + /** + * Create a slider bar. + */ + public SliderBar() { + this(null, null, null); + } + + /** + * Create a slider bar. + * + * @param minValue + * the minimum value in the range + * @param maxValue + * the maximum value in the range + */ + public SliderBar(double minValue, double maxValue) { + this(minValue, maxValue, null); + } + + /** + * Create a slider bar. + * + * @param minValue + * the minimum value in the range + * @param maxValue + * the maximum value in the range + * @param tickLabelFormatter + * the label formatter + */ + public SliderBar(Double minValue, Double maxValue, LabelFormatter tickLabelFormatter) { + this(minValue, maxValue, tickLabelFormatter, SliderBarImages.INSTANCE); + } + + /** + * Create a slider bar. + * + * @param minValue + * the minimum value in the range + * @param maxValue + * the maximum value in the range + * @param tickLabelFormatter + * the label formatter + * @param images + * the images to use for the slider + */ + public SliderBar(Double minValue, Double maxValue, LabelFormatter tickLabelFormatter, SliderBarImages images) { + super(); + images.sliderBarCss().ensureInjected(); + this.minValue = minValue; + this.maxValue = maxValue; + this.images = images; + setLabelFormatter(tickLabelFormatter); + + // Create the outer shell + getElement().getStyle().setPosition(Position.RELATIVE); + setStyleName("gwt-SliderBar-shell"); + + // Create the line + lineElement = DOM.createDiv(); + DOM.appendChild(getElement(), lineElement); + lineElement.getStyle().setPosition(Position.ABSOLUTE); + lineElement.setPropertyString("className", "gwt-SliderBar-line"); + + // Create the knob + knobImage.setResource(images.slider()); + Element knobElement = knobImage.getElement(); + DOM.appendChild(getElement(), knobElement); + knobElement.getStyle().setPosition(Position.ABSOLUTE); + knobElement.setPropertyString("className", "gwt-SliderBar-knob"); + + sinkEvents(Event.MOUSEEVENTS | Event.KEYEVENTS | Event.FOCUSEVENTS); + + // workaround to render properly when parent Widget does not + // implement ProvidesResize since DOM doesn't provide element + // height and width until onModuleLoad() finishes. + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + onResize(); + } + }); + } + + public boolean isMinMaxInitialized() { + return minValue != null && maxValue != null; + } + + public HandlerRegistration addValueChangeHandler(ValueChangeHandler handler) { + return addHandler(handler, ValueChangeEvent.getType()); + } + + /** + * Return the current value. + * + * @return the current value + */ + public Double getCurrentValue() { + return curValue; + } + + /** + * Return the label formatter. + * + * @return the label formatter + */ + public LabelFormatter getLabelFormatter() { + return tickLabelFormatter; + } + + /** + * Return the max value. + * + * @return the max value + */ + public Double getMaxValue() { + return maxValue; + } + + /** + * Return the minimum value. + * + * @return the minimum value + */ + public Double getMinValue() { + return minValue; + } + + /** + * Return the number of labels. + * + * @return the number of labels + */ + public int getNumLabels() { + return numTickLabels; + } + + /** + * Return the number of ticks. + * + * @return the number of ticks + */ + public int getNumTicks() { + return numTicks; + } + + /** + * Return the step size. + * + * @return the step size + */ + public double getStepSize() { + return stepSize; + } + + /** + * Return the total range between the minimum and maximum values. + * + * @return the total range + */ + public double getTotalRange() { + if (minValue == null || maxValue == null || minValue > maxValue) { + return 0; + } else { + return maxValue - minValue; + } + } + + public Double getValue() { + return curValue; + } + + /** + * @return Gets whether this widget is enabled + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Listen for events that will move the knob. + * + * @param event + * the event that occurred + */ + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (enabled) { + switch (DOM.eventGetType(event)) { + // Unhighlight and cancel keyboard events + case Event.ONBLUR: + keyTimer.cancel(); + if (slidingMouse) { + DOM.releaseCapture(getElement()); + slidingMouse = false; + slideKnob(event); + stopSliding(true, true); + } else if (slidingKeyboard) { + slidingKeyboard = false; + stopSliding(true, true); + } + unhighlight(); + break; + + // Highlight on focus + case Event.ONFOCUS: + highlight(); + break; + + // Mousewheel events + case Event.ONMOUSEWHEEL: + int velocityY = event.getMouseWheelVelocityY(); + event.preventDefault(); + if (velocityY > 0) { + shiftRight(1); + } else { + shiftLeft(1); + } + break; + + // Shift left or right on key press + case Event.ONKEYDOWN: + if (!slidingKeyboard) { + int multiplier = 1; + if (event.getCtrlKey()) { + multiplier = (int) (getTotalRange() / stepSize / 10); + } + + switch (event.getKeyCode()) { + case KeyCodes.KEY_HOME: + event.preventDefault(); + setCurrentValue(minValue); + break; + case KeyCodes.KEY_END: + event.preventDefault(); + setCurrentValue(maxValue); + break; + case KeyCodes.KEY_LEFT: + event.preventDefault(); + slidingKeyboard = true; + startSliding(false, true); + shiftLeft(multiplier); + keyTimer.schedule(400, false, multiplier); + break; + case KeyCodes.KEY_RIGHT: + event.preventDefault(); + slidingKeyboard = true; + startSliding(false, true); + shiftRight(multiplier); + keyTimer.schedule(400, true, multiplier); + break; + case 32: + event.preventDefault(); + setCurrentValue(minValue + getTotalRange() / 2); + break; + } + } + break; + // Stop shifting on key up + case Event.ONKEYUP: + keyTimer.cancel(); + if (slidingKeyboard) { + slidingKeyboard = false; + stopSliding(true, true); + } + break; + + // Mouse Events + case Event.ONMOUSEDOWN: + setFocus(true); + slidingMouse = true; + DOM.setCapture(getElement()); + startSliding(true, true); + event.preventDefault(); + slideKnob(event); + break; + case Event.ONMOUSEUP: + if (slidingMouse) { + DOM.releaseCapture(getElement()); + slidingMouse = false; + slideKnob(event); + stopSliding(true, true); + } + break; + case Event.ONMOUSEMOVE: + if (slidingMouse) { + slideKnob(event); + } + break; + } + } + } + + /** + * This method is called when the dimensions of the parent element change. Subclasses should override this method as + * needed. + * + * @param width + * the new client width of the element + * @param height + * the new client height of the element + */ + public void onResize(int width, int height) { + // Center the line in the shell + int lineWidth = lineElement.getOffsetWidth(); + lineLeftOffset = (width / 2) - (lineWidth / 2); + lineElement.getStyle().setLeft(lineLeftOffset, Unit.PX); + + // Draw the other components + drawTickLabels(); + drawTicks(); + drawMarkers(); + drawMarkerLabels(); + drawKnob(); + } + + /** + * Redraw the progress bar when something changes the layout. + */ + public void redraw() { + if (isAttached()) { + int width = getElement().getClientWidth(); + int height = getElement().getClientHeight(); + onResize(width, height); + } + } + + /** + * Set the current value and fire the onValueChange event. + * + * @param curValue + * the current value + */ + public synchronized void setCurrentValue(Double curValue) { + setCurrentValue(curValue, true); + } + + /** + * Set the current value and optionally fire the onValueChange event. + * + * @param curValue + * the current value + * @param fireEvent + * fire the onValue change event if true + */ + public synchronized void setCurrentValue(Double curValue, boolean fireEvent) { + // Confine the value to the range + if (!isMinMaxInitialized() || curValue == null) { + return; + } + this.curValue = Math.max(minValue, Math.min(maxValue, curValue)); + double remainder = (this.curValue - minValue) % stepSize; + this.curValue -= remainder; + // Go to next step if more than halfway there + if ((remainder > (stepSize / 2)) && ((this.curValue + stepSize) <= maxValue)) { + this.curValue += stepSize; + } + // Redraw the knob + drawKnob(); + // Fire the ValueChangeEvent if the value actually changed + if (fireEvent && !curValue.equals(this.curValue)) { + ValueChangeEvent.fire(this, this.curValue); + } + } + + /** + * Sets whether this widget is enabled. + * + * @param enabled + * true to enable the widget, false to disable it + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + if (enabled) { + knobImage.setResource(images.slider()); + lineElement.setPropertyString("className", "gwt-SliderBar-line"); + } else { + knobImage.setResource(images.sliderDisabled()); + lineElement.setPropertyString("className", "gwt-SliderBar-line gwt-SliderBar-line-disabled"); + } + redraw(); + } + + /** + * Set the label formatter. + * + * @param labelFormatter + * the label formatter + */ + public void setLabelFormatter(LabelFormatter labelFormatter) { + this.tickLabelFormatter = labelFormatter; + } + + /** + * Set the max value. + * + * @param maxValue + * the current value + */ + public void setMaxValue(Double maxValue, boolean fireEvent) { + if (!Util.equalsWithNull(maxValue, this.maxValue)) { + this.maxValue = maxValue; + onMinMaxValueChanged(fireEvent); + } + } + + /** + * Set the minimum value. + * + * @param minValue + * the current value + */ + public void setMinValue(Double minValue, boolean fireEvent) { + if (!Util.equalsWithNull(minValue, this.minValue)) { + this.minValue = minValue; + onMinMaxValueChanged(fireEvent); + } + } + + /** + * Set the minimum and maximum value + * + * @param minValue + * the current value for min + * @param maxValue + * the current valuefor max + */ + public void setMinAndMaxValue(Double minValue, Double maxValue, boolean fireEvent) { + boolean changed = false; + if (!Util.equalsWithNull(minValue, this.minValue)) { + this.minValue = minValue; + changed = true; + } + if (!Util.equalsWithNull(maxValue, this.maxValue)) { + this.maxValue = maxValue; + changed = true; + } + if (changed) { + onMinMaxValueChanged(fireEvent); + } + } + + /** + * Handle value changes of min and/or max value + */ + protected void onMinMaxValueChanged(boolean fireEvent) { + redraw(); + resetCurrentValue(fireEvent); + } + + /** + * Set the number of tick labels to show on the line. Tick labels indicate the value of the slider at that point. + * Use this method to enable tick labels. + * + * If you set the number of tick labels equal to the total range divided by the step size, you will get a properly + * aligned "jumping" effect where the knob jumps between tick labels. + * + * Note that the number of tick labels displayed will be one more than the number you specify, so specify 1 labels + * to show labels on either end of the line. In other words, numTickLabels is really the number of slots between the + * labels. + * + * setNumTickLabels(0) will disable the labels. + * + * @param numTickLabels + * the number of tick labels to show + */ + public void setNumTickLabels(int numTickLabels) { + this.numTickLabels = numTickLabels; + drawTickLabels(); + } + + /** + * Set the number of ticks to show on the line. A tick is a vertical line that represents a division of the overall + * line. Use this method to enable ticks. + * + * If you set the number of ticks equal to the total range divided by the step size, you will get a properly aligned + * "jumping" effect where the knob jumps between ticks. + * + * Note that the number of ticks displayed will be one more than the number you specify, so specify 1 tick to show + * ticks on either end of the line. In other words, numTicks is really the number of slots between the ticks. + * + * setNumTicks(0) will disable ticks. + * + * @param numTicks + * the number of ticks to show + */ + public void setNumTicks(int numTicks) { + this.numTicks = numTicks; + drawTicks(); + } + + /** + * Set the step size. + * + * @param stepSize + * the current value + */ + public void setStepSize(double stepSize, boolean fireEvent) { + this.stepSize = stepSize; + resetCurrentValue(fireEvent); + } + + public void setValue(Double value) { + setCurrentValue(value, false); + } + + public void setValue(Double value, boolean fireEvent) { + setCurrentValue(value, fireEvent); + } + + /** + * Shift to the left (smaller value). + * + * @param numSteps + * the number of steps to shift + */ + public void shiftLeft(int numSteps) { + setCurrentValue(getCurrentValue() - numSteps * stepSize); + } + + /** + * Shift to the right (greater value). + * + * @param numSteps + * the number of steps to shift + */ + public void shiftRight(int numSteps) { + setCurrentValue(getCurrentValue() + numSteps * stepSize); + } + + /** + * Format the label to display above the ticks + * + * Override this method in a subclass to customize the format. By default, this method returns the integer portion + * of the value. + * + * @param value + * the value at the label + * @return the text to put in the label + */ + protected String formatTickLabel(Double value, Double previousValue) { + if (tickLabelFormatter != null) { + return tickLabelFormatter.formatLabel(this, value, previousValue); + } else { + return (int) (10 * value) / 10.0 + ""; + } + } + + /** + * Get the percentage of the knob's position relative to the size of the line. The return value will be between 0.0 + * and 1.0. + * + * @return the current percent complete + */ + protected double getKnobPercent() { + // If we have no range + if (maxValue <= minValue) { + return 0; + } + + // Calculate the relative progress + double percent = (curValue - minValue) / (maxValue - minValue); + return Math.max(0.0, Math.min(1.0, percent)); + } + + /** + * This method is called immediately after a widget becomes attached to the browser's document. + */ + @Override + protected void onLoad() { + // Reset the position attribute of the parent element + getElement().getStyle().setPosition(Position.RELATIVE); + } + + /** + * Draw the knob where it is supposed to be relative to the line. + */ + protected void drawKnob() { + if (isAttached() && isMinMaxInitialized()) { + // Move the knob to the correct position + Element knobElement = knobImage.getElement(); + int lineWidth = lineElement.getOffsetWidth(); + int knobWidth = knobElement.getOffsetWidth(); + int knobLeftOffset = (int) (lineLeftOffset + (getKnobPercent() * lineWidth) - (knobWidth / 2)); + knobLeftOffset = Math.min(knobLeftOffset, lineLeftOffset + lineWidth - (knobWidth / 2) - 1); + knobElement.getStyle().setLeft(knobLeftOffset, Unit.PX); + } + } + + /** + * Draw the labels along the line. + */ + protected void drawTickLabels() { + if (isAttached() && isMinMaxInitialized()) { + // Draw the tick labels + int lineWidth = lineElement.getOffsetWidth(); + if (numTickLabels > 0) { + // Create the labels or make them visible + Double previousValue = null; + for (int i = 0; i <= numTickLabels; i++) { + Element label = null; + if (i < tickLabelElements.size()) { + label = tickLabelElements.get(i); + } else { // Create the new label + label = DOM.createDiv(); + label.getStyle().setPosition(Position.ABSOLUTE); + label.getStyle().setDisplay(Display.NONE); + if (enabled) { + label.setPropertyString("className", "gwt-SliderBar-ticklabel"); + } else { + label.setPropertyString("className", "gwt-SliderBar-ticklabel-disabled"); + } + DOM.appendChild(getElement(), label); + tickLabelElements.add(label); + } + + // Set the label text + double value = minValue + (getTotalRange() * i / numTickLabels); + label.getStyle().setVisibility(Visibility.HIDDEN); + label.getStyle().setProperty("display", ""); + label.setPropertyString("innerHTML", formatTickLabel(value, previousValue)); + + // Move to the left so the label width is not clipped by the shell + label.getStyle().setLeft(0, Unit.PX); + + // Position the label and make it visible + int labelWidth = label.getOffsetWidth(); + int labelLeftOffset = lineLeftOffset + (lineWidth * i / numTickLabels) - (labelWidth / 2); + labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth); + labelLeftOffset = Math.max(labelLeftOffset, lineLeftOffset); + label.getStyle().setLeft(labelLeftOffset, Unit.PX); + label.getStyle().setVisibility(Visibility.VISIBLE); + + previousValue = value; + } + + // Hide unused labels + for (int i = (numTickLabels + 1); i < tickLabelElements.size(); i++) { + tickLabelElements.get(i).getStyle().setDisplay(Display.NONE); + } + } else { // Hide all labels + for (Element elem : tickLabelElements) { + elem.getStyle().setDisplay(Display.NONE); + } + } + } + } + + /** + * Draw the tick along the line. + */ + protected void drawTicks() { + if (isAttached() && isMinMaxInitialized()) { + // Draw the ticks + int lineWidth = lineElement.getOffsetWidth(); + if (numTicks > 0) { + // Create the ticks or make them visible + for (int i = 0; i <= numTicks; i++) { + Element tick = null; + if (i < tickElements.size()) { + tick = tickElements.get(i); + } else { // Create the new tick + tick = DOM.createDiv(); + tick.getStyle().setPosition(Position.ABSOLUTE); + tick.getStyle().setDisplay(Display.NONE); + DOM.appendChild(getElement(), tick); + tickElements.add(tick); + } + if (enabled) { + tick.setPropertyString("className", "gwt-SliderBar-tick"); + } else { + tick.setPropertyString("className", "gwt-SliderBar-tick gwt-SliderBar-tick-disabled"); + } + // Position the tick and make it visible + tick.getStyle().setVisibility(Visibility.HIDDEN); + tick.getStyle().setProperty("display", ""); + int tickWidth = tick.getOffsetWidth(); + int tickLeftOffset = lineLeftOffset + (lineWidth * i / numTicks) - (tickWidth / 2); + tickLeftOffset = Math.min(tickLeftOffset, lineLeftOffset + lineWidth - tickWidth); + tick.getStyle().setLeft(tickLeftOffset, Unit.PX); + tick.getStyle().setVisibility(Visibility.VISIBLE); + } + + // Hide unused ticks + for (int i = (numTicks + 1); i < tickElements.size(); i++) { + tickElements.get(i).getStyle().setDisplay(Display.NONE); + } + } else { // Hide all ticks + for (Element elem : tickElements) { + elem.getStyle().setDisplay(Display.NONE); + } + } + } + } + + /** + * Draw the markers. + */ + private void drawMarkers() { + if (isAttached() && isMinMaxInitialized()) { + int numMarkers = markers.size(); + // Draw the markers + int lineWidth = lineElement.getOffsetWidth(); + if (numMarkers > 0) { + // Create the markers or make them visible + Element lastMarkerElement = null; + int lastMarkerOffsetLeft = 0; + for (int i = 0; i < numMarkers; i++) { + Marker marker = markers.get(i); + Element markerElem = null; + if (i < markerElements.size()) { + markerElem = markerElements.get(i); + } else { // Create the new markes + markerElem = DOM.createDiv(); + markerElem.getStyle().setPosition(Position.ABSOLUTE); + markerElem.getStyle().setDisplay(Display.NONE); + DOM.appendChild(getElement(), markerElem); + markerElements.add(markerElem); + } + if (enabled) { + markerElem.setPropertyString("className", "gwt-SliderBar-mark"); + } else { + markerElem.setPropertyString("className", "gwt-SliderBar-mark gwt-SliderBar-mark-disabled"); + } + // Position the marker and make it visible + markerElem.getStyle().setVisibility(Visibility.HIDDEN); + markerElem.getStyle().setProperty("display", ""); + double markerLinePosition = (marker.position - minValue) * lineWidth / getTotalRange(); + int markerWidth = markerElem.getOffsetWidth(); + int markerLeftOffset = lineLeftOffset + (int) markerLinePosition - (markerWidth / 2); + markerLeftOffset = Math.min(markerLeftOffset, lineLeftOffset + lineWidth - markerWidth); + markerElem.getStyle().setLeft(markerLeftOffset, Unit.PX); + markerElem.getStyle().setVisibility(Visibility.VISIBLE); + // change class of last marker if it is too close to the one just created + if (lastMarkerOffsetLeft > 0 && (markerLeftOffset-lastMarkerOffsetLeft) <= markerWidth) { + if (lastMarkerElement != null) { + lastMarkerElement.setPropertyString("className", "gwt-SliderBar-mark-tooClose"); + lastMarkerElement.getStyle().setLeft(markerLeftOffset-10, Unit.PX); + } + } + lastMarkerElement = markerElem; + lastMarkerOffsetLeft = markerLeftOffset; + } + + // Hide unused markers + for (int i = numMarkers; i < markerElements.size(); i++) { + markerElements.get(i).getStyle().setDisplay(Display.NONE); + } + } else { // Hide all markers + for (Element elem : markerElements) { + elem.getStyle().setDisplay(Display.NONE); + } + } + } + } + + /** + * Draw the marker labels. + */ + private void drawMarkerLabels() { + if (isAttached() && isMinMaxInitialized()) { + int numMarkers = markers.size(); + // Draw the marker labels + int lineWidth = lineElement.getOffsetWidth(); + if (numMarkers > 0) { + Element lastMarkerLabel = null; + Marker lastMarker = null; + int lastMarkerLabelOffsetLeft = 0; + // Create the labels or make them visible + for (int i = 0; i < numMarkers; i++) { + Marker marker = markers.get(i); + Element label = null; + if (i < markerLabelElements.size()) { + label = markerLabelElements.get(i); + } else { // Create the new label + label = DOM.createDiv(); + label.getStyle().setPosition(Position.ABSOLUTE); + label.getStyle().setDisplay(Display.NONE); + DOM.appendChild(getElement(), label); + markerLabelElements.add(label); + } + if (enabled) { + label.setPropertyString("className", "gwt-SliderBar-markerlabel"); + } else { + label.setPropertyString("className", "gwt-SliderBar-markerlabel-disabled"); + } + + // Set the marker label text + label.getStyle().setVisibility(Visibility.HIDDEN); + label.getStyle().setProperty("display", ""); + label.setPropertyString("innerHTML", marker.name); + + // Move to the left so the label width is not clipped by the shell + label.getStyle().setLeft(0, Unit.PX); + + // Position the label and make it visible + double markerLinePosition = (marker.position - minValue) * lineWidth / getTotalRange(); + int labelWidth = label.getOffsetWidth(); + int labelLeftOffset = lineLeftOffset + (int) markerLinePosition - (labelWidth / 2); + labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth); + + label.getStyle().setLeft(labelLeftOffset, Unit.PX); + label.getStyle().setVisibility(Visibility.VISIBLE); + + // hide last marker label if it is too close to the one just created + if (lastMarkerLabelOffsetLeft > 0 && (labelLeftOffset-lastMarkerLabelOffsetLeft) <= 20) { + if (lastMarkerLabel != null && lastMarker != null) { + lastMarkerLabel.setPropertyString("className", "gwt-SliderBar-markerlabel-tooClose"); + lastMarkerLabel.setPropertyString("innerHTML", lastMarker.name); // no space for more + int currentMarkerOffsetLeft = labelLeftOffset; + try { + currentMarkerOffsetLeft = Integer.parseInt(markerElements.get(i).getStyle().getLeft().replace("px", "")); + } catch (NumberFormatException ex) { + // ignore, can't do anything + } + lastMarkerLabel.getStyle().setLeft(currentMarkerOffsetLeft-8, Unit.PX); + } + } + lastMarker = marker; + lastMarkerLabel = label; + lastMarkerLabelOffsetLeft = labelLeftOffset; + } + + // Hide unused labels + for (int i = (numMarkers + 1); i < markerLabelElements.size(); i++) { + markerLabelElements.get(i).getStyle().setDisplay(Display.NONE); + } + } else { // Hide all labels + for (Element elem : markerLabelElements) { + elem.getStyle().setDisplay(Display.NONE); + } + } + } + } + + /** + * Highlight this widget. + */ + private void highlight() { + String styleName = getStylePrimaryName(); + getElement().setPropertyString("className", styleName + " " + styleName + "-focused"); + } + + /** + * Reset the progress to constrain the progress to the current range and redraw the knob as needed. + */ + private synchronized void resetCurrentValue(boolean fireEvent) { + setCurrentValue(getCurrentValue(), fireEvent); + } + + /** + * Slide the knob to a new location. + * + * @param event + * the mouse event + */ + private void slideKnob(Event event) { + // Adding scrollLeft to adjust the position, if the user had scrolled with the lower scroll bar + int x = event.getClientX() + Window.getScrollLeft(); + if (x > 0) { + int lineWidth = lineElement.getOffsetWidth(); + int lineLeft = lineElement.getAbsoluteLeft(); + double percent = (double) (x - lineLeft) / lineWidth * 1.0; + setCurrentValue(getTotalRange() * percent + minValue, true); + } + } + + /** + * Start sliding the knob. + * + * @param highlight + * true to change the style + * @param fireEvent + * true to fire the event + */ + private void startSliding(boolean highlight, boolean fireEvent) { + if (highlight) { + lineElement.setPropertyString("className", "gwt-SliderBar-line gwt-SliderBar-line-sliding"); + knobImage.getElement().setPropertyString("className", "gwt-SliderBar-knob gwt-SliderBar-knob-sliding"); + knobImage.setResource(images.sliderSliding()); + } + } + + /** + * Stop sliding the knob. + * + * @param unhighlight + * true to change the style + * @param fireEvent + * true to fire the event + */ + private void stopSliding(boolean unhighlight, boolean fireEvent) { + if (unhighlight) { + lineElement.setPropertyString("className", "gwt-SliderBar-line"); + knobImage.getElement().setPropertyString("className", "gwt-SliderBar-knob"); + knobImage.setResource(images.slider()); + } + } + + /** + * Unhighlight this widget. + */ + private void unhighlight() { + getElement().setPropertyString("className", getStylePrimaryName()); + } + + @Override + public void onResize() { + redraw(); + } + + public void clearMarkers() { + markers.clear(); + } + + public boolean addMarker(String markerName, Double markerPosition) { + return markers.add(new Marker(markerName, markerPosition)); + } + + public boolean setMarker(String markerName, Double markerPosition) { + Marker marker = findMarkerByName(markerName); + if (marker != null) { + marker.position = markerPosition; + return true; + } + return false; + } + + public boolean removeMarker(String markerName) { + Marker marker = findMarkerByName(markerName); + if (marker != null) { + return markers.remove(marker); + } + return false; + } + + public Iterator getMarkers() { + return markers.iterator(); + } + + private Marker findMarkerByName(String markerName) { + for (Marker marker : markers) { + if (marker.name.equals(markerName)) { + return marker; + } + } + return null; + } +} diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeSlider.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeSlider.java index 99dafe676bc..8383b5a6c60 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeSlider.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeSlider.java @@ -26,7 +26,9 @@ public class TimeSlider extends SliderBar { private List calculatedTimeTicks; private boolean isZoomed; - + + private int visibleLabelsInterval = 1; + private final int TICKCOUNT = 10; public TimeSlider() { @@ -84,7 +86,7 @@ public class TimeSlider extends SliderBar { int tickLeftOffset = lineLeftOffset + (int) pos - (tickWidth / 2); tickLeftOffset = Math.min(tickLeftOffset, lineLeftOffset + lineWidth - tickWidth); tick.getStyle().setLeft(tickLeftOffset, Unit.PX); - tick.getStyle().setVisibility(Visibility.VISIBLE); + tick.getStyle().setVisibility(isTickInVisibleRange(tickPosition) ? Visibility.VISIBLE : Visibility.HIDDEN); } } @@ -106,7 +108,7 @@ public class TimeSlider extends SliderBar { int lineWidth = lineElement.getOffsetWidth(); // Create the labels or make them visible Double previousValue = null; - for (int i = 0; i < calculatedTimeTicks.size(); i++) { + for (int i = 0, ticksInVisibleRange = 0; i < calculatedTimeTicks.size(); i++) { TickPosition tickPosition = calculatedTimeTicks.get(i); Element label = null; if (i < tickLabelElements.size()) { @@ -142,7 +144,8 @@ public class TimeSlider extends SliderBar { labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth); labelLeftOffset = Math.max(labelLeftOffset, lineLeftOffset); label.getStyle().setLeft(labelLeftOffset, Unit.PX); - label.getStyle().setVisibility(Visibility.VISIBLE); + boolean visible = isTickInVisibleRange(tickPosition) && ticksInVisibleRange++ % visibleLabelsInterval == 0; + label.getStyle().setVisibility(visible ? Visibility.VISIBLE : Visibility.HIDDEN); } previousValue = value; } @@ -152,6 +155,11 @@ public class TimeSlider extends SliderBar { tickLabelElements.get(i).getStyle().setDisplay(Display.NONE); } } + + private boolean isTickInVisibleRange(TickPosition tickPosition) { + long tickValue = tickPosition.getPosition().getTime(); + return tickValue >= minValue && tickValue <= maxValue; + } public void clearMarkersAndLabelsAndTicks() { clearMarkers(); @@ -245,4 +253,10 @@ public class TimeSlider extends SliderBar { public void setZoomed(boolean isZoomed) { this.isZoomed = isZoomed; } -} + + @Override + public void onResize() { + visibleLabelsInterval = (TICKCOUNT / Math.max(getOffsetWidth() / 35, 1)) + 1; + super.onResize(); + } +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeTicksCalculator.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeTicksCalculator.java index 8a28bd12fb2..735989a2776 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeTicksCalculator.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeTicksCalculator.java @@ -1,246 +1,247 @@ -package com.sap.sse.gwt.client.controls.slider; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -/** - * This calculator emulates the way the HighCharts charting library is computing the chart ticks - * in case of a x-axis based on time values. - */ -public class TimeTicksCalculator { - - public TimeTicksCalculator() { - } - - /** - * Get a normalized tick interval for dates. Returns a configuration object with unit range (interval), count and - * name. Used to prepare data for getTimeTicks. Previously this logic was part of getTimeTicks, but as getTimeTicks - * now runs of segments in stock charts, the normalizing logic was extracted in order to prevent it for running over - * again for each segment having the same interval. - */ - public NormalizedInterval normalizeTimeTickInterval(long tickInterval) { - TimeUnits[] units = TimeUnits.values(); - - TimeUnits unit = units[units.length - 1]; // default unit is years - long interval = unit.unitInMs; - int[] multiples = unit.allowedMultiples; - int count; - - // loop through the units to find the one that best fits the tickInterval - for (int i = 0; i < units.length; i++) { - unit = units[i]; - interval = unit.unitInMs; - multiples = unit.allowedMultiples; - - if (i + 1 < units.length) { - // lessThan is in the middle between the highest multiple and the next unit. - long lessThan = (interval * multiples[multiples.length - 1] + units[i + 1].unitInMs) / 2; - - // break and keep the current unit - if (tickInterval <= lessThan) { - break; - } - } - } - - // prevent 2.5 years intervals, though 25, 250 etc. are allowed - if (interval == TimeUnits.YEAR.unitInMs && tickInterval < 5 * interval) { - multiples = new int[] { 1, 2, 5 }; - } - - // get the count - count = (int) normalizeTickInterval(tickInterval / interval, multiples, 1); - - return new NormalizedInterval(unit.name(), interval, count); - } - - /** - * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5 - * - * @param {Number} interval - * @param {Array} multiples - * @param {Number} magnitude - * @param {Object} options - */ - public long normalizeTickInterval(long interval, int[] multiples, int magnitude) { - long normalized; - - // round to a tenfold of 1, 2, 2.5 or 5 - normalized = interval / magnitude; - - // normalize the interval to the nearest multiple - for (int i = 0; i < multiples.length; i++) { - interval = multiples[i]; - int addedValue = i + 1 < multiples.length ? multiples[i + 1] : multiples[i]; - if (normalized <= (multiples[i] + addedValue) / 2) { - break; - } - } - - // multiply back to the correct magnitude - interval *= magnitude; - - return interval; - } - - private long makeTime(int year, int month) { - return makeTime(year, month, 1, 0, 0, 0); - } - - private long makeTime(int year, int month, int date) { - return makeTime(year, month, date, 0, 0, 0); - } - - @SuppressWarnings("deprecation") - private long makeTime(int year, int month, int date, int hours, int minutes, int seconds) { - return new Date(year, month, date, hours, minutes, seconds).getTime(); - } - - /** - * Set the tick positions to a time unit that makes sense, for example on the first of each month or on every - * Monday. Return an array with the time positions. Used in datetime axes as well as for grouping data on a datetime - * axis. - * - * @param {Object} normalizedInterval The interval in axis values (ms) and the count - * @param {Number} min The minimum in axis values - * @param {Number} max The maximum in axis values - * @param {Number} startOfWeek - */ - @SuppressWarnings("deprecation") - public List calculateTimeTicks(NormalizedInterval normalizedInterval, long min, long max, - long startOfWeek) { - List tickPositions = new ArrayList(); - int i; - boolean useUTC = true; - int minYear = 0; // used in months and years as a basis for Date.UTC() - Date minDate = new Date(min); - long interval = normalizedInterval.unitRange; - int count = (int) normalizedInterval.count; - - - if (interval >= TimeUnits.SECOND.unitInMs) { // second - minDate.setTime(min - (min % 1000)); - minDate.setSeconds(interval >= TimeUnits.MINUTE.unitInMs ? 0 : count - * Math.round(minDate.getSeconds() / count)); - } - - if (interval >= TimeUnits.MINUTE.unitInMs) { // minute - minDate.setMinutes(interval >= TimeUnits.HOUR.unitInMs ? 0 : count - * Math.round(minDate.getMinutes() / count)); - } - - if (interval >= TimeUnits.HOUR.unitInMs) { // hour - minDate.setHours(interval >= TimeUnits.DAY.unitInMs ? 0 : count * Math.round(minDate.getHours() / count)); - } - - if (interval >= TimeUnits.DAY.unitInMs) { // day - minDate.setDate(interval >= TimeUnits.MONTH.unitInMs ? 1 : count * Math.round(minDate.getDate() / count)); - } - - if (interval >= TimeUnits.MONTH.unitInMs) { // month - minDate.setMonth(interval >= TimeUnits.YEAR.unitInMs ? 0 : count * Math.round(minDate.getMonth() + 1 / count) - 1); - minYear = minDate.getYear() + 1900; - } - - if (interval >= TimeUnits.YEAR.unitInMs) { // year - minYear -= minYear % count; - minDate.setYear(minYear - 1900); - } - - // week is a special case that runs outside the hierarchy - if (interval == TimeUnits.WEEK.unitInMs) { - // get start of current week, independent of count - long value = minDate.getDate() - minDate.getDay() + startOfWeek; - minDate.setDate((int) value); - } - - // get tick positions - i = 1; - minYear = minDate.getYear() + 1900; - int minMonth = minDate.getMonth() + 1; - int minDateDate = minDate.getDate(); - long time = minDate.getTime(); - - // iterate and add tick positions at appropriate values - while (time < max) { - if(time >= min) - tickPositions.add(new TickPosition(time)); - - // if the interval is years, use Date.UTC to increase years - if (interval == TimeUnits.YEAR.unitInMs) { - time = makeTime(minYear + i * count, 0); - - // if the interval is months, use Date.UTC to increase months - } else if (interval == TimeUnits.MONTH.unitInMs) { - time = makeTime(minYear, minMonth + i * count); - - // if we're using global time, the interval is not fixed as it jumps - // one hour at the DST crossover - } else if (!useUTC && (interval == TimeUnits.DAY.unitInMs || interval == TimeUnits.WEEK.unitInMs)) { - time = makeTime(minYear, minMonth, minDateDate + i * count * (interval == TimeUnits.DAY.unitInMs ? 1 : 7)); - - // else, the interval is fixed and we use simple addition - } else { - time += interval * count; - } - - i++; - } - - // push the last time - if(time <= max) - tickPositions.add(new TickPosition(time)); - - return tickPositions; - } - - public class NormalizedInterval { - public String unitName; - public long unitRange; - public int count; - - public NormalizedInterval(String unitName, long unitRange, int count) { - this.unitName = unitName; - this.unitRange = unitRange; - this.count = count; - } - - @Override - public String toString() { - return "NormalizedInterval [unitName=" + unitName + ", unitRange=" + unitRange + ", count=" + count + "]"; - } - } - - public class TickPosition { - private Date position; - - public TickPosition(long time) { - this.position = new Date(time); - } - - public Date getPosition() { - return position; - } - } - - enum TimeUnits { - MILLISECOND(1, new int[] { 1, 2, 5, 10, 20, 25, 50, 100, 200, 500 }), - SECOND(1000, new int[] { 1, 2, 5, 10, 15, 30 }), - MINUTE(60 * 1000, new int[] { 1, 2, 5, 10, 15, 30 }), - HOUR(60 * 60 * 1000, new int[] { 1, 2, 3, 4, 6, 8, 12 }), - DAY(24 * 3600000l, new int[] { 1, 2 }), - WEEK(7 * 24 * 3600000l, new int[] { 1, 2 }), - MONTH(30 * 24 * 3600000l, new int[] { 1, 2, 3, 4, 6 }), - YEAR(31556952000l, new int[] {}); - - int[] allowedMultiples; - - long unitInMs; - - TimeUnits(long unitInMs, int[] allowedMultiples) { - this.unitInMs = unitInMs; - this.allowedMultiples = allowedMultiples; - } - } -} +package com.sap.sse.gwt.client.controls.slider; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * This calculator emulates the way the HighCharts charting library is computing the chart ticks + * in case of a x-axis based on time values. + */ +public class TimeTicksCalculator { + + public TimeTicksCalculator() { + } + + /** + * Get a normalized tick interval for dates. Returns a configuration object with unit range (interval), count and + * name. Used to prepare data for getTimeTicks. Previously this logic was part of getTimeTicks, but as getTimeTicks + * now runs of segments in stock charts, the normalizing logic was extracted in order to prevent it for running over + * again for each segment having the same interval. + */ + public NormalizedInterval normalizeTimeTickInterval(long tickInterval) { + TimeUnits[] units = TimeUnits.values(); + + TimeUnits unit = units[units.length - 1]; // default unit is years + long interval = unit.unitInMs; + int[] multiples = unit.allowedMultiples; + int count; + + // loop through the units to find the one that best fits the tickInterval + for (int i = 0; i < units.length; i++) { + unit = units[i]; + interval = unit.unitInMs; + multiples = unit.allowedMultiples; + + if (i + 1 < units.length) { + // lessThan is in the middle between the highest multiple and the next unit. + double lessThan = (interval * multiples[multiples.length - 1] + units[i + 1].unitInMs) / 2; + + // break and keep the current unit + if (tickInterval <= lessThan) { + break; + } + } + } + + // prevent 2.5 years intervals, though 25, 250 etc. are allowed + if (interval == TimeUnits.YEAR.unitInMs && tickInterval < 5 * interval) { + multiples = new int[] { 1, 2, 5 }; + } + + // get the count + double temp = ((double) tickInterval) / ((double) interval); + count = (int) normalizeTickInterval(temp, multiples, 1); + + return new NormalizedInterval(unit.name(), interval, count); + } + + /** + * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5 + * + * @param {Number} interval + * @param {Array} multiples + * @param {Number} magnitude + * @param {Object} options + */ + public long normalizeTickInterval(double interval, int[] multiples, int magnitude) { + double normalized; + + // round to a tenfold of 1, 2, 2.5 or 5 + normalized = interval / magnitude; + + // normalize the interval to the nearest multiple + for (int i = 0; i < multiples.length; i++) { + interval = multiples[i]; + int addedValue = i + 1 < multiples.length ? multiples[i + 1] : multiples[i]; + if (normalized <= (multiples[i] + addedValue) / 2) { + break; + } + } + + // multiply back to the correct magnitude + interval *= magnitude; + + return Math.round(interval); + } + + private long makeTime(int year, int month) { + return makeTime(year, month, 1, 0, 0, 0); + } + + private long makeTime(int year, int month, int date) { + return makeTime(year, month, date, 0, 0, 0); + } + + @SuppressWarnings("deprecation") + private long makeTime(int year, int month, int date, int hours, int minutes, int seconds) { + return new Date(year, month, date, hours, minutes, seconds).getTime(); + } + + /** + * Set the tick positions to a time unit that makes sense, for example on the first of each month or on every + * Monday. Return an array with the time positions. Used in datetime axes as well as for grouping data on a datetime + * axis. + * + * @param {Object} normalizedInterval The interval in axis values (ms) and the count + * @param {Number} min The minimum in axis values + * @param {Number} max The maximum in axis values + * @param {Number} startOfWeek + */ + @SuppressWarnings("deprecation") + public List calculateTimeTicks(NormalizedInterval normalizedInterval, long min, long max, + long startOfWeek) { + List tickPositions = new ArrayList(); + int i; + boolean useUTC = true; + int minYear = 0; // used in months and years as a basis for Date.UTC() + Date minDate = new Date(min); + long interval = normalizedInterval.unitRange; + int count = (int) normalizedInterval.count; + + + if (interval >= TimeUnits.SECOND.unitInMs) { // second + minDate.setTime(min - (min % 1000)); + minDate.setSeconds(interval >= TimeUnits.MINUTE.unitInMs ? 0 : count + * Math.round(minDate.getSeconds() / count)); + } + + if (interval >= TimeUnits.MINUTE.unitInMs) { // minute + minDate.setMinutes(interval >= TimeUnits.HOUR.unitInMs ? 0 : count + * Math.round(minDate.getMinutes() / count)); + } + + if (interval >= TimeUnits.HOUR.unitInMs) { // hour + minDate.setHours(interval >= TimeUnits.DAY.unitInMs ? 0 : count * Math.round(minDate.getHours() / count)); + } + + if (interval >= TimeUnits.DAY.unitInMs) { // day + minDate.setDate(interval >= TimeUnits.MONTH.unitInMs ? 1 : count * Math.round(minDate.getDate() / count)); + } + + if (interval >= TimeUnits.MONTH.unitInMs) { // month + minDate.setMonth(interval >= TimeUnits.YEAR.unitInMs ? 0 : count * Math.round(minDate.getMonth() + 1 / count) - 1); + minYear = minDate.getYear() + 1900; + } + + if (interval >= TimeUnits.YEAR.unitInMs) { // year + minYear -= minYear % count; + minDate.setYear(minYear - 1900); + } + + // week is a special case that runs outside the hierarchy + if (interval == TimeUnits.WEEK.unitInMs) { + // get start of current week, independent of count + long value = minDate.getDate() - minDate.getDay() + startOfWeek; + minDate.setDate((int) value); + } + + // get tick positions + i = 1; + minYear = minDate.getYear() + 1900; + int minMonth = minDate.getMonth() + 1; + int minDateDate = minDate.getDate(); + long time = minDate.getTime(); + + // iterate and add tick positions at appropriate values + while (time < max) { + if(time >= min) + tickPositions.add(new TickPosition(time)); + + // if the interval is years, use Date.UTC to increase years + if (interval == TimeUnits.YEAR.unitInMs) { + time = makeTime(minYear + i * count, 0); + + // if the interval is months, use Date.UTC to increase months + } else if (interval == TimeUnits.MONTH.unitInMs) { + time = makeTime(minYear, minMonth + i * count); + + // if we're using global time, the interval is not fixed as it jumps + // one hour at the DST crossover + } else if (!useUTC && (interval == TimeUnits.DAY.unitInMs || interval == TimeUnits.WEEK.unitInMs)) { + time = makeTime(minYear, minMonth, minDateDate + i * count * (interval == TimeUnits.DAY.unitInMs ? 1 : 7)); + + // else, the interval is fixed and we use simple addition + } else { + time += interval * count; + } + + i++; + } + + // push the last time + if(time <= max) + tickPositions.add(new TickPosition(time)); + + return tickPositions; + } + + public class NormalizedInterval { + public String unitName; + public long unitRange; + public int count; + + public NormalizedInterval(String unitName, long unitRange, int count) { + this.unitName = unitName; + this.unitRange = unitRange; + this.count = count; + } + + @Override + public String toString() { + return "NormalizedInterval [unitName=" + unitName + ", unitRange=" + unitRange + ", count=" + count + "]"; + } + } + + public class TickPosition { + private Date position; + + public TickPosition(long time) { + this.position = new Date(time); + } + + public Date getPosition() { + return position; + } + } + + enum TimeUnits { + MILLISECOND(1l, 1, 2, 5, 10, 20, 25, 50, 100, 200, 500), + SECOND(1000l, 1, 2, 5, 10, 15, 30), + MINUTE(60 * 1000l, 1, 2, 5, 10, 15, 30), + HOUR(60 * 60 * 1000l, 1, 2, 3, 4, 6, 8, 12), + DAY(24 * 3600000l, 1, 2), + WEEK(7 * 24 * 3600000l, 1, 2), + MONTH(30 * 24 * 3600000l, 1, 2, 3, 4, 6), + YEAR(31556952000l); + + int[] allowedMultiples; + + long unitInMs; + + TimeUnits(long unitInMs, int... allowedMultiples) { + this.unitInMs = unitInMs; + this.allowedMultiples = allowedMultiples; + } + } +} diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/shared/components/IsEmbeddableComponent.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/shared/components/IsEmbeddableComponent.java index 0f83295b75c..f07d2bf1fbe 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/shared/components/IsEmbeddableComponent.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/shared/components/IsEmbeddableComponent.java @@ -1,7 +1,6 @@ package com.sap.sse.gwt.client.shared.components; import com.google.gwt.user.client.ui.Widget; -import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator; public interface IsEmbeddableComponent { @@ -13,11 +12,7 @@ public interface IsEmbeddableComponent { Widget getLegendWidget(); - BusyIndicator getBusyIndicator(); - boolean isEmbedded(); boolean hasToolbar(); - - boolean hasBusyIndicator(); } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/resources/css/CommonControls.css b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/resources/css/CommonControls.css index a15efd6a2c7..a30d6ccc9a7 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/resources/css/CommonControls.css +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/resources/css/CommonControls.css @@ -169,8 +169,7 @@ body ::-webkit-scrollbar-track { .gwt-SplitLayoutPanel-EastToggleButton-Panel { position: absolute; - bottom: 77px; - margin-left: -35px; + bottom: -10px; } .gwt-SplitLayoutPanel-EastToggleButton { @@ -181,9 +180,14 @@ body ::-webkit-scrollbar-track { -moz-transform: rotate(-90deg); -o-transform: rotate(-90deg); -ms-transform: rotate(-90deg); + transform: rotate(-90deg); + -webkit-transform-origin: 0 0; + -moz-transform-origin: 0 0; + -o-transform-origin: 0 0; + -ms-transform-origin: 0 0; + transform-origin: 0 0; padding-right: 25px; padding-left: 30px; - margin-left: -15px; border-bottom: 1px solid #cecece; border-right: 1px solid #cecece; border-left: 1px solid #cecece; diff --git a/java/com.sap.sse.replication/META-INF/MANIFEST.MF b/java/com.sap.sse.replication/META-INF/MANIFEST.MF index a2ca9d4a9a5..ded07e7ce34 100644 --- a/java/com.sap.sse.replication/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.replication/META-INF/MANIFEST.MF @@ -12,7 +12,8 @@ Export-Package: com.sap.sse.replication, Require-Bundle: com.sap.sse.common, com.sap.sse.operationaltransformation, com.sap.sse, - lz4-java;bundle-version="1.3.0" + lz4-java;bundle-version="1.3.0", + org.apache.commons.lang Bundle-ClassPath: . Web-ContextPath: /replication Import-Package: com.rabbitmq.client;version="2.8.4", diff --git a/java/com.sap.sse.replication/src/com/sap/sse/replication/impl/ReplicationMasterDescriptorImpl.java b/java/com.sap.sse.replication/src/com/sap/sse/replication/impl/ReplicationMasterDescriptorImpl.java index 0897034a1e6..34dc073b159 100755 --- a/java/com.sap.sse.replication/src/com/sap/sse/replication/impl/ReplicationMasterDescriptorImpl.java +++ b/java/com.sap.sse.replication/src/com/sap/sse/replication/impl/ReplicationMasterDescriptorImpl.java @@ -78,7 +78,8 @@ public class ReplicationMasterDescriptorImpl implements ReplicationMasterDescrip UnsupportedEncodingException { return new URL("http", getHostname(), servletPort, REPLICATION_SERVLET + "?" + ReplicationServlet.ACTION + "=" + ReplicationServlet.Action.REGISTER.name() + "&" + ReplicationServlet.SERVER_UUID + "=" - + uuid.toString() + "&" + ReplicationServlet.ADDITIONAL_INFORMATION + "=" + + java.net.URLEncoder.encode(uuid.toString(), "UTF-8") + "&" + + ReplicationServlet.ADDITIONAL_INFORMATION + "=" + java.net.URLEncoder.encode(ServerInfo.getBuildVersion(), "UTF-8")); } diff --git a/java/com.sap.sse.replication/src/com/sap/sse/replication/impl/ReplicationServlet.java b/java/com.sap.sse.replication/src/com/sap/sse/replication/impl/ReplicationServlet.java index 67009dc4f53..3277eb00540 100755 --- a/java/com.sap.sse.replication/src/com/sap/sse/replication/impl/ReplicationServlet.java +++ b/java/com.sap.sse.replication/src/com/sap/sse/replication/impl/ReplicationServlet.java @@ -19,6 +19,7 @@ import javax.servlet.http.HttpServletResponse; import net.jpountz.lz4.LZ4BlockOutputStream; +import org.apache.commons.lang.StringEscapeUtils; import org.osgi.framework.BundleContext; import org.osgi.util.tracker.ServiceTracker; @@ -111,7 +112,7 @@ public class ReplicationServlet extends AbstractHttpServlet { if (replicable == null) { final String msg = "Couldn't find replicable with ID "+replicableIdAsString+". Aborting serialization of initial load."; logger.severe(msg); - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, msg); + resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, StringEscapeUtils.escapeHtml(msg)); break; // causing an error on the replica which is expecting the replica's initial load } try { @@ -130,7 +131,7 @@ public class ReplicationServlet extends AbstractHttpServlet { channel.getConnection().close(); } default: - resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Action " + action + " not understood. Must be one of " + resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Action " + StringEscapeUtils.escapeHtml(action) + " not understood. Must be one of " + Arrays.toString(Action.values())); } } diff --git a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java index 08b962910ce..3249477de36 100755 --- a/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java +++ b/java/com.sap.sse.security.replication.test/src/com/sap/sse/security/replication/test/SimpleSecurityReplicationTest.java @@ -45,7 +45,11 @@ public class SimpleSecurityReplicationTest extends AbstractSecurityReplicationTe final String newEmail = "ernie2@sesame-street.com"; final String password = "BertMyFriend"; final String validationBaseURL = "http://me.to.back.com"; + final String fullName = "Ernie's Full Name"; + final String company = "Ernie's Company"; User user = master.createSimpleUser(username, email, password, validationBaseURL); + user.setFullName(fullName); + user.setCompany(company); final String emailValidationSecretAfterCreation = user.getValidationSecret(); master.updateSimpleUserEmail(username, newEmail, validationBaseURL); final String emailValidationSecretAfterChangingEmail = user.getValidationSecret(); @@ -60,6 +64,8 @@ public class SimpleSecurityReplicationTest extends AbstractSecurityReplicationTe assertEquals(username, replicatedErnie.getName()); assertEquals(newEmail, replicatedErnie.getEmail()); assertEquals(emailValidationSecretAfterChangingEmail, replicatedErnie.getValidationSecret()); + assertEquals(fullName, replicatedErnie.getFullName()); + assertEquals(company, replicatedErnie.getCompany()); } @Test diff --git a/java/com.sap.sse.security.ui/.settings/org.eclipse.core.resources.prefs b/java/com.sap.sse.security.ui/.settings/org.eclipse.core.resources.prefs index db751d38ff8..ef93b4ecac3 100755 --- a/java/com.sap.sse.security.ui/.settings/org.eclipse.core.resources.prefs +++ b/java/com.sap.sse.security.ui/.settings/org.eclipse.core.resources.prefs @@ -1,2 +1,7 @@ eclipse.preferences.version=1 +encoding//src/main/java/com/sap/sse/security/ui/client/UserService.java=utf-8 encoding//src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_de.properties=UTF-8 +encoding//src/main/java/com/sap/sse/security/ui/client/shared/oauthlogin/OAuthLogin.java=utf-8 +encoding//src/main/java/com/sap/sse/security/ui/login/LoginView.java=utf-8 +encoding//src/main/java/com/sap/sse/security/ui/oauth/client/util/ClientUtils.java=utf-8 +encoding//src/main/java/com/sap/sse/security/ui/servlet/RedirectServlet.java=utf-8 diff --git a/java/com.sap.sse.security.ui/WEB-INF/web.xml b/java/com.sap.sse.security.ui/WEB-INF/web.xml index 6a4bf7d0912..e96f26129ec 100644 --- a/java/com.sap.sse.security.ui/WEB-INF/web.xml +++ b/java/com.sap.sse.security.ui/WEB-INF/web.xml @@ -4,16 +4,6 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> - - default - org.eclipse.jetty.servlet.DefaultServlet - - - useFileMappedBuffer - false - - - default diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java index 01a4f952a44..0f4d7ddb0dc 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementService.java @@ -31,6 +31,8 @@ public interface UserManagementService extends RemoteService { void updateSimpleUserEmail(String username, String newEmail, String validationBaseURL) throws UserManagementException, MailException; + void updateUserProperties(String username, String fullName, String company) throws UserManagementException; + void resetPassword(String username, String eMailAddress, String baseURL) throws UserManagementException, MailException; boolean validateEmail(String username, String validationSecret) throws UserManagementException; diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementServiceAsync.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementServiceAsync.java index b2a3724b729..273369b509b 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementServiceAsync.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/UserManagementServiceAsync.java @@ -27,6 +27,8 @@ public interface UserManagementServiceAsync { void updateSimpleUserEmail(String username, String newEmail, String validationBaseURL, AsyncCallback callback); + void updateUserProperties(String username, String fullName, String company, AsyncCallback callback); + void getFilteredSortedUserList(String filter, AsyncCallback> callback); void setRolesForUser(String username, Iterable roles, AsyncCallback callback); diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/shared/oauthlogin/OAuthLogin.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/shared/oauthlogin/OAuthLogin.java index 75fc66ae37c..964b23d1821 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/shared/oauthlogin/OAuthLogin.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/shared/oauthlogin/OAuthLogin.java @@ -2,6 +2,7 @@ package com.sap.sse.security.ui.client.shared.oauthlogin; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.safehtml.shared.UriUtils; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; @@ -62,7 +63,7 @@ public class OAuthLogin extends Composite { @Override public void onSuccess(String result) { - String authorizationUrl = result; + String authorizationUrl = UriUtils.fromString(result).asString(); GWT.log("Authorization url: " + authorizationUrl); // clear all cookies first diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/util/ClientUtils.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/util/ClientUtils.java index 479e3c74c2f..e3f658c6911 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/util/ClientUtils.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/oauth/client/util/ClientUtils.java @@ -1,5 +1,6 @@ package com.sap.sse.security.ui.oauth.client.util; +import com.google.gwt.safehtml.shared.UriUtils; import com.google.gwt.user.client.Cookies; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.Window.Location; @@ -275,7 +276,7 @@ public class ClientUtils { } public static void redirect(String url) { - Window.Location.assign(url); + Window.Location.assign(UriUtils.fromString(url).asString()); } public static boolean redirected() { diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java index aaa2abe6dd9..a0d612137ef 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/server/UserManagementServiceImpl.java @@ -163,6 +163,17 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U } } + @Override + public void updateUserProperties(final String username, String fullName, String company) throws UserManagementException { + final Subject subject = SecurityUtils.getSubject(); + // the signed-in subject has role ADMIN or is changing own user + if (subject.hasRole(DefaultRoles.ADMIN.getRolename()) || username.equals(subject.getPrincipal().toString())) { + getSecurityService().updateUserProperties(username, fullName, company); + } else { + throw new UserManagementException(UserManagementException.INVALID_CREDENTIALS); + } + } + @Override public void updateSimpleUserEmail(String username, String newEmail, String validationBaseURL) throws UserManagementException, MailException { final Subject subject = SecurityUtils.getSubject(); @@ -288,7 +299,8 @@ public class UserManagementServiceImpl extends RemoteServiceServlet implements U break; } } - userDTO = new UserDTO(user.getName(), user.getEmail(), user.isEmailValidated(), accountDTOs, user.getRoles(), user.getPermissions()); + userDTO = new UserDTO(user.getName(), user.getEmail(), user.getFullName(), user.getCompany(), + user.isEmailValidated(), accountDTOs, user.getRoles(), user.getPermissions()); return userDTO; } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/servlet/RedirectServlet.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/servlet/RedirectServlet.java index db07d90417f..1eb7b581106 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/servlet/RedirectServlet.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/servlet/RedirectServlet.java @@ -14,6 +14,8 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import com.google.gwt.safehtml.shared.UriUtils; + public class RedirectServlet extends HttpServlet { private static final Logger logger = Logger.getLogger(RedirectServlet.class.getName()); @@ -57,7 +59,7 @@ public class RedirectServlet extends HttpServlet { private void doHiddenRedirect(HttpServletResponse resp, String target) throws IOException{ String html = ""; resp.setContentType("text/html"); resp.getWriter().append(html); @@ -97,12 +99,12 @@ public class RedirectServlet extends HttpServlet { } catch (MalformedURLException e) { e.printStackTrace(); } - if (host.contains("local")){ + if (host.startsWith("localhost")) { host = "127.0.0.1"; } String link = "http://" + host + ":" + port + baseLink; int i = 1; - for(Entry entry: parameters.entrySet()) { + for (Entry entry : parameters.entrySet()) { link += i == 1 ? "?" : "&"; link += entry.getKey() + "=" + entry.getValue(); i++; @@ -115,7 +117,7 @@ public class RedirectServlet extends HttpServlet { link += i == 1 ? "?" : "&"; link += "locale=" + localeParam; } - logger.info("Redirecting from " + req.getRequestURL().toString() +" to: " + link); + logger.info("Redirecting from " + req.getRequestURL().toString() + " to: " + link); return link; } } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java index 6d7b51d460d..0711d793807 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/shared/UserDTO.java @@ -14,6 +14,8 @@ import com.sap.sse.security.shared.WildcardPermission; public class UserDTO implements IsSerializable { private String name; private String email; + private String fullName; + private String company; private List accounts; private Set roles; private Set permissions; @@ -21,9 +23,12 @@ public class UserDTO implements IsSerializable { UserDTO() {} // for serialization only - public UserDTO(String name, String email, boolean emailValidated, List accounts, Iterable roles, Iterable stringPermissions) { + public UserDTO(String name, String email, String fullName, String company, boolean emailValidated, + List accounts, Iterable roles, Iterable stringPermissions) { this.name = name; this.email = email; + this.fullName = fullName; + this.company = company; this.emailValidated = emailValidated; this.accounts = accounts; this.roles = new HashSet<>(); @@ -38,6 +43,14 @@ public class UserDTO implements IsSerializable { return name; } + public String getFullName() { + return fullName; + } + + public String getCompany() { + return company; + } + public Iterable getRoles() { return roles; } diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java index db19c10abea..28711e4b69a 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/UserStoreImpl.java @@ -183,7 +183,7 @@ public class UserStoreImpl implements UserStore { throw new UserManagementException(UserManagementException.USER_ALREADY_EXISTS); } User user = new User(name, email, accounts); - logger.info("Creating user: " + user); + logger.info("Creating user: " + user + " with e-mail "+email); if (mongoObjectFactory != null) { mongoObjectFactory.storeUser(user); } diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/DomainObjectFactoryImpl.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/DomainObjectFactoryImpl.java index 9f6ef354951..921b99b44d1 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/DomainObjectFactoryImpl.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/DomainObjectFactoryImpl.java @@ -62,8 +62,10 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { } private User loadUser(DBObject userDBObject) { - String name = (String) userDBObject.get(FieldNames.User.NAME.name()); - String email = (String) userDBObject.get(FieldNames.User.EMAIL.name()); + final String name = (String) userDBObject.get(FieldNames.User.NAME.name()); + final String email = (String) userDBObject.get(FieldNames.User.EMAIL.name()); + final String fullName = (String) userDBObject.get(FieldNames.User.FULLNAME.name()); + final String company = (String) userDBObject.get(FieldNames.User.COMPANY.name()); Boolean emailValidated = (Boolean) userDBObject.get(FieldNames.User.EMAIL_VALIDATED.name()); String passwordResetSecret = (String) userDBObject.get(FieldNames.User.PASSWORD_RESET_SECRET.name()); String validationSecret = (String) userDBObject.get(FieldNames.User.VALIDATION_SECRET.name()); @@ -83,7 +85,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { } DBObject accountsMap = (DBObject) userDBObject.get(FieldNames.User.ACCOUNTS.name()); Map accounts = createAccountMapFromdDBObject(accountsMap); - User result = new User(name, email, emailValidated==null?false:emailValidated, passwordResetSecret, validationSecret, accounts.values()); + User result = new User(name, email, fullName, company, emailValidated==null?false:emailValidated, passwordResetSecret, validationSecret, accounts.values()); for (String role : roles) { result.addRole(role); } diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/FieldNames.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/FieldNames.java index ef41cc62a25..2827246a87a 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/FieldNames.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/FieldNames.java @@ -4,6 +4,8 @@ public class FieldNames { public static enum User { NAME, + FULLNAME, + COMPANY, EMAIL, ACCOUNTS, ROLES, PERMISSIONS, diff --git a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java index 41ebcee5c8f..9a70a948896 100644 --- a/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java +++ b/java/com.sap.sse.security.userstore.mongodb/src/com/sap/sse/security/userstore/mongodb/impl/MongoObjectFactoryImpl.java @@ -38,6 +38,8 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { DBObject query = new BasicDBObject(FieldNames.User.NAME.name(), user.getName()); dbUser.put(FieldNames.User.NAME.name(), user.getName()); dbUser.put(FieldNames.User.EMAIL.name(), user.getEmail()); + dbUser.put(FieldNames.User.FULLNAME.name(), user.getFullName()); + dbUser.put(FieldNames.User.COMPANY.name(), user.getCompany()); dbUser.put(FieldNames.User.EMAIL_VALIDATED.name(), user.isEmailValidated()); dbUser.put(FieldNames.User.PASSWORD_RESET_SECRET.name(), user.getPasswordResetSecret()); dbUser.put(FieldNames.User.VALIDATION_SECRET.name(), user.getValidationSecret()); diff --git a/java/com.sap.sse.security/META-INF/MANIFEST.MF b/java/com.sap.sse.security/META-INF/MANIFEST.MF index d7c272be079..2b55f782ce3 100644 --- a/java/com.sap.sse.security/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.security/META-INF/MANIFEST.MF @@ -18,7 +18,6 @@ Import-Package: com.sap.sse.util, Bundle-ActivationPolicy: lazy Bundle-ClassPath: . Require-Bundle: javax.servlet;bundle-version="2.5.0", - com.google.gwt.osgi, slf4j.api;bundle-version="1.6.4", org.json.simple, com.sun.jersey, diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/ClientUtils.java b/java/com.sap.sse.security/src/com/sap/sse/security/ClientUtils.java index 2df683f171e..83e38a9c9ac 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/ClientUtils.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/ClientUtils.java @@ -1,92 +1,65 @@ package com.sap.sse.security; -import com.google.gwt.user.client.Cookies; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.Window.Location; -import com.google.gwt.user.client.rpc.AsyncCallback; - -public class ClientUtils -{ - private final static String SESSION_ID_COOKIE = "gwtgoogleoauthtest_session_id"; - private final static String AUTH_PROVIDER_COOKIE = "gwtgoogleoauthtest_provider"; - private final static String AUTH_PROVIDER_NAME_COOKIE = "gwtgoogleoauthtest_provider_name"; - private final static String USERNAME_COOKIE = "gwtgoogleoauthtest_user"; - private final static String REDIRECT_URL_COOKIE = "gwtgoogleoauthtest_redirect_url"; - - public final static int DIALOG_X_POSITION = 20; - public final static int DIALOG_Y_POSITION = 110; - - +public class ClientUtils { // Callback URL is registered with each OAuth Provider - //private static final String APP_CALLBACK_URL = "http://oauthdemo2012.com:8888/GWTOAuthLoginDemo.html"; - //http://127.0.0.1:8888/GWTOAuthLoginDemo.html?gwt.codesvr=127.0.0.1:9997& - private static final String APP_CALLBACK_URL = "http://local.sapsailing.com:8888/security/ui/redirect?redirectTo=oauthlogin"; - - - private static final String FACEBOOK_PROTECTED_RESOURCE_URL = "https://graph.facebook.com/me"; - - private static final String GOOGLE_PROTECTED_RESOURSE_URL = "https://www.googleapis.com/oauth2/v1/userinfo"; - - private static final String TWITTER_PROTECTED_RESOURCE_URL = "https://api.twitter.com/1/account/verify_credentials.json"; - + // private static final String APP_CALLBACK_URL = "http://oauthdemo2012.com:8888/GWTOAuthLoginDemo.html"; + // http://127.0.0.1:8888/GWTOAuthLoginDemo.html?gwt.codesvr=127.0.0.1:9997& + private static final String APP_CALLBACK_URL = "http://local.sapsailing.com:8888/security/ui/redirect?redirectTo=oauthlogin"; + + private static final String FACEBOOK_PROTECTED_RESOURCE_URL = "https://graph.facebook.com/me"; + + private static final String GOOGLE_PROTECTED_RESOURSE_URL = "https://www.googleapis.com/oauth2/v1/userinfo"; + + private static final String TWITTER_PROTECTED_RESOURCE_URL = "https://api.twitter.com/1/account/verify_credentials.json"; + // %s is guid and the caller must replace - private static final String YAHOO_PROTECTED_RESOURCE_URL = "http://social.yahooapis.com/v1/user/%s/profile?format=json"; - - private static final String LINKEDIN_PROTECTED_RESOURCE_URL = "http://api.linkedin.com/v1/people/~?format=json"; - + private static final String YAHOO_PROTECTED_RESOURCE_URL = "http://social.yahooapis.com/v1/user/%s/profile?format=json"; + + private static final String LINKEDIN_PROTECTED_RESOURCE_URL = "http://api.linkedin.com/v1/people/~?format=json"; + // %s is userId and acess token and the caller must replace private static final String INSTAGRAM_PROTECTED_RESOURCE_URL = "https://api.instagram.com/v1/users/%s/?access_token=%s"; - - private static final String IMGUR_PROTECTED_RESOURCE_URL = "http://api.imgur.com/2/account.json"; - - private static final String GITHUB_PROTECTED_RESOURCE_URL = "https://api.github.com/user?access_token=%s"; - - private static final String LIVE_PROTECTED_RESOURCE_URL = "https://apis.live.net/v5.0/me"; - - private static final String FLICKR_PROTECTED_RESOURCE_URL = "http://api.flickr.com/services/rest?format=json&nojsoncallback=1&method=flickr.test.login"; - - private static final String VIMEO_PROTECTED_RESOURCE_URL = "http://vimeo.com/api/rest/v2?format=json&method=vimeo.people.getInfo"; - - private static final String TUMBLR_PROTECTED_RESOURCE_URL = "https://api.tumblr.com/v2/user/info"; - + + private static final String IMGUR_PROTECTED_RESOURCE_URL = "http://api.imgur.com/2/account.json"; + + private static final String GITHUB_PROTECTED_RESOURCE_URL = "https://api.github.com/user?access_token=%s"; + + private static final String LIVE_PROTECTED_RESOURCE_URL = "https://apis.live.net/v5.0/me"; + + private static final String FLICKR_PROTECTED_RESOURCE_URL = "http://api.flickr.com/services/rest?format=json&nojsoncallback=1&method=flickr.test.login"; + + private static final String VIMEO_PROTECTED_RESOURCE_URL = "http://vimeo.com/api/rest/v2?format=json&method=vimeo.people.getInfo"; + + private static final String TUMBLR_PROTECTED_RESOURCE_URL = "https://api.tumblr.com/v2/user/info"; + // caller must replace %s with access token private static final String FOURSQUARE_PROTECTED_RESOURCE_URL = "https://api.foursquare.com/v2/users/self?oauth_token=%s"; - - - public static final String SESSION_EXPIRED_MESSAGE = "Session has expired"; - - private static String[] authProviders = - { - "Facebook", "Google", "Twitter", "Yahoo!", - "Flickr", "ImaGur", "LinkedIn", "Windows Live", - "Instagram","github", "Vimeo", "Evernote", - "tumblr.", "foursquare", - }; - - public final static int UNKNOWN = -1; - public final static int DEFAULT = 0; - public final static int FACEBOOK = 1; - public final static int GOOGLE = 2; - public final static int TWITTER = 3; - public final static int YAHOO = 4; - public final static int FLICKR = 5; - public final static int IMGUR = 6; - public final static int LINKEDIN = 7; + + private static String[] authProviders = { "Facebook", "Google", "Twitter", "Yahoo!", "Flickr", "ImaGur", + "LinkedIn", "Windows Live", "Instagram", "github", "Vimeo", "Evernote", "tumblr.", "foursquare", }; + + public final static int UNKNOWN = -1; + public final static int DEFAULT = 0; + public final static int FACEBOOK = 1; + public final static int GOOGLE = 2; + public final static int TWITTER = 3; + public final static int YAHOO = 4; + public final static int FLICKR = 5; + public final static int IMGUR = 6; + public final static int LINKEDIN = 7; public final static int WINDOWS_LIVE = 8; - public final static int INSTAGRAM = 9; - public final static int GITHUB = 10; - public final static int VIMEO = 11; - public final static int EVERNOTE = 12; - public final static int TUMBLR = 13; - public final static int FOURSQUARE = 14; - - public static String[] getAuthProviders() - { + public final static int INSTAGRAM = 9; + public final static int GITHUB = 10; + public final static int VIMEO = 11; + public final static int EVERNOTE = 12; + public final static int TUMBLR = 13; + public final static int FOURSQUARE = 14; + + public static String[] getAuthProviders() { return authProviders; - } - - public static int getAuthProvider(String authProviderName) - { + } + + public static int getAuthProvider(String authProviderName) { int authProvider = DEFAULT; authProviderName = authProviderName.toLowerCase(); if (authProviderName.equals("facebook")) @@ -119,12 +92,11 @@ public class ClientUtils return TUMBLR; else if (authProviderName.equals("foursquare")) return FOURSQUARE; - + return authProvider; } - - public static String getAuthProviderName(int authProvider) - { + + public static String getAuthProviderName(int authProvider) { if (authProvider == FACEBOOK) return "Facebook"; else if (authProvider == GOOGLE) @@ -153,288 +125,71 @@ public class ClientUtils return "tumblr."; else if (authProvider == FOURSQUARE) return "foursquare"; - + return "Default"; } - - - public static String getCallbackUrl() - { + + public static String getCallbackUrl() { return APP_CALLBACK_URL; } - - public static String getProctedResourceUrl(int authProvider) - { - switch(authProvider) - { - case FACEBOOK: - { - return FACEBOOK_PROTECTED_RESOURCE_URL; - } - - case GOOGLE: - { - return GOOGLE_PROTECTED_RESOURSE_URL; - } - case TWITTER: - { - return TWITTER_PROTECTED_RESOURCE_URL; - } - - case YAHOO: - { - return YAHOO_PROTECTED_RESOURCE_URL; - } - - case LINKEDIN: - { - return LINKEDIN_PROTECTED_RESOURCE_URL; - } - - case INSTAGRAM: - { - return INSTAGRAM_PROTECTED_RESOURCE_URL; - } - - case IMGUR: - { - return IMGUR_PROTECTED_RESOURCE_URL; - } - - case GITHUB: - { - return GITHUB_PROTECTED_RESOURCE_URL; - } - - case FLICKR: - { - return FLICKR_PROTECTED_RESOURCE_URL; - } - - case VIMEO: - { - return VIMEO_PROTECTED_RESOURCE_URL; - } - - case WINDOWS_LIVE: - { - return LIVE_PROTECTED_RESOURCE_URL; - } - - case TUMBLR: - { - return TUMBLR_PROTECTED_RESOURCE_URL; - } - - case FOURSQUARE: - { - return FOURSQUARE_PROTECTED_RESOURCE_URL; - } - - default: - { - return null; - } + public static String getProctedResourceUrl(int authProvider) { + switch (authProvider) { + case FACEBOOK: { + return FACEBOOK_PROTECTED_RESOURCE_URL; } - } - - public static void clearCookies() - { - Cookies.removeCookie(SESSION_ID_COOKIE); - Cookies.removeCookie(AUTH_PROVIDER_COOKIE); - Cookies.removeCookie(AUTH_PROVIDER_NAME_COOKIE); - Cookies.removeCookie(USERNAME_COOKIE); - Cookies.removeCookie(REDIRECT_URL_COOKIE); - } - - public static String getSessionIdFromCookie() - { - return Cookies.getCookie(SESSION_ID_COOKIE); - } - - public static String getAuthProviderFromCookie() - { - return Cookies.getCookie(AUTH_PROVIDER_COOKIE); - } - - public static int getAuthProviderFromCookieAsInt() - { - String authProviderStr = getAuthProviderFromCookie(); - int authProvider = UNKNOWN; - if (authProviderStr != null) - { - try - { - authProvider = Integer.parseInt(authProviderStr); - - } - catch(NumberFormatException e) - { - return UNKNOWN; - } - } - - return authProvider; - } - - public static String getAuthProviderNameFromCookie() - { - return Cookies.getCookie(AUTH_PROVIDER_NAME_COOKIE); - } - - public static String getUsernameFromCookie() - { - return Cookies.getCookie(USERNAME_COOKIE); - } - - public static boolean alreadyLoggedIn() - { - if (getSessionIdFromCookie() != null) - return true; - return false; - } - - public static void saveSessionId(String sessionId) - { - Cookies.setCookie(SESSION_ID_COOKIE,sessionId); - } - - public static void saveAuthProvider(int authProvider) - { - Cookies.setCookie(AUTH_PROVIDER_COOKIE,Integer.toString(authProvider)); - String authProviderName = getAuthProviderName(authProvider); - saveAuthProviderName(authProviderName); - } - - public static void saveAuthProviderName(String authProviderName) - { - Cookies.setCookie(AUTH_PROVIDER_NAME_COOKIE,authProviderName); - } - - public static void saveUsername(String username) - { - Cookies.setCookie(USERNAME_COOKIE,username); - } - - public static void saveRediretUrl(String url) - { - Cookies.setCookie(REDIRECT_URL_COOKIE,url); - } - - public static String getRedirectUrlFromCookie() - { - return Cookies.getCookie(REDIRECT_URL_COOKIE); - } - - public static void redirect(String url) - { - Window.Location.assign(url); - } - - public static boolean redirected() - { - String authProvider = getAuthProviderFromCookie(); - if (authProvider == null) - { - return false; - } - - if (Location.getParameter("code") != null) //facebook,google,github,windows live - return true; - - if (Location.getParameter("oauth_token") != null) // twitter,yahoo,flickr - return true; - - if (Location.getParameter("oauth_verifier") != null) // Flickr - return true; - - String error = Location.getParameter("error"); - if (error != null) - { - String errorMessage = Location.getParameter("error_description"); - Window.alert("Error: " + error + ":" + errorMessage); - reload(); - return false; - } - - return false; - } - - public static void reload() - { - String appUrl = getRedirectUrlFromCookie(); -// int savedAuthProvider = getAuthProviderFromCookieAsInt(); - - clearCookies(); - -// if (savedAuthProvider == DEFAULT || savedAuthProvider == UNKNOWN) -// { -// GWTOAuthLoginDemo.get().updateLoginStatus(); -// } - - if (appUrl != null) - { - redirect(appUrl); - } - } - - public static void handleException(Throwable caught) - { - if (caught.getMessage().equals(ClientUtils.SESSION_EXPIRED_MESSAGE)) - { - showSessionExpires(); - } - else - { - showGenericException(caught); + case GOOGLE: { + return GOOGLE_PROTECTED_RESOURSE_URL; + } + + case TWITTER: { + return TWITTER_PROTECTED_RESOURCE_URL; + } + + case YAHOO: { + return YAHOO_PROTECTED_RESOURCE_URL; + } + + case LINKEDIN: { + return LINKEDIN_PROTECTED_RESOURCE_URL; + } + + case INSTAGRAM: { + return INSTAGRAM_PROTECTED_RESOURCE_URL; + } + + case IMGUR: { + return IMGUR_PROTECTED_RESOURCE_URL; + } + + case GITHUB: { + return GITHUB_PROTECTED_RESOURCE_URL; + } + + case FLICKR: { + return FLICKR_PROTECTED_RESOURCE_URL; + } + + case VIMEO: { + return VIMEO_PROTECTED_RESOURCE_URL; + } + + case WINDOWS_LIVE: { + return LIVE_PROTECTED_RESOURCE_URL; + } + + case TUMBLR: { + return TUMBLR_PROTECTED_RESOURCE_URL; + } + + case FOURSQUARE: { + return FOURSQUARE_PROTECTED_RESOURCE_URL; + } + + default: { + return null; + } } } - - public static void showSessionExpires() - { - Window.alert("Your session seems to have expired!\n" + "You will be logged out"); - ClientUtils.reload(); - } - - public static void showGenericException(Throwable caught) - { - String message = "Exception: " + caught; - caught.printStackTrace(); - message += "\n"; - message += "Please Logout/reload the application"; - //String st = LogUtil.stackTraceToString(caught); - //message += "Stack Trace:\n" + st; - Window.alert(message); - } - public static void logout() - { -// final String sessionId = ClientUtils.getSessionIdFromCookie(); - - new AsyncCallback() - { - - @Override - public void onFailure(Throwable caught) - { - ClientUtils.reload(); // reload anyway otherwise we're toast! we will never be able to log out - } - - @Override - public void onSuccess(Void result) - { -// GWTOAuthLoginDemo.get().log("Logged out.. clearing cookies"); -// GWTOAuthLoginDemo.get().log("Redirecting to site url"); - ClientUtils.reload(); - } - -// @Override -// protected void callService(AsyncCallback cb) -// { -// OAuthLoginService.Util.getInstance().logout(sessionId,cb); -// } - }; - } } - diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java index c04bab7bfef..32d36bbad80 100644 --- a/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/SecurityService.java @@ -7,6 +7,7 @@ import javax.servlet.ServletContext; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.mgt.SecurityManager; +import org.osgi.framework.BundleContext; import com.sap.sse.common.mail.MailException; import com.sap.sse.replication.impl.ReplicableWithObjectInputStream; @@ -16,6 +17,14 @@ import com.sap.sse.security.shared.DefaultRoles; import com.sap.sse.security.shared.SocialUserAccount; import com.sap.sse.security.shared.UserManagementException; +/** + * A service interface for security management. Intended to be used as an OSGi service that can be registered, e.g., by + * {@link BundleContext#registerService(Class, Object, java.util.Dictionary)} and can be discovered by other bundles. + * + * @author Axel Uhl (D043530) + * @author Benjamin Ebling + * + */ public interface SecurityService extends ReplicableWithObjectInputStream> { SecurityManager getSecurityManager(); @@ -47,6 +56,8 @@ public interface SecurityService extends ReplicableWithObjectInputStream accounts) { - this(name, email, /* is email validated */ false, /* password reset secret */ null, /* validation secret */ null, accounts); + this(name, email, /* fullName */ null, /* company */ null, /* is email validated */ false, + /* password reset secret */ null, /* validation secret */ null, accounts); } - public User(String name, String email, Boolean emailValidated, String passwordResetSecret, String validationSecret, - Collection accounts) { + public User(String name, String email, String fullName, String company, Boolean emailValidated, + String passwordResetSecret, String validationSecret, Collection accounts) { super(); this.name = name; + this.fullName = fullName; + this.company = company; this.roles = new HashSet<>(); this.permissions = new HashSet<>(); this.email = email; @@ -86,6 +104,22 @@ public class User implements Named, WithID { this.name = name; } + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getCompany() { + return company; + } + + public void setCompany(String company) { + this.company = company; + } + public Iterable getRoles() { return roles; } @@ -171,7 +205,7 @@ public class User implements Named, WithID { private String createRandomSecret() { final byte[] bytes1 = new byte[64]; - new Random().nextBytes(bytes1); + new SecureRandom().nextBytes(bytes1); final byte[] bytes2 = new byte[64]; new Random().nextBytes(bytes2); return new Sha256Hash(bytes1, bytes2, 1024).toBase64(); diff --git a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java index e21cd9203b8..932c668b127 100755 --- a/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java +++ b/java/com.sap.sse.security/src/com/sap/sse/security/impl/SecurityServiceImpl.java @@ -62,7 +62,6 @@ import org.scribe.builder.api.YahooApi; import org.scribe.model.Token; import org.scribe.oauth.OAuthService; -import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.sap.sse.common.Util; import com.sap.sse.common.mail.MailException; import com.sap.sse.mail.MailService; @@ -90,10 +89,7 @@ import com.sap.sse.security.shared.UserManagementException; import com.sap.sse.security.shared.UsernamePasswordAccount; import com.sap.sse.util.ClearStateTestSupport; -public class SecurityServiceImpl extends RemoteServiceServlet implements ReplicableSecurityService, ClearStateTestSupport { - - private static final long serialVersionUID = -3490163216601311858L; - +public class SecurityServiceImpl implements ReplicableSecurityService, ClearStateTestSupport { private static final Logger logger = Logger.getLogger(SecurityServiceImpl.class.getName()); private CachingSecurityManager securityManager; @@ -390,6 +386,21 @@ public class SecurityServiceImpl extends RemoteServiceServlet implements Replica apply(s->s.internalStoreUser(user)); } + @Override + public void updateUserProperties(String username, String fullName, String company) throws UserManagementException { + final User user = store.getUserByName(username); + if (user == null) { + throw new UserManagementException(UserManagementException.USER_DOES_NOT_EXIST); + } + updateUserProperties(user, fullName, company); + } + + private void updateUserProperties(User user, String fullName, String company) { + user.setFullName(fullName); + user.setCompany(company); + apply(s->s.internalStoreUser(user)); + } + @Override public boolean checkPassword(String username, String password) throws UserManagementException { final User user = store.getUserByName(username); diff --git a/java/com.tractrac.clientmodule/.classpath b/java/com.tractrac.clientmodule/.classpath index d85a01f6fba..91a9c24eec3 100644 --- a/java/com.tractrac.clientmodule/.classpath +++ b/java/com.tractrac.clientmodule/.classpath @@ -1,12 +1,12 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/java/com.tractrac.clientmodule/README.txt b/java/com.tractrac.clientmodule/README.txt index 13c1e81daff..fef732815ec 100644 --- a/java/com.tractrac.clientmodule/README.txt +++ b/java/com.tractrac.clientmodule/README.txt @@ -15,6 +15,47 @@ It contains also some files: - test.sh -> script that compiles the code in the src folder, creates the test.jar library and execute the code of the example. - Manifest.txt -> manifest used to create the test.jar file + +******************************************** +************* TracAPI 3.1.0 **************** +******************************************** +This is a final version. It fixes bugs in the implementation and it adds some new features. +These features add methods to the API breaking the backward compatibility. These changes are: + + - The IRaceCompetitorListener interface implements a new method removeOffsetPositions. If + your app implements this interface, you have to implement this extra method. + +This version provides a new JavaDoc version. + +Release date: 11/12/2015 +Build number: 11801 + + 1) Features + + - Adding the method IRaceCompetitorListener.removeOffsetPositions that is invoked + to invalidate a set of positions. It is used when the event administrator detects + an error in the snapping algorithm (in route sports) and he want to remove the wrong + positions (Requested by Chris Terkelsen, 26/11/2015) + + - Adding the method IControl.getMapName() that contains the name that has to be used to + display the control on the map (Requested by Chris Terkelsen, 09/12/2015) + + 2) Bugs + + - The static controls send a position event using the race start time instead the + event start time. (Reported by Jerome Soussens, 11/12/2015) + +******************************************** +************* TracAPI 3.0.14 *************** +******************************************** +This is a final version. Only fixes bugs in the implementation + +Release date: 29/09/2015 +Build number: 11514 + + 1) Bugs + + - NullPointerException caused by a synchronization error (Reported by Jérome Soussens, 28/09/2015) ******************************************** ************* TracAPI 3.0.13 *************** diff --git a/java/com.tractrac.clientmodule/lib/TracAPI-src.jar b/java/com.tractrac.clientmodule/lib/TracAPI-src.jar new file mode 100755 index 00000000000..19863a47498 Binary files /dev/null and b/java/com.tractrac.clientmodule/lib/TracAPI-src.jar differ diff --git a/java/com.tractrac.clientmodule/lib/TracAPI.jar b/java/com.tractrac.clientmodule/lib/TracAPI.jar index 70fda01cf7e..9955422bc65 100644 Binary files a/java/com.tractrac.clientmodule/lib/TracAPI.jar and b/java/com.tractrac.clientmodule/lib/TracAPI.jar differ diff --git a/java/org.moxieapps.gwt.highcharts/src/org/moxieapps/gwt/highcharts/client/Series.java b/java/org.moxieapps.gwt.highcharts/src/org/moxieapps/gwt/highcharts/client/Series.java index a7749f03a8a..d60d1c9fd4e 100755 --- a/java/org.moxieapps.gwt.highcharts/src/org/moxieapps/gwt/highcharts/client/Series.java +++ b/java/org.moxieapps.gwt.highcharts/src/org/moxieapps/gwt/highcharts/client/Series.java @@ -16,16 +16,23 @@ package org.moxieapps.gwt.highcharts.client; -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.JsArray; -import com.google.gwt.dom.client.Document; -import com.google.gwt.json.client.*; -import org.moxieapps.gwt.highcharts.client.plotOptions.PlotOptions; - import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; +import org.moxieapps.gwt.highcharts.client.plotOptions.PlotOptions; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; +import com.google.gwt.core.client.JsonUtils; +import com.google.gwt.dom.client.Document; +import com.google.gwt.json.client.JSONArray; +import com.google.gwt.json.client.JSONNull; +import com.google.gwt.json.client.JSONNumber; +import com.google.gwt.json.client.JSONObject; +import com.google.gwt.json.client.JSONString; +import com.google.gwt.json.client.JSONValue; + /** * Manages a data series (and its options) that can then be added to a {@link org.moxieapps.gwt.highcharts.client.Chart}. As an extension * of {@link org.moxieapps.gwt.highcharts.client.Configurable} each series instance can also optionally have configuration options set on it. @@ -1230,13 +1237,25 @@ public class Series extends Configurable { }-*/; public void updateThreshold(String threshold) { - updateNativeWay(this.getNativeSeries(), threshold); + updateThresHoldNative(this.getNativeSeries(), threshold); + } + + public void updateSeriesFillColor(String fillColor) { + JavaScriptObject fillColorObject = JsonUtils.safeEval(fillColor); + updateFillColorNative(this.getNativeSeries(), fillColorObject); } - public static native void updateNativeWay(JavaScriptObject series, String threshold) /*-{ + public static native void updateThresHoldNative(JavaScriptObject series, String threshold) /*-{ series.update({ threshold: threshold, borderColor: '#FF0000' }) }-*/; + + public static native void updateFillColorNative(JavaScriptObject series, JavaScriptObject fillColorValue) /*-{ + series.update({ + fillColor: fillColorValue + }) + }-*/; + } diff --git a/java/pom.xml b/java/pom.xml index fc283d0da09..bb6019ccf4f 100755 --- a/java/pom.xml +++ b/java/pom.xml @@ -57,7 +57,6 @@ com.sap.sailing.expeditionconnector com.sap.sailing.server com.sap.sailing.server.gateway - com.sap.sailing.server.gateway.ess40 com.sap.sailing.server.gateway.serialization com.sap.sailing.server.gateway.serialization.shared.android com.sap.sailing.www diff --git a/java/target/configuration/logging_debug.properties b/java/target/configuration/logging_debug.properties index 9791b7cd1e2..e8bf8fcb010 100644 --- a/java/target/configuration/logging_debug.properties +++ b/java/target/configuration/logging_debug.properties @@ -2,7 +2,7 @@ handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler # Assign the default log level -.level= INFO +.level = INFO # fileHandler specific properties java.util.logging.FileHandler.level = ALL diff --git a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/data/http/FileBasedHttpGetRequest.java b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/data/http/FileBasedHttpGetRequest.java index 6d7264b8e1c..ba26d31b05b 100644 --- a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/data/http/FileBasedHttpGetRequest.java +++ b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/data/http/FileBasedHttpGetRequest.java @@ -33,7 +33,7 @@ public class FileBasedHttpGetRequest extends HttpGetRequest { return null; } } finally { - outputStream.close(); + safeClose(outputStream); } return new FileInputStream(outputFile); } diff --git a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/data/http/HttpJsonPostRequest.java b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/data/http/HttpJsonPostRequest.java index 96f5a7a0ccc..01381da8efa 100644 --- a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/data/http/HttpJsonPostRequest.java +++ b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/data/http/HttpJsonPostRequest.java @@ -32,11 +32,12 @@ public class HttpJsonPostRequest extends HttpRequest { connection.setRequestProperty("Content-Type", ContentType); connection.setRequestProperty("Accept", ContentType); - OutputStream outputStream = new BufferedOutputStream(connection.getOutputStream()); - sendBody(outputStream); - outputStream.close(); - + try { + sendBody(outputStream); + } finally { + safeClose(outputStream); + } return new BufferedInputStream(connection.getInputStream()); } diff --git a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/data/http/HttpRequest.java b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/data/http/HttpRequest.java index 0a175a23e2e..701eb8a74a0 100644 --- a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/data/http/HttpRequest.java +++ b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/data/http/HttpRequest.java @@ -3,6 +3,7 @@ package com.sap.sailing.android.shared.data.http; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.Closeable; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -105,9 +106,7 @@ public abstract class HttpRequest { ExLog.i(context, TAG, String.format("(Request %d) HTTP request failed.", this.hashCode())); throw e; } finally { - if (responseInputStream != null) { - responseInputStream.close(); - } + safeClose(responseInputStream); } } @@ -140,6 +139,16 @@ public abstract class HttpRequest { return true; } + protected void safeClose(Closeable c) { + if (c != null) { + try { + c.close(); + } catch (IOException e) { + ExLog.ex(context, TAG, e); + } + } + } + protected abstract BufferedInputStream doRequest(HttpURLConnection connection) throws IOException; } diff --git a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/logging/FileLoggingTask.java b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/logging/FileLoggingTask.java index 3533686e40b..ded70290c51 100644 --- a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/logging/FileLoggingTask.java +++ b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/logging/FileLoggingTask.java @@ -90,6 +90,7 @@ public class FileLoggingTask implements Runnable { logWriter.newLine(); logWriter.flush(); } catch (Exception e) { + // if logging don't work, how can we log that it didn't work...? } } diff --git a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/AppUtils.java b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/AppUtils.java index 0a29886f40c..6cf10ee3465 100644 --- a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/AppUtils.java +++ b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/AppUtils.java @@ -46,7 +46,7 @@ public class AppUtils { try { reader.close(); } catch (IOException e) { - e.printStackTrace(); + Log.e(TAG, "Error trying to read build info", e); } } } diff --git a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/SelectionBuilder.java b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/SelectionBuilder.java index 4bdcf605d57..9703f7f149d 100644 --- a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/SelectionBuilder.java +++ b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/SelectionBuilder.java @@ -133,8 +133,9 @@ public class SelectionBuilder { public Cursor query(SQLiteDatabase db, boolean distinct, String[] columns, String orderBy, String limit) { assertTable(); - if (columns != null) + if (columns != null) { mapColumns(columns); + } ExLog.i(mContext, TAG, "query(columns=" + Arrays.toString(columns) + ", distinct=" + distinct + ") " + this); return db.query(distinct, mTable, columns, getSelection(), getSelectionArgs(), mGroupBy, mHaving, orderBy, limit); diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/RegattaActivity.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/RegattaActivity.java index f66bdca92b4..c873843a4d0 100755 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/RegattaActivity.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/RegattaActivity.java @@ -526,7 +526,7 @@ public class RegattaActivity extends AbstractRegattaActivity implements RegattaF try { in = new java.net.URL(downloadUrl).openStream(); imageFile = getImageFile(getLeaderboardImageFileName(leaderboard.name)); - if (!imageFile.exists()){ + if (!imageFile.exists()) { imageFile.createNewFile(); } outputStream = new FileOutputStream(imageFile); @@ -539,7 +539,7 @@ public class RegattaActivity extends AbstractRegattaActivity implements RegattaF outputStream.close(); in.close(); } catch (Exception e) { - e.printStackTrace(); + ExLog.e(RegattaActivity.this, TAG, "Failed to download image file " + imageFile); } finally { if (in != null) { safeClose(in); @@ -582,7 +582,7 @@ public class RegattaActivity extends AbstractRegattaActivity implements RegattaF InputStream in = new java.net.URL(downloadUrl).openStream(); mIcon11 = BitmapFactory.decodeStream(in); } catch (Exception e) { - e.printStackTrace(); + ExLog.e(RegattaActivity.this, TAG, "Failed to download flat image at url " + downloadUrl+": "+e.getMessage()); } return mIcon11; } diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/TrackingActivity.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/TrackingActivity.java index 9cbb55c9723..407d4b34a36 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/TrackingActivity.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/TrackingActivity.java @@ -427,7 +427,7 @@ public class TrackingActivity extends BaseActivity implements GPSQualityListener try { Thread.sleep(1000); } catch (InterruptedException e) { - e.printStackTrace(); + ExLog.e(TrackingActivity.this, TAG, "Interrupted sleep"); } } } diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/RegattaFragment.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/RegattaFragment.java index bfd4413bf60..9cbca5a4413 100755 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/RegattaFragment.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/RegattaFragment.java @@ -395,7 +395,7 @@ public class RegattaFragment extends BaseFragment implements OnClickListener { try { Thread.sleep(1000); } catch (InterruptedException e) { - e.printStackTrace(); + ExLog.w(RegattaFragment.this.getActivity(), TAG, "Interrupted sleep"); } } } diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/DatabaseHelper.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/DatabaseHelper.java index c484621a633..bb645c100fe 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/DatabaseHelper.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/DatabaseHelper.java @@ -42,32 +42,14 @@ public class DatabaseHelper { int result = 0; ContentResolver cr = context.getContentResolver(); - Cursor cursor = cr.query(Event.CONTENT_URI, null, Event.EVENT_CHECKIN_DIGEST + " = \"" + checkinDigest + "\"", - null, null); + Cursor cursor = cr.query(Event.CONTENT_URI, null, Event.EVENT_CHECKIN_DIGEST + " = ?", + new String[] { checkinDigest }, null); cursor.moveToFirst(); result = cursor.getInt(cursor.getColumnIndex(BaseColumns._ID)); cursor.close(); return result; } - // public EventInfo getEventInfoWithLeaderboard(Context context, String eventId) { - // EventInfo result = new EventInfo(); - // - // ContentResolver cr = context.getContentResolver(); - // String projectionStr = "events._id,leaderboards.leaderboard_name,events.event_name"; - // String[] projection = projectionStr.split(","); - // Cursor cursor = cr.query(LeaderboardsEventsJoined.CONTENT_URI, projection, "events.event_id = \"" + eventId + - // "\"", null, null); - // if (cursor.moveToFirst()) - // { - // result.name = cursor.getString(cursor.getColumnIndex("event_name")); - // result.leaderboardName = cursor.getString(cursor.getColumnIndex("leaderboard_name")); - // } - // - // cursor.close(); - // return result; - // } - public EventInfo getEventInfoWithLeaderboardAndCompetitor(Context context, String checkinDigest) { EventInfo result = new EventInfo(); @@ -76,7 +58,7 @@ public class DatabaseHelper { + " events.event_name, competitors.competitor_id"; String[] projection = projectionStr.split(","); Cursor cursor = cr.query(EventLeaderboardCompetitorJoined.CONTENT_URI, projection, "events." - + Event.EVENT_CHECKIN_DIGEST + " = \"" + checkinDigest + "\"", null, null); + + Event.EVENT_CHECKIN_DIGEST + " = ?", new String[] { checkinDigest }, null); if (cursor.moveToFirst()) { result.name = cursor.getString(cursor.getColumnIndex("event_name")); result.leaderboardName = cursor.getString(cursor.getColumnIndex("leaderboard_name")); @@ -88,32 +70,12 @@ public class DatabaseHelper { return result; } - // public EventInfo getEventInfoWithLeaderboardAndCompetitor(Context context, String eventId) { - // EventInfo result = new EventInfo(); - // - // ContentResolver cr = context.getContentResolver(); - // String projectionStr = "events._id,leaderboards.leaderboard_name,events.event_name, competitors.competitor_id"; - // String[] projection = projectionStr.split(","); - // Cursor cursor = cr.query(EventLeaderboardCompetitorJoined.CONTENT_URI, projection, "events.event_id = \"" + - // eventId + "\"", null, null); - // if (cursor.moveToFirst()) - // { - // result.name = cursor.getString(cursor.getColumnIndex("event_name")); - // result.leaderboardName = cursor.getString(cursor.getColumnIndex("leaderboard_name")); - // result.competitorId = cursor.getString(cursor.getColumnIndex("competitor_id")); - // } - // - // cursor.close(); - // return result; - // } - // - public EventInfo getEventInfo(Context context, String checkinDigest) { EventInfo event = new EventInfo(); event.checkinDigest = checkinDigest; Cursor cursor = context.getContentResolver().query(Event.CONTENT_URI, null, - Event.EVENT_CHECKIN_DIGEST + " = \"" + checkinDigest + "\"", null, null); + Event.EVENT_CHECKIN_DIGEST + " = ?", new String[] { checkinDigest }, null); if (cursor.moveToFirst()) { event.name = cursor.getString(cursor.getColumnIndex(Event.EVENT_NAME)); @@ -129,30 +91,12 @@ public class DatabaseHelper { return event; } - // public CompetitorInfo getCompetitor(Context context, String competitorId) - // { - // CompetitorInfo competitor = new CompetitorInfo(); - // competitor.id = competitorId; - // - // Cursor cursor = context.getContentResolver().query(Competitor.CONTENT_URI, null, "competitor_id = \"" + - // competitorId + "\"", null, null); - // if (cursor.moveToFirst()) { - // competitor.name = cursor.getString(cursor.getColumnIndex(Competitor.COMPETITOR_DISPLAY_NAME)); - // competitor.countryCode = cursor.getString(cursor.getColumnIndex(Competitor.COMPETITOR_COUNTRY_CODE)); - // competitor.sailId = cursor.getString(cursor.getColumnIndex(Competitor.COMPETITOR_SAIL_ID)); - // competitor.rowId = cursor.getInt(cursor.getColumnIndex(BaseColumns._ID)); - // } - // - // cursor.close(); - // return competitor; - // } - public CompetitorInfo getCompetitor(Context context, String checkinDigest) { CompetitorInfo competitor = new CompetitorInfo(); competitor.checkinDigest = checkinDigest; Cursor cursor = context.getContentResolver().query(Competitor.CONTENT_URI, null, - Competitor.COMPETITOR_CHECKIN_DIGEST + " = \"" + checkinDigest + "\"", null, null); + Competitor.COMPETITOR_CHECKIN_DIGEST + " = ?", new String[] { checkinDigest }, null); if (cursor.moveToFirst()) { competitor.name = cursor.getString(cursor.getColumnIndex(Competitor.COMPETITOR_DISPLAY_NAME)); competitor.countryCode = cursor.getString(cursor.getColumnIndex(Competitor.COMPETITOR_COUNTRY_CODE)); @@ -165,29 +109,12 @@ public class DatabaseHelper { return competitor; } - // public LeaderboardInfo getLeaderboard(Context context, String leaderboardName) - // { - // LeaderboardInfo leaderboard = new LeaderboardInfo(); - // leaderboard.name = leaderboardName; - // - // Cursor lc = context.getContentResolver().query(Leaderboard.CONTENT_URI, null, "leaderboard_name = \"" + - // leaderboardName + "\"", null, null); - // if (lc.moveToFirst()) { - // leaderboard.rowId = lc.getInt(lc.getColumnIndex(BaseColumns._ID)); - // leaderboard.checkinDigest = lc.getString(lc.getColumnIndex(Leaderboard.LEADERBOARD_CHECKIN_DIGEST)); - // } - // - // lc.close(); - // - // return leaderboard; - // } - public LeaderboardInfo getLeaderboard(Context context, String checkinDigest) { LeaderboardInfo leaderboard = new LeaderboardInfo(); leaderboard.checkinDigest = checkinDigest; Cursor lc = context.getContentResolver().query(Leaderboard.CONTENT_URI, null, - Leaderboard.LEADERBOARD_CHECKIN_DIGEST + " = \"" + checkinDigest + "\"", null, null); + Leaderboard.LEADERBOARD_CHECKIN_DIGEST + " = ?", new String[] { checkinDigest }, null); if (lc.moveToFirst()) { leaderboard.rowId = lc.getInt(lc.getColumnIndex(BaseColumns._ID)); leaderboard.name = lc.getString(lc.getColumnIndex(Leaderboard.LEADERBOARD_NAME)); @@ -203,7 +130,7 @@ public class DatabaseHelper { checkinUrlInfo.checkinDigest = checkinDigest; Cursor uc = context.getContentResolver().query(CheckinUri.CONTENT_URI, null, - CheckinUri.CHECKIN_URI_CHECKIN_DIGEST + " = \"" + checkinDigest + "\"", null, null); + CheckinUri.CHECKIN_URI_CHECKIN_DIGEST + " = ?", new String[] { checkinDigest }, null); if (uc.moveToFirst()) { checkinUrlInfo.rowId = uc.getInt(uc.getColumnIndex(BaseColumns._ID)); checkinUrlInfo.urlString = uc.getString(uc.getColumnIndex(CheckinUri.CHECKIN_URI_VALUE)); @@ -214,34 +141,13 @@ public class DatabaseHelper { return checkinUrlInfo; } - // public void deleteRegattaFromDatabase(Context context, String eventId, String competitorId, String - // leaderboardName) - // { - // ContentResolver cr = context.getContentResolver(); - // - // int d1 = cr.delete(Event.CONTENT_URI, Event.EVENT_ID + " = \"" + eventId + "\"", null); - // int d2 = cr.delete(Competitor.CONTENT_URI, Competitor.COMPETITOR_ID + " = \"" + competitorId + "\"", null); - // int d3 = cr.delete(Leaderboard.CONTENT_URI, Leaderboard.LEADERBOARD_NAME + " = \"" + leaderboardName + "\"", - // null); - // - // if (BuildConfig.DEBUG) - // { - // ExLog.i(context, TAG, "Checkout, number of events deleted: " + d1); - // ExLog.i(context, TAG, "Checkout, number of competitors deleted: " + d2); - // ExLog.i(context, TAG, "Checkout, number of leaderbards deleted: " + d3); - // } - // } - public void deleteRegattaFromDatabase(Context context, String checkinDigest) { ContentResolver cr = context.getContentResolver(); - int d1 = cr.delete(Event.CONTENT_URI, Event.EVENT_CHECKIN_DIGEST + " = \"" + checkinDigest + "\"", null); - int d2 = cr.delete(Competitor.CONTENT_URI, Competitor.COMPETITOR_CHECKIN_DIGEST + " = \"" + checkinDigest - + "\"", null); - int d3 = cr.delete(Leaderboard.CONTENT_URI, Leaderboard.LEADERBOARD_CHECKIN_DIGEST + " = \"" + checkinDigest - + "\"", null); - int d4 = cr.delete(CheckinUri.CONTENT_URI, CheckinUri.CHECKIN_URI_CHECKIN_DIGEST + " = \"" + checkinDigest - + "\"", null); + int d1 = cr.delete(Event.CONTENT_URI, Event.EVENT_CHECKIN_DIGEST + " = ?", new String[] { checkinDigest }); + int d2 = cr.delete(Competitor.CONTENT_URI, Competitor.COMPETITOR_CHECKIN_DIGEST + " = ?", new String[] { checkinDigest }); + int d3 = cr.delete(Leaderboard.CONTENT_URI, Leaderboard.LEADERBOARD_CHECKIN_DIGEST + " = ?", new String[] { checkinDigest }); + int d4 = cr.delete(CheckinUri.CONTENT_URI, CheckinUri.CHECKIN_URI_CHECKIN_DIGEST + " = ?", new String[] { checkinDigest }); if (BuildConfig.DEBUG) { ExLog.i(context, TAG, "Checkout, number of events deleted: " + d1); @@ -333,16 +239,11 @@ public class DatabaseHelper { * @return */ public boolean eventLeaderboardCompetitorCombnationAvailable(Context context, String checkinDigest) { - ContentResolver cr = context.getContentResolver(); - String sel = "leaderboards.leaderboard_checkin_digest = \"" + checkinDigest - + "\" AND competitors.competitor_checkin_digest = \"" + checkinDigest - + "\" AND events.event_checkin_digest = \"" + checkinDigest + "\""; - - Cursor cursor = cr.query(AnalyticsContract.EventLeaderboardCompetitorJoined.CONTENT_URI, null, sel, null, null); - + String sel = "leaderboards.leaderboard_checkin_digest = ? AND competitors.competitor_checkin_digest = ? AND events.event_checkin_digest = ?"; + Cursor cursor = cr.query(AnalyticsContract.EventLeaderboardCompetitorJoined.CONTENT_URI, null, sel, + new String[] { checkinDigest, checkinDigest, checkinDigest }, null); int count = cursor.getCount(); - cursor.close(); return count == 0; } diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/ImageController.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/ImageController.java index cc79941f713..233cc747700 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/ImageController.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/ImageController.java @@ -1,5 +1,8 @@ package com.sap.sailing.android.tracking.app.utils; +import java.io.ByteArrayOutputStream; +import java.io.File; + import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; @@ -10,21 +13,14 @@ import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.RectF; -import android.media.ExifInterface; import android.net.Uri; import android.provider.MediaStore; import android.util.Base64; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; - /** * Todo: Use this class? */ public class ImageController { - public static String getRealPathFromURI(ContentResolver contentResolver, Uri contentUri) { String res = null; String[] proj = { MediaStore.Images.Media.DATA }; @@ -371,76 +367,6 @@ public class ImageController { return inSampleSize; } - public static Bitmap decodeSampledBitmapFromFile(ContentResolver contentResolver, Uri uri, String filename, - int reqWidth, int reqHeight) { - - // First decode with inJustDecodeBounds=true to check dimensions - final BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - if (filename == null) { - try { - InputStream inputStream = contentResolver.openInputStream(uri); - BitmapFactory.decodeStream(inputStream, null, options); - } catch (IOException e) { - - } - } else { - BitmapFactory.decodeFile(filename, options); - - } - // Calculate inSampleSize - options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); - - // Decode bitmap with inSampleSize set - options.inJustDecodeBounds = false; - Bitmap bitmap = null; - if (filename == null) { - try { - InputStream inputStream = contentResolver.openInputStream(uri); - bitmap = BitmapFactory.decodeStream(inputStream, null, options); - } catch (IOException e) { - - } - } else { - bitmap = BitmapFactory.decodeFile(filename, options); - } - - // check orientation - if (bitmap != null && filename != null) { - try { - ExifInterface exif = new ExifInterface(filename); - int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1); - int angle = 0; - - switch (orientation) { - case ExifInterface.ORIENTATION_ROTATE_90: - angle = -90; - break; - case ExifInterface.ORIENTATION_ROTATE_180: - angle = -180; - break; - case ExifInterface.ORIENTATION_ROTATE_270: - angle = -270; - break; - - default: - break; - } - if (angle != 0) { - Matrix matrix = new Matrix(); - matrix.postRotate(90); - Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), - matrix, true); - bitmap.recycle(); - bitmap = rotatedBitmap; - } - } catch (IOException e) { - - } - } - return bitmap; - } - // ----------------------------------------------------------------------- // http://stackoverflow.com/a/8557147 diff --git a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/provider/AnalyticsProvider.java b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/provider/AnalyticsProvider.java index 110ba1812c7..ca153e8dde0 100644 --- a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/provider/AnalyticsProvider.java +++ b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/provider/AnalyticsProvider.java @@ -80,13 +80,6 @@ public class AnalyticsProvider extends ContentProvider { return true; } - // private void deleteDatabase() { - // Context context = getContext(); - // mOpenHelper.close(); - // AnalyticsDatabase.deleteDatabase(context); - // mOpenHelper = new AnalyticsDatabase(context); - // } - @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { if (BuildConfig.DEBUG && AppPreferences.getPrintDatabaseOperationDebugMessages()) { diff --git a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/service/MarkerService.java b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/service/MarkerService.java index 99753aed34e..4e25a2937f4 100644 --- a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/service/MarkerService.java +++ b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/service/MarkerService.java @@ -63,9 +63,8 @@ public class MarkerService extends Service implements CheckinManager.DataChange DatabaseHelper helper = DatabaseHelper.getInstance(); helper.deleteRegattaFromDatabase(this, checkinData.checkinDigest); helper.storeCheckinRow(this, checkinData.marks, checkinData.getLeaderboard(), checkinData.getCheckinUrl(), checkinData.pings); - } - catch (DatabaseHelper.GeneralDatabaseHelperException e) { - e.printStackTrace(); + } catch (DatabaseHelper.GeneralDatabaseHelperException e) { + Log.e(TAG, "Error trying to analyze mark checkin data", e); } } @@ -78,10 +77,9 @@ public class MarkerService extends Service implements CheckinManager.DataChange } private class TimerRunnable implements Runnable { - ; - private boolean running; + private boolean running; - public TimerRunnable(){ + public TimerRunnable() { running = true; } @@ -89,15 +87,14 @@ public class MarkerService extends Service implements CheckinManager.DataChange public void run() { while (running) { long timeDiff = Calendar.getInstance().getTimeInMillis() - lastCheck; - - if(timeDiff > preferences.getDataRefreshInterval() * 1000) { + if (timeDiff > preferences.getDataRefreshInterval() * 1000) { manager.callServerAndGenerateCheckinData(); lastCheck = Calendar.getInstance().getTimeInMillis(); } try { Thread.sleep(1000); } catch (InterruptedException e) { - e.printStackTrace(); + Log.d(TAG, "Interrupted Sleep", e); } } } diff --git a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/ui/fragments/BuoyFragment.java b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/ui/fragments/BuoyFragment.java index bc760615baa..d8f0f755b39 100644 --- a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/ui/fragments/BuoyFragment.java +++ b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/ui/fragments/BuoyFragment.java @@ -304,7 +304,7 @@ public class BuoyFragment extends BaseFragment implements LocationListener { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); Log.d(TAG, "Action: " + action); - if(action.equals(getString(R.string.database_changed))) { + if (action.equals(getString(R.string.database_changed))) { positioningActivity.loadDataFromDatabase(); setUpTextUI(lastKnownLocation); updateMap(); diff --git a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/util/CheckinManager.java b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/util/CheckinManager.java index 406296e0cb3..16466234caf 100644 --- a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/util/CheckinManager.java +++ b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/util/CheckinManager.java @@ -1,10 +1,26 @@ package com.sap.sailing.android.buoy.positioning.app.util; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.net.Uri; +import android.util.Log; import android.widget.Toast; + import com.sap.sailing.android.buoy.positioning.app.R; import com.sap.sailing.android.buoy.positioning.app.valueobjects.CheckinData; import com.sap.sailing.android.buoy.positioning.app.valueobjects.MarkInfo; @@ -19,19 +35,6 @@ import com.sap.sailing.android.shared.util.UniqueDeviceUuid; import com.sap.sailing.domain.common.racelog.tracking.DeviceMappingConstants; import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.impl.SmartphoneUUIDIdentifierImpl; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLEncoder; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; public class CheckinManager { @@ -77,6 +80,7 @@ public class CheckinManager { } private URLData extractRequestParametersFromUri(Uri uri, String scheme) { + assert uri != null; URLData urlData = new URLData(); urlData.uriStr = uri.toString(); urlData.server = scheme + "://" + uri.getHost(); @@ -290,11 +294,9 @@ public class CheckinManager { checkinDigest = buf.toString(); } } catch (UnsupportedEncodingException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + Log.e(TAG, "Exception trying to generate check-in digest", e); } catch (NoSuchAlgorithmException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + Log.e(TAG, "Exception trying to generate check-in digest", e); } return checkinDigest; } diff --git a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/util/DatabaseHelper.java b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/util/DatabaseHelper.java index e4e15c6c2b9..7a496ff2ab8 100644 --- a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/util/DatabaseHelper.java +++ b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/util/DatabaseHelper.java @@ -46,7 +46,7 @@ public class DatabaseHelper { leaderboard.checkinDigest = checkinDigest; Cursor lc = context.getContentResolver().query(Leaderboard.CONTENT_URI, null, - Leaderboard.LEADERBOARD_CHECKIN_DIGEST + " = \"" + checkinDigest + "\"", null, null); + Leaderboard.LEADERBOARD_CHECKIN_DIGEST + " = ?", new String[] { checkinDigest }, null); if (lc.moveToFirst()) { leaderboard.rowId = lc.getInt(lc.getColumnIndex(BaseColumns._ID)); leaderboard.name = lc.getString(lc.getColumnIndex(Leaderboard.LEADERBOARD_NAME)); @@ -63,7 +63,7 @@ public class DatabaseHelper { checkinUrlInfo.checkinDigest = checkinDigest; Cursor uc = context.getContentResolver().query(CheckinUri.CONTENT_URI, null, - CheckinUri.CHECKIN_URI_CHECKIN_DIGEST + " = \"" + checkinDigest + "\"", null, null); + CheckinUri.CHECKIN_URI_CHECKIN_DIGEST + " = ?", new String[] { checkinDigest }, null); if (uc.moveToFirst()) { checkinUrlInfo.rowId = uc.getInt(uc.getColumnIndex(BaseColumns._ID)); checkinUrlInfo.urlString = uc.getString(uc.getColumnIndex(CheckinUri.CHECKIN_URI_VALUE)); @@ -77,7 +77,7 @@ public class DatabaseHelper { public List getMarks(Context context, String checkinDigest) { List marks = new ArrayList(); Cursor mc = context.getContentResolver().query(Mark.CONTENT_URI, null, - Mark.MARK_CHECKIN_DIGEST + " = \"" + checkinDigest + "\"", null, null); + Mark.MARK_CHECKIN_DIGEST + " = ?", new String[] { checkinDigest }, null); mc.moveToFirst(); while (!mc.isAfterLast()) { MarkInfo markInfo = new MarkInfo(); @@ -96,7 +96,7 @@ public class DatabaseHelper { public List getMarkPings(Context context, String markID) { List marks = new ArrayList(); Cursor mpc = context.getContentResolver().query(MarkPing.CONTENT_URI, null, - MarkPing.MARK_ID + " = \"" + markID + "\"", null, MarkPing.MARK_PING_TIMESTAMP + " DESC"); + MarkPing.MARK_ID + " = ?", new String[] { markID }, MarkPing.MARK_PING_TIMESTAMP + " DESC"); mpc.moveToFirst(); while (!mpc.isAfterLast()) { MarkPingInfo markPingInfo = new MarkPingInfo(); @@ -114,7 +114,7 @@ public class DatabaseHelper { public void deletePingsFromDataBase(Context context, String markID) { ContentResolver cr = context.getContentResolver(); - int d1 = cr.delete(MarkPing.CONTENT_URI, MarkPing.MARK_ID + " = \"" + markID + "\"", null); + int d1 = cr.delete(MarkPing.CONTENT_URI, MarkPing.MARK_ID + " = ?", new String[] { markID }); if (BuildConfig.DEBUG) { ExLog.i(context, TAG, "Checkout, number of markpings for mark: " + markID + " deleted: " + d1); @@ -130,11 +130,9 @@ public class DatabaseHelper { deletePingsFromDataBase(context, mark.getId()); } - int d2 = cr.delete(Leaderboard.CONTENT_URI, Leaderboard.LEADERBOARD_CHECKIN_DIGEST + " = \"" + checkinDigest - + "\"", null); - int d3 = cr.delete(Mark.CONTENT_URI, Mark.MARK_CHECKIN_DIGEST + " = \"" + checkinDigest + "\"", null); - int d4 = cr.delete(CheckinUri.CONTENT_URI, CheckinUri.CHECKIN_URI_CHECKIN_DIGEST + " = \"" + checkinDigest - + "\"", null); + int d2 = cr.delete(Leaderboard.CONTENT_URI, Leaderboard.LEADERBOARD_CHECKIN_DIGEST + " = ?", new String[] { checkinDigest }); + int d3 = cr.delete(Mark.CONTENT_URI, Mark.MARK_CHECKIN_DIGEST + " = ?", new String[] { checkinDigest }); + int d4 = cr.delete(CheckinUri.CONTENT_URI, CheckinUri.CHECKIN_URI_CHECKIN_DIGEST + " = ?", new String[] { checkinDigest }); if (BuildConfig.DEBUG) { ExLog.i(context, TAG, "Checkout, number of leaderbards deleted: " + d2); @@ -236,7 +234,7 @@ public class DatabaseHelper { public boolean markLeaderboardCombnationAvailable(Context context, String checkinDigest) { Cursor lc = context.getContentResolver().query(Leaderboard.CONTENT_URI, null, - Leaderboard.LEADERBOARD_CHECKIN_DIGEST + " = \"" + checkinDigest + "\"", null, null); + Leaderboard.LEADERBOARD_CHECKIN_DIGEST + " = ?", new String[] { checkinDigest }, null); int count = lc.getCount(); diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OnlineDataManager.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OnlineDataManager.java index a18a4bf0c0d..9af47c3e976 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OnlineDataManager.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/OnlineDataManager.java @@ -121,7 +121,7 @@ public class OnlineDataManager extends DataManager { // ExLog.i(context, TAG, "getEventsLoader created new loader..."); - URL url = UrlHelper.generateUrl(preferences.getServerBaseURL(), "/sailingserver/events", null); + URL url = UrlHelper.generateUrl(preferences.getServerBaseURL(), "/sailingserver/api/v1/events", null); return new OnlineDataLoader>(context, url, parser, handler); } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/coursedesign/CourseDesignComputer.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/coursedesign/CourseDesignComputer.java index 9bb0fc36265..ee6284b58c1 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/coursedesign/CourseDesignComputer.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/coursedesign/CourseDesignComputer.java @@ -1,9 +1,13 @@ package com.sap.sailing.racecommittee.app.domain.coursedesign; +import android.util.Log; + import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Position; public class CourseDesignComputer { + private static final String TAG = CourseDesignComputer.class.getName(); + private Position startBoatPosition; private Double windSpeed; private Bearing windDirection; @@ -86,10 +90,9 @@ public class CourseDesignComputer { computedCourseDesign = courseLayout.getCourseDesignFactoryClass().newInstance() .createCourseDesign(startBoatPosition, windSpeed, windDirection, boatClass, courseLayout, numberOfRounds, targetTime); } catch (InstantiationException e) { - - e.printStackTrace(); + Log.e(TAG, "Exception trying compute course design", e); } catch (IllegalAccessException e) { - e.printStackTrace(); + Log.e(TAG, "Exception trying compute course design", e); } } else throw new IllegalStateException("At least one mandatory parameter was not set in the computer!"); diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java index be674ce1965..f051b8955c5 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java @@ -346,7 +346,7 @@ public class RacingActivity extends SessionActivity implements RaceInfoListener, RacingActivity.this.startService(registerIntent); } } - }).run(); + }).start(); } public void setProgressSpinnerVisibility(boolean visible) { diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/ResultsCapturingActivity.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/ResultsCapturingActivity.java index c6ade285df7..3b42580ba6c 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/ResultsCapturingActivity.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/ResultsCapturingActivity.java @@ -125,6 +125,18 @@ public class ResultsCapturingActivity extends SessionActivity { super.onPause(); releaseCamera(); } + + @Override + public void onStop() { + super.onStop(); + releaseCamera(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + releaseCamera(); + } private void setupCamera() { int cameraId = getBackCameraId(); diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/CameraView.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/CameraView.java index a1c90c2a7ac..a961d4c3525 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/CameraView.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/CameraView.java @@ -60,7 +60,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback { requestLayout(); camera.startPreview(); } catch (IOException e) { - Log.d(TAG, "Error setting camera preview: " + e.getMessage()); + Log.d(TAG, "Error setting camera preview", e); } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/autoupdate/AutoUpdaterVersionDownloader.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/autoupdate/AutoUpdaterVersionDownloader.java index 741855818b7..29a233b57f9 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/autoupdate/AutoUpdaterVersionDownloader.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/autoupdate/AutoUpdaterVersionDownloader.java @@ -7,12 +7,14 @@ import java.net.URL; import java.nio.charset.Charset; import android.content.Context; +import android.util.Log; import com.sap.sailing.android.shared.data.http.HttpGetRequest; import com.sap.sailing.racecommittee.app.utils.autoupdate.AutoUpdaterChecker.AutoUpdaterState; import com.sap.sse.common.Util; public class AutoUpdaterVersionDownloader extends AutoUpdaterDownloader> { + private final static String TAG = AutoUpdaterVersionDownloader.class.getName(); public AutoUpdaterVersionDownloader(AutoUpdaterState state, Context context) { super(state, context); @@ -29,11 +31,13 @@ public class AutoUpdaterVersionDownloader extends AutoUpdaterDownloader(code, apkFileName); } catch (NumberFormatException e) { + Log.e(TAG, "Exception while trying to parse version from version file"); } } return null; diff --git a/wiki/fortify.md b/wiki/fortify.md new file mode 100644 index 00000000000..825540f47b5 --- /dev/null +++ b/wiki/fortify.md @@ -0,0 +1,30 @@ +# Fortify Security Scans + +During the SAP-internal "I2M" (Idea-to-Market) process all software that will be shipped needs to undergo security validation. A point of contact for security validation is Frank Koehntopp (frank.koehntopp@sap.com). An essential step for security validation is a scan report created using the HP Fortify tool suite. + +We have an internal Wiki page documenting security aspects: [https://wiki.wdf.sap.corp/wiki/display/sailing/Security](https://wiki.wdf.sap.corp/wiki/display/sailing/Security). + +## Scanning Android Apps + +To perform a scan of the Android apps, install the Fortify SCA Eclipse plugin. [https://fortify1.wdf.sap.corp](https://fortify1.wdf.sap.corp) is the link to the Fortify Software Security Center. Installation and configuration is explained here: +[https://jam4.sapjam.com/wiki/show/e3aMSS4m2c078KSre2WfTn#pluginInstallation](https://jam4.sapjam.com/wiki/show/e3aMSS4m2c078KSre2WfTn#pluginInstallation) + +Then, open a Java file from the workspace in Eclipse (yes, Fortify is very sensitive to what is currently selected and opened, and the "HP Fortify" menu shows very different options based on this context which can be highly confusing). Then select "Advanced Scan..." from the "HP Fortify" menu. In the dialog, exclude all bin/, build/, gen/, target/ and tests-* folders in the app projects. Those folder icons should change from yellow to white color, indicating the exclusion rule. Then from the tree select the apps and the ``com.sap.sailing.android.shared`` folders, choose JDK 1.7 from the drop-down and click "Next." In case of repeated scans, select "Merge with previous scans." + +On the next page, say "No" to "Is this a J2EE Web application?" and select "No" for "Does this program run with escalated privileges...?" and click the "Scan" button. + +Be patient, the scan of the apps can last an hour or more. Progress indication is unreliable. When done, the Fortify plug-in will change to the Fortify perspective and show the scan results. Those can be uploaded to the Fortify Software Security Center and may be stored locally as a ``.fpr`` file. + +The project in the Fortify Software Security Center should use the "SAP 3.0" template so that the categories match with the Fortify Eclipse plugin's view. We need to audit all findings in the "Corporate Security Requirements" category, all from "Audit All" and one from each category in the "Spot Checks" tab. + +When done, a report of type "SAP Q-Gate Report" can be generated in the Fortify Software Security Center which, when "green," will be the basis for security validation. + +## Scanning the iOS Boat Tracker App + +A working Xcode development environment on a Mac computer is required to install Fortify there. Alexander Ries (alexander.ries@sap.com) was so kind as to support us in this process. A scan produces the usual ``.fpr`` file and has the code snippets included in the report. The report can be audited as usual, e.g., by loading the .fpr file into an Eclipse Fortify plugin installation, and can then be uploaded again to the Fortify Software Security Center. + +## Scanning the Back-End + +As we participate in the CloudCycle variant of the I2M process, our back-end also is subject to security validation. Scanning it takes too long for running it on a developer's PC, particularly as the workspace should not change really while a scan is running. With scans taking as long as a day or two, leaving a developer's workspace untouched for this long is not an option. + +Therefore, we have a Monsoon instance ``mo-11705429d.mo.sap.corp`` which has an installation of the Fortify software as a Linux command-line set of tools. There is a user ``sailing`` on this machine where under ``/home/sailing/code`` a git workspace exists. This git workspace contains the three scripts ``configuration/RunFortifyClean``, ``configuration/RunFortifyBuild``, and ``configuration/RunFortifyScan`` which should be executed in this order and will produce a new file ``java/com.sap.sailing_scan.fpr`` which can then be used for further processing and auditing. \ No newline at end of file diff --git a/wiki/howto.md b/wiki/howto.md index c7b90c10326..0c1490c5158 100644 --- a/wiki/howto.md +++ b/wiki/howto.md @@ -1,17 +1,17 @@ # How to work with this Wiki ### General Information -This Wiki is being stored into a GIT repository on branch master that is tracking remote branch master of origin at `/home/trac/git`. The physical location is `/home/gollum/wiki/gitwiki`. The changes here are pushed to origin on a more or less regular base (every week) by one of the admins. +This Wiki is being stored into a GIT repository on branch master that is tracking remote branch master of origin at `/home/trac/git`. The physical location is `/home/wiki/gitwiki`. The changes here are pushed to origin on a more or less regular base (every week) by one of the admins. -The script `/home/gollum/wiki/serve.sh` can be executed to either start or restart the process being responsible for this wiki. The process is a Ruby webserver (Rack) that uses the handler defined in `/home/gollum/wiki/app.rb`. This handler embeds Gollum wiki software into Rack and also makes sure that only authenticated access is possible (see `/home/gollum/wiki/users.yml`). +The script `/home/wiki/serve.sh` can be executed to either start or restart the process being responsible for this wiki. The process is a Ruby webserver (Rack) that uses the handler defined in `/home/wiki/app.rb`. This handler embeds Gollum wiki software into Rack and also makes sure that only authenticated access is possible (see `/home/wiki/users.yml`). ### Making websites public -Accessing this wiki is normally only allowed for authenticated users. If you want to share your work with others not having an account here then you can just request making a given url publicly available. On the server this is done by simply putting a new line to `/home/gollum/wiki/public.txt`. +Accessing this wiki is normally only allowed for authenticated users. If you want to share your work with others not having an account here then you can just request making a given url publicly available. On the server this is done by simply putting a new line to `/home/wiki/public.txt`. ### Creating a New Account Send a request to Axel Uhl or Simon Marcel Pamies that includes the SHA1 hash of your desired password. Obtain such an SHA1 hash for your password here: http://www.sha1-online.com/. -A Wiki administrator can add a new account by adding a new entry to `/home/gollum/wiki/users.yml`. The password there is encrypted using sha1sum (`echo -n "password" | sha1sum`) or, for those who don't happen to have sha1sum available, using an online SHA1 generator such as [this one](http://www.sha1-online.com/). +A Wiki administrator can add a new account by adding a new entry to `/home/wiki/users.yml`. The password there is encrypted using sha1sum (`echo -n "password" | sha1sum`) or, for those who don't happen to have sha1sum available, using an online SHA1 generator such as [this one](http://www.sha1-online.com/). ### Editing You can edit information here freely but do not leave any nonsense here.